mirror of
				https://github.com/open-source-parsers/jsoncpp.git
				synced 2025-11-03 18:22:32 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			385 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			385 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
"""Tag the sandbox for release, make source and doc tarballs.
 | 
						|
 | 
						|
Requires Python 2.6
 | 
						|
 | 
						|
Example of invocation (use to test the script):
 | 
						|
python makerelease.py --platform=msvc6,msvc71,msvc80,msvc90,mingw -ublep 0.6.0 0.7.0-dev
 | 
						|
 | 
						|
When testing this script:
 | 
						|
python makerelease.py --force --retag --platform=msvc6,msvc71,msvc80,mingw -ublep test-0.6.0 test-0.6.1-dev
 | 
						|
 | 
						|
Example of invocation when doing a release:
 | 
						|
python makerelease.py 0.5.0 0.6.0-dev
 | 
						|
 | 
						|
Note: This was for Subversion. Now that we are in GitHub, we do not
 | 
						|
need to build versioned tarballs anymore, so makerelease.py is defunct.
 | 
						|
"""
 | 
						|
from __future__ import print_function
 | 
						|
import os.path
 | 
						|
import subprocess
 | 
						|
import sys
 | 
						|
import doxybuild
 | 
						|
import subprocess
 | 
						|
import xml.etree.ElementTree as ElementTree
 | 
						|
import shutil
 | 
						|
import urllib2
 | 
						|
import tempfile
 | 
						|
import os
 | 
						|
import time
 | 
						|
from devtools import antglob, fixeol, tarball
 | 
						|
import amalgamate
 | 
						|
 | 
						|
SVN_ROOT = 'https://jsoncpp.svn.sourceforge.net/svnroot/jsoncpp/'
 | 
						|
SVN_TAG_ROOT = SVN_ROOT + 'tags/jsoncpp'
 | 
						|
SCONS_LOCAL_URL = 'http://sourceforge.net/projects/scons/files/scons-local/1.2.0/scons-local-1.2.0.tar.gz/download'
 | 
						|
SOURCEFORGE_PROJECT = 'jsoncpp'
 | 
						|
 | 
						|
def set_version( version ):
 | 
						|
    with open('version','wb') as f:
 | 
						|
        f.write( version.strip() )
 | 
						|
 | 
						|
def rmdir_if_exist( dir_path ):
 | 
						|
    if os.path.isdir( dir_path ):
 | 
						|
        shutil.rmtree( dir_path )
 | 
						|
 | 
						|
class SVNError(Exception):
 | 
						|
    pass
 | 
						|
 | 
						|
def svn_command( command, *args ):
 | 
						|
    cmd = ['svn', '--non-interactive', command] + list(args)
 | 
						|
    print('Running:', ' '.join( cmd ))
 | 
						|
    process = subprocess.Popen( cmd,
 | 
						|
                                stdout=subprocess.PIPE,
 | 
						|
                                stderr=subprocess.STDOUT )
 | 
						|
    stdout = process.communicate()[0]
 | 
						|
    if process.returncode:
 | 
						|
        error = SVNError( 'SVN command failed:\n' + stdout )
 | 
						|
        error.returncode = process.returncode
 | 
						|
        raise error
 | 
						|
    return stdout
 | 
						|
 | 
						|
def check_no_pending_commit():
 | 
						|
    """Checks that there is no pending commit in the sandbox."""
 | 
						|
    stdout = svn_command( 'status', '--xml' )
 | 
						|
    etree = ElementTree.fromstring( stdout )
 | 
						|
    msg = []
 | 
						|
    for entry in etree.getiterator( 'entry' ):
 | 
						|
        path = entry.get('path')
 | 
						|
        status = entry.find('wc-status').get('item')
 | 
						|
        if status != 'unversioned' and path != 'version':
 | 
						|
            msg.append( 'File "%s" has pending change (status="%s")' % (path, status) )
 | 
						|
    if msg:
 | 
						|
        msg.insert(0, 'Pending change to commit found in sandbox. Commit them first!' )
 | 
						|
    return '\n'.join( msg )
 | 
						|
 | 
						|
def svn_join_url( base_url, suffix ):
 | 
						|
    if not base_url.endswith('/'):
 | 
						|
        base_url += '/'
 | 
						|
    if suffix.startswith('/'):
 | 
						|
        suffix = suffix[1:]
 | 
						|
    return base_url + suffix
 | 
						|
 | 
						|
