#! /home/bwarsaw/projects/python/python
#
# REQUIRES Python 1.5.2+
#
##! /usr/bin/env python

"""Manage releases of JPython.

Usage: %(program)s [options] tagname

Where `options' are:

    --tag
    -t
        Tag all release files with tagname.

    --TAG
    -T
        Like --tag, but relocates any existing tag.  See `cvs tag -F'.  Only
        one of --tag or --TAG can be given on the command line.

    --prep
    -p
        Prep the release for running the Jshield installer

    --build
    -B
        Do the build by running the Jshield installer

    --bump
    -b
        Bump the revision number to tagname

    --clean
    -c
        Clean up after ourselves

    --help
    -h
        Print this help message.

    tagname is used in the various commands above.  It should essentially be
    the version number for the release, and is required.  E.g. `1.1beta4'

"""

import sys
import os
import string
import re
import time
import tempfile
import getopt

PYLIB = 'pylib152b.jar'

program = sys.argv[0]

def usage(status, msg=''):
    print __doc__ % globals()
    if msg:
        print msg
    sys.exit(status)



# CVS related commands and other utils

def cvsdo(cvscmd, indir=None):
    curdir = os.getcwd()
    try:
        if indir:
            os.chdir(indir)
        os.system('cvs %s' % cvscmd)
    finally:
        os.chdir(curdir)

def tag_release(tagname, retag):
    # watch out for dots in the name
    table = string.maketrans('.', '_')
    # To be done from writeable repository
    relname = '"Release_' + tagname.translate(table) + '"'
    print 'Tagging release with', relname, '...'
    option = ''
    if retag:
	option = '-F'
    cvsdo('tag %s %s' % (option, relname))

def listdir_ex(dir):
    try: return os.listdir(dir)
    except OSError, e:
        print e
        return []

def unlink_ex(filename):
    try: os.unlink(filename)
    except OSError, e:
        print e

def rmdir_ex(dirname):
    try: os.rmdir(dirname)
    except OSError, e:
        print e

def rmdirhier(dirname):
    def zapit(dirs, dirname, names):
        for file in names:
            filename = os.path.join(dirname, file)
            if os.path.isfile(filename):
                unlink_ex(filename)
            else:
                dirs.append(filename)
    dirs = []
    os.path.walk(dirname, zapit, dirs)
    dirs.reverse()
    dirs.append(dirname)
    for dir in dirs:
        rmdir_ex(dir)



# preparations

def make_jpython_jar(withoro=1, jarname='jpython.jar'):
    # change dirs no manifest file, no zip compression
    jarcmd = ['jar cvfM0', jarname]
    jarcmd.append('-C dist')
    jarcmd.append('dummy')                        # JDK 1.2 jar bug workaround
    #
    # add all the .class files in these dirs inside org/python
    for dir in ['core', 'modules', 'parser', 'compiler', 'util', 'rmi']:
        jarcmd.append(os.path.join('dist/org/python/', dir, '*.class'))
    #
    # add all the classes from ORO
    if withoro:
        jarcmd.append('-C nondist/ORO')
        jarcmd.append('dummy')                    # JDK 1.2 jar bug workaround
        jarcmd.append('nondist/ORO/com/oroinc/text/regex/*.class')
    #
    # invoke to build jpython.jar
    cmd = ' '.join(jarcmd)
    os.system(cmd)


