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[0]):
# 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="example@example.net",
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 [1]. 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.