def svn_check_if_tag_exist( tag_url ):
 | 
						|
    """Checks if a tag exist.
 | 
						|
    Returns: True if the tag exist, False otherwise.
 | 
						|
    """
 | 
						|
    try:
 | 
						|
        list_stdout = svn_command( 'list', tag_url )
 | 
						|
    except SVNError as e:
 | 
						|
        if e.returncode != 1 or not str(e).find('tag_url'):
 | 
						|
            raise e
 | 
						|
        # otherwise ignore error, meaning tag does not exist
 | 
						|
        return False
 | 
						|
    return True
 | 
						|
 | 
						|
def svn_commit( message ):
 | 
						|
    """Commit the sandbox, providing the specified comment.
 | 
						|
    """
 | 
						|
    svn_command( 'ci', '-m', message )
 | 
						|
 | 
						|
def svn_tag_sandbox( tag_url, message ):
 | 
						|
    """Makes a tag based on the sandbox revisions.
 | 
						|
    """
 | 
						|
    svn_command( 'copy', '-m', message, '.', tag_url )
 | 
						|
 | 
						|
def svn_remove_tag( tag_url, message ):
 | 
						|
    """Removes an existing tag.
 | 
						|
    """
 | 
						|
    svn_command( 'delete', '-m', message, tag_url )
 | 
						|
 | 
						|
def svn_export( tag_url, export_dir ):
 | 
						|
    """Exports the tag_url revision to export_dir.
 | 
						|
       Target directory, including its parent is created if it does not exist.
 | 
						|
       If the directory export_dir exist, it is deleted before export proceed.
 | 
						|
    """
 | 
						|
    rmdir_if_exist( export_dir )
 | 
						|
    svn_command( 'export', tag_url, export_dir )
 | 
						|
 | 
						|
def fix_sources_eol( dist_dir ):
 | 
						|
    """Set file EOL for tarball distribution.
 | 
						|
    """
 | 
						|
    print('Preparing exported source file EOL for distribution...')
 | 
						|
    prune_dirs = antglob.prune_dirs + 'scons-local* ./build* ./libs ./dist'
 | 
						|
    win_sources = antglob.glob( dist_dir, 
 | 
						|
        includes = '**/*.sln **/*.vcproj',
 | 
						|
        prune_dirs = prune_dirs )
 | 
						|
    unix_sources = antglob.glob( dist_dir,
 | 
						|
        includes = '''**/*.h **/*.cpp **/*.inl **/*.txt **/*.dox **/*.py **/*.html **/*.in
 | 
						|
        sconscript *.json *.expected AUTHORS LICENSE''',
 | 
						|
        excludes = antglob.default_excludes + 'scons.py sconsign.py scons-*',
 | 
						|
        prune_dirs = prune_dirs )
 | 
						|
    for path in win_sources:
 | 
						|
        fixeol.fix_source_eol( path, is_dry_run = False, verbose = True, eol = '\r\n' )
 | 
						|
    for path in unix_sources:
 | 
						|
        fixeol.fix_source_eol( path, is_dry_run = False, verbose = True, eol = '\n' )
 | 
						|
 | 
						|
def download( url, target_path ):
 | 
						|
    """Download file represented by url to target_path.
 | 
						|
    """
 | 
						|
    f = urllib2.urlopen( url )
 | 
						|
    try:
 | 
						|
        data = f.read()
 | 
						|
    finally:
 | 
						|
        f.close()
 | 
						|
    fout = open( target_path, 'wb' )
 | 
						|
    try:
 | 
						|
        fout.write( data )
 | 
						|
    finally:
 | 
						|
        fout.close()
 | 
						|
 | 
						|
def check_compile( distcheck_top_dir, platform ):
 | 
						|
    cmd = [sys.executable, 'scons.py', 'platform=%s' % platform, 'check']
 | 
						|
    print('Running:', ' '.join( cmd ))
 | 
						|
    log_path = os.path.join( distcheck_top_dir, 'build-%s.log' % platform )
 | 
						|
    flog = open( log_path, 'wb' )
 | 
						|
    try:
 | 
						|
        process = subprocess.Popen( cmd,
 | 
						|
                                    stdout=flog,
 | 
						|
                                    stderr=subprocess.STDOUT,
 | 
						|
                                    cwd=distcheck_top_dir )
 | 
						|
        stdout = process.communicate()[0]
 | 
						|
        status = (process.returncode == 0)
 | 
						|
    finally:
 | 
						|
        flog.close()
 | 
						|
    return (status, log_path)
 | 
						|
 | 
						|
