Baptiste Lepilleur | 1f4847c | 2010-02-23 07:57:38 +0000 | [diff] [blame] | 1 | """Tag the sandbox for release, make source and doc tarballs. |
| 2 | |
| 3 | Requires Python 2.6 |
| 4 | |
| 5 | Example of invocation (use to test the script): |
| 6 | python makerelease.py --force --retag 0.5.0 0.6.0-dev |
| 7 | |
| 8 | Example of invocation when doing a release: |
| 9 | python makerelease.py 0.5.0 0.6.0-dev |
| 10 | """ |
| 11 | import os.path |
| 12 | import subprocess |
| 13 | import sys |
| 14 | import doxybuild |
| 15 | import subprocess |
| 16 | import xml.etree.ElementTree as ElementTree |
Baptiste Lepilleur | 7c171ee | 2010-02-23 08:44:52 +0000 | [diff] [blame] | 17 | import shutil |
Baptiste Lepilleur | e94d2f4 | 2010-02-23 21:00:30 +0000 | [diff] [blame^] | 18 | from devtools import antglob, fixeol, tarball |
Baptiste Lepilleur | 1f4847c | 2010-02-23 07:57:38 +0000 | [diff] [blame] | 19 | |
| 20 | SVN_ROOT = 'https://jsoncpp.svn.sourceforge.net/svnroot/jsoncpp/' |
| 21 | SVN_TAG_ROOT = SVN_ROOT + 'tags/jsoncpp' |
| 22 | |
| 23 | def set_version( version ): |
| 24 | with open('version','wb') as f: |
| 25 | f.write( version.strip() ) |
| 26 | |
| 27 | class SVNError(Exception): |
| 28 | pass |
| 29 | |
| 30 | def svn_command( command, *args ): |
| 31 | cmd = ['svn', '--non-interactive', command] + list(args) |
| 32 | print 'Running:', ' '.join( cmd ) |
| 33 | process = subprocess.Popen( cmd, |
| 34 | stdout=subprocess.PIPE, |
| 35 | stderr=subprocess.STDOUT ) |
| 36 | stdout = process.communicate()[0] |
| 37 | if process.returncode: |
| 38 | error = SVNError( 'SVN command failed:\n' + stdout ) |
| 39 | error.returncode = process.returncode |
| 40 | raise error |
| 41 | return stdout |
| 42 | |
| 43 | def check_no_pending_commit(): |
| 44 | """Checks that there is no pending commit in the sandbox.""" |
| 45 | stdout = svn_command( 'status', '--xml' ) |
| 46 | etree = ElementTree.fromstring( stdout ) |
| 47 | msg = [] |
| 48 | for entry in etree.getiterator( 'entry' ): |
| 49 | path = entry.get('path') |
| 50 | status = entry.find('wc-status').get('item') |
| 51 | if status != 'unversioned': |
| 52 | msg.append( 'File "%s" has pending change (status="%s")' % (path, status) ) |
| 53 | if msg: |
| 54 | msg.insert(0, 'Pending change to commit found in sandbox. Commit them first!' ) |
| 55 | return '\n'.join( msg ) |
| 56 | |
| 57 | def svn_join_url( base_url, suffix ): |
| 58 | if not base_url.endswith('/'): |
| 59 | base_url += '/' |
| 60 | if suffix.startswith('/'): |
| 61 | suffix = suffix[1:] |
| 62 | return base_url + suffix |
| 63 | |
| 64 | def svn_check_if_tag_exist( tag_url ): |
| 65 | """Checks if a tag exist. |
| 66 | Returns: True if the tag exist, False otherwise. |
| 67 | """ |
| 68 | try: |
| 69 | list_stdout = svn_command( 'list', tag_url ) |
| 70 | except SVNError, e: |
| 71 | if e.returncode != 1 or not str(e).find('tag_url'): |
| 72 | raise e |
| 73 | # otherwise ignore error, meaning tag does not exist |
| 74 | return False |
| 75 | return True |
| 76 | |
| 77 | def svn_tag_sandbox( tag_url, message ): |
| 78 | """Makes a tag based on the sandbox revisions. |
| 79 | """ |
| 80 | svn_command( 'copy', '-m', message, '.', tag_url ) |
| 81 | |
| 82 | def svn_remove_tag( tag_url, message ): |
| 83 | """Removes an existing tag. |
| 84 | """ |
| 85 | svn_command( 'delete', '-m', message, tag_url ) |
| 86 | |
Baptiste Lepilleur | 7c171ee | 2010-02-23 08:44:52 +0000 | [diff] [blame] | 87 | def svn_export( tag_url, export_dir ): |
| 88 | """Exports the tag_url revision to export_dir. |
| 89 | Target directory, including its parent is created if it does not exist. |
| 90 | If the directory export_dir exist, it is deleted before export proceed. |
| 91 | """ |
| 92 | if os.path.isdir( export_dir ): |
| 93 | shutil.rmtree( export_dir ) |
| 94 | svn_command( 'export', tag_url, export_dir ) |
| 95 | |
Baptiste Lepilleur | e94d2f4 | 2010-02-23 21:00:30 +0000 | [diff] [blame^] | 96 | def fix_sources_eol( dist_dir ): |
| 97 | """Set file EOL for tarball distribution. |
| 98 | """ |
| 99 | print 'Preparing exported source file EOL for distribution...' |
| 100 | prune_dirs = antglob.prune_dirs + 'scons-local* ./build* ./libs ./dist' |
| 101 | win_sources = antglob.glob( dist_dir, |
| 102 | includes = '**/*.sln **/*.vcproj', |
| 103 | prune_dirs = prune_dirs ) |
| 104 | unix_sources = antglob.glob( dist_dir, |
| 105 | includes = '''**/*.h **/*.cpp **/*.inl **/*.txt **/*.dox **/*.py **/*.html **/*.in |
| 106 | sconscript *.json *.expected AUTHORS LICENSE''', |
| 107 | excludes = antglob.default_excludes + 'scons.py sconsign.py scons-*', |
| 108 | prune_dirs = prune_dirs ) |
| 109 | for path in win_sources: |
| 110 | fixeol.fix_source_eol( path, is_dry_run = False, verbose = True, eol = '\r\n' ) |
| 111 | for path in unix_sources: |
| 112 | fixeol.fix_source_eol( path, is_dry_run = False, verbose = True, eol = '\n' ) |
| 113 | |
Baptiste Lepilleur | 1f4847c | 2010-02-23 07:57:38 +0000 | [diff] [blame] | 114 | def main(): |
| 115 | usage = """%prog release_version next_dev_version |
| 116 | Update 'version' file to release_version and commit. |
| 117 | Generates the document tarball. |
| 118 | Tags the sandbox revision with release_version. |
| 119 | Update 'version' file to next_dev_version and commit. |
| 120 | |
| 121 | Performs an svn export of tag release version, and build a source tarball. |
| 122 | |
| 123 | Must be started in the project top directory. |
| 124 | """ |
| 125 | from optparse import OptionParser |
| 126 | parser = OptionParser(usage=usage) |
| 127 | parser.allow_interspersed_args = False |
| 128 | parser.add_option('--dot', dest="dot_path", action='store', default=doxybuild.find_program('dot'), |
| 129 | help="""Path to GraphViz dot tool. Must be full qualified path. [Default: %default]""") |
| 130 | parser.add_option('--doxygen', dest="doxygen_path", action='store', default=doxybuild.find_program('doxygen'), |
| 131 | help="""Path to Doxygen tool. [Default: %default]""") |
| 132 | parser.add_option('--force', dest="ignore_pending_commit", action='store_true', default=False, |
| 133 | help="""Ignore pending commit. [Default: %default]""") |
| 134 | parser.add_option('--retag', dest="retag_release", action='store_true', default=False, |
| 135 | help="""Overwrite release existing tag if it exist. [Default: %default]""") |
| 136 | parser.enable_interspersed_args() |
| 137 | options, args = parser.parse_args() |
| 138 | |
| 139 | if len(args) < 1: |
| 140 | parser.error( 'release_version missing on command-line.' ) |
| 141 | release_version = args[0] |
| 142 | |
| 143 | if options.ignore_pending_commit: |
| 144 | msg = '' |
| 145 | else: |
| 146 | msg = check_no_pending_commit() |
| 147 | if not msg: |
| 148 | print 'Setting version to', release_version |
| 149 | set_version( release_version ) |
| 150 | tag_url = svn_join_url( SVN_TAG_ROOT, release_version ) |
Baptiste Lepilleur | e94d2f4 | 2010-02-23 21:00:30 +0000 | [diff] [blame^] | 151 | if svn_check_if_tag_exist( tag_url ): |
| 152 | if options.retag_release: |
| 153 | svn_remove_tag( tag_url, 'Overwriting previous tag' ) |
| 154 | else: |
| 155 | print 'Aborting, tag %s already exist. Use --retag to overwrite it!' % tag_url |
| 156 | sys.exit( 1 ) |
| 157 | svn_tag_sandbox( tag_url, 'Release ' + release_version ) |
| 158 | |
| 159 | print 'Generated doxygen document...' |
| 160 | doxybuild.build_doc( options, make_release=True ) |
| 161 | |
| 162 | export_dir = 'dist/export' |
| 163 | svn_export( tag_url, export_dir ) |
| 164 | fix_sources_eol( export_dir ) |
| 165 | |
| 166 | source_dir = 'jsoncpp-src-' + release_version |
| 167 | source_tarball_path = 'dist/%s.tar.gz' % source_dir |
| 168 | print 'Generating source tarball to', source_tarball_path |
| 169 | tarball.make_tarball( source_tarball_path, [export_dir], export_dir, prefix_dir=source_dir ) |
Baptiste Lepilleur | 1f4847c | 2010-02-23 07:57:38 +0000 | [diff] [blame] | 170 | #@todo: |
Baptiste Lepilleur | 1f4847c | 2010-02-23 07:57:38 +0000 | [diff] [blame] | 171 | # decompress source tarball |
| 172 | # ?compile & run & check |
| 173 | # ?upload documentation |
| 174 | else: |
| 175 | sys.stderr.write( msg + '\n' ) |
| 176 | |
| 177 | if __name__ == '__main__': |
| 178 | main() |