Eliminate / replace remaining cPickle references in test scripts.
[scons.git] / SConstruct
index 374cef0cdbe814cfcf86ac4865c8ccae2cdea431..605f43f116f4a0d6b861d2785bf47509d3f3b0f3 100644 (file)
@@ -3,13 +3,14 @@
 #
 # See the README file for an overview of how SCons is built and tested.
 #
+from __future__ import generators  ### KEEP FOR COMPATIBILITY FIXERS
 
-# When this gets changed, you also need to change test/option-v.py
-# so it looks for the right string.
-copyright_years = '2001, 2002, 2003, 2004, 2005, 2006, 2007'
+# When this gets changed, you must also change the copyright_years string
+# in QMTest/TestSCons.py so the test scripts look for the right string.
+copyright_years = '2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010'
 
 # This gets inserted into the man pages to reflect the month of release.
-month_year = 'January 2007'
+month_year = 'January 2010'
 
 #
 # __COPYRIGHT__
@@ -35,20 +36,19 @@ month_year = 'January 2007'
 #
 
 import distutils.util
+import fnmatch
 import os
 import os.path
 import re
-import socket
 import stat
-import string
 import sys
-import time
+import tempfile
 
 project = 'scons'
-default_version = '0.96.94'
+default_version = '1.3.0'
 copyright = "Copyright (c) %s The SCons Foundation" % copyright_years
 
-Default('.')
+platform = distutils.util.get_platform()
 
 SConsignFile()
 
@@ -57,128 +57,206 @@ SConsignFile()
 # is available on this system.
 #
 def whereis(file):
-    for dir in string.split(os.environ['PATH'], os.pathsep):
+    exts = ['']
+    if platform == "win32":
+        exts += ['.exe']
+    for dir in os.environ['PATH'].split(os.pathsep):
         f = os.path.join(dir, file)
-        if os.path.isfile(f):
-            try:
-                st = os.stat(f)
-            except:
-                continue
-            if stat.S_IMODE(st[stat.ST_MODE]) & 0111:
-                return f
+        for ext in exts:
+            f_ext = f + ext
+            if os.path.isfile(f_ext):
+                try:
+                    st = os.stat(f_ext)
+                except:
+                    continue
+                if stat.S_IMODE(st[stat.ST_MODE]) & 0111:
+                    return f_ext
     return None
 
 #
-# We let the presence or absence of various utilities determine
-# whether or not we bother to build certain pieces of things.
-# This should allow people to still do SCons work even if they
-# don't have Aegis or RPM installed, for example.
+# We let the presence or absence of various utilities determine whether
+# or not we bother to build certain pieces of things.  This should allow
+# people to still do SCons packaging work even if they don't have all
+# of the utilities installed (e.g. RPM).
 #
-aegis = whereis('aegis')
-aesub = whereis('aesub')
 dh_builddeb = whereis('dh_builddeb')
 fakeroot = whereis('fakeroot')
 gzip = whereis('gzip')
 rpmbuild = whereis('rpmbuild') or whereis('rpm')
+hg = os.path.exists('.hg') and whereis('hg')
+svn = os.path.exists('.svn') and whereis('svn')
 unzip = whereis('unzip')
 zip = whereis('zip')
 
 #
 # Now grab the information that we "build" into the files.
 #
-try:
-    date = ARGUMENTS['date']
-except:
+date = ARGUMENTS.get('DATE')
+if not date:
+    import time
     date = time.strftime("%Y/%m/%d %H:%M:%S", time.localtime(time.time()))
 
-if ARGUMENTS.has_key('developer'):
-    developer = ARGUMENTS['developer']
-elif os.environ.has_key('USERNAME'):
-    developer = os.environ['USERNAME']
-elif os.environ.has_key('LOGNAME'):
-    developer = os.environ['LOGNAME']
-elif os.environ.has_key('USER'):
-    developer = os.environ['USER']
-
-if ARGUMENTS.has_key('build_system'):
-    build_system = ARGUMENTS['build_system']
-else:
-    build_system = string.split(socket.gethostname(), '.')[0]
-
-if ARGUMENTS.has_key('version'):
-    revision = ARGUMENTS['version']
-elif aesub:
-    revision = os.popen(aesub + " \\$version", "r").read()[:-1]
-else:
-    revision = default_version
-
-# This is old code that adds an initial "0" to revision numbers < 10.
-#a = string.split(revision, '.')
-#arr = [a[0]]
-#for s in a[1:]:
-#    if len(s) == 1:
-#        s = '0' + s
-#    arr.append(s)
-#revision = string.join(arr, '.')
-
-# Here's how we'd turn the calculated $revision into our package $version.
-# This makes it difficult to coordinate with other files (debian/changelog
-# and rpm/scons.spec) that hard-code the version number, so just go with
-# the flow for now and hard code it here, too.
-#if len(arr) >= 2:
-#    arr = arr[:-1]
-#def xxx(str):
-#    if str[0] == 'C' or str[0] == 'D':
-#        str = str[1:]
-#    while len(str) > 2 and str[0] == '0':
-#        str = str[1:]
-#    return str
-#arr = map(lambda x, xxx=xxx: xxx(x), arr)
-#version = string.join(arr, '.')
-version = default_version
-
-build_id = string.replace(revision, version + '.', '')
-
-if ARGUMENTS.has_key('change'):
-    change = ARGUMENTS['change']
-elif aesub:
-    change = os.popen(aesub + " \\$change", "r").read()[:-1]
-else:
-    change = default_version
+developer = ARGUMENTS.get('DEVELOPER')
+if not developer:
+    for variable in ['USERNAME', 'LOGNAME', 'USER']:
+        developer = os.environ.get(variable)
+        if developer:
+            break
+
+build_system = ARGUMENTS.get('BUILD_SYSTEM')
+if not build_system:
+    import socket
+    build_system = socket.gethostname().split('.')[0]
+
+version = ARGUMENTS.get('VERSION', '')
+if not version:
+    version = default_version
+
+hg_status_lines = []
+svn_status_lines = []
+
+if hg:
+    cmd = "%s status --all 2> /dev/null" % hg
+    hg_status_lines = os.popen(cmd, "r").readlines()
+
+if svn:
+    cmd = "%s status --verbose 2> /dev/null" % svn
+    svn_status_lines = os.popen(cmd, "r").readlines()
+
+revision = ARGUMENTS.get('REVISION', '')
+def generate_build_id(revision):
+    return revision
+
+if not revision and hg:
+    hg_heads = os.popen("%s heads 2> /dev/null" % hg, "r").read()
+    cs = re.search('changeset:\s+(\S+)', hg_heads)
+    if cs:
+        revision = cs.group(1)
+        b = re.search('branch:\s+(\S+)', hg_heads)
+        if b:
+            revision = b.group(1) + ':' + revision
+        def generate_build_id(revision):
+            result = revision
+            if [l for l in hg_status_lines if l[0] in 'AMR!']:
+                result = result + '[MODIFIED]'
+            return result
+
+if not revision and svn:
+    svn_info = os.popen("%s info 2> /dev/null" % svn, "r").read()
+    m = re.search('Revision: (\d+)', svn_info)
+    if m:
+        revision = m.group(1)
+        def generate_build_id(revision):
+            result = 'r' + revision
+            if [l for l in svn_status_lines if l[0] in 'ACDMR']:
+                result = result + '[MODIFIED]'
+            return result
+
+checkpoint = ARGUMENTS.get('CHECKPOINT', '')
+if checkpoint:
+    if checkpoint == 'd':
+        import time
+        checkpoint = time.strftime('d%Y%m%d', time.localtime(time.time()))
+    elif checkpoint == 'r':
+        checkpoint = 'r' + revision
+    version = version + '.' + checkpoint
+
+build_id = ARGUMENTS.get('BUILD_ID')
+if build_id is None:
+    if revision:
+        build_id = generate_build_id(revision)
+    else:
+        build_id = ''
 
 python_ver = sys.version[0:3]
 