def write_tempfile( content, **kwargs ):
 | 
						|
    fd, path = tempfile.mkstemp( **kwargs )
 | 
						|
    f = os.fdopen( fd, 'wt' )
 | 
						|
    try:
 | 
						|
        f.write( content )
 | 
						|
    finally:
 | 
						|
        f.close()
 | 
						|
    return path
 | 
						|
 | 
						|
class SFTPError(Exception):
 | 
						|
    pass
 | 
						|
 | 
						|
def run_sftp_batch( userhost, sftp, batch, retry=0 ):
 | 
						|
    path = write_tempfile( batch, suffix='.sftp', text=True )
 | 
						|
    # psftp -agent -C blep,jsoncpp@web.sourceforge.net -batch -b batch.sftp -bc
 | 
						|
    cmd = [sftp, '-agent', '-C', '-batch', '-b', path, '-bc', userhost]
 | 
						|
    error = None
 | 
						|
    for retry_index in range(0, max(1,retry)):
 | 
						|
        heading = retry_index == 0 and 'Running:' or 'Retrying:'
 | 
						|
        print(heading, ' '.join( cmd ))
 | 
						|
        process = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT )
 | 
						|
        stdout = process.communicate()[0]
 | 
						|
        if process.returncode != 0:
 | 
						|
            error = SFTPError( 'SFTP batch failed:\n' + stdout )
 | 
						|
        else:
 | 
						|
            break
 | 
						|
    if error:
 | 
						|
        raise error
 | 
						|
    return stdout
 | 
						|
 | 
						|
def sourceforge_web_synchro( sourceforge_project, doc_dir,
 | 
						|
                             user=None, sftp='sftp' ):
 | 
						|
    """Notes: does not synchronize sub-directory of doc-dir.
 | 
						|
    """
 | 
						|
    userhost = '%s,%s@web.sourceforge.net' % (user, sourceforge_project)
 | 
						|
    stdout = run_sftp_batch( userhost, sftp, """
 | 
						|
cd htdocs
 | 
						|
dir
 | 
						|
exit
 | 
						|
""" )
 | 
						|
    existing_paths = set()
 | 
						|
    collect = 0
 | 
						|
    for line in stdout.split('\n'):
 | 
						|
        line = line.strip()
 | 
						|
        if not collect and line.endswith('> dir'):
 | 
						|
            collect = True
 | 
						|
        elif collect and line.endswith('> exit'):
 | 
						|
            break
 | 
						|
        elif collect == 1:
 | 
						|
            collect = 2
 | 
						|
        elif collect == 2:
 | 
						|
            path = line.strip().split()[-1:]
 | 
						|
            if path and path[0] not in ('.', '..'):
 | 
						|
                existing_paths.add( path[0] )
 | 
						|
    upload_paths = set( [os.path.basename(p) for p in antglob.glob( doc_dir )] )
 | 
						|
    paths_to_remove = existing_paths - upload_paths
 | 
						|
    if paths_to_remove:
 | 
						|
        print('Removing the following file from web:')
 | 
						|
        print('\n'.join( paths_to_remove ))
 | 
						|
        stdout = run_sftp_batch( userhost, sftp, """cd htdocs
 | 
						|
rm %s
 | 
						|
exit""" % ' '.join(paths_to_remove) )
 | 
						|
    print('Uploading %d files:' % len(upload_paths))
 | 
						|
    batch_size = 10
 | 
						|
    upload_paths = list(upload_paths)
 | 
						|
    start_time = time.time()
 | 
						|
    for index in range(0,len(upload_paths),batch_size):
 | 
						|
        paths = upload_paths[index:index+batch_size]
 | 
						|
        file_per_sec = (time.time() - start_time) / (index+1)
 | 
						|
        remaining_files = len(upload_paths) - index
 | 
						|
        remaining_sec = file_per_sec * remaining_files
 | 
						|
        print('%d/%d, ETA=%.1fs' % (index+1, len(upload_paths), remaining_sec))
 | 
						|
        run_sftp_batch( userhost, sftp, """cd htdocs
 | 
						|
lcd %s
 | 
						|
mput %s
 | 
						|
exit""" % (doc_dir, ' '.join(paths) ), retry=3 )
 | 
						|
 | 
						|
def sourceforge_release_tarball( sourceforge_project, paths, user=None, sftp='sftp' ):
 | 
						|
    userhost = '%s,%s@frs.sourceforge.net' % (user, sourceforge_project)
 | 
						|
    run_sftp_batch( userhost, sftp, """
 | 
						|
mput %s
 | 
						|
exit
 | 
						|
""" % (' '.join(paths),) )
 | 
						|
 | 
						|
 | 
						|
