The script shows how the standard Distutils module can be easily customized to add new features. In this example, a typical "setup.py" file is configured to extract revision information from a Subversion (revision control repository) database and use the information to set the version of the distribution.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
# setup.py -- customized Python Disutils distribution / installation script # Written By: Chadwick Stryker from distutils.core import setup, Distribution from os.path import join as pjoin import os, sys def committed_rev(): """ Fetches the last committed rev to the repository and returns it. This uses the pysvn module. If anything goes wrong it sets the revision to zero. """ try: import pysvn client = pysvn.Client() revs =  # search through the directory tree, starting at setup.py's directory for root, dirs, files in os.walk(sys.path): # don't search through the .svn directories if '.svn' in dirs: dirs.remove('.svn') # get the revision info for each file, one at a time for f in files: try: # try to get the SVN info. When checking a non working-copy # directory, an exception will be raised entry = client.info(pjoin(root,f)) # verify that the commit revision for the file is a number if entry.commit_revision.kind != \ pysvn.opt_revision_kind.number: raise # if we made it this far (i.e. no exception raised), # remember the revision of the file for later revs.append(entry.commit_revision.number) # otherwise, if client.info() fails or an exception is raised, # then skip this file except: pass # return the highest revision number of any file found in our search return max(revs) # if an unhandled exception occurs, then abort the search process and # return a rev of zero. An example would be an ImportError generated if # the pysvn module is not installed on the system except Exception, msg: print msg, print '-- aborting search for subversion repository revision number' return 0 class svnDistribution(Distribution): """ This subclass of the Distribution class is Subversion aware and searches to find the overall revision of the current working copy. """ def __init__(self, attrs): # this does most of the work... build_num = committed_rev() if build_num: # if there is SVN revision data, assign the new version number attrs['version'] = '%i' % build_num try: # then try to create a user specified version file filename, format = attrs['version_file'] file(filename,'w').write(format % build_num) # in case a 'version_file' attribute was not set, do nothing except KeyError: pass # the parent class does not know about 'version_file', so delete it del attrs['version_file'] Distribution.__init__(self, attrs) setup( name='example', description='example python module', author='Chad Stryker', author_email="email@example.com", py_modules=['example'], # the following attributes are used with the version number feature... # use this version ID if .svn data cannot be found version='SVN data unavailable', distclass = svnDistribution, # the version_file attribute is understood by the svnDistribution class # and provides a way for the version information to be accessible by # the module after it is installed version_file = ('_version.py', \ '#This file is generated automatically\nversion = "%i"') )
Being a user of both the Python programming language and the Subversion revision control system, I looked for a way to have my distribution package creation process automatically assign the distribution revision number to that of the latest committed revision in the Subversion repository. My original approach dynamically created a Distutils setup.py file from a Windows batch file. The batch file would run another Python script to get the revision information out of the repository and dynamically create the setup.py file to add the revision information. This process always seemed messy and complicated, so I looked for a cleaner approach.
This example demonstrates my eventual solution which confines all of the customization to the setup.py file. The most interesting part of this approach is the modification of the standard Distutils distribution class by creating a new distribution subclass. The new subclass performs the revision control assignment in the subclass' constructor and then calls the constructor for the original parent class. Distutils offers a convenient way to specify the new subclass . In order to make this approach work cleanly, the new subclass has to handle the case were no Subversion repository can be found. This will happen if someone tries to create a package from something other than Subversion repository's working copy. In this case, I preferred that the distribution creation process would continue but just omit the revision information.