#!/usr/bin/env python import os import os.path import sys import getopt import shutil import re changelog_version_matcher = re.compile(r'\((.*)-\d*\)') init_version_matcher = re.compile("VERSION = '.*'") ZOPE3_REVISION = 38491 ST_RELEASE_BRANCH = 'schooltool-0.11.x' SB_RELEASE_BRANCH = 'schoolbell-1.2.x' class Options(object): tag = False schoolbell = False schooltool = False clean = False strict = True # abort immediately when a command fails sign = True test = True builder = 'dpkg' force_source_include = False last_uploads = {'schooltool': '0.10-1', 'schoolbell': '1.1-1'} options = Options() def runcmd(cmd): """Run `cmd` on the shell.""" print "=== Running command: '%s' ===" % cmd retval = os.system(cmd) if retval and options.strict: print "%s failed!" % cmd sys.exit(3) return retval def get_version(package): """Get version of package from its Debian changelog.""" f = file('%s-debian/changelog' % package) firstline = f.readline() changelog_version = changelog_version_matcher.search(firstline).group(1) return changelog_version def tagrelease(): """Tag a release. Uses the version numbers from the Debian changelogs. Interactively asks for confirmation. Actions: - tags SchoolBell and SchoolTool releases - sets version number in schoolbell/__init__.py in the new tags - binds ST release to SB release """ sb_version = get_version('schoolbell') st_version = get_version('schooltool') answer = raw_input(""" About to tag SchoolBell version %s and SchoolTool version %s You may want to update the RELEASE.txt files for SB and ST before tagging. Proceed (y/n)? """ % (sb_version, st_version)) if answer != 'y': sys.exit(3) print "Tagging SchoolBell %s" % sb_version runcmd('svn cp svn+ssh://source.schooltool.org/svn/schooltool/branches/%s' ' svn+ssh://source.schooltool.org/svn/schooltool/tags/schoolbell-%s' ' -m "Tagging SchoolBell %s"' % (SB_RELEASE_BRANCH, sb_version, sb_version)) print "Tagging SchoolTool %s" % st_version runcmd('svn cp svn+ssh://source.schooltool.org/svn/schooltool/branches/%s' ' svn+ssh://source.schooltool.org/svn/schooltool/tags/schooltool-%s' ' -m "Tagging SchoolTool %s"' % (ST_RELEASE_BRANCH, st_version, st_version)) # We modify tags in place to set the version number. People might frown # on this, but I think it's OK to do in this situation. print "Setting version number for SchoolBell" runcmd('svn checkout' ' svn+ssh://source.schooltool.org/svn/schooltool/tags/schoolbell-%s' % sb_version) init_code_fn = 'schoolbell-%s/src/schoolbell/__init__.py' % sb_version sb_init_code = file(init_code_fn).read() assert sb_init_code.count('VERSION =') == 1 new_sb_init_code = init_version_matcher.sub('VERSION = "%s"' % sb_version, sb_init_code) file(init_code_fn, 'w').write(new_sb_init_code) runcmd('cd schoolbell-%s && svn ci -m' ' "Set SchoolBell version number in release"' % sb_version) shutil.rmtree('schoolbell-%s' % sb_version) print "Setting version number for SchoolTool" runcmd('svn checkout' ' svn+ssh://source.schooltool.org/svn/schooltool/tags/schooltool-%s' % st_version) init_code_fn = 'schooltool-%s/src/schooltool/__init__.py' % st_version st_init_code = file(init_code_fn).read() assert st_init_code.count('VERSION =') == 1 new_st_init_code = init_version_matcher.sub('VERSION = "%s"' % st_version, st_init_code) file(init_code_fn, 'w').write(new_st_init_code) runcmd('cd schooltool-%s && svn ci -m' ' "Set SchoolTool version number in release"' % st_version) print ('Binding SchoolTool %s external to SchoolBell %s.' % (st_version, sb_version)) runcmd("""\ cd schooltool-%s && svn propset svn:externals "schoolbell http://source.schooltool.org/svn/tags/schoolbell-%s/src/schoolbell" src && svn up && svn ci src -m "Bound SchoolTool %s to SchoolBell %s" """ % (st_version, sb_version, st_version, sb_version)) shutil.rmtree('schooltool-%s' % st_version) def build_deb(package): version = get_version(package) # locate the Zope 3 tarball zope3dist = 'Zope3-%d.tar.gz' % ZOPE3_REVISION if not os.path.exists(zope3dist): runcmd('svn export -r 38491 svn://svn.zope.org/repos/main/Zope3/branches/Zope-3.1 Zope3') runcmd('tar -czf %s Zope3' % zope3dist) shutil.rmtree('Zope3') # locate the distribution package dir = '%s-%s' % (package, version) distfilename = dir + '.tar.gz' if not os.path.exists(distfilename): # dist package not found; create one runcmd('svn export http://source.schooltool.org/svn/tags/%s' % dir) runcmd('cd %s && tar -xzf ../%s' % (dir, zope3dist)) runcmd('cd %s && make dist' % dir) os.rename('%s/dist/%s' % (dir, distfilename), distfilename) shutil.rmtree(dir) #now that we've made the tarball, we must test it if os.path.exists('testing'): shutil.rmtree('testing') runcmd('mkdir testing && cd testing && tar -xzf ../%s' % distfilename) if options.test and runcmd('cd testing/%s && make test ftest' % dir): print >> sys.stderr("Tests failed, removing %s!" % distfilename) os.unlink(distfilename) sys.exit(4) # create a .orig.tar.gz for debian tools origfilename = '%s_%s.orig.tar.gz' % (package, version) if os.path.exists(origfilename): os.unlink(origfilename) shutil.copyfile(distfilename, origfilename) if not os.path.isdir(dir): # extract the contents of the dist package runcmd('tar -xzf %s' % distfilename) assert os.path.isdir(dir) # apply debian specific patches to the source directory for filename in os.listdir(os.path.join(os.path.dirname(__file__), 'patches')): if filename.endswith('.%s' % package): absfilename = os.path.join(os.path.dirname(__file__), 'patches', filename) runcmd('patch -p1 -d %s < %s' % (dir, absfilename)) # add debian packaging info from the current directory shutil.rmtree('%s/debian' % dir, ignore_errors=True) # remove old Debian stuff if any runcmd("cp -R '%s-debian' '%s/debian'" % (package, dir)) shutil.rmtree("%s/debian/.svn" % dir) shutil.rmtree("%s/debian/po/.svn" % dir) if options.builder == 'dpkg': # invoke dpkg-buildpackage cmd = 'cd %s && dpkg-buildpackage -rfakeroot' % dir if not options.sign: cmd += ' -uc -us' if options.force_source_include: cmd += ' -sa' if options.last_uploads.has_key(package): cmd += ' -v%s' % options.last_uploads[package] elif options.builder == 'pdebuild': cmd = 'cd %s && pdebuild --buildresult ..' % dir if options.sign: cmd += ' --auto-debsign' debuildopts = '' if options.force_source_include: debuildopts += '-sa ' if options.last_uploads.has_key(package): debuildopts += '-v%s ' % options.last_uploads[package] if debuildopts != '': cmd += ' --debbuildopts %s' % repr(debuildopts) runcmd(cmd) def cleanup(): """Remove cruft from the current directory. Removes everything but schoolbell-debian, schooltool-debian and distribution files (schoolbell-1.2.3.tar.gz). Asks for confirmation interactively if there is anything to delete, otherwise passes silently. """ omit_files = ['.svn', 'build-debs.py', 'Zope3-%d.tar.gz' % ZOPE3_REVISION, 'schoolbell-debian', 'schooltool-debian', 'schoolbell-%s.tar.gz' % get_version('schoolbell'), 'schooltool-%s.tar.gz' % get_version('schooltool'), 'patches'] files = [] for filename in os.listdir(os.path.dirname(__file__)): if filename not in omit_files: files.append(filename) if not files: return print "Are you sure you want to delete these files:\n" + "\n".join(files) answer = raw_input() if answer == 'y': for filename in files: print 'Deleting %s' % filename if os.path.isdir(filename): shutil.rmtree(filename, ignore_errors=True) else: os.unlink(filename) print 'Done.' def usage(): print >> sys.stderr, """Usage: build-debs.py [--nosign] [--notest] [tag] schoolbell | schooltool | clean Example: build-debs.py clean tag schoolbell will remove all unnecessary files from the current directory, will tag new releases of SchoolBell and SchoolTool in the Subversion repository, and will build a tarball and a Debian package for SchoolBell. """ sys.exit(2) def main(argv): try: opts, args = getopt.gnu_getopt(sys.argv[1:], "", ["nosign", "notest"]) except getopt.GetoptError: usage() for opt, arg in opts: if opt == '--nosign': options.sign = False elif opt == '--notest': options.test = False else: print 'Unrecognized option: %s' % opt sys.exit(1) for arg in args: if arg == 'schoolbell': options.schoolbell = True elif arg == 'schooltool': options.schooltool = True elif arg == 'clean': options.clean = True elif arg == 'tag': options.tag = True else: print 'Unrecognized argument: %s' % arg usage() if not (options.clean or options.tag or options.schoolbell or options.schooltool): print >> sys.stderr, ("You must specify at least one of 'clean'," " 'tag', 'schoolbell' and 'schooltool'.") if options.clean: cleanup() if options.tag: tagrelease() if options.schoolbell: build_deb('schoolbell') if options.schooltool: build_deb('schooltool') if __name__ == '__main__': main(sys.argv)