-platform = distutils.util.get_platform()
+# Re-exporting LD_LIBRARY_PATH is necessary if the Python version was
+# built with the --enable-shared option.
 
 ENV = { 'PATH' : os.environ['PATH'] }
-for key in ['AEGIS_PROJECT', 'LOGNAME', 'PYTHONPATH']:
-    if os.environ.has_key(key):
+for key in ['LOGNAME', 'PYTHONPATH', 'LD_LIBRARY_PATH']:
+    if key in os.environ:
         ENV[key] = os.environ[key]
 
-cwd_build = os.path.join(os.getcwd(), "build")
+build_dir = ARGUMENTS.get('BUILDDIR', 'build')
+if not os.path.isabs(build_dir):
+    build_dir = os.path.normpath(os.path.join(os.getcwd(), build_dir))
+
+command_line_variables = [
+    ("BUILDDIR=",       "The directory in which to build the packages.  " +
+                        "The default is the './build' subdirectory."),
+
+    ("BUILD_ID=",       "An identifier for the specific build." +
+                        "The default is the Subversion revision number."),
+
+    ("BUILD_SYSTEM=",   "The system on which the packages were built.  " +
+                        "The default is whatever hostname is returned " +
+                        "by socket.gethostname()."),
+
+    ("CHECKPOINT=",     "The specific checkpoint release being packaged, " +
+                        "which will be appended to the VERSION string.  " +
+                        "A value of CHECKPOINT=d will generate a string " +
+                        "of 'd' plus today's date in the format YYYMMDD.  " +
+                        "A value of CHECKPOINT=r will generate a " +
+                        "string of 'r' plus the Subversion revision " +
+                        "number.  Any other CHECKPOINT= string will be " +
+                        "used as is.  There is no default value."),
+
+    ("DATE=",           "The date string representing when the packaging " +
+                        "build occurred.  The default is the day and time " +
+                        "the SConstruct file was invoked, in the format " +
+                        "YYYY/MM/DD HH:MM:SS."),
+
+    ("DEVELOPER=",      "The developer who created the packages.  " +
+                        "The default is the first set environment " +
+                        "variable from the list $USERNAME, $LOGNAME, $USER."),
+
+    ("REVISION=",       "The revision number of the source being built.  " +
+                        "The default is the Subversion revision returned " +
+                        "'svn info', with an appended string of " +
+                        "'[MODIFIED]' if there are any changes in the " +
+                        "working copy."),
+
+    ("VERSION=",        "The SCons version being packaged.  The default " +
+                        "is the hard-coded value '%s' " % default_version +
+                        "from this SConstruct file."),
+]
+
+Default('.', build_dir)
 
 packaging_flavors = [
-    'deb',
-    'rpm',
-    'tar-gz',
-    'src-tar-gz',
-    'local-tar-gz',
-    'zip',
-    'src-zip',
-    'local-zip',
+    ('deb',             "A .deb package.  (This is currently not supported.)"),
+
+    ('rpm',             "A RedHat Package Manager file."),
+
+    ('tar-gz',          "The normal .tar.gz file for end-user installation."),
+
+    ('src-tar-gz',      "A .tar.gz file containing all the source " +
+                        "(including tests and documentation)."),
+
+    ('local-tar-gz',    "A .tar.gz file for dropping into other software " +
+                        "for local use."),
+
+    ('zip',             "The normal .zip file for end-user installation."),
+
+    ('src-zip',         "A .zip file containing all the source " +
+                        "(including tests and documentation)."),
+
+    ('local-zip',       "A .zip file for dropping into other software " +
+                        "for local use."),
 ]
 
-test_deb_dir          = os.path.join(cwd_build, "test-deb")
-test_rpm_dir          = os.path.join(cwd_build, "test-rpm")
-test_tar_gz_dir       = os.path.join(cwd_build, "test-tar-gz")
-test_src_tar_gz_dir   = os.path.join(cwd_build, "test-src-tar-gz")
-test_local_tar_gz_dir = os.path.join(cwd_build, "test-local-tar-gz")
-test_zip_dir          = os.path.join(cwd_build, "test-zip")
-test_src_zip_dir      = os.path.join(cwd_build, "test-src-zip")
-test_local_zip_dir    = os.path.join(cwd_build, "test-local-zip")
+test_deb_dir          = os.path.join(build_dir, "test-deb")
+test_rpm_dir          = os.path.join(build_dir, "test-rpm")
+test_tar_gz_dir       = os.path.join(build_dir, "test-tar-gz")
+test_src_tar_gz_dir   = os.path.join(build_dir, "test-src-tar-gz")
+test_local_tar_gz_dir = os.path.join(build_dir, "test-local-tar-gz")
+test_zip_dir          = os.path.join(build_dir, "test-zip")
+test_src_zip_dir      = os.path.join(build_dir, "test-src-zip")
+test_local_zip_dir    = os.path.join(build_dir, "test-local-zip")
 