def main():
 | 
						|
    usage = """%prog release_version next_dev_version
 | 
						|
Update 'version' file to release_version and commit.
 | 
						|
Generates the document tarball.
 | 
						|
Tags the sandbox revision with release_version.
 | 
						|
Update 'version' file to next_dev_version and commit.
 | 
						|
 | 
						|
Performs an svn export of tag release version, and build a source tarball.    
 | 
						|
 | 
						|
Must be started in the project top directory.
 | 
						|
 | 
						|
Warning: --force should only be used when developping/testing the release script.
 | 
						|
"""
 | 
						|
    from optparse import OptionParser
 | 
						|
    parser = OptionParser(usage=usage)
 | 
						|
    parser.allow_interspersed_args = False
 | 
						|
    parser.add_option('--dot', dest="dot_path", action='store', default=doxybuild.find_program('dot'),
 | 
						|
        help="""Path to GraphViz dot tool. Must be full qualified path. [Default: %default]""")
 | 
						|
    parser.add_option('--doxygen', dest="doxygen_path", action='store', default=doxybuild.find_program('doxygen'),
 | 
						|
        help="""Path to Doxygen tool. [Default: %default]""")
 | 
						|
    parser.add_option('--force', dest="ignore_pending_commit", action='store_true', default=False,
 | 
						|
        help="""Ignore pending commit. [Default: %default]""")
 | 
						|
    parser.add_option('--retag', dest="retag_release", action='store_true', default=False,
 | 
						|
        help="""Overwrite release existing tag if it exist. [Default: %default]""")
 | 
						|
    parser.add_option('-p', '--platforms', dest="platforms", action='store', default='',
 | 
						|
        help="""Comma separated list of platform passed to scons for build check.""")
 | 
						|
    parser.add_option('--no-test', dest="no_test", action='store_true', default=False,
 | 
						|
        help="""Skips build check.""")
 | 
						|
    parser.add_option('--no-web', dest="no_web", action='store_true', default=False,
 | 
						|
        help="""Do not update web site.""")
 | 
						|
    parser.add_option('-u', '--upload-user', dest="user", action='store',
 | 
						|
                      help="""Sourceforge user for SFTP documentation upload.""")
 | 
						|
    parser.add_option('--sftp', dest='sftp', action='store', default=doxybuild.find_program('psftp', 'sftp'),
 | 
						|
                      help="""Path of the SFTP compatible binary used to upload the documentation.""")
 | 
						|
    parser.enable_interspersed_args()
 | 
						|
    options, args = parser.parse_args()
 | 
						|
 | 
						|
    if len(args) != 2:
 | 
						|
        parser.error( 'release_version missing on command-line.' )
 | 
						|
    release_version = args[0]
 | 
						|
    next_version = args[1]
 | 
						|
 | 
						|
    if not options.platforms and not options.no_test:
 | 
						|
        parser.error( 'You must specify either --platform or --no-test option.' )
 | 
						|
 | 
						|
    if options.ignore_pending_commit:
 | 
						|
        msg = ''
 | 
						|
    else:
 | 
						|
        msg = check_no_pending_commit()
 | 
						|
    if not msg:
 | 
						|
        print('Setting version to', release_version)
 | 
						|
        set_version( release_version )
 | 
						|
        svn_commit( 'Release ' + release_version )
 | 
						|
        tag_url = svn_join_url( SVN_TAG_ROOT, release_version )
 | 
						|
        if svn_check_if_tag_exist( tag_url ):
 | 
						|
            if options.retag_release:
 | 
						|
                svn_remove_tag( tag_url, 'Overwriting previous tag' )
 | 
						|
            else:
 | 
						|
                print('Aborting, tag %s already exist. Use --retag to overwrite it!' % tag_url)
 | 
						|
                sys.exit( 1 )
 | 
						|
        svn_tag_sandbox( tag_url, 'Release ' + release_version )
 | 
						|
 | 
						|
        print('Generated doxygen document...')
 | 
						|
##        doc_dirname = r'jsoncpp-api-html-0.5.0'
 | 
						|
