#!/usr/bin/env python # Twisted, the Framework of Your Internet # Copyright (C) 2001 Matthew W. Lefkowitz # # This library is free software; you can redistribute it and/or # modify it under the terms of version 2.1 of the GNU Lesser General Public # License as published by the Free Software Foundation. # # This library 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 # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA from __future__ import nested_scopes ### Twisted Preamble # This makes sure that users don't have to set up their environment # specially in order to run these programs from bin/. import sys, os, string, time, glob if string.find(os.path.abspath(sys.argv[0]), os.sep+'Twisted') != -1: sys.path.insert(0, os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), os.pardir, os.pardir))) sys.path.insert(0, os.curdir) ### end of preamble # # The Twisted release script. This is terribly unix-centric. # from twisted.python import usage, util, failure from twisted import copyright import shutil, tempfile # magic for CVS guessing try: defaultRoot = open(util.sibpath(sys.argv[0], 'CVS/Root')).read().strip() except: defaultRoot = None defaultOptions = '--upver --tag --exp --dist --docs --balls --rel --deb' # --debi' debug = 0 class Options(usage.Options): optParameters = [['release-version', 'V', None, "The version of this release."], ['oldver', 'o', copyright.version, "The previous version to replace with the new version (only relevant for --upver)"], ['release', 'r', os.path.expanduser('~/Releases'), "The directory where your Twisted release archive is (only relevant for --rel)."], ['sfname', 'n', os.environ['USER'], "The Sourceforge.net user name"], ['cvsroot', 'c', defaultRoot, "The CVSROOT to export and/or checkout from. (only revelant for --exp and --checkout)."], ['root', 't', os.path.abspath('Twisted.CVS'), "The directory containing your CVS checkout (unneccessary if using --checkout)."]] longdesc = "Specify -v and all steps to execute; if no steps are given, default is %r." % defaultOptions def __init__(self): usage.Options.__init__(self) self['commands'] = [] def opt_checkout(self): """Checkout Twisted HEAD to $CWD/$root. Reqs: Access to a repository containing Twisted """ self['commands'].append(CheckOut) def opt_upver(self): self['commands'].append(UpdateVersion) def opt_tag(self): """`cvs tag -c 's a CVS checkout. Reqs: A `Twisted' directory.""" self['commands'].append(Tag) def opt_exp(self): """Exports Twisted from cvsroot as given with -c. Reqs: release-$ver has been tagged.""" self['commands'].append(Export) def opt_dist(self): """Copies and prepares a Twisted directory for distribution. Reqs: 'Twisted' exists, 'Twisted-$ver' doesn't.""" self['commands'].append(PrepareDist) def opt_docs(self): """Generate documentation. Reqs: Twisted-$ver exists.""" self['commands'].append(GenerateDocs) def opt_balls(self): """Creates tarballs. Reqs: Twisted-$ver exists.""" self['commands'].append(CreateTarballs) def opt_rel(self): """Copies tarballs to ~/Releases. Reqs: Tarballs.""" self['commands'].append(Release) def opt_deb(self): """Creates a debian mini-repository in ~/Releases/debian-ver/. Reqs: release and access to the twistedmatrix.com unstable chroot (through localhost: this means you need to run the script from pyramid!).""" self['commands'].append(MakeDebs) def opt_debi(self): """Copies a debian mini-repository in ~/Releases/debian-ver/ to /twisted/Debian/. Reqs: debs and existence of skeleton in /twisted/Debian""" self['commands'].append(InstallDebs) def opt_sourceforge(self): """Copies everything from /twisted/Releases to sourceforge""" self['commands'].append(Sourceforge) def opt_upgrade(self): """Upgrade Debian packages""" self['commands'].append(UpgradeDebian) def opt_debug(self): """Enable debug-mode: ask before running *every* shell command.""" global debug debug = 1 opt_d = opt_debug class Transaction: """I am a dead-simple Transaction.""" def run(self, data): """Try to run this self.doIt; if it fails, call self.undoIt and return a Failure.""" try: self.doIt(data) except: f = failure.Failure() print "%s failed, running uncommit." % self.__class__.__name__ self.undoIt(data, f) return f def doIt(self, data): """Le's get it on!""" raise NotImplementedError def undoIt(self, data, fail): """Oops.""" raise NotImplementedError #errors class DirectoryExists(OSError): """Some directory exists when it shouldn't.""" pass class DirectoryDoesntExist(OSError): """Some directory doesn't exist when it should.""" pass class CommandFailed(OSError): pass def main(): try: opts = Options() opts.parseOptions() except usage.UsageError, ue: print "%s: %s" % (sys.argv[0], ue) sys.exit(2) if not opts['release-version']: print "Please specify a version." sys.exit(2) defaultCommands = [UpdateVersion, Tag, Export, PrepareDist, GenerateDocs, CreateTarballs, Release, MakeDebs]#, InstallDebs] if not opts['commands']: opts['commands'] = defaultCommands sys.path.insert(0, os.path.abspath('Twisted')) print "going to do", [x.__name__ for x in opts['commands']] last = None for command in opts['commands']: try: f = command().run(opts) if f is not None: raise f except: print "ERROR: %s failed. last successful command was %s. Traceback follows:" % (command.__name__, last) import traceback traceback.print_exc() break last = command # utilities def sh(command, sensitive=0): """ I'll try to execute `command', and if `sensitive' is true, I'll ask before running it. If the command returns something other than 0, I'll raise CommandFailed(command). """ if debug or sensitive: if raw_input("%r ?? " % command).startswith('n'): return print command if os.system(command) != 0: raise CommandFailed(command) def replaceInFile(filename, oldstr, newstr): """I replace the text `oldstr' with `newstr' in `filename' using sed and mv.""" sh('cp %s %s.bak' % (filename, filename)) sh("sed -e 's/%s/%s/' < %s > %s.new" % (oldstr, newstr, filename, filename)) sh('cp %s.new %s' % (filename, filename)) ## # The transactions. ## class CheckOut(Transaction): # failure modes: # * `root' already exists. # # state changes: # * `root' is created. def doIt(self, opts): print "CheckOut" cvsroot = opts['cvsroot'] target = opts['root'] if os.path.exists(target): raise DirectoryExists("%s (--root) already exists" % target) tmp = None if os.path.exists('Twisted'): tmp = tempfile.mktemp() sh('mv Twisted %s' % tmp) #hrm... sh('cvs -d %s co Twisted' % (cvsroot)) sh('mv Twisted %s' % target) if tmp: sh('mv %s Twisted' % tmp) def undoIt(self, opts, fail): if fail.check(DirectoryExists): return #we don't want to remove the directory if we didn't create it if os.path.exists(opts['root']): sh('rm -rf %s' % opts['root']) class UpdateVersion(Transaction): # fail on # * ? (a failed `sh' command) # # state changes: # * the CVS tree is modified. to back out we must copy all the .bak files created by replaceInFile back to their original location, and remove them. # * the tree is committed. this is atomic, afaict. [this isn't, but # pretend it is anyway -- Moshe] files = None def doIt(self, opts): print "UpdateVersion" oldver = opts['oldver'] newver = opts['release-version'] r = opts['root'] self.files = ('README', 'doc/howto/installing-twisted.html', 'twisted/copyright.py') for file in self.files: replaceInFile(os.path.join(r, file), oldver, newver) sh('cd %s && cvs -q diff || true' % (r)) sh('cd %s && cvs commit -m "Preparing for %s" %s' % (r, newver, ' '.join(self.files)), 1) def undoIt(self, opts, fail): if self.files: for file in self.files: try: sh('mv %s.bak %s' % (file, file)) except: print "WARNING: couldn't move %s.bak back to %s, chugging along" % (file, file) class Tag(Transaction): def doIt(self, opts): print "Tag" ver = opts['release-version'] sh('cd %s && cvs tag -c release-%s' % (opts['root'], ver.replace('.', '_')), 1) def undoIt(self, opts, fail): #tagging is atomic, afaict pass class Export(Transaction): # errors? no idea def doIt(self, opts): print "Export" root = opts['cvsroot'] ver = opts['release-version'] sh('cvs -d%s export -r release-%s Twisted' % (root, ver.replace('.', '_'))) def undoIt(self, opts, fail): sh('rm -rf Twisted') class PrepareDist(Transaction): def doIt(self, opts): print "PrepareDist" ver = opts['release-version'] tdir = "Twisted-%s" % ver if os.path.exists(tdir): raise DirectoryExists("PrepareDist: %s exists already." % tdir) shutil.copytree('Twisted', tdir) def undoIt(self, opts, fail): #don't delete the directory if we didn't create it! if not fail.check(DirectoryExists): ver = opts['release-version'] tdir = "Twisted-%s" % ver sh('rm -rf %s' % tdir) class GenerateDocs(Transaction): def doIt(self, opts): print "GenerateDocs" ver = opts['release-version'] tdir = "Twisted-%s" % ver if not os.path.exists('%s' % tdir): raise DirectoryDoesntExist("GenerateDocs: %s doesn't exist!" % tdir) print "makeDocs: epydoc." sh('cd %s && ./admin/epyrun -o doc/api' % (tdir)) print "makeDocs: process-docs." sh('cd %s && ./admin/process-docs %s' % (tdir, ver)) #shwack the crap print "copyDist: Stripping *.pyc, .cvsignore" for x in ['*.pyc', '.cvsignore']: sh('find %s -name "%s" | xargs rm -f' % (tdir, x)) def undoIt(self, opts, fail): if fail.check(DirectoryDoesntExist): #no state change here return ver = opts['release-version'] tdir = "Twisted-%s" % ver #first shwack the epydocs try: sh('mv %s/doc/api/index.html.bak gendocs.index.html' % tdir) except: sh('mv %s/doc/api/index.html gendocs.index.html' % tdir) sh('rm -rf %s/doc/api/*' % tdir) sh('mv gendocs.index.html %s/doc/api/index.html' % tdir) #then swhack the results of generate-domdocs sh('rm -f %s/doc/howto/*.xhtml' % tdir) sh('rm -f %s/doc/specifications/*.xhtml' % tdir) #then swhack the results of the latex stuff sh('cd %s/doc/howto && rm -f *.eps *.tex *.aux *.log book.*' % tdir) class CreateTarballs(Transaction): def doIt(self, opts): print "CreateTarballs" ver = opts['release-version'] tdir = "Twisted-%s" % ver if not os.path.exists(tdir): raise DirectoryDoesntExist("%s doesn't exist" % tdir) print "CreateTarballs: Twisted_NoDocs." sh(''' tar --exclude %(tdir)s/doc -cf - %(tdir)s |gzip -9 > Twisted_NoDocs-%(ver)s.tar.gz && tar --exclude %(tdir)s/doc -cjf Twisted_NoDocs-%(ver)s.tar.bz2 %(tdir)s ''' % locals()) print "CreateTarballs: Twisted" sh(''' tar cf - %(tdir)s | gzip -9 > %(tdir)s.tar.gz&& tar cjf %(tdir)s.tar.bz2 %(tdir)s ''' % locals()) print "makeBalls: TwistedDocs" docdir = "TwistedDocs-%s" % ver sh( ''' cd %(tdir)s && mv doc %(docdir)s tar cf - %(docdir)s | gzip -9 > %(docdir)s.tar.gz&& mv %(docdir)s.tar.gz ../ && tar cjf %(docdir)s.tar.bz2 %(docdir)s && mv %(docdir)s.tar.bz2 ../ ''' % locals()) def undoIt(self, opts, fail): ver = opts['release-version'] for ext in ['tar.gz', 'tar.bz2']: for prefix in ['Twisted_NoDocs', 'TwistedDocs', 'Twisted']: try: sh('rm -f %s-%s.%s' % (prefix, ver, ext)) except: pass class Release(Transaction): def doIt(self, opts): print "Release" rel = opts['release'] ver = opts['release-version'] tdir = "Twisted-%s" % ver if not os.path.exists(rel): print "Release: Creating", rel os.mkdir(rel) if not os.path.exists("%s/old" % rel): print "Release: creating %s/old" % rel os.mkdir("%s/old" % rel) print "Release: Moving old releases to %s/old" % rel sh('mv %(rel)s/*.tar.gz %(rel)s/*.tar.bz2 %(rel)s/old || true' % locals()) print "Release: Copying Twisted_NoDocs." sh(''' cp Twisted_NoDocs-%(ver)s.tar.gz %(rel)s && cp Twisted_NoDocs-%(ver)s.tar.bz2 %(rel)s ''' % locals()) print "Release: Copying TwistedDocs." sh(''' cp TwistedDocs-%(ver)s.tar.gz %(rel)s && cp TwistedDocs-%(ver)s.tar.bz2 %(rel)s ''' % locals()) print "Release: Copying Twisted." sh(''' cp %(tdir)s.tar.gz %(rel)s && cp %(tdir)s.tar.bz2 %(rel)s ''' % locals()) def undoIt(self, opts, fail): ver = opts['release-version'] rel = opts['release'] for ext in ['zip', 'tar.gz', 'tar.bz2']: for prefix in ['Twisted_NoDocs', 'TwistedDocs', 'Twisted']: try: sh('rm -f %s/%s-%s.%s' % (rel, prefix, ver, ext)) except: pass class MakeDebs(Transaction): def doIt(self, opts): print "MakeDebs" rel = opts['release'] ver = opts['release-version'] tgz = os.path.join(rel, 'Twisted-%s.tar.gz' % ver) unique = '%s.%s' % (time.time(), os.getpid()) os.mkdir('/sid-chroot/tmp/%s' % unique) sh('cd /sid-chroot/tmp/%s && tar xzf %s' % (unique, tgz)) sh("ssh -p 9022 localhost " "'cd /tmp/%(unique)s && ./Twisted-%(ver)s/admin/make-deb -a'"%vars()) if not os.path.isdir(os.path.join(rel, 'debian-%s' % ver)): os.mkdir(os.path.join(rel, 'debian-%s' % ver)) sys.stdout.write("Moving files to %s" % os.path.join(rel, 'debian-%s'%ver)) sys.stdout.flush() for file in glob.glob('/sid-chroot/tmp/%s/*' % unique): if not os.path.isfile(file): continue sh('cp %(file)s %(rel)s/debian-%(ver)s' % vars()) sys.stdout.write(".") sys.stdout.flush() sys.stdout.write("\n") os.chdir("%(rel)s/debian-%(ver)s" % vars()) sh('tar xzf %(tgz)s Twisted-%(ver)s/admin' % vars()) sh('mv Twisted-%(ver)s/admin/override .' % vars()) sh('./Twisted-%(ver)s/admin/createpackages override'% vars()) sh('rm -rf woody') os.mkdir('woody') sh('cp *.orig.tar.gz *.diff.gz *.dsc woody/') os.chdir('woody') sh('dpkg-source -x *.dsc') twisted_dir = filter(os.path.isdir, glob.glob('twisted-*'))[0] os.chdir(twisted_dir) replaceInFile('debian/changelog', ver+'-1', ver+'-1woody') replaceInFile('debian/control', ', python2.3-dev', '') sh('rm -f debian/*.bak') sh('dpkg-buildpackage -rfakeroot -us -uc') os.chdir('..') sh('rm -rf %(twisted_dir)s' % vars()) sh('../Twisted-%(ver)s/admin/createpackages ../override'% vars()) sh('rm -rf ../Twisted-%(ver)s' % vars()) sh('rm -rf /sid-chroot/tmp/%s' % unique) def undoIt(self, opts, fail): rel = opts['release'] ver = opts['release-version'] sh("rm -rf %(rel)s/debian-%(ver)s" % vars()) class InstallDebs(Transaction): target = '/twisted/Debian' def doIt(self, opts): rel = opts['release'] ver = opts['release-version'] target = self.target for file in ('Packages.gz', 'Sources.gz', 'override'): if os.path.isfile('%(target)s/%(file)s' % vars()): sh('rm -f %(target)s/%(file)s.old' % vars()) sh('mv %(target)s/%(file)s %(target)s/%(file)s.old' % vars()) if os.path.isfile('%(target)s/woody/%(file)s' % vars()): sh('rm -f %(target)s/woody/%(file)s.old' % vars()) sh('mv %(target)s/woody/%(file)s %(target)s/woody/%(file)s.old' % vars()) for file in os.listdir('%(rel)s/debian-%(ver)s/' % vars()): if file == 'woody': continue sh("cp %(rel)s/debian-%(ver)s/%(file)s %(target)s/" % vars()) sh("cp %(rel)s/debian-%(ver)s/woody/* %(target)s/woody/" % vars()) def undoIt(self, opts, fail): rel = opts['release'] ver = opts['release-version'] target = self.target for dir in (target, target+'/woody'): for file in glob.glob('%(dir)s/*.old' % vars()): new = os.path.splitext(file)[0] sh('mv %(file)s %(new)s' % vars()) sh('mv %(file)s %(new)s' % vars()) sh('rm -f %(dir)s/*%(ver)s*' % vars()) class Sourceforge(Transaction): def doIt(self, opts): name = opts['sfname'] rel = opts['release'] ver = opts['release-version'] path = '/home/users/'+name[0]+'/'+name[:2]+'/'+name sh("ssh %(name)s@shell.sf.net mkdir Twisted-%(ver)s || true" % vars()) sh("scp -r %(rel)s/Twisted*%(ver)s* %(rel)s/debian-%(ver)s " "%(name)s@shell.sf.net:%(path)s/Twisted-%(ver)s/" % vars()) sh("echo " "'" "rm -f /home/groups/t/tw/twisted/htdocs/debian/woody/Packages.gz&&" "rm -f /home/groups/t/tw/twisted/htdocs/debian/woody/Sources.gz&&" "rm -f /home/groups/t/tw/twisted/htdocs/debian/woody/override&&" "rm -f /home/groups/t/tw/twisted/htdocs/debian/Packages.gz&&" "rm -f /home/groups/t/tw/twisted/htdocs/debian/Sources.gz&&" "rm -f /home/groups/t/tw/twisted/htdocs/debian/override&&" "mv Twisted-%(ver)s/debian-%(ver)s/woody/* " "/home/groups/t/tw/twisted/htdocs/debian/woody/&&" "rmdir Twisted-%(ver)s/debian-%(ver)s/woody&&" "mv Twisted-%(ver)s/debian-%(ver)s/* " "/home/groups/t/tw/twisted/htdocs/debian/&&" "rmdir Twisted-%(ver)s/debian-%(ver)s&&" "mv Twisted-%(ver)s/* /home/groups/t/tw/twisted/htdocs&&" "cd /home/groups/t/tw/twisted/htdocs&&" "tar xzf TwistedDocs-%(ver)s.tar.gz'" "|ssh %(name)s@shell.sf.net newgrp twisted" % vars()) class UpgradeDebian(Transaction): def doIt(self, opts): rel = opts['release'] ver = opts['release-version'] path = "%(release)s/debian-%(ver)s/woody/" % vars() sh("sudo dpkg -i %(path)s/*.deb" % vars()) if __name__=='__main__': main()