-unpack_tar_gz_dir     = os.path.join(cwd_build, "unpack-tar-gz")
-unpack_zip_dir        = os.path.join(cwd_build, "unpack-zip")
+unpack_tar_gz_dir     = os.path.join(build_dir, "unpack-tar-gz")
+unpack_zip_dir        = os.path.join(build_dir, "unpack-zip")
 
 if platform == "win32":
     tar_hflag = ''
@@ -190,6 +268,43 @@ else:
     project_script_subinst_dir = 'bin'
 
 
+
+import textwrap
+
+indent_fmt = '  %-26s  '
+
+Help("""\
+The following aliases build packages of various types, and unpack the
+contents into build/test-$PACKAGE subdirectories, which can be used by the
+runtest.py -p option to run tests against what's been actually packaged:
+
+""")
+
+aliases = sorted(packaging_flavors + [('doc', 'The SCons documentation.')])
+
+for alias, help_text in aliases:
+    tw = textwrap.TextWrapper(
+        width = 78,
+        initial_indent = indent_fmt % alias,
+        subsequent_indent = indent_fmt % '' + '  ',
+    )
+    Help(tw.fill(help_text) + '\n')
+
+Help("""
+The following command-line variables can be set:
+
+""")
+
+for variable, help_text in command_line_variables:
+    tw = textwrap.TextWrapper(
+        width = 78,
+        initial_indent = indent_fmt % variable,
+        subsequent_indent = indent_fmt % '' + '  ',
+    )
+    Help(tw.fill(help_text) + '\n')
+
+
+
 zcat = 'gzip -d -c'
 
 #
@@ -249,16 +364,16 @@ def SCons_revision(target, source, env):
     # Note:  We construct the __*__ substitution strings here
     # so that they don't get replaced when this file gets
     # copied into the tree for packaging.
-    contents = string.replace(contents, '__BUILD'     + '__', env['BUILD'])
-    contents = string.replace(contents, '__BUILDSYS'  + '__', env['BUILDSYS'])
-    contents = string.replace(contents, '__COPYRIGHT' + '__', env['COPYRIGHT'])
-    contents = string.replace(contents, '__DATE'      + '__', env['DATE'])
-    contents = string.replace(contents, '__DEVELOPER' + '__', env['DEVELOPER'])
-    contents = string.replace(contents, '__FILE'      + '__', str(source[0]))
-    contents = string.replace(contents, '__MONTH_YEAR'+ '__', env['MONTH_YEAR'])
-    contents = string.replace(contents, '__REVISION'  + '__', env['REVISION'])
-    contents = string.replace(contents, '__VERSION'   + '__', env['VERSION'])
-    contents = string.replace(contents, '__NULL'      + '__', '')
+    contents = contents.replace('__BUILD'     + '__', env['BUILD'])
+    contents = contents.replace('__BUILDSYS'  + '__', env['BUILDSYS'])
+    contents = contents.replace('__COPYRIGHT' + '__', env['COPYRIGHT'])
+    contents = contents.replace('__DATE'      + '__', env['DATE'])
+    contents = contents.replace('__DEVELOPER' + '__', env['DEVELOPER'])
+    contents = contents.replace('__FILE'      + '__', str(source[0]))
+    contents = contents.replace('__MONTH_YEAR'+ '__', env['MONTH_YEAR'])
+    contents = contents.replace('__REVISION'  + '__', env['REVISION'])
+    contents = contents.replace('__VERSION'   + '__', env['VERSION'])
+    contents = contents.replace('__NULL'      + '__', '')
     open(t, 'wb').write(contents)
     os.chmod(t, os.stat(s)[0])
 
@@ -291,7 +406,7 @@ def soelim(target, source, env):
     tfp.close()
 
 def soscan(node, env, path):