##        doc_tarball_path = r'e:\prg\vc\Lib\jsoncpp-trunk\dist\jsoncpp-api-html-0.5.0.tar.gz'
 | 
						|
        doc_tarball_path, doc_dirname = doxybuild.build_doc( options, make_release=True )
 | 
						|
        doc_distcheck_dir = 'dist/doccheck'
 | 
						|
        tarball.decompress( doc_tarball_path, doc_distcheck_dir )
 | 
						|
        doc_distcheck_top_dir = os.path.join( doc_distcheck_dir, doc_dirname )
 | 
						|
        
 | 
						|
        export_dir = 'dist/export'
 | 
						|
        svn_export( tag_url, export_dir )
 | 
						|
        fix_sources_eol( export_dir )
 | 
						|
        
 | 
						|
        source_dir = 'jsoncpp-src-' + release_version
 | 
						|
        source_tarball_path = 'dist/%s.tar.gz' % source_dir
 | 
						|
        print('Generating source tarball to', source_tarball_path)
 | 
						|
        tarball.make_tarball( source_tarball_path, [export_dir], export_dir, prefix_dir=source_dir )
 | 
						|
 | 
						|
        amalgamation_tarball_path = 'dist/%s-amalgamation.tar.gz' % source_dir
 | 
						|
        print('Generating amalgamation source tarball to', amalgamation_tarball_path)
 | 
						|
        amalgamation_dir = 'dist/amalgamation'
 | 
						|
        amalgamate.amalgamate_source( export_dir, '%s/jsoncpp.cpp' % amalgamation_dir, 'json/json.h' )
 | 
						|
        amalgamation_source_dir = 'jsoncpp-src-amalgamation' + release_version
 | 
						|
        tarball.make_tarball( amalgamation_tarball_path, [amalgamation_dir],
 | 
						|
                              amalgamation_dir, prefix_dir=amalgamation_source_dir )
 | 
						|
 | 
						|
        # Decompress source tarball, download and install scons-local
 | 
						|
        distcheck_dir = 'dist/distcheck'
 | 
						|
        distcheck_top_dir = distcheck_dir + '/' + source_dir
 | 
						|
        print('Decompressing source tarball to', distcheck_dir)
 | 
						|
        rmdir_if_exist( distcheck_dir )
 | 
						|
        tarball.decompress( source_tarball_path, distcheck_dir )
 | 
						|
        scons_local_path = 'dist/scons-local.tar.gz'
 | 
						|
        print('Downloading scons-local to', scons_local_path)
 | 
						|
        download( SCONS_LOCAL_URL, scons_local_path )
 | 
						|
        print('Decompressing scons-local to', distcheck_top_dir)
 | 
						|
        tarball.decompress( scons_local_path, distcheck_top_dir )
 | 
						|
 | 
						|
        # Run compilation
 | 
						|
        print('Compiling decompressed tarball')
 | 
						|
        all_build_status = True
 | 
						|
        for platform in options.platforms.split(','):
 | 
						|
            print('Testing platform:', platform)
 | 
						|
            build_status, log_path = check_compile( distcheck_top_dir, platform )
 | 
						|
            print('see build log:', log_path)
 | 
						|
            print(build_status and '=> ok' or '=> FAILED')
 | 
						|
            all_build_status = all_build_status and build_status
 | 
						|
        if not build_status:
 | 
						|
            print('Testing failed on at least one platform, aborting...')
 | 
						|
            svn_remove_tag( tag_url, 'Removing tag due to failed testing' )
 | 
						|
            sys.exit(1)
 | 
						|
        if options.user:
 | 
						|
            if not options.no_web:
 | 
						|
                print('Uploading documentation using user', options.user)
 | 
						|
                sourceforge_web_synchro( SOURCEFORGE_PROJECT, doc_distcheck_top_dir, user=options.user, sftp=options.sftp )
 | 
						|
                print('Completed documentation upload')
 | 
						|
            print('Uploading source and documentation tarballs for release using user', options.user)
 | 
						|
            sourceforge_release_tarball( SOURCEFORGE_PROJECT,
 | 
						|
                                         [source_tarball_path, doc_tarball_path],
 | 
						|
                                         user=options.user, sftp=options.sftp )
 | 
						|
            print('Source and doc release tarballs uploaded')
 | 
						|
        else:
 | 
						|
            print('No upload user specified. Web site and download tarbal were not uploaded.')
 | 
						|
            print('Tarball can be found at:', doc_tarball_path)
 | 
						|
 | 
						|
        # Set next version number and commit            
 | 
						|
        set_version( next_version )
 | 
						|
        svn_commit( 'Released ' + release_version )
 | 
						|
    else:
 | 
						|
        sys.stderr.write( msg + '\n' )
 | 
						|
 
 | 
						|
if __name__ == '__main__':
 | 
						|
    main()
 |