release   [plain text]


#! /usr/bin/env python
#
# Copyright (C) 1998-2006 by the Free Software Foundation, Inc.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
# USA.

# XXX This file does not need to be compatible with Python 2.1.  It is only
# used by the release manager.

import os
import re
import sys
import time
import errno
import optparse
import tempfile

from subprocess import Popen, PIPE
from urlparse import urlparse


__revision__ = '$Revision: 8022 $'

parts = __revision__.split()
if len(parts) == 3:
    __version__ = parts[1]
else:
    __version__ = parts[0]


SLASH   = '/'
SPACE   = ' '



def calculate_urls(relname):
    srcurl = None
    stdout, stderr = do('svn info')
    for line in stdout.splitlines():
        key, val = line.split(':', 1)
        if key.lower() == 'url':
            srcurl = val
            break
    else:
        print >> sys.stderr, 'No source url found'
        sys.exit(1)
    scheme, netloc, path, params, query, frag = urlparse(srcurl)
    if params or query or frag:
        print >> sys.stderr, 'src url has params, query and/or frag'
        sys.exit(1)
    parts = path.split(SLASH)
    # XXX Fix this to work on the trunk too
    for i, part in enumerate(parts):
        if part == 'branches':
            break
    else:
        print >> sys.stderr, 'No branches directory found in src url'
        sys.exit(1)
    del parts[i:]
    parts.extend(['tags', relname])
    dsturl = SLASH.join(parts)
    return srcurl, dsturl


def do(cmd):
    proc = Popen(cmd.split(), stdout=PIPE, stderr=PIPE)
    stdout, stderr = proc.communicate()
    return stdout, stderr


def now():
    return time.strftime('%d-%b-%Y', time.localtime(time.time()))


def releasedir(tagname=None):
    tmpdir = tempfile.gettempdir()
    return os.path.join(tmpdir, 'mailman-' + tagname)


def tag2rel(tagname):
    return 'Release_' + tagname.replace('.', '_')



def tag_release(tagname):
    # Convert dots in tagname to underscores
    relname = tag2rel(tagname)
    # Calculate the 'tags' directory, which should be a sibling of the
    # 'branches' directory.
    srcurl, dsturl = calculate_urls(relname)
    print 'Tag url:', dsturl
    fd, msgfile = tempfile.mkstemp(text=True)
    try:
        os.close(fd)
        fp = open(msgfile, 'w')
        try:
            print >> fp, 'Tagging release', tagname
        finally:
            fp.close()
        do('svn cp %s %s -F %s' % (srcurl, dsturl, msgfile))
    finally:
        os.remove(msgfile)



def make_pkg(tagname, sign):
    reldir = releasedir(tagname)
    if os.path.exists(reldir):
        print >> sys.stderr, 'Release directory already exists:', reldir
        sys.exit(1)
    relname = tag2rel(tagname)
    srcurl, dsturl = calculate_urls(relname)
    print 'Exporting to release dir', reldir, '...'
    do('svn export %s %s' % (dsturl, reldir))
    if not os.path.exists(reldir):
        print >> sys.stderr, 'svn export failed:', dsturl
        sys.exit(1)
    curdir = os.getcwd()
    try:
        os.chdir(os.path.dirname(reldir))
        print 'Making tarball...'
        relname = 'mailman-' + tagname
        tarfile = relname + '.tgz'
        do('tar cvzf %s --exclude .svn %s' % (tarfile, relname))
        do('tar cvzf mailman-doc.tgz --exclude .svn mailman-doc')
        if sign:
            do('gpg -bas %s' % tarfile)
    finally:
        os.chdir(curdir)



VERSIONMARK = '<!-VERSION--->'
DATEMARK    = '<!-DATE--->'


def do_bump(newvers):
    print 'Doing bump...',
    for file in ('index.ht',):
        print '\t%s...' % file,
        fp = open(os.path.join('admin', 'www', file), 'r+')
        text = fp.read()
        parts = text.split(VERSIONMARK)
        parts[1] = newvers
        text = VERSIONMARK.join(parts)
        parts = text.split(DATEMARK)
        parts[1] = now()
        text = DATEMARK.join(parts)
        fp.seek(0)
        fp.write(text)
        fp.close()
    # hack the configure.in file
    print 'Version.py...',
    infp = open('Mailman/Version.py')
    outfp = open('Mailman/Version.py.new', 'w')
    matched = False
    cre = re.compile(r'^VERSION(?P<ws>[ \t]*)=')
    for line in infp:
        mo = cre.search(line)
        if matched or not mo:
            outfp.write(line)
        else:
            outfp.write('VERSION%s= "%s"\n' % (mo.group('ws'), newvers))
            matched = True
    if not matched:
        print >> sys.stderr, 'Error! VERSION line not found'
    infp.close()
    outfp.close()
    os.rename('Mailman/Version.py.new', 'Mailman/Version.py')



def parseargs():
    parser = optparse.OptionParser(version=__version__,
                                   usage="""\
%prog [options] tagname

Manage releases of Mailman.  tagname is used in the various commands above.
It should essentially be the version number for the release, and is
required.""")
    parser.add_option('-t', '--tag',
                      default=False, action='store_true', help="""\
Tag all release files with tagname.""")
    parser.add_option('-p', '--package',
                      default=False, action='store_true',
                      help='Create the distribution package.')
    parser.add_option('-b', '--bump',
                      default=False, action='store_true', help="""\
Bump the revision number in key files to tagname.  This is done by textual
substitution.""")
    parser.add_option('-s', '--sign',
                      default=False, action='store_true', help="""\
Sign the release.  gpg will prompt you for your passphrase.""")
    opts, args = parser.parse_args()
    if len(args) > 1:
        print >> sys.stderr, 'Unexpected arguments:', SPACE(args[1:])
        sys.exit(1)
    if len(args) < 1:
        print >> sys.stderr, 'Required tagname argument is missing'
        sys.exit(1)
    return parser, opts, args[0]



def main():
    parser, opts, tagname = parseargs()

    # Very important!!!
    omask = os.umask(0)
    try:
        if opts.bump:
            do_bump(tagname)
        if opts.tag:
            tag_release(tagname)
        if opts.package:
            make_pkg(tagname, opts.sign)
    finally:
        os.umask(omask)



if __name__ == '__main__':
    main()