-    c = node.get_contents()
+    c = node.get_text_contents()
     return re.compile(r"^[\.']so\s+(\S+)", re.M).findall(c)
 
 soelimbuilder = Builder(action = Action(soelim),
@@ -305,10 +420,12 @@ env = Environment(
                    ENV                 = ENV,
 
                    BUILD               = build_id,
+                   BUILDDIR            = build_dir,
                    BUILDSYS            = build_system,
                    COPYRIGHT           = copyright,
                    DATE                = date,
                    DEVELOPER           = developer,
+                   DISTDIR             = os.path.join(build_dir, 'dist'),
                    MONTH_YEAR          = month_year,
                    REVISION            = revision,
                    VERSION             = version,
@@ -339,7 +456,7 @@ env = Environment(
                    BUILDERS            = { 'SCons_revision' : revbuilder,
                                            'SOElim' : soelimbuilder },
 
-                   PYTHON              = sys.executable,
+                   PYTHON              = '"%s"' % sys.executable,
                    PYTHONFLAGS         = '-tt',
                  )
 
@@ -390,11 +507,62 @@ python_scons = {
 
         'buildermap'    : {},
 
+        'extra_rpm_files' : [],
+
         'explicit_deps' : {
                             'SCons/__init__.py' : Version_values,
                           },
 }
 
+# Figure out the name of a .egg-info file that might be generated
+# as part of the RPM package.  There are two complicating factors.
+#
+# First, the RPM spec file we generate will just execute "python", not
+# necessarily the one in sys.executable.  If *that* version of python has
+# a distutils that knows about Python eggs, then setup.py will generate a
+# .egg-info file, so we have to execute any distutils logic in a subshell.
+#
+# Second, we can't just have the subshell check for the existence of the
+# distutils.command.install_egg_info module and generate the expected
+# file name by hand, the way we used to, because different systems can
+# have slightly different .egg-info naming conventions.  (Specifically,
+# Ubuntu overrides the default behavior to remove the Python version
+# string from the .egg-info file name.)  The right way to do this is to
+# actually call into the install_egg_info() class to have it generate
+# the expected name for us.
+#
+# This is all complicated enough that we do it by writing an in-line
+# script to a temporary file and then feeding it to a separate invocation
+# of "python" to tell us the actual name of the generated .egg-info file.
+
+print_egg_info_name = """
+try:
+    from distutils.dist import Distribution
+    from distutils.command.install_egg_info import install_egg_info
+except ImportError:
+    pass
+else:
+    dist = Distribution({'name' : "scons", 'version' : '%s'})
+    i = install_egg_info(dist)
+    i.finalize_options()
+    import os.path
+    print os.path.split(i.outputs[0])[1]
+""" % version
+
+try:
+    fd, tfname = tempfile.mkstemp()
+    tfp = os.fdopen(fd, "w")
+    tfp.write(print_egg_info_name)
+    tfp.close()
+    egg_info_file = os.popen("python %s" % tfname).read()[:-1]
+    if egg_info_file:
+        python_scons['extra_rpm_files'].append(egg_info_file)
+finally:
+    try:
+        os.unlink(tfname)
+    except EnvironmentError:
+        pass
+
 #
 # The original packaging scheme would have have required us to push
 # the Python version number into the package name (python1.5-scons,
@@ -501,14 +669,15 @@ scons = {
                             'sconsign.1',
                             'scons-time.1',
                             'script/scons.bat',
+                            #'script/scons-post-install.py',
                             'setup.cfg',
                             'setup.py',
                           ],
 
         'filemap'       : {
-                            'scons.1' : '../build/doc/man/scons.1',
-                            'sconsign.1' : '../build/doc/man/sconsign.1',
-                            'scons-time.1' : '../build/doc/man/scons-time.1',
+                            'scons.1' : '$BUILDDIR/doc/man/scons.1',
+                            'sconsign.1' : '$BUILDDIR/doc/man/sconsign.1',
+                            'scons-time.1' : '$BUILDDIR/doc/man/scons-time.1',
                           },
 
         'buildermap'    : {
@@ -525,7 +694,7 @@ scons = {
                            },
 }
 
-scripts = ['scons', 'sconsign']
+scripts = ['scons', 'sconsign', 'scons-time']
 
 src_deps = []
 src_files = []
@@ -538,10 +707,10 @@ for p in [ scons ]:
     pkg_version = "%s-%s" % (pkg, version)
 
     src = 'src'
-    if p.has_key('src_subdir'):
+    if 'src_subdir' in p:
         src = os.path.join(src, p['src_subdir'])
 
-    build = os.path.join('build', pkg)
+    build = os.path.join(build_dir, pkg)
 
     tar_gz = os.path.join(build, 'dist', "%s.tar.gz" % pkg_version)
     platform_tar_gz = os.path.join(build,
@@ -551,7 +720,10 @@ for p in [ scons ]:
     platform_zip = os.path.join(build,
                                 'dist',
                                 "%s.%s.zip" % (pkg_version, platform))
-    win32_exe = os.path.join(build, 'dist', "%s.win32.exe" % pkg_version)
+    if platform == "win-amd64":
+        win32_exe = os.path.join(build, 'dist', "%s.win-amd64.exe" % pkg_version)
+    else:
+        win32_exe = os.path.join(build, 'dist', "%s.win32.exe" % pkg_version)
 
     #
     # Update the environment with the relevant information
@@ -564,7 +736,7 @@ for p in [ scons ]:
     setup_py = os.path.join(build, 'setup.py')
     env.Replace(PKG = pkg,
                 PKG_VERSION = pkg_version,
-                SETUP_PY = setup_py)
+                SETUP_PY = '"%s"' % setup_py)
     Local(setup_py)
 
     #
@@ -574,15 +746,14 @@ for p in [ scons ]:
     # destination files.
     #
     manifest_in = File(os.path.join(src, 'MANIFEST.in')).rstr()
-    src_files = map(lambda x: x[:-1],
-                    open(manifest_in).readlines())
+    src_files = [x[:-1] for x in open(manifest_in).readlines()]
     raw_files = src_files[:]
     dst_files = src_files[:]
     rpm_files = []
 
     MANIFEST_in_list = []
 
-    if p.has_key('subpkgs'):
+    if 'subpkgs' in p:
         #
         # This package includes some sub-packages.  Read up their
         # MANIFEST.in files, and add them to our source and destination
@@ -594,9 +765,9 @@ for p in [ scons ]:
             isubdir = p['subinst_dirs'][sp['pkg']]
             MANIFEST_in = File(os.path.join(src, ssubdir, 'MANIFEST.in')).rstr()
             MANIFEST_in_list.append(MANIFEST_in)
-            files = map(lambda x: x[:-1], open(MANIFEST_in).readlines())
+            files = [x[:-1] for x in open(MANIFEST_in).readlines()]
             raw_files.extend(files)
-            src_files.extend(map(lambda x, s=ssubdir: os.path.join(s, x), files))
+            src_files.extend([os.path.join(ssubdir, x) for x in files])
             for f in files:
                 r = os.path.join(sp['rpm_dir'], f)
                 rpm_files.append(r)
@@ -605,7 +776,7 @@ for p in [ scons ]:
             for f in sp.get('extra_rpm_files', []):
                 r = os.path.join(sp['rpm_dir'], f)
                 rpm_files.append(r)
-            files = map(lambda x, i=isubdir: os.path.join(i, x), files)
+            files = [os.path.join(isubdir, x) for x in files]
             dst_files.extend(files)
             for k, f in sp['filemap'].items():
                 if f:
@@ -629,8 +800,10 @@ for p in [ scons ]:
     #
     for b in src_files:
         s = p['filemap'].get(b, b)
+        if not s[0] == '$' and not os.path.isabs(s):
+            s = os.path.join(src, s)
         builder = p['buildermap'].get(b, env.SCons_revision)
-        x = builder(os.path.join(build, b), os.path.join(src, s))
+        x = builder(os.path.join(build, b), s)
         Local(x)
 
     #
@@ -657,23 +830,32 @@ for p in [ scons ]:
     #
     # Now go through and arrange to create whatever packages we can.
     #
-    build_src_files = map(lambda x, b=build: os.path.join(b, x), src_files)
-    apply(Local, build_src_files, {})
+    build_src_files = [os.path.join(build, x) for x in src_files]
+    Local(*build_src_files)
 
     distutils_formats = []
 
     distutils_targets = [ win32_exe ]
 
-    install_targets = distutils_targets[:]
+    dist_distutils_targets = env.Install('$DISTDIR', distutils_targets)
+    Local(dist_distutils_targets)
+    AddPostAction(dist_distutils_targets, Chmod(dist_distutils_targets, 0644))
 
-    if gzip:
+    if not gzip:
+        print "gzip not found in %s; skipping .tar.gz package for %s." % (os.environ['PATH'], pkg)
+    else:
 
         distutils_formats.append('gztar')
 
         src_deps.append(tar_gz)
 
         distutils_targets.extend([ tar_gz, platform_tar_gz ])
-        install_targets.extend([ tar_gz, platform_tar_gz ])
+
+        dist_tar_gz             = env.Install('$DISTDIR', tar_gz)
+        dist_platform_tar_gz    = env.Install('$DISTDIR', platform_tar_gz)
+        Local(dist_tar_gz, dist_platform_tar_gz)
+        AddPostAction(dist_tar_gz, Chmod(dist_tar_gz, 0644))
+        AddPostAction(dist_platform_tar_gz, Chmod(dist_platform_tar_gz, 0644))
 
         #
         # Unpack the tar.gz archive created by the distutils into
@@ -686,10 +868,9 @@ for p in [ scons ]:
         # but that gives heartburn to Cygwin's tar, so work around it
         # with separate zcat-tar-rm commands.
         #
-        unpack_tar_gz_files = map(lambda x, u=unpack_tar_gz_dir, pv=pkg_version:
-                                         os.path.join(u, pv, x),
-                                  src_files)
-        env.Command(unpack_tar_gz_files, tar_gz, [
+        unpack_tar_gz_files = [os.path.join(unpack_tar_gz_dir, pkg_version, x)
+                               for x in src_files]
+        env.Command(unpack_tar_gz_files, dist_tar_gz, [
                     Delete(os.path.join(unpack_tar_gz_dir, pkg_version)),
                     "$ZCAT $SOURCES > .temp",
                     "tar xf .temp -C $UNPACK_TAR_GZ_DIR",
@@ -709,7 +890,7 @@ for p in [ scons ]:
         # like this because we put a preamble in it that will chdir()
         # to the directory in which setup.py exists.
         #
-        dfiles = map(lambda x, d=test_tar_gz_dir: os.path.join(d, x), dst_files)
+        dfiles = [os.path.join(test_tar_gz_dir, x) for x in dst_files]
         env.Command(dfiles, unpack_tar_gz_files, [
             Delete(os.path.join(unpack_tar_gz_dir, pkg_version, 'build')),
             Delete("$TEST_TAR_GZ_DIR"),
@@ -720,51 +901,45 @@ for p in [ scons ]:
         #
         # Generate portage files for submission to Gentoo Linux.
         #
-        gentoo = os.path.join('build', 'gentoo')
+        gentoo = os.path.join(build, 'gentoo')
         ebuild = os.path.join(gentoo, 'scons-%s.ebuild' % version)
         digest = os.path.join(gentoo, 'files', 'digest-scons-%s' % version)
         env.Command(ebuild, os.path.join('gentoo', 'scons.ebuild.in'), SCons_revision)
         def Digestify(target, source, env):
             import md5
-            def hexdigest(s):
-                """Return a signature as a string of hex characters.
-                """
-                # NOTE:  This routine is a method in the Python 2.0 interface
-                # of the native md5 module, but we want SCons to operate all
-                # the way back to at least Python 1.5.2, which doesn't have it.
-                h = string.hexdigits
-                r = ''
-                for c in s:
-                    i = ord(c)
-                    r = r + h[(i >> 4) & 0xF] + h[i & 0xF]
-                return r
             src = source[0].rfile()
             contents = open(str(src)).read()
-            sig = hexdigest(md5.new(contents).digest())
+            sig = md5.new(contents).hexdigest()
             bytes = os.stat(str(src))[6]
             open(str(target[0]), 'w').write("MD5 %s %s %d\n" % (sig,
                                                                 src.name,
                                                                 bytes))
         env.Command(digest, tar_gz, Digestify)
 
-    if zipit:
+    if not zipit:
+        print "zip not found; skipping .zip package for %s." % pkg
+    else:
 
         distutils_formats.append('zip')
 
         src_deps.append(zip)
 
         distutils_targets.extend([ zip, platform_zip ])
-        install_targets.extend([ zip, platform_zip ])
+
+        dist_zip            = env.Install('$DISTDIR', zip)
+        dist_platform_zip   = env.Install('$DISTDIR', platform_zip)
+        Local(dist_zip, dist_platform_zip)
+        AddPostAction(dist_zip, Chmod(dist_zip, 0644))
+        AddPostAction(dist_platform_zip, Chmod(dist_platform_zip, 0644))
 
         #
         # Unpack the zip archive created by the distutils into
         # build/unpack-zip/scons-{version}.
         #
-        unpack_zip_files = map(lambda x, u=unpack_zip_dir, pv=pkg_version:
-                                      os.path.join(u, pv, x),
-                               src_files)
+        unpack_zip_files = [os.path.join(unpack_zip_dir, pkg_version, x)
+                                         for x in src_files]
 
-        env.Command(unpack_zip_files, zip, [
+        env.Command(unpack_zip_files, dist_zip, [
             Delete(os.path.join(unpack_zip_dir, pkg_version)),
             unzipit,
         ])
@@ -782,7 +957,7 @@ for p in [ scons ]:
         # like this because we put a preamble in it that will chdir()
         # to the directory in which setup.py exists.
         #
-        dfiles = map(lambda x, d=test_zip_dir: os.path.join(d, x), dst_files)
+        dfiles = [os.path.join(test_zip_dir, x) for x in dst_files]
         env.Command(dfiles, unpack_zip_files, [
             Delete(os.path.join(unpack_zip_dir, pkg_version, 'build')),
             Delete("$TEST_ZIP_DIR"),
@@ -790,11 +965,14 @@ for p in [ scons ]:
                 os.path.join(unpack_zip_dir, pkg_version, 'setup.py'),
         ])
 
-    if rpmbuild:
-        topdir = os.path.join(os.getcwd(), build, 'build',
+    if not rpmbuild:
+        msg = "@echo \"Warning:  Can not build 'rpm':  no rpmbuild utility found\""
+        AlwaysBuild(Alias('rpm', [], msg))
+    else:
+        topdir = os.path.join(build, 'build',
                               'bdist.' + platform, 'rpm')
 
-        buildroot = os.path.join(os.getcwd(), 'build', 'rpm-buildroot')
+        buildroot = os.path.join(build_dir, 'rpm-buildroot')
 
         BUILDdir = os.path.join(topdir, 'BUILD', pkg + '-' + version)
         RPMSdir = os.path.join(topdir, 'RPMS', 'noarch')
@@ -816,11 +994,12 @@ for p in [ scons ]:
             maintain multiple lists.
             """
             c = open(str(source[0]), 'rb').read()
-            c = string.replace(c, '__RPM_FILES__', env['RPM_FILES'])
+            c = c.replace('__VERSION' + '__', env['VERSION'])
+            c = c.replace('__RPM_FILES' + '__', env['RPM_FILES'])
             open(str(target[0]), 'wb').write(c)
 
         rpm_files.sort()
-        rpm_files_str = string.join(rpm_files, "\n") + "\n"
+        rpm_files_str = "\n".join(rpm_files) + "\n"
         rpm_spec_env = env.Clone(RPM_FILES = rpm_files_str)
         rpm_spec_action = Action(spec_function, varlist=['RPM_FILES'])
         rpm_spec_env.Command(specfile, specfile_in, rpm_spec_action)
@@ -832,21 +1011,24 @@ for p in [ scons ]:
         cmd = "$RPMBUILD --define '_topdir $(%s$)' --buildroot %s -ba $SOURCES" % (topdir, buildroot)
         if not os.path.isdir(BUILDdir):
             cmd = ("$( mkdir -p %s; $)" % BUILDdir) + cmd
-        env.Command(targets, specfile, cmd)
-        env.Depends(targets, sourcefile)
+        t = env.Command(targets, specfile, cmd)
+        env.Depends(t, sourcefile)
 
-        install_targets.extend(targets)
+        dist_noarch_rpm = env.Install('$DISTDIR', noarch_rpm)
+        dist_src_rpm    = env.Install('$DISTDIR', src_rpm)
+        Local(dist_noarch_rpm, dist_src_rpm)
+        AddPostAction(dist_noarch_rpm, Chmod(dist_noarch_rpm, 0644))
+        AddPostAction(dist_src_rpm, Chmod(dist_src_rpm, 0644))
 
-        dfiles = map(lambda x, d=test_rpm_dir: os.path.join(d, 'usr', x),
-                     dst_files)
+        dfiles = [os.path.join(test_rpm_dir, 'usr', x) for x in dst_files]
         env.Command(dfiles,
-                    noarch_rpm,
+                    dist_noarch_rpm,
                     "$RPM2CPIO $SOURCES | (cd $TEST_RPM_DIR && cpio -id)")
 
     if dh_builddeb and fakeroot:
         # Our Debian packaging builds directly into build/dist,
-        # so we don't need to add the .debs to install_targets.
-        deb = os.path.join('build', 'dist', "%s_%s-1_all.deb" % (pkg, version))
+        # so we don't need to Install() the .debs.
+        deb = os.path.join(build_dir, 'dist', "%s_%s-1_all.deb" % (pkg, version))
         for d in p['debian_deps']:
             b = env.SCons_revision(os.path.join(build, d), d)
             env.Depends(deb, b)
@@ -856,13 +1038,12 @@ for p in [ scons ]:
                     ])
 
         old = os.path.join('lib', 'scons', '')
-        new = os.path.join('lib', 'python2.2', 'site-packages', '')
+        new = os.path.join('lib', 'python' + python_ver, 'site-packages', '')
         def xxx(s, old=old, new=new):
             if s[:len(old)] == old:
                 s = new + s[len(old):]
             return os.path.join('usr', s)
-        dfiles = map(lambda x, t=test_deb_dir: os.path.join(t, x),
-                     map(xxx, dst_files))
+        dfiles = [os.path.join(test_deb_dir, xxx(x)) for x in dst_files]
         env.Command(dfiles,
                     deb,
                     "dpkg --fsys-tarfile $SOURCES | (cd $TEST_DEB_DIR && tar -xf -)")
@@ -885,7 +1066,7 @@ for p in [ scons ]:
             commands.append("$PYTHON $PYTHONFLAGS $SETUP_PY bdist_dumb -f %s" % format)
 
         commands.append("$PYTHON $PYTHONFLAGS $SETUP_PY sdist --formats=%s" %  \
-                            string.join(distutils_formats, ','))
+                            ','.join(distutils_formats))
 
     commands.append("$PYTHON $PYTHONFLAGS $SETUP_PY bdist_wininst")
 
@@ -898,81 +1079,72 @@ for p in [ scons ]:
     #
     s_l_v = '%s-local-%s' % (pkg, version)
 
-    local = os.path.join('build', pkg + '-local')
-    cwd_local = os.path.join(os.getcwd(), local)
-    cwd_local_slv = os.path.join(os.getcwd(), local, s_l_v)
+    local = pkg + '-local'
+    build_dir_local = os.path.join(build_dir, local)
+    build_dir_local_slv = os.path.join(build_dir, local, s_l_v)
 
-    local_tar_gz = os.path.join('build', 'dist', "%s.tar.gz" % s_l_v)
-    local_zip = os.path.join('build', 'dist', "%s.zip" % s_l_v)
+    dist_local_tar_gz = os.path.join("$DISTDIR/%s.tar.gz" % s_l_v)
+    dist_local_zip = os.path.join("$DISTDIR/%s.zip" % s_l_v)
+    AddPostAction(dist_local_tar_gz, Chmod(dist_local_tar_gz, 0644))
+    AddPostAction(dist_local_zip, Chmod(dist_local_zip, 0644))
 
     commands = [
-        Delete(local),
+        Delete(build_dir_local),
         '$PYTHON $PYTHONFLAGS $SETUP_PY install "--install-script=%s" "--install-lib=%s" --no-install-man --no-compile --standalone-lib --no-version-script' % \
-                                                (cwd_local, cwd_local_slv),
+                                                (build_dir_local, build_dir_local_slv),
     ]
 
     for script in scripts:
         #commands.append("mv %s/%s %s/%s.py" % (local, script, local, script))
-        local_script = os.path.join(local, script)
+        local_script = os.path.join(build_dir_local, script)
         commands.append(Move(local_script + '.py', local_script))
 
-    rf = filter(lambda x: not x in scripts, raw_files)
-    rf = map(lambda x, slv=s_l_v: os.path.join(slv, x), rf)
+    rf = [x for x in raw_files if not x in scripts]
+    rf = [os.path.join(s_l_v, x) for x in rf]
     for script in scripts:
         rf.append("%s.py" % script)
-    local_targets = map(lambda x, s=local: os.path.join(s, x), rf)
+    local_targets = [os.path.join(build_dir_local, x) for x in rf]
 
     env.Command(local_targets, build_src_files, commands)
 
-    scons_LICENSE = os.path.join(local, 'scons-LICENSE')
+    scons_LICENSE = os.path.join(build_dir_local, 'scons-LICENSE')
     l = env.SCons_revision(scons_LICENSE, 'LICENSE-local')
     local_targets.append(l)
     Local(l)
 
-    scons_README = os.path.join(local, 'scons-README')
+    scons_README = os.path.join(build_dir_local, 'scons-README')
     l = env.SCons_revision(scons_README, 'README-local')
     local_targets.append(l)
     Local(l)
 
     if gzip:
-        env.Command(local_tar_gz,
+        env.Command(dist_local_tar_gz,
                     local_targets,
-                    "cd %s && tar czf $( ${TARGET.abspath} $) *" % local)
+                    "cd %s && tar czf $( ${TARGET.abspath} $) *" % build_dir_local)
 
-        unpack_targets = map(lambda x, d=test_local_tar_gz_dir:
-                                    os.path.join(d, x),
-                             rf)
+        unpack_targets = [os.path.join(test_local_tar_gz_dir, x) for x in rf]
         commands = [Delete(test_local_tar_gz_dir),
                     Mkdir(test_local_tar_gz_dir),
                     "cd %s && tar xzf $( ${SOURCE.abspath} $)" % test_local_tar_gz_dir]
 
-        env.Command(unpack_targets, local_tar_gz, commands)
+        env.Command(unpack_targets, dist_local_tar_gz, commands)
 
     if zipit:
-        env.Command(local_zip, local_targets, zipit,
-                    CD = local, PSV = '.')
+        env.Command(dist_local_zip, local_targets, zipit,
+                    CD = build_dir_local, PSV = '.')
 
-        unpack_targets = map(lambda x, d=test_local_zip_dir:
-                                    os.path.join(d, x),
-                             rf)
+        unpack_targets = [os.path.join(test_local_zip_dir, x) for x in rf]
         commands = [Delete(test_local_zip_dir),
                     Mkdir(test_local_zip_dir),
                     unzipit]
 
-        env.Command(unpack_targets, local_zip, unzipit,
+        env.Command(unpack_targets, dist_local_zip, unzipit,
                     UNPACK_ZIP_DIR = test_local_zip_dir)
 
-    #
-    # And, lastly, install the appropriate packages in the
-    # appropriate subdirectory.
-    #
-    b_d_files = env.Install(os.path.join('build', 'dist'), install_targets)
-    Local(b_d_files)
-
 #
 #
 #
-Export('env')
+Export('build_dir', 'env')
 
 SConscript('QMTest/SConscript')
 
@@ -992,67 +1164,66 @@ for file in files:
     # Guarantee that real copies of these files always exist in
     # build/.  If there's a symlink there, then this is an Aegis
     # build and we blow them away now so that they'll get "built" later.
-    p = os.path.join('build', file)
+    p = os.path.join(build_dir, file)
     if os.path.islink(p):
         os.unlink(p)
-    sp = '#' + p
-    env.Command(sp, file, copy)
+    if not os.path.isabs(p):
+        p = '#' + p
+    sp = env.Command(p, file, copy)
     Local(sp)
 
 #
 # Documentation.
 #
-Export('env', 'whereis')
+Export('build_dir', 'env', 'whereis')
 
 SConscript('doc/SConscript')
 
 #
-# If we're running in the actual Aegis project, pack up a complete
-# source archive from the project files and files in the change,
-# so we can share it with helpful developers who don't use Aegis.
+# If we're running in a Subversion working directory, pack up a complete
+# source archive from the project files and files in the change.
 #
 
-if change:
-    df = []
-    cmd = "aegis -list -unf -c %s cf 2>/dev/null" % change
-    for line in map(lambda x: x[:-1], os.popen(cmd, "r").readlines()):
-        a = string.split(line)
-        if a[1] == "remove":
-            df.append(a[-1])
-
-    cmd = "aegis -list -terse pf 2>/dev/null"
-    pf = map(lambda x: x[:-1], os.popen(cmd, "r").readlines())
-    cmd = "aegis -list -terse -c %s cf 2>/dev/null" % change
-    cf = map(lambda x: x[:-1], os.popen(cmd, "r").readlines())
-    u = {}
-    for f in pf + cf:
-        u[f] = 1
-    for f in df:
-        try:
-            del u[f]
-        except KeyError:
-            pass
-    sfiles = filter(lambda x: x[-9:] != '.aeignore' and
-                              x[-9:] != '.sconsign' and
-                              x[-10:] != '.cvsignore',
-                    u.keys())
+sfiles = None
+if hg_status_lines:
+    slines = [l for l in hg_status_lines if l[0] in 'ACM']
+    sfiles = [l.split()[-1] for l in slines]
+elif svn_status_lines:
+    slines = [l for l in svn_status_lines if l[0] in ' MA']
+    sentries = [l.split()[-1] for l in slines]
+    sfiles = list(filter(os.path.isfile, sentries))
+else:
+   "Not building in a Mercurial or Subversion tree; skipping building src package."
+
+if sfiles:
+    remove_patterns = [
+        '.hgt/*',
+        '.svnt/*',
+        '*.aeignore',
+        '*.cvsignore',
+        '*.hgignore',
+        'www/*',
+    ]
+
+    for p in remove_patterns:
+        sfiles = [s for s in sfiles if not fnmatch.fnmatch(s, p)]
 
     if sfiles:
         ps = "%s-src" % project
         psv = "%s-%s" % (ps, version)
-        b_ps = os.path.join('build', ps)
-        b_psv = os.path.join('build', psv)
+        b_ps = os.path.join(build_dir, ps)
+        b_psv = os.path.join(build_dir, psv)
         b_psv_stamp = b_psv + '-stamp'
 
-        src_tar_gz = os.path.join('build', 'dist', '%s.tar.gz' % psv)
-        src_zip = os.path.join('build', 'dist', '%s.zip' % psv)
+        src_tar_gz = os.path.join(build_dir, 'dist', '%s.tar.gz' % psv)
+        src_zip = os.path.join(build_dir, 'dist', '%s.zip' % psv)
 
         Local(src_tar_gz, src_zip)
 
         for file in sfiles:
             env.SCons_revision(os.path.join(b_ps, file), file)
 
-        b_ps_files = map(lambda x, d=b_ps: os.path.join(d, x), sfiles)
+        b_ps_files = [os.path.join(b_ps, x) for x in sfiles]
         cmds = [
             Delete(b_psv),
             Copy(b_psv, b_ps),
@@ -1061,7 +1232,7 @@ if change:
 
         env.Command(b_psv_stamp, src_deps + b_ps_files, cmds)
 
-        apply(Local, b_ps_files, {})
+        Local(*b_ps_files)
 
         if gzip:
 
@@ -1071,9 +1242,8 @@ if change:
             #
             # Unpack the archive into build/unpack/scons-{version}.
             #
-            unpack_tar_gz_files = map(lambda x, u=unpack_tar_gz_dir, psv=psv:
-                                             os.path.join(u, psv, x),
-                                      sfiles)
+            unpack_tar_gz_files = [os.path.join(unpack_tar_gz_dir, psv, x)
+                                   for x in sfiles]
 
             #
             # We'd like to replace the last three lines with the following:
@@ -1102,8 +1272,7 @@ if change:
             # like this because we put a preamble in it that will chdir()
             # to the directory in which setup.py exists.
             #
-            dfiles = map(lambda x, d=test_src_tar_gz_dir: os.path.join(d, x),
-                            dst_files)
+            dfiles = [os.path.join(test_src_tar_gz_dir, x) for x in dst_files]
             scons_lib_dir = os.path.join(unpack_tar_gz_dir, psv, 'src', 'engine')
             ENV = env.Dictionary('ENV').copy()
             ENV['SCONS_LIB_DIR'] = scons_lib_dir
@@ -1116,7 +1285,7 @@ if change:
                                     'scons',
                                     'build')),
                 Delete("$TEST_SRC_TAR_GZ_DIR"),
-                'cd "%s" && $PYTHON $PYTHONFLAGS "%s" "%s"' % \
+                'cd "%s" && $PYTHON $PYTHONFLAGS "%s" "%s" VERSION="$VERSION"' % \
                     (os.path.join(unpack_tar_gz_dir, psv),
                      os.path.join('src', 'script', 'scons.py'),
                      os.path.join('build', 'scons')),
@@ -1136,9 +1305,8 @@ if change:
             #
             # Unpack the archive into build/unpack/scons-{version}.
             #
-            unpack_zip_files = map(lambda x, u=unpack_zip_dir, psv=psv:
-                                             os.path.join(u, psv, x),
-                                      sfiles)
+            unpack_zip_files = [os.path.join(unpack_zip_dir, psv, x)
+                                for x in sfiles]
 
             env.Command(unpack_zip_files, src_zip, [
                 Delete(os.path.join(unpack_zip_dir, psv)),
@@ -1158,8 +1326,7 @@ if change:
             # like this because we put a preamble in it that will chdir()
             # to the directory in which setup.py exists.
             #
-            dfiles = map(lambda x, d=test_src_zip_dir: os.path.join(d, x),
-                            dst_files)
+            dfiles = [os.path.join(test_src_zip_dir, x) for x in dst_files]
             scons_lib_dir = os.path.join(unpack_zip_dir, psv, 'src', 'engine')
             ENV = env.Dictionary('ENV').copy()
             ENV['SCONS_LIB_DIR'] = scons_lib_dir
@@ -1172,7 +1339,7 @@ if change:
                                     'scons',
                                     'build')),
                 Delete("$TEST_SRC_ZIP_DIR"),
-                'cd "%s" && $PYTHON $PYTHONFLAGS "%s" "%s"' % \
+                'cd "%s" && $PYTHON $PYTHONFLAGS "%s" "%s" VERSION="$VERSION"' % \
                     (os.path.join(unpack_zip_dir, psv),
                      os.path.join('src', 'script', 'scons.py'),
                      os.path.join('build', 'scons')),
@@ -1185,5 +1352,9 @@ if change:
                 ],
                 ENV = ENV)
 
-for pf in packaging_flavors:
-    Alias(pf, ['build/test-'+pf, 'build/QMTest', 'build/runtest.py'])
+for pf, help_text in packaging_flavors:
+    Alias(pf, [
+        os.path.join(build_dir, 'test-'+pf),
+        os.path.join(build_dir, 'QMTest'),
+        os.path.join(build_dir, 'runtest.py'),
+    ])