def make_pylib_jar():
    # TBD: Jim only includes these files from the standard library.  Why?  I
    # guess every download byte counts!  I still don't like explicitly
    # including specific files; probably better to explicitly exclude those we 
    # don't want
    files = ['BaseHTTPServer.py', 'binhex.py', 'bisect.py', 'calendar.py',
             'cgi.py',
             'CGIHTTPServer.py', 'cmd.py', 'cmp.py', 'cmpcache.py',
             'colorsys.py',
             'commands.py', 'compileall.py', 'ConfigParser.py', 'copy.py',
             'copy_reg.py',
             'dircache.py', 'dircmp.py', 'dospath.py', 'dumbdbm.py',
             'dump.py', 'exceptions.py',
             'fileinput.py', 'find.py', 'fnmatch.py', 'formatter.py',
             'fpformat.py', 'ftplib.py', 
             'getopt.py', 'glob.py', 'gopherlib.py', 'grep.py',
             'htmlentitydefs.py', 'htmllib.py',
             'httplib.py', 'imghdr.py', 'keyword.py', 'linecache.py',
             'macpath.py', 'macurl2path.py',
             'mailbox.py', 'mailcap.py', 'mhlib.py', 'mimetools.py',
             'mimetypes.py', 'MimeWriter.py',
             'mimify.py', 'multifile.py', 'mutex.py', 'newdir.py',
             'nntplib.py', 'ntpath.py',
             'nturl2path.py', 'packmail.py', 'pickle.py', 'pipes.py',
             'poly.py', 'popen2.py',
             'posixfile.py', 'posixpath.py', 'pprint.py', 'pyclbr.py',
             'Queue.py', 'quopri.py',
             'rand.py', 'random.py', 'reconvert.py', 'repr.py', 'rfc822.py',
             'sched.py',
             'sgmllib.py', 'shelve.py', 'shutil.py', 'SimpleHTTPServer.py',
             'sndhdr.py',
             'SocketServer.py', 'stat.py', 'StringIO.py', 'symbol.py',
             'tb.py',
             'telnetlib.py', 'tempfile.py', 'token.py', 'tokenize.py',
             'traceback.py',
             'tzparse.py', 'urllib.py', 'urlparse.py', 'user.py',
             'UserDict.py', 'UserList.py',
             'whrandom.py', 'xdrlib.py', 'xmllib.py', 'zmod.py',
             'poplib.py', 'smtplib.py', 'imaplib.py',
             'bdb.py', 'pdb.py', 'profile.py', 'anydbm.py',
             ]
    src = '/home/bwarsaw/projects/python'
    libsrc = os.path.join(src, 'Lib')
    print 'updating in', libsrc
    cvsdo('-q up -P -d', libsrc)
    includep = {}
    for file in files:
        includep[file] = 1
    # glom up the jar command
    jarcmd = ['jar cvfM', PYLIB]
    # add the __run__.py file
    jarcmd.append('-C dist/util')
    jarcmd.append('doesnotexist')                 # JDK 1.2 jar bug workaround
    jarcmd.append('dist/util/__run__.py')
    jarcmd.append('-C ' + src)
    jarcmd.append('doesnotexist')                 # JDK 1.2 jar bug workaround
    for file in os.listdir(libsrc):
        if file[-3:] <> '.py':
##            print '    skipping', file
            continue
        if includep.has_key(file):
            jarcmd.append(os.path.join(libsrc, file))
        else:
            print '    skipping', file
    jarcmd.append(os.path.join(libsrc, 'test', 'pystone.py'))
    cmd = ' '.join(jarcmd)
    os.system(cmd)


def make_jars():
    print 'making all-inclusive jar...'
    make_jpython_jar()
    print 'making jpython-only jar...'
    make_jpython_jar(withoro=0, jarname='jpython-only.jar')
    print 'making pylib jar...'
    make_pylib_jar()



def prep_distro(tagname):
    # This function creates a directory image that the Jshield installer gets
    # ponted at.  Jshield then gloms everything under this directory into the
    # Java installer.
    #
    print 'exporting dist...',
    # watch out for dots in the name
    table = string.maketrans('.', '_')
    # To be done from writeable repository
    relname = '"Release_' + tagname.translate(table) + '"'
    cvsdo('export -k kv -r %s -d export jpython/dist' % relname)
    #
    print 'preparing distribution...'
    make_jars()
    #
    # The directory structure laid out by the installer isn't the same as the
    # CVS tree, so we need to move some things around
    #
    # get rid of Tools/freeze and tools/mkjava.py
    freezedir = 'export/Tools/freeze'
    for file in listdir_ex(freezedir):
        unlink_ex(os.path.join(freezedir, file))
    rmdir_ex(freezedir)
    unlink_ex('export/Tools/mkjava.py')
    # get rid of the experimental stuff
    rmdirhier('export/experiments')
    rmdirhier('export/tests')
    rmdirhier('export/Tools/peekaboo')
    unlink_ex('export/jpython.bat')
    unlink_ex('export/jpython.isj')
    unlink_ex('export/mkisj.bat')



def bump_isj(tagname):
    infile = 'dist/jpython.isj'
    outfile = infile + '.new'
    infp = open(infile)
    outfp = open(outfile, 'w')
    while 1:
        line = infp.readline()
        if not line:
            break
        parts = line.split('=')
        if parts[0] == 'OutputFile':
            outfp.write('OutputFile=/home/bwarsaw/projects/jpython/JPython')
            table = string.maketrans('', '')
            outfp.write(tagname.translate(table, '.'))
            outfp.write('.class\n')
        elif parts[0] == 'BuildNumber':
            outfp.write('BuildNumber='+tagname+'\n')
        elif parts[0] == 'ApplicationName':
            outfp.write('ApplicationName=JPython '+tagname+'\n')
        elif parts[0] == 'DefaultDir':
            outfp.write('DefaultDir=~/JPython-'+tagname+'\n')
        else:
            outfp.write(line)
    infp.close()
    outfp.close()
    os.rename(outfile, infile)


def bump_sysversion(tagname):
    infile = 'dist/org/python/core/PySystemState.java'
    outfile = infile + '.new'
    infp = open(infile)
    outfp = open(outfile, 'w')
    while 1:
        line = infp.readline()
        if not line:
            break
        parts = line.split()
        if parts[0:4] == ['public', 'static', 'String', 'version']:
            outfp.write('    public static String version = "')
            outfp.write(tagname + '";\n')
        else:
            outfp.write(line)
    infp.close()
    outfp.close()
    os.rename(outfile, infile)


def do_bump(tagname):
    bump_isj(tagname)
    bump_sysversion(tagname)
    print 'YOU MUST DO:'
    todo = ['update docs/differences.ht',
            'update NEWS',
            'update faq.ht',
            'update ACKNOWLEDGMENTS',
            'run make',
            ]
    i = 1
    for d in todo:
        print '    %2d.' % i, d
        i = i + 1



def do_build(tagname):
    table = string.maketrans('', '')
    tagname = tagname.translate(table, '.')
    print 'tagname:', tagname
    # make full distribution
    unlink_ex('export/LICENSE.txt')
    os.system('cp nondist/LICENSE-ORO.txt export/LICENSE.txt')
    os.system('cp jpython.jar export')
    os.system('nondist/Jshield/bin/jshield dist/jpython.isj')
    os.rename('JPython'+tagname+'.class', 'JPythonORO'+tagname+'.class')
    # make non-ORO distribution
    unlink_ex('export/LICENSE.txt')
    os.system('cp dist/LICENSE.txt export/LICENSE.txt')
    unlink_ex('export/jpython.jar')
    os.system('cp jpython-only.jar export/jpython.jar')
    os.system('nondist/Jshield/bin/jshield dist/jpython.isj')
    os.rename('JPython'+tagname+'.class', 'JPythonONLY'+tagname+'.class')
##    os.rename('JPythonORO'+tagname+'.class', 'JPython'+tagname+'.class')
    

def do_clean():
    rmdirhier('export')
    unlink_ex('jpython.jar')
    unlink_ex('jpython-only.jar')
    # clean up the CVS/Entries file
    os.rename('CVS/Entries', 'CVS/Entries.old')
    infp = open('CVS/Entries.old')
    outfp = open('CVS/Entries', 'w')
    while 1:
        line = infp.readline()
        if not line:
            break
        if line[:8] == 'D/export':
            continue
        outfp.write(line)
    infp.close()
    outfp.close()
    unlink_ex('CVS/Entries.old')
    unlink_ex(PYLIB)



def main():
    try:
	opts, args = getopt.getopt(
            sys.argv[1:], 'htTpBcb',
            ['help', 'tag', 'TAG', 'prep', 'build', 'clean', 'bump'])
    except getopt.error, msg:
	usage(1, msg)

    # make sure we're in the proper directory
    dirs = os.listdir('.')
    if 'dist' not in dirs or 'nondist' not in dirs:
        usage(1, 'run this script from the top of the JPython CVS tree')

    # default options
    tag = 0
    retag = 0
    prep = 0
    build = 0
    clean = 0
    bump = 0

    for opt, arg in opts:
	if opt in ('-h', '--help'):
	    usage(0)
	elif opt in ('-t', '--tag'):
	    tag = 1
	elif opt in ('-T', '--TAG'):
	    tag = 1
	    retag = 1
        elif opt in ('-p', '--prep'):
            prep = 1
        elif opt in ('-B', '--build'):
            build = 1
        elif opt in ('-b', '--bump'):
            bump = 1
        elif opt in ('-c', '--clean'):
            clean = 1

    # required minor rev number
    if tag or prep or build or bump:
        if len(args) <> 1:
            usage(1, 'tagname argument is required')
        else:
            tagname = args[0]

    # very important!!!
    omask = os.umask(0)
    try:
        if bump:
            do_bump(tagname)

        if tag:
            tag_release(tagname, retag)

        if prep:
            prep_distro(tagname)

        if build:
            do_build(tagname)

        if clean:
            do_clean()
    finally:
        os.umask(omask)

if __name__ == '__main__':
    main()
