Anonymous avatar Anonymous committed e2b47d1

Merged revisions 1582-1665 via svnmerge from
http://scons.tigris.org/svn/scons/branches/core

........
r1585 | stevenknight | 2006-08-06 21:21:12 -0500 (Sun, 06 Aug 2006) | 1 line

0.96.D430 - Fix bug with finding Fortran modules in build directories. (Nicolas Vigier)
........
r1586 | stevenknight | 2006-08-06 22:54:39 -0500 (Sun, 06 Aug 2006) | 1 line

0.96.D431 - Fix use of BuildDir when the source file is a relative-path symlink. (Nicola Vi
........
r1587 | timot | 2006-08-10 14:45:00 -0500 (Thu, 10 Aug 2006) | 1 line

fix Platform SDK init
........
r1589 | stevenknight | 2006-08-12 13:28:51 -0500 (Sat, 12 Aug 2006) | 1 line

0.96.D432 - Change the default mingw linker from g++ to gcc. (Mariusz Olko)
........
r1590 | stevenknight | 2006-08-13 11:16:32 -0500 (Sun, 13 Aug 2006) | 1 line

0.96.D433 - More runtest.py enhancements.
........
r1594 | stevenknight | 2006-08-15 04:47:46 -0500 (Tue, 15 Aug 2006) | 1 line

0.96.D434 - Print the full path of SConscript files in stack traces. (Dobes Vandermeer)
........
r1600 | timot | 2006-08-16 11:34:44 -0500 (Wed, 16 Aug 2006) | 1 line

add M4 to mingw tools
........
r1611 | stevenknight | 2006-08-19 16:25:24 -0500 (Sat, 19 Aug 2006) | 1 line

0.96.D435 - Add an SCons XMLResultStream to capture test results for mailing in. Get Aegis
........
r1617 | timot | 2006-08-21 16:19:03 -0500 (Mon, 21 Aug 2006) | 1 line

handling of spawnve returning an error code that is not in exitvalmap
........
r1619 | stevenknight | 2006-09-01 19:07:47 -0500 (Fri, 01 Sep 2006) | 1 line

0.96.D436 - Win32 test portability and other test fixes.
........
r1620 | stevenknight | 2006-09-02 20:21:51 -0500 (Sat, 02 Sep 2006) | 2 lines

Bring msvc.py in sync with Aegis repository.
........
r1621 | stevenknight | 2006-09-02 20:23:48 -0500 (Sat, 02 Sep 2006) | 2 lines

Move to keep symlink tests together.
........
r1622 | garyo | 2006-09-06 11:51:42 -0500 (Wed, 06 Sep 2006) | 1 line

Fix for Issue #1370; allow exit values not in exitvalmap. Added some tests for this kind of thing. Also improved win32 err msg if command exits with nonzero to show actual cmd, not just "cmd.exe". Note this fix improves posix and win32 behavior.
........
r1623 | stevenknight | 2006-09-07 06:35:16 -0500 (Thu, 07 Sep 2006) | 1 line

0.96.D440 - Fix runtest.py with QMTest on Windows.
........
r1625 | stevenknight | 2006-09-09 09:22:15 -0500 (Sat, 09 Sep 2006) | 3 lines

Comment out a long-command test which fails on older Pythons (1.5.2)
on Fedora Core 3. We can restore it in the future.
........
r1626 | stevenknight | 2006-09-09 16:17:44 -0500 (Sat, 09 Sep 2006) | 1 line

0.96.D441 - Allow Python Values to be the targets of Builders. (Anonymous)
........
r1627 | stevenknight | 2006-09-09 20:25:53 -0500 (Sat, 09 Sep 2006) | 1 line

0.96.D442 - Support src_dir on SConscript() calls. (Dobes Vandermeer)
........
r1628 | stevenknight | 2006-09-10 07:28:54 -0500 (Sun, 10 Sep 2006) | 1 line

0.96.D443 - Add miscellaneous utility scripts and config changes.
........
r1629 | stevenknight | 2006-09-11 04:45:01 -0500 (Mon, 11 Sep 2006) | 1 line

0.96.D444 - Add a test case for BuildDir handling of nested SConscript files. (Adam Simpkin
........
r1630 | stevenknight | 2006-09-11 11:34:07 -0500 (Mon, 11 Sep 2006) | 1 line

0.96.D445 - Workaround bug in early versions of thePython 2.4 profiler.
........
r1631 | stevenknight | 2006-09-19 19:12:51 -0500 (Tue, 19 Sep 2006) | 1 line

0.96.D446 - Fix Visual Studio common prefix handling to only treat common prefixes on comple
........
r1632 | stevenknight | 2006-09-25 07:11:44 -0500 (Mon, 25 Sep 2006) | 1 line

0.96.D447 - Fix tests that fail due to warnings from (some versions?) of gcc. (Sohail Soman
........
r1633 | stevenknight | 2006-09-25 07:57:48 -0500 (Mon, 25 Sep 2006) | 1 line

0.96.D448 - Handle python paths with quotes in tests.
........
r1634 | stevenknight | 2006-09-25 14:38:07 -0500 (Mon, 25 Sep 2006) | 1 line

0.96.D449 - Fix SCons build when python is not in the path (e.g. on Windows). (Chad Austin)
........
r1635 | stevenknight | 2006-09-26 11:28:23 -0500 (Tue, 26 Sep 2006) | 1 line

0.96.D450 - Handle warnings from Python 2.1; make sure we still test on Python 1.5.
........
r1636 | stevenknight | 2006-09-27 05:34:23 -0500 (Wed, 27 Sep 2006) | 1 line

0.96.D451 - Avoid calling Options validators and converters twice.
........
r1637 | stevenknight | 2006-09-28 08:12:38 -0500 (Thu, 28 Sep 2006) | 1 line

0.96.D452 - Allow setting MSVS_VERSION after initialization to select the Visual Studio vers
........
r1638 | stevenknight | 2006-09-30 08:38:15 -0500 (Sat, 30 Sep 2006) | 1 line

0.96.D453 - Give the MSVC resource builder a src_builder list and .rc src_suffix. (Leanid N
........
r1639 | stevenknight | 2006-10-12 08:50:58 -0500 (Thu, 12 Oct 2006) | 1 line

0.96.D454 - Test handling of env.Append() and env.Prepend(), making sure it works on later P
........
r1640 | stevenknight | 2006-10-15 20:42:09 -0500 (Sun, 15 Oct 2006) | 1 line

0.96.D455 - Support the runtest.py -f option when using QMTest.
........
r1641 | stevenknight | 2006-10-15 21:20:02 -0500 (Sun, 15 Oct 2006) | 1 line

0.96.D456 - Fix an error in ListOption handling caused by making new copies of Options objec
........
r1642 | stevenknight | 2006-10-16 05:53:14 -0500 (Mon, 16 Oct 2006) | 1 line

0.96.D457 - Fix new Append()/Prepend() handling of dictionaries in later Python versions (2.
........
r1643 | stevenknight | 2006-10-16 07:13:16 -0500 (Mon, 16 Oct 2006) | 1 line

0.96.D458 - Allow Install() to handle directories as sources. (Matthew A. Nicholson)
........
r1644 | stevenknight | 2006-10-17 09:17:58 -0500 (Tue, 17 Oct 2006) | 1 line

0.96.D459 - Add a test to make sure SideEffect() doesn't interfere with CacheDir(). Refacto
........
r1645 | stevenknight | 2006-10-17 10:20:22 -0500 (Tue, 17 Oct 2006) | 1 line

0.96.D460 - Do not use -fPIC when using gcc on win32 (MinGW). (Jan Nijtmans)
........
r1646 | stevenknight | 2006-10-17 17:21:58 -0500 (Tue, 17 Oct 2006) | 6 lines

Move all the scons.org stuff from the scons source tree itself to a
directory next to the trunk, and delete the copies from the branches.
There's a lot of stuff there (what with all of the documentation of
the different versions) and it's ridiculous to make everyone sync it
just to work on the code.
........
r1647 | stevenknight | 2006-10-17 23:18:29 -0500 (Tue, 17 Oct 2006) | 1 line

0.96.D461 - Fix the tests of runtest.py so they skip appropriately if qmtest.py isn't instal
........
r1648 | stevenknight | 2006-10-18 08:48:47 -0500 (Wed, 18 Oct 2006) | 1 line

0.96.D462 - When using --implicit-cache, do not re-scan files if the scanner returned no imp
........
r1649 | stevenknight | 2006-10-18 19:42:13 -0500 (Wed, 18 Oct 2006) | 1 line

0.96.D463 - More test portability fixes.
........
r1650 | stevenknight | 2006-10-19 00:30:23 -0500 (Thu, 19 Oct 2006) | 1 line

0.96.D464 - Add a cpp.py module that knows how to find dependencies from #include lines like
........
r1651 | stevenknight | 2006-10-20 06:49:51 -0500 (Fri, 20 Oct 2006) | 1 line

0.96.D465 - Fix unresolved variable name in win32 portion of test.
........
r1652 | stevenknight | 2006-10-23 00:20:38 -0500 (Mon, 23 Oct 2006) | 1 line

0.96.D466 - Add an option for tracing files to and from the CacheDir.
........
r1653 | stevenknight | 2006-10-23 00:29:32 -0500 (Mon, 23 Oct 2006) | 1 line

0.96.D467 - Make {Append,Prepend}Unique() handle adding elements to empty lists like {Append
........
r1654 | stevenknight | 2006-10-23 02:38:06 -0500 (Mon, 23 Oct 2006) | 1 line

0.96.D468 - Allow Debug.caller() to take multiple arguments; add a debug utility to post-pro
........
r1655 | stevenknight | 2006-10-23 03:16:42 -0500 (Mon, 23 Oct 2006) | 1 line

0.96.D469 - Reduce unnecessary calls to Node.FS.disambiguate(), undoing (?) a performance hi
........
r1656 | stevenknight | 2006-10-25 00:06:27 -0500 (Wed, 25 Oct 2006) | 1 line

0.96.D470 - More test portability fixes.
........
r1657 | stevenknight | 2006-10-25 00:16:22 -0500 (Wed, 25 Oct 2006) | 1 line

0.96.D471 - Have runtest.py fall back to the --noqmtest option (with a warning) if qmtest.py
........
r1658 | stevenknight | 2006-10-25 12:12:02 -0500 (Wed, 25 Oct 2006) | 1 line

0.96.D472 - Document the default use of the /Z7 flag for Visual Studio and ways to use /Zi.
........
r1659 | stevenknight | 2006-10-26 23:53:51 -0500 (Thu, 26 Oct 2006) | 1 line

0.96.D473 - Have runtest.py -d accomodate different Python library locations.
........
r1660 | stevenknight | 2006-10-27 00:03:59 -0500 (Fri, 27 Oct 2006) | 1 line

0.96.D474 - Patch to support running SCons under WingIDE. (Allen Bierbaum)
........
r1661 | stevenknight | 2006-10-27 12:17:27 -0500 (Fri, 27 Oct 2006) | 1 line

0.96.D475 - Restore execution of all Environment unit tests.
........
r1662 | stevenknight | 2006-10-31 23:22:58 -0600 (Tue, 31 Oct 2006) | 1 line

0.96.D476 - Eliminate unnecessary print from a test, left over from debugging.
........
r1663 | stevenknight | 2006-10-31 23:32:00 -0600 (Tue, 31 Oct 2006) | 1 line

0.96.D477 - Support creating shared object files from assembly language. (James Y. Knight)
........
r1664 | stevenknight | 2006-10-31 23:44:08 -0600 (Tue, 31 Oct 2006) | 1 line

0.96.D478 - Fix the Memoizer to deal with a compiled .pyo or .pyc file that's in a different
........
r1665 | stevenknight | 2006-11-01 21:59:18 -0600 (Wed, 01 Nov 2006) | 1 line

0.96.D479 - Put back the scons-{LICENSE,README} files in the scons-loacal package; add tests
........

Comments (0)

Files changed (401)

HOWTO/release.txt

                 aecp rpm/scons.spec.in
                 vi rpm/scons.spec.in
 
-                aecp src/test_setup.py
-                vi src/test_setup.py
+                aecp QMTest/TestSCons.py
+                vi QMTest/TestSCons.py
 
                 # Read through and update the README files if necessary
 		[optional] aecp README

HOWTO/subrelease.txt

                 aecp src/setup.py
                 vi src/setup.py
 
-                aecp src/test_setup.py
-                vi src/test_setup.py
+                aecp QMTest/TestSCons.py
+                vi QMTest/TestSCons.py
 
                 # Read through and update the README files if necessary
 		[optional] aecp README

QMTest/TestRuntest.py

 
 __all__.extend([ 'TestRuntest',
                  'python',
+                 '_python_',
                ])
 
 python = python_executable
+_python_ = '"' + python_executable + '"'
 
 
 failing_test_template = """\
 sys.exit(0)
 """
 
+fake_scons_py = """
+__version__ = '1.2.3'
+__build__ = 'D123'
+__buildsys__ = 'fake_system'
+__date__ = 'Jan 1 1970'
+__developer__ = 'Anonymous'
+"""
+
+fake___init___py = """
+__version__ = '4.5.6'
+__build__ = 'D456'
+__buildsys__ = 'another_fake_system'
+__date__ = 'Dec 31 1999'
+__developer__ = 'John Doe'
+"""
+
 class TestRuntest(TestCommon):
     """Class for testing the runtest.py script.
 
             kw['match'] = match_exact
         if not kw.has_key('workdir'):
             kw['workdir'] = ''
+
+        try:
+            noqmtest = kw['noqmtest']
+        except KeyError:
+            noqmtest = 0
+        else:
+            del kw['noqmtest']
+
         orig_cwd = os.getcwd()
         apply(TestCommon.__init__, [self], kw)
+  
+        if not noqmtest:
+            qmtest_py = self.where_is('qmtest.py')
+            if not qmtest_py:
+                self.skip_test("Could not find 'qmtest.py'; skipping test(s).\n")
 
         things_to_copy = [
             'runtest.py',
             'QMTest',
         ]
 
-        dirs = [orig_cwd]
+        dirs = [os.environ.get('SCONS_RUNTEST_DIR', orig_cwd)]
         
         spe = os.environ.get('SCONS_SOURCE_PATH_EXECUTABLE', orig_cwd)
         for d in string.split(spe, os.pathsep):
         os.environ['PYTHONPATH'] = ''
         os.environ['SCONS_SOURCE_PATH_EXECUTABLE'] = ''
 
+    def skip_test(self, message="Skipping test.\n"):
+        """Skips a test.
+
+        Proper test-skipping behavior is dependent on whether we're being
+        executed as part of development of a change under Aegis.
+
+        Technically, skipping a test is a NO RESULT, but Aegis will
+        treat that as a test failure and prevent the change from going
+        to the next step.  We don't want to force anyone using Aegis
+        to have to install absolutely every tool used by the tests,
+        so we actually report to Aegis that a skipped test has PASSED
+        so that the workflow isn't held up.
+        """
+        if message:
+            sys.stdout.write(message)
+            sys.stdout.flush()
+        devdir = os.popen("aesub '$dd' 2>/dev/null", "r").read()[:-1]
+        intdir = os.popen("aesub '$intd' 2>/dev/null", "r").read()[:-1]
+        if devdir and self._cwd[:len(devdir)] == devdir or \
+           intdir and self._cwd[:len(intdir)] == intdir:
+            # We're under the development directory for this change,
+            # so this is an Aegis invocation; pass the test (exit 0).
+            self.pass_test()
+        else:
+            # skip=1 means skip this function when showing where this
+            # result came from.  They only care about the line where the
+            # script called test.skip_test(), not the line number where
+            # we call test.no_result().
+            self.no_result(skip=1)
+
+    def write_fake_scons_source_tree(self):
+        os.mkdir('src')
+        os.mkdir('src/script')
+        self.write('src/script/scons.py', fake_scons_py)
+
+        os.mkdir('src/engine')
+        os.mkdir('src/engine/SCons')
+        self.write('src/engine/SCons/__init__.py', fake___init___py)
+        os.mkdir('src/engine/SCons/Script')
+        self.write('src/engine/SCons/Script/__init__.py', fake___init___py)
+
     def write_failing_test(self, name):
         self.write(name, failing_test_template)
 

QMTest/TestSCons.py

 from TestCommon import *
 from TestCommon import __all__
 
+# Some tests which verify that SCons has been packaged properly need to
+# look for specific version file names.  Replicating the version number
+# here provides independent verification that what we packaged conforms
+# to what we expect.  (If we derived the version number from the same
+# data driving the build we might miss errors if the logic breaks.)
+
+SConsVersion = '0.96.92'
+
 __all__.extend([ 'TestSCons',
                  'python',
                  '_exe',
                ])
 
 python = python_executable
+_python_ = '"' + python_executable + '"'
 _exe = exe_suffix
 _obj = obj_suffix
 _shobj = shobj_suffix
     initializations.
     """
 
+    scons_version = SConsVersion
+
     def __init__(self, **kw):
         """Initialize an SCons testing object.
 
 }
 """)
 
-        self.write(['qt', 'lib', 'SConstruct'], r"""
+        self.write([dir, 'lib', 'SConstruct'], r"""
 env = Environment()
-env.StaticLibrary( 'myqt', 'my_qobject.cpp' )
+env.SharedLibrary( 'myqt', 'my_qobject.cpp' )
 """)
 
-        self.run(chdir = self.workpath('qt', 'lib'),
+        self.run(chdir = self.workpath(dir, 'lib'),
                  arguments = '.',
                  stderr = noisy_ar,
                  match = self.match_re_dotall)
         self.QT_LIB = 'myqt'
         self.QT_MOC = '%s %s' % (python, self.workpath(dir, 'bin', 'mymoc.py'))
         self.QT_UIC = '%s %s' % (python, self.workpath(dir, 'bin', 'myuic.py'))
+        self.QT_LIB_DIR = self.workpath(dir, 'lib')
 
     def Qt_create_SConstruct(self, place):
         if type(place) is type([]):

QMTest/classes.qmc

  <class kind="result_stream" name="scons_tdb.AegisChangeStream"/>
  <class kind="result_stream" name="scons_tdb.AegisBaselineStream"/>
  <class kind="result_stream" name="scons_tdb.AegisBatchStream"/>
+ <class kind="result_stream" name="scons_tdb.SConsXMLResultStream"/>
 </class-directory>

QMTest/scons_tdb.py

 from   qm.test.result import Result
 from   qm.test.file_result_stream import FileResultStream
 from   qm.test.classes.text_result_stream import TextResultStream
+from   qm.test.classes.xml_result_stream import XMLResultStream
 from   qm.test.directory_suite import DirectorySuite
 from   qm.extension import get_extension_class_name, get_class_arguments_as_dictionary
-import os, dircache
+
+import dircache
+import os
+import imp
 
 if sys.platform == 'win32':
     console = 'con'
 
     return True
 
-# XXX I'd like to annotate the overall test run with the following
-# information about the Python version, SCons version, and environment.
-# Not sure how to do that yet; ask Stefan.
-#
-#    sys_keys = ['byteorder', 'exec_prefix', 'executable', 'maxint', 'maxunicode', 'platform', 'prefix', 'version', 'version_info']
 
-# "    <%s>" % tag
-# "      <version>%s</version>" % module.__version__
-# "      <build>%s</build>" % module.__build__
-# "      <buildsys>%s</buildsys>" % module.__buildsys__
-# "      <date>%s</date>" % module.__date__
-# "      <developer>%s</developer>" % module.__developer__
-# "    </%s>" % tag
 
-# "  <scons>"
-#    print_version_info("script", scons)
-#    print_version_info("engine", SCons)
-# "  </scons>"
+class Null:
+    pass
 
-#    environ_keys = [
-#        'PATH',
-#        'SCONSFLAGS',
-#        'SCONS_LIB_DIR',
-#        'PYTHON_ROOT',
-#        'QTDIR',
-#
-#        'COMSPEC',
-#        'INTEL_LICENSE_FILE',
-#        'INCLUDE',
-#        'LIB',
-#        'MSDEVDIR',
-#        'OS',
-#        'PATHEXT',
-#        'SYSTEMROOT',
-#        'TEMP',
-#        'TMP',
-#        'USERNAME',
-#        'VXDOMNTOOLS',
-#        'WINDIR',
-#        'XYZZY'
-#
-#        'ENV',
-#        'HOME',
-#        'LANG',
-#        'LANGUAGE',
-#        'LOGNAME',
-#        'MACHINE',
-#        'OLDPWD',
-#        'PWD',
-#        'OPSYS',
-#        'SHELL',
-#        'TMPDIR',
-#        'USER',
-#    ]
+_null = Null()
+
+sys_attributes = [
+    'byteorder',
+    'exec_prefix',
+    'executable',
+    'maxint',
+    'maxunicode',
+    'platform',
+    'prefix',
+    'version',
+    'version_info',
+]
+
+def get_sys_values():
+    sys_attributes.sort()
+    result = map(lambda k: (k, getattr(sys, k, _null)), sys_attributes)
+    result = filter(lambda t: not t[1] is _null, result)
+    result = map(lambda t: t[0] + '=' + repr(t[1]), result)
+    return string.join(result, '\n ')
+
+module_attributes = [
+    '__version__',
+    '__build__',
+    '__buildsys__',
+    '__date__',
+    '__developer__',
+]
+
+def get_module_info(module):
+    module_attributes.sort()
+    result = map(lambda k: (k, getattr(module, k, _null)), module_attributes)
+    result = filter(lambda t: not t[1] is _null, result)
+    result = map(lambda t: t[0] + '=' + repr(t[1]), result)
+    return string.join(result, '\n ')
+
+environ_keys = [
+   'PATH',
+   'SCONS',
+   'SCONSFLAGS',
+   'SCONS_LIB_DIR',
+   'PYTHON_ROOT',
+   'QTDIR',
+
+   'COMSPEC',
+   'INTEL_LICENSE_FILE',
+   'INCLUDE',
+   'LIB',
+   'MSDEVDIR',
+   'OS',
+   'PATHEXT',
+   'SYSTEMROOT',
+   'TEMP',
+   'TMP',
+   'USERNAME',
+   'VXDOMNTOOLS',
+   'WINDIR',
+   'XYZZY'
+
+   'ENV',
+   'HOME',
+   'LANG',
+   'LANGUAGE',
+   'LC_ALL',
+   'LC_MESSAGES',
+   'LOGNAME',
+   'MACHINE',
+   'OLDPWD',
+   'PWD',
+   'OPSYS',
+   'SHELL',
+   'TMPDIR',
+   'USER',
+]
+
+def get_environment():
+    environ_keys.sort()
+    result = map(lambda k: (k, os.environ.get(k, _null)), environ_keys)
+    result = filter(lambda t: not t[1] is _null, result)
+    result = map(lambda t: t[0] + '-' + t[1], result)
+    return string.join(result, '\n ')
+
+class SConsXMLResultStream(XMLResultStream):
+    def __init__(self, *args, **kw):
+        super(SConsXMLResultStream, self).__init__(*args, **kw)
+    def WriteAllAnnotations(self, context):
+        # Load (by hand) the SCons modules we just unwrapped so we can
+        # extract their version information.  Note that we have to override
+        # SCons.Script.main() with a do_nothing() function, because loading up
+        # the 'scons' script will actually try to execute SCons...
+
+        src_engine = os.environ.get('SCONS_LIB_DIR')
+        if not src_engine:
+            src_engine = os.path.join('src', 'engine')
+        fp, pname, desc = imp.find_module('SCons', [src_engine])
+        SCons = imp.load_module('SCons', fp, pname, desc)
+
+        # Override SCons.Script.main() with a do-nothing function, because
+        # loading the 'scons' script will actually try to execute SCons...
+
+        src_engine_SCons = os.path.join(src_engine, 'SCons')
+        fp, pname, desc = imp.find_module('Script', [src_engine_SCons])
+        SCons.Script = imp.load_module('Script', fp, pname, desc)
+        def do_nothing():
+            pass
+        SCons.Script.main = do_nothing
+
+        scons_file = os.environ.get('SCONS')
+        if scons_file:
+            src_script, scons_py = os.path.split(scons_file)
+            scons = os.path.splitext(scons_py)[0]
+        else:
+            src_script = os.path.join('src', 'script')
+            scons = 'scons'
+        fp, pname, desc = imp.find_module(scons, [src_script])
+        scons = imp.load_module('scons', fp, pname, desc)
+        fp.close()
+
+        self.WriteAnnotation("scons_test.engine", get_module_info(SCons))
+        self.WriteAnnotation("scons_test.script", get_module_info(scons))
+
+        self.WriteAnnotation("scons_test.sys", get_sys_values())
+        self.WriteAnnotation("scons_test.os.environ", get_environment())
 
 class AegisStream(TextResultStream):
+    arguments = [
+        qm.fields.IntegerField(
+            name = "print_time",
+            title = "print individual test times",
+            description = """
+            """,
+            default_value = 0,
+        ),
+    ]
     def __init__(self, *args, **kw):
         super(AegisStream, self).__init__(*args, **kw)
         self._num_tests = 0
             self._DisplayText(result["Test.stderr"])
         except KeyError:
             pass
-        if result["Test.print_time"] != "0":
+        if self.print_time:
             start = float(result['qmtest.start_time'])
             end = float(result['qmtest.end_time'])
             fmt = "    Total execution time: %.1f seconds\n\n"
                     )
 
 class AegisBatchStream(FileResultStream):
-    arguments = [
-        qm.fields.TextField(
-            name = "results_file",
-            title = "Aegis Results File",
-            description = """
-            """,
-            verbatim = "true",
-            default_value = "aegis-results.txt",
-        ),
-    ]
     def __init__(self, arguments):
-        self.filename = arguments['results_file']
         super(AegisBatchStream, self).__init__(arguments)
         self._outcomes = {}
     def WriteResult(self, result):
         self._outcomes[test_id] = exit_status
     def Summarize(self):
         self.file.write('test_result = [\n')
-        for file_name, exit_status in self._outcomes.items():
+        file_names = self._outcomes.keys()
+        file_names.sort()
+        for file_name in file_names:
+            exit_status = self._outcomes[file_name]
+            file_name = string.replace(file_name, '\\', '/')
             self.file.write('    { file_name = "%s";\n' % file_name)
             self.file.write('      exit_status = %s; },\n' % exit_status)
         self.file.write('];\n')
         and fails otherwise. The program output is logged, but not validated."""
 
         command = RedirectedExecutable()
-        args = [context.get('python', 'python'), self.script]
+        args = [context.get('python', sys.executable), self.script]
         status = command.Run(args, os.environ)
-        result["Test.print_time"] = context.get('print_time', '0')
         if not check_exit_status(result, 'Test.', self.script, status):
             # In case of failure record exit code, stdout, and stderr.
             result.Fail("Non-zero exit_code.")
     env.Command(local_targets, build_src_files, commands)
 
     scons_LICENSE = os.path.join(local, 'scons-LICENSE')
-    env.SCons_revision(scons_LICENSE, 'LICENSE-local')
-    local_targets.append(scons_LICENSE)
+    l = env.SCons_revision(scons_LICENSE, 'LICENSE-local')
+    local_targets.append(l)
+    Local(l)
 
     scons_README = os.path.join(local, 'scons-README')
-    env.SCons_revision(scons_README, 'README-local')
-    local_targets.append(scons_README)
+    l = env.SCons_revision(scons_README, 'README-local')
+    local_targets.append(l)
+    Local(l)
 
     if gzip:
         env.Command(local_tar_gz,
+#
+#	aegis - project change supervisor
+#	Copyright (C) 2004 Peter Miller;
+#	All rights reserved.
+#
+#	As a specific exception to the GPL, you are allowed to copy
+#	this source file into your own project and modify it, without
+#	releasing your project under the GPL, unless there is some other
+#	file or condition which would require it.
+#
+# MANIFEST: shell script to commit changes to CVS
+#
+# It is assumed that your CVSROOT and CVS_RSH environment variables have
+# already been set appropriately.
+#
+# This script is expected to be run as by integrate_pass_notify_command
+# and as such the baseline has already assumed the shape asked for by
+# the change.
+#
+#	integrate_pass_notify_command =
+#    	    "$bin/ae-cvs-ci $project $change";
+#
+# Alternatively, you may wish to tailor this script to the individual
+# needs of your project.  Make it a source file, e.g. "etc/ae-cvs-ci.sh"
+# and then use the following:
+#
+#	integrate_pass_notify_command =
+#    	    "$sh ${s etc/ae-cvs-ci} $project $change";
+#
+
+USAGE="Usage: $0 <project> <change>"
+
+PRINT="echo"
+EXECUTE="eval"
+
+while getopts "hnq" FLAG
+do
+    case ${FLAG} in
+    h )
+        echo "${USAGE}"
+        exit 0
+        ;;
+    n )
+        EXECUTE=":"
+        ;;
+    q )
+        PRINT=":"
+        ;;
+    * )
+        echo "$0: unknown option ${FLAG}" >&2
+        exit 1
+        ;;
+    esac
+done
+
+shift `expr ${OPTIND} - 1`
+
+case $# in
+2)
+    project=$1
+    change=$2
+    ;;
+*)
+    echo "${USAGE}" 1>&2
+    exit 1
+    ;;
+esac
+
+here=`pwd`
+
+AEGIS_PROJECT=$project
+export AEGIS_PROJECT
+AEGIS_CHANGE=$change
+export AEGIS_CHANGE
+
+module=`echo $project | sed 's|[.].*||'`
+
+baseline=`aegis -cd -bl`
+
+if test X${TMPDIR} = X; then TMPDIR=/var/tmp; fi
+
+TMP=${TMPDIR}/ae-cvs-ci.$$
+mkdir ${TMP}
+cd ${TMP}
+
+PWD=`pwd`
+if test X${PWD} != X${TMP}; then
+    echo "$0: ended up in ${PWD}, not ${TMP}" >&2
+    exit 1
+fi
+
+fail()
+{
+    set +x
+    cd $here
+    rm -rf ${TMP}
+    echo "FAILED" 1>&2
+    exit 1
+}
+trap "fail" 1 2 3 15
+
+Command()
+{
+    ${PRINT} "$*"
+    ${EXECUTE} "$*"
+}
+
+#
+# Create a new CVS work area.
+#
+# Note: this assumes the module is checked-out into a directory of the
+# same name.  Is there a way to ask CVS where is is going to put a
+# modules, so we can always get the "cd" right?
+#
+${PRINT} cvs co $module
+${EXECUTE} cvs co $module > LOG 2>&1
+if test $? -ne 0; then cat LOG; fail; fi
+${EXECUTE} cd $module
+
+#
+# Now we need to extract the sources from Aegis and drop them into the
+# CVS work area.  There are two ways to do this.
+#
+# The first way is to use the generated tarball.
+# This has the advantage that it has the Makefile.in file in it, and
+# will work immediately.
+#
+# The second way is to use aetar, which will give exact sources, and
+# omit all derived files.  This will *not* include the Makefile.in,
+# and so will not be readily compilable.
+#
+# gunzip < $baseline/export/${project}.tar.gz | tardy -rp ${project} | tar xf -
+aetar -send -o - | tar xzf -
+
+#
+# If any new directories have been created we will need to add them
+# to CVS before we can add the new files which we know are in them,
+# or they would not have been created.  Do this only if the -n option
+# isn't used, because if it is, we won't have actually checked out the
+# source and we'd erroneously report that all of them need to be added.
+#
+if test "X${EXECUTE}" != "X:"
+then
+    find . \( -name CVS -o -name Attic \) -prune -o -type d -print |
+    xargs --max-args=1 |
+    while read dir
+    do
+        if [ ! -d $dir/CVS ]
+        then
+	    Command cvs add $dir
+        fi
+    done
+fi
+
+#
+# Use the Aegis meta-data to perform some CVS commands that CVS can't
+# figure out for itself.
+#
+aegis -l cf -unf | sed 's| -> [0-9][0-9.]*||' |
+while read usage action rev filename
+do
+    if test "x$filename" = "x"
+    then
+        filename="$rev"
+    fi
+    case $action in
+    create)
+	Command cvs add $filename
+	;;
+    remove)
+	Command rm -f $filename
+	Command cvs remove $filename
+	;;
+    *)
+	;;
+    esac
+done
+
+#
+# Now commit all the changes.
+#
+message=`aesub '${version} - ${change description}'`
+Command cvs -q commit -m \"$message\"
+
+#
+# All done.  Clean up and go home.
+#
+cd $here
+rm -rf ${TMP}
+exit 0
+#
+#	aegis - project change supervisor
+#	Copyright (C) 2004 Peter Miller;
+#	All rights reserved.
+#
+#	As a specific exception to the GPL, you are allowed to copy
+#	this source file into your own project and modify it, without
+#	releasing your project under the GPL, unless there is some other
+#	file or condition which would require it.
+#
+# MANIFEST: shell script to commit changes to Subversion
+#
+# This script is expected to be run by the integrate_pass_notify_command
+# and as such the baseline has already assumed the shape asked for by
+# the change.
+#
+#	integrate_pass_notify_command =
+#    	    "$bin/ae-svn-ci $project $change http://svn.site.com/svn/trunk --username svn_user";
+#
+# Alternatively, you may wish to tailor this script to the individual
+# needs of your project.  Make it a source file, e.g. "etc/ae-svn-ci.sh"
+# and then use the following:
+#
+#	integrate_pass_notify_command =
+#    	    "$sh ${s etc/ae-svn-ci} $project $change http://svn.site.com/svn/trunk --username svn_user";
+#
+
+USAGE="Usage: $0 [-hnq] <project> <change> <url> [<co_options>]"
+
+PRINT="echo"
+EXECUTE="eval"
+
+while getopts "hnq" FLAG
+do
+    case ${FLAG} in
+    h )
+        echo "${USAGE}"
+        exit 0
+        ;;
+    n )
+        EXECUTE=":"
+        ;;
+    q )
+        PRINT=":"
+        ;;
+    * )
+        echo "$0: unknown option ${FLAG}" >&2
+        exit 1
+        ;;
+    esac
+done
+
+shift `expr ${OPTIND} - 1`
+
+case $# in
+[012])
+    echo "${USAGE}" 1>&2
+    exit 1
+    ;;
+*)
+    project=$1
+    change=$2
+    svn_url=$3
+    shift 3
+    svn_co_flags=$*
+    ;;
+esac
+
+here=`pwd`
+
+AEGIS_PROJECT=$project
+export AEGIS_PROJECT
+AEGIS_CHANGE=$change
+export AEGIS_CHANGE
+
+module=`echo $project | sed 's|[.].*||'`
+
+baseline=`aegis -cd -bl`
+
+if test X${TMPDIR} = X; then TMPDIR=/var/tmp; fi
+
+TMP=${TMPDIR}/ae-svn-ci.$$
+mkdir ${TMP}
+cd ${TMP}
+
+PWD=`pwd`
+if test X${PWD} != X${TMP}; then
+    echo "$0: ended up in ${PWD}, not ${TMP}" >&2
+    exit 1
+fi
+
+fail()
+{
+    set +x
+    cd $here
+    rm -rf ${TMP}
+    echo "FAILED" 1>&2
+    exit 1
+}
+trap "fail" 1 2 3 15
+
+Command()
+{
+    ${PRINT} "$*"
+    ${EXECUTE} "$*"
+}
+
+#
+# Create a new Subversion work area.
+#
+# Note: this assumes the module is checked-out into a directory of the
+# same name.  Is there a way to ask Subversion where it is going to put a
+# module, so we can always get the "cd" right?
+#
+${PRINT} svn co $svn_url $module $svn_co_flags
+${EXECUTE} svn co $svn_url $module $svn_co_flags > LOG 2>&1
+if test $? -ne 0; then cat LOG; fail; fi
+${EXECUTE} cd $module
+
+#
+# Now we need to extract the sources from Aegis and drop them into the
+# Subversion work area.  There are two ways to do this.
+#
+# The first way is to use the generated tarball.
+# This has the advantage that it has the Makefile.in file in it, and
+# will work immediately.
+#
+# The second way is to use aetar, which will give exact sources, and
+# omit all derived files.  This will *not* include the Makefile.in,
+# and so will not be readily compilable.
+#
+# gunzip < $baseline/export/${project}.tar.gz | tardy -rp ${project} | tar xf -
+aetar -send -o - | tar xzf -
+
+#
+# If any new directories have been created we will need to add them
+# to Subversion before we can add the new files which we know are in them,
+# or they would not have been created.  Do this only if the -n option
+# isn't used, because if it is, we won't have actually checked out the
+# source and we'd erroneously report that all of them need to be added.
+#
+if test "X${EXECUTE}" != "X:"
+then
+    find . -name .svn -prune -o -type d -print |
+    xargs --max-args=1 |
+    while read dir
+    do
+        if [ ! -d $dir/.svn ]
+        then
+            Command svn add -N $dir
+        fi
+    done
+fi
+
+#
+# Use the Aegis meta-data to perform some commands that Subversion can't
+# figure out for itself.  We use an inline "aer" report script to identify
+# when a remove-create pair are actually due to a move.
+#
+aegis -rpt -nph -f - <<_EOF_ |
+auto cs;
+cs = project[project_name()].state.branch.change[change_number()];
+
+columns({width = 1000;});
+
+auto file, moved;
+for (file in cs.src)
+{
+    if (file.move != "")
+        moved[file.move] = 1;
+}
+
+auto action;
+for (file in cs.src)
+{
+    if (file.action == "remove" && file.move != "")
+        action = "move";
+    else
+        action = file.action;
+    /*
+     * Suppress printing of any files created as the result of a move.
+     * These are printed as the destination when printing the line for
+     * the file that was *removed* as a result of the move.
+     */
+    if (action != "create" || ! moved[file.file_name])
+        print(sprintf("%s %s \\"%s\\" \\"%s\\"", file.usage, action, file.file_name, file.move));
+}
+_EOF_
+while read line
+do
+    eval set -- "$line"
+    usage="$1"
+    action="$2"
+    srcfile="$3"
+    dstfile="$4"
+    case $action in
+    create)
+        Command svn add $srcfile
+        ;;
+    remove)
+        Command rm -f $srcfile
+        Command svn remove $srcfile
+        ;;
+    move)
+        Command mv $dstfile $dstfile.move
+        Command svn move $srcfile $dstfile
+        Command cp $dstfile.move $dstfile
+        Command rm -f $dstfile.move
+        ;;
+    *)
+        ;;
+    esac
+done
+
+#
+# Now commit all the changes.
+#
+message=`aesub '${version} - ${change description}'`
+Command svn commit -m \"$message\"
+
+#
+# All done.  Clean up and go home.
+#
+cd $here
+rm -rf ${TMP}
+exit 0

bin/caller-tree.py

+#!/usr/bin/env python
+#
+# Quick script to process the *summary* output from SCons.Debug.caller()
+# and print indented calling trees with call counts.
+#
+# The way to use this is to add something like the following to a function
+# for which you want information about who calls it and how many times:
+#
+#       from SCons.Debug import caller
+#       caller(0, 1, 2, 3, 4, 5)
+#
+# Each integer represents how many stack frames back SCons will go
+# and capture the calling information, so in the above example it will
+# capture the calls six levels up the stack in a central dictionary.
+#
+# At the end of any run where SCons.Debug.caller() is used, SCons will
+# print a summary of the calls and counts that looks like the following:
+#
+#       Callers of Node/__init__.py:629(calc_signature):
+#                1 Node/__init__.py:683(calc_signature)
+#       Callers of Node/__init__.py:676(gen_binfo):
+#                6 Node/FS.py:2035(current)
+#                1 Node/__init__.py:722(get_bsig)
+#
+# If you cut-and-paste that summary output and feed it to this script
+# on standard input, it will figure out how these entries hook up and
+# print a calling tree for each one looking something like:
+#
+#   Node/__init__.py:676(gen_binfo)
+#     Node/FS.py:2035(current)                                           6
+#       Taskmaster.py:253(make_ready_current)                           18
+#         Script/Main.py:201(make_ready)                                18
+#
+# Note that you should *not* look at the call-count numbers in the right
+# hand column as the actual number of times each line *was called by*
+# the function on the next line.  Rather, it's the *total* number
+# of times each function was found in the call chain for any of the
+# calls to SCons.Debug.caller().  If you're looking at more than one
+# function at the same time, for example, their counts will intermix.
+# So use this to get a *general* idea of who's calling what, not for
+# fine-grained performance tuning.
+
+import sys
+
+class Entry:
+    def __init__(self, file_line_func):
+        self.file_line_func = file_line_func
+        self.called_by = []
+        self.calls = []
+
+AllCalls = {}
+
+def get_call(flf):
+    try:
+        e = AllCalls[flf]
+    except KeyError:
+        e = AllCalls[flf] = Entry(flf)
+    return e
+
+prefix = 'Callers of '
+
+c = None
+for line in sys.stdin.readlines():
+    if line[0] == '#':
+        pass
+    elif line[:len(prefix)] == prefix:
+        c = get_call(line[len(prefix):-2])
+    else:
+        num_calls, flf = line.strip().split()
+        e = get_call(flf)
+        c.called_by.append((e, num_calls))
+        e.calls.append(c)
+
+stack = []
+
+def print_entry(e, level, calls):
+    print '%-72s%6s' % ((' '*2*level) + e.file_line_func, calls)
+    if e in stack:
+        print (' '*2*(level+1))+'RECURSION'
+        print
+    elif e.called_by:
+        stack.append(e)
+        for c in e.called_by:
+            print_entry(c[0], level+1, c[1])
+        stack.pop()
+    else:
+        print
+
+for e in [ e for e in AllCalls.values() if not e.calls ]:
+    print_entry(e, 0, '')
 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
 PROG=`basename $0`
-FLAGS="ahnqrstz"
-USAGE="Usage:  ${PROG} [-${FLAGS}] change"
+NOARGFLAGS="afhlnqrstz"
+ARGFLAGS="p:"
+ALLFLAGS="${NOARGFLAGS}${ARGFLAGS}"
+USAGE="Usage:  ${PROG} [-${NOARGFLAGS}] [-p project] change"
 
 HELP="$USAGE
 
-  -a    Update the latest Aegis baseline (aedist) file.
-  -h    Print this help message and exit.
-  -n    Don't execute, just echo commands.
-  -q    Quiet, don't print commands before executing them.
-  -r    Rsync the Aegis repository to SourceForge.
-  -s    Update the sourceforge.net CVS repository.
-  -t    Update the tigris.org CVS repository.
-  -z    Update the latest .zip file.
+  -a            Update the latest Aegis baseline (aedist) file.
+  -f            Force update, skipping up-front sanity check.
+  -h            Print this help message and exit.
+  -l            Update the local CVS repository.
+  -n            Don't execute, just echo commands.
+  -p project    Set the Aegis project.
+  -q            Quiet, don't print commands before executing them.
+  -r            Rsync the Aegis repository to SourceForge.
+  -s            Update the sourceforge.net CVS repository.
+  -t            Update the tigris.org CVS repository.
+  -z            Update the latest .tar.gz and .zip files.
 "
 
 DO=""
 PRINT="echo"
 EXECUTE="eval"
+SANITY_CHECK="yes"
 
-while getopts $FLAGS FLAG; do
-	case $FLAG in
-	a | r | s | t | z )
-		DO="${DO}${FLAG}"
-		;;
-	h )
-		echo "${HELP}"
-                exit 0
-		;;
-	n )
-		EXECUTE=":"
-		;;
-        q )
-		PRINT=":"
-                ;;
-	* )
-		echo "${USAGE}" >&2
-		exit 1
-		;;
-	esac
+while getopts $ALLFLAGS FLAG; do
+    case $FLAG in
+    a | l | r | s | t | z )
+        DO="${DO}${FLAG}"
+        ;;
+    f )
+        SANITY_CHECK="no"
+        ;;
+    h )
+        echo "${HELP}"
+        exit 0
+        ;;
+    n )
+        EXECUTE=":"
+        ;;
+    p )
+        AEGIS_PROJECT="${OPTARG}"
+        ;;
+    q )
+        PRINT=":"
+        ;;
+    * )
+        echo "FLAG = ${FLAG}" >&2
+        echo "${USAGE}" >&2
+        exit 1
+        ;;
+    esac
 done
 
 shift `expr ${OPTIND} - 1`
     exit 1
 fi
 
+if test "X${AEGIS_PROJECT}" = "X"; then
+    echo "$PROG: No AEGIS_PROJECT set." >&2
+    echo "${USAGE}" >&2
+    exit 1
+fi
+
 if test "X$DO" = "X"; then
-    DO="arstz"
+    DO="alrstz"
 fi
 
 cmd()
 
 CHANGE=$1
 
+if test "X${SANITY_CHECK}" = "Xyes"; then
+    SCM="cvs"
+    SCMROOT="/home/scons/CVSROOT/scons"
+    DELTA=`aegis -l -ter cd ${CHANGE} | sed -n 's/.*, Delta \([0-9]*\)\./\1/p'`
+    if test "x${DELTA}" = "x"; then
+        echo "${PROG}:  Could not find delta for change ${CHANGE}." >&2
+        echo "Has this finished integrating?  Change ${CHANGE} not distributed." >&2
+        exit 1
+    fi
+    PREV_DELTA=`expr ${DELTA} - 1`
+    COMMAND="scons-scmcheck -D ${PREV_DELTA} -d q -p ${AEGIS_PROJECT} -s ${SCM} ${SCMROOT}"
+    $PRINT "${COMMAND}"
+    OUTPUT=`${COMMAND}`
+    if test "X${OUTPUT}" != "X"; then
+        echo "${PROG}: ${SCMROOT} is not up to date:" >&2
+        echo "${OUTPUT}" >& 2
+        echo "Did you skip any changes?  Change ${CHANGE} not distributed." >&2
+        exit 1
+    fi
+fi
+
 if test X$EXECUTE != "X:" -a "X$SSH_AGENT_PID" = "X"; then
     eval `ssh-agent`
     ssh-add
 TMPBLAE="/tmp/${BASELINE}.ae"
 TMPCAE="/tmp/${AEGIS_PROJECT}.C${CHANGE}.ae"
 
-SFLOGIN="stevenknight"
-SFHOST="scons.sourceforge.net"
-SFDEST="/home/groups/s/sc/scons/htdocs"
+# Original values for SourceForge.
+#SFLOGIN="stevenknight"
+#SFHOST="scons.sourceforge.net"
+#SFDEST="/home/groups/s/sc/scons/htdocs"
+
+SCONSLOGIN="scons"
+SCONSHOST="manam.pair.com"
+#SCONSDEST="public_html/production"
+SCONSDEST="public_ftp"
 
 #
 # Copy the baseline .ae to the constant location on SourceForge.
 #
 case "${DO}" in
-    *a* )
-        cmd "aedist -s -bl -p ${AEGIS_PROJECT} > ${TMPBLAE}"
-        cmd "scp ${TMPBLAE} ${SFLOGIN}@${SFHOST}:${SFDEST}/${BASELINE}.ae"
-        cmd "rm ${TMPBLAE}"
-        ;;
+*a* )
+    cmd "aedist -s -bl -p ${AEGIS_PROJECT} > ${TMPBLAE}"
+    cmd "scp ${TMPBLAE} ${SCONSLOGIN}@${SCONSHOST}:${SCONSDEST}/${BASELINE}.ae"
+    cmd "rm ${TMPBLAE}"
+    ;;
 esac
 
 #
-# Copy the latest .zip file to the constant location on SourceForge.
+# Copy the latest .tar.gz and .zip files to the constant location on
+# SourceForge.
 #
 case "${DO}" in
-    *z* )
-        BUILD_DIST=`aegis -p ${AEGIS_PROJECT} -cd -bl`/build/dist
-        SCONS_SRC=`echo ${AEGIS_PROJECT} | sed 's/scons./scons-src-/'`.zip
-        cmd "scp ${BUILD_DIST}/${SCONS_SRC} ${SFLOGIN}@${SFHOST}:${SFDEST}/scons-src-latest.zip"
+*z* )
+    BUILD_DIST=`aegis -p ${AEGIS_PROJECT} -cd -bl`/build/dist
+    SCONS_SRC_TAR_GZ=`echo ${AEGIS_PROJECT} | sed 's/scons./scons-src-/'`*.tar.gz
+    SCONS_SRC_ZIP=`echo ${AEGIS_PROJECT} | sed 's/scons./scons-src-/'`*.zip
+    cmd "scp ${BUILD_DIST}/${SCONS_SRC_TAR_GZ} ${SCONSLOGIN}@${SCONSHOST}:${SCONSDEST}/scons-src-latest.tar.gz"
+    cmd "scp ${BUILD_DIST}/${SCONS_SRC_ZIP} ${SCONSLOGIN}@${SCONSHOST}:${SCONSDEST}/scons-src-latest.zip"
 esac
 
 #
 # We no longer use the --stats option.
 #
 case "${DO}" in
-    *r* )
-	LOCAL=/home/scons/scons
-	REMOTE=/home/groups/s/sc/scons/scons
-	cmd "/usr/bin/rsync --rsh=ssh -l -p -r -t -z \
-		--exclude build \
-		--exclude '*,D' \
-		--exclude '*.pyc' \
-		--exclude aegis.log \
-                --exclude '.sconsign*' \
-		--delete --delete-excluded \
-		--progress -v \
-		${LOCAL}/. scons.sourceforge.net:${REMOTE}/."
-        ;;
+*r* )
+    LOCAL=/home/scons/scons
+    REMOTE=/home/groups/s/sc/scons/scons
+    cmd "/usr/bin/rsync --rsh='ssh -l stevenknight' \
+            -l -p -r -t -z \
+            --exclude build \
+            --exclude '*,D' \
+            --exclude '*.pyc' \
+            --exclude aegis.log \
+            --exclude '.sconsign*' \
+            --delete --delete-excluded \
+            --progress -v \
+            ${LOCAL}/. scons.sourceforge.net:${REMOTE}/."
+    ;;
 esac
 
 #
-# Sync the CVS tree with Tigris.org.
+# Sync the CVS tree with the local repository.
 #
 case "${DO}" in
-    *t* )
-        cmd "ae2cvs -X -aegis -p ${AEGIS_PROJECT} -c ${CHANGE} -u $HOME/SCons/tigris.org/scons"
-        ;;
+*l* )
+    (
+        export CVSROOT=/home/scons/CVSROOT/scons
+        #cmd "ae2cvs -X -aegis -p ${AEGIS_PROJECT} -c ${CHANGE} -u $HOME/SCons/baldmt.com/scons"
+        cmd "ae-cvs-ci ${AEGIS_PROJECT} ${CHANGE}"
+    )
+    ;;
+esac
+
+#
+# Sync the Subversion tree with Tigris.org.
+#
+case "${DO}" in
+*t* )
+    (
+        SVN=http://scons.tigris.org/svn/scons
+        case ${AEGIS_PROJECT} in
+        scons.0.96 )
+            SVN_URL=${SVN}/branches/core
+            ;;
+        scons.0.96.513 )
+            SVN_URL=${SVN}/branches/sigrefactor
+            ;;
+        * )
+            echo "$PROG: Don't know SVN branch for '${AEGIS_PROJECT}'" >&2
+            exit 1
+            ;;
+        esac
+        SVN_CO_FLAGS="--username stevenknight"
+        #cmd "ae2cvs -X -aegis -p ${AEGIS_PROJECT} -c ${CHANGE} -u $HOME/SCons/tigris.org/scons"
+        cmd "ae-svn-ci ${AEGIS_PROJECT} ${CHANGE} ${SVN_URL} ${SVN_CO_FLAGS}"
+    )
+    ;;
 esac
 
 #
 # Sync the CVS tree with SourceForge.
 #
 case "${DO}" in
-    *s* )
+*s* )
+    (
         export CVS_RSH=ssh
-        cmd "ae2cvs -X -aegis -p ${AEGIS_PROJECT} -c ${CHANGE} -u $HOME/SCons/sourceforge.net/scons"
-        ;;
+        export CVSROOT=:ext:stevenknight@scons.cvs.sourceforge.net:/cvsroot/scons
+        #cmd "ae2cvs -X -aegis -p ${AEGIS_PROJECT} -c ${CHANGE} -u $HOME/SCons/sourceforge.net/scons"
+        cmd "ae-cvs-ci ${AEGIS_PROJECT} ${CHANGE}"
+    )
+    ;;
 esac
 
 #
 #
 #aedist -s -p ${AEGIS_PROJECT} ${CHANGE} > ${TMPCAE}
 #aegis -l -p ${AEGIS_PROJECT} -c ${CHANGE} cd |
-#	pine -attach_and_delete ${TMPCAE} scons-aedist@lists.sourceforge.net
+#        pine -attach_and_delete ${TMPCAE} scons-aedist@lists.sourceforge.net

bin/scons-diff.py

+#!/usr/bin/env python
+#
+# scons-diff.py - diff-like utility for comparing SCons trees
+#
+# This supports most common diff options (with some quirks, like you can't
+# just say -c and have it use a default value), but canonicalizes the
+# various version strings within the file like __revision__, __build__,
+# etc. so that you can diff trees without having to ignore changes in
+# version lines.
+#
+
+import difflib
+import getopt
+import os.path
+import re
+import sys
+
+Usage = """\
+Usage: scons-diff.py [OPTIONS] dir1 dir2
+Options:
+    -c NUM, --context=NUM       Print NUM lines of copied context.
+    -h, --help                  Print this message and exit.
+    -n                          Don't canonicalize SCons lines.
+    -q, --quiet                 Print only whether files differ.
+    -r, --recursive             Recursively compare found subdirectories.
+    -s                          Report when two files are the same.
+    -u NUM, --unified=NUM       Print NUM lines of unified context.
+"""
+
+opts, args = getopt.getopt(sys.argv[1:],
+                           'c:dhnqrsu:',
+		           ['context=', 'help', 'recursive', 'unified='])
+
+diff_type = None
+edit_type = None
+context = 2
+recursive = False
+report_same = False
+diff_options = []
+
+def diff_line(left, right):
+    if diff_options:
+        opts = ' ' + ' '.join(diff_options)
+    else:
+        opts = ''
+    print 'diff%s %s %s' % (opts, left, right)
+
+for o, a in opts:
+    if o in ('-c', '-u'):
+        diff_type = o
+        context = int(a)
+        diff_options.append(o)
+    elif o in ('-h', '--help'):
+        print Usage
+	sys.exit(0)
+    elif o in ('-n'):
+        diff_options.append(o)
+        edit_type = o
+    elif o in ('-q'):
+        diff_type = o
+        diff_line = lambda l, r: None
+    elif o in ('-r', '--recursive'):
+        recursive = True
+        diff_options.append(o)
+    elif o in ('-s'):
+        report_same = True
+
+try:
+    left, right = args
+except ValueError:
+    sys.stderr.write(Usage)
+    sys.exit(1)
+
+def quiet_diff(a, b, fromfile='', tofile='',
+               fromfiledate='', tofiledate='', n=3, lineterm='\n'):
+    """
+    A function with the same calling signature as difflib.context_diff
+    (diff -c) and difflib.unified_diff (diff -u) but which prints
+    output like the simple, unadorned 'diff" command.
+    """
+    if a == b:
+        return []
+    else:
+        return ['Files %s and %s differ\n' % (fromfile, tofile)]
+
+def simple_diff(a, b, fromfile='', tofile='',
+                fromfiledate='', tofiledate='', n=3, lineterm='\n'):
+    """
+    A function with the same calling signature as difflib.context_diff
+    (diff -c) and difflib.unified_diff (diff -u) but which prints
+    output like the simple, unadorned 'diff" command.
+    """
+    sm = difflib.SequenceMatcher(None, a, b)
+    def comma(x1, x2):
+        return x1+1 == x2 and str(x2) or '%s,%s' % (x1+1, x2)
+    result = []
+    for op, a1, a2, b1, b2 in sm.get_opcodes():
+        if op == 'delete':
+            result.append("%sd%d\n" % (comma(a1, a2), b1))
+            result.extend(map(lambda l: '< ' + l, a[a1:a2]))
+        elif op == 'insert':
+            result.append("%da%s\n" % (a1, comma(b1, b2)))
+            result.extend(map(lambda l: '> ' + l, b[b1:b2]))
+        elif op == 'replace':
+            result.append("%sc%s\n" % (comma(a1, a2), comma(b1, b2)))
+            result.extend(map(lambda l: '< ' + l, a[a1:a2]))
+            result.append('---\n')
+            result.extend(map(lambda l: '> ' + l, b[b1:b2]))
+    return result
+
+diff_map = {
+    '-c'        : difflib.context_diff,
+    '-q'        : quiet_diff,
+    '-u'        : difflib.unified_diff,
+}
+
+diff_function = diff_map.get(diff_type, simple_diff)
+
+baseline_re = re.compile('(# |@REM )/home/\S+/baseline/')
+comment_rev_re = re.compile('(# |@REM )(\S+) 0.96.[CD]\d+ \S+ \S+( knight)')
+revision_re = re.compile('__revision__ = "[^"]*"')
+build_re = re.compile('__build__ = "[^"]*"')
+date_re = re.compile('__date__ = "[^"]*"')
+
+def lines_read(file):
+    return open(file).readlines()
+
+def lines_massage(file):
+    text = open(file).read()
+    text = baseline_re.sub('\\1', text)
+    text = comment_rev_re.sub('\\1\\2\\3', text)
+    text = revision_re.sub('__revision__ = "__FILE__"', text)
+    text = build_re.sub('__build__ = "0.96.92.DXXX"', text)
+    text = date_re.sub('__date__ = "2006/08/25 02:59:00"', text)
+    return text.splitlines(1)
+
+lines_map = {
+    '-n'        : lines_read,
+}
+
+lines_function = lines_map.get(edit_type, lines_massage)
+
+def do_diff(left, right, diff_subdirs):
+    if os.path.isfile(left) and os.path.isfile(right):
+        diff_file(left, right)
+    elif not os.path.isdir(left):
+        diff_file(left, os.path.join(right, os.path.split(left)[1]))
+    elif not os.path.isdir(right):
+        diff_file(os.path.join(left, os.path.split(right)[1]), right)
+    elif diff_subdirs:
+        diff_dir(left, right)
+
+def diff_file(left, right):
+    l = lines_function(left)
+    r = lines_function(right)
+    d = diff_function(l, r, left, right, context)
+    try:
+        text = ''.join(d)
+    except IndexError:
+        sys.stderr.write('IndexError diffing %s and %s\n' % (left, right))
+    else:
+        if text:
+            diff_line(left, right)
+            print text,
+        elif report_same:
+            print 'Files %s and %s are identical' % (left, right)
+
+def diff_dir(left, right):
+    llist = os.listdir(left)
+    rlist = os.listdir(right)
+    u = {}
+    for l in llist:
+        u[l] = 1
+    for r in rlist:
+        u[r] = 1
+    clist = [ x for x in u.keys() if x[-4:] != '.pyc' ]
+    clist.sort()
+    for x in clist:
+        if x in llist:
+            if x in rlist:
+                do_diff(os.path.join(left, x),
+                        os.path.join(right, x),
+                        recursive)
+            else:
+                print 'Only in %s: %s' % (left, x)
+        else:
+            print 'Only in %s: %s' % (right, x)
+
+do_diff(left, right, True)
  *
  * Look in aesub(5) for more information about command substitutions.
  */
-build_command = "python1.5 ${Source bootstrap.py} -Y${SUBSTitute : \\ -Y $Search_Path} date='${DAte %Y/%m/%d %H:%M:%S}' developer=${DEVeloper} version=${VERsion} change=${Change}";
+build_command = "python2.1 ${Source bootstrap.py} -Y${SUBSTitute : \\ -Y $Search_Path} date='${DAte %Y/%m/%d %H:%M:%S}' developer=${DEVeloper} version=${VERsion} change=${Change}";
 
 /*
  * SCons removes its targets before constructing them, which qualifies it
  * is set appropriately during a baseline test.  So we just use the
  * proper aesub variable to comment out the expanded $spe.
  */
-test_command = "python1.5 ${Source runtest.py Absolute} -p tar-gz -t -v ${SUBSTitute '\\.[CD][0-9]+$' '' ${VERsion}} -q --sp ${Search_Path} --spe ${Search_Path_Executable} ${File_Name}";
+test_command = "python1.5 ${Source runtest.py Absolute} --noqmtest -p tar-gz -t -v ${SUBSTitute '\\.[CD][0-9]+$' '' ${VERsion}} -q --sp ${Search_Path} --spe ${Search_Path_Executable} ${File_Name}";
 
-batch_test_command = "python1.5 ${Source runtest.py Absolute} -p tar-gz -t -v ${SUBSTitute '\\.[CD][0-9]+$' '' ${VERsion}} -o ${Output} --aegis  --sp ${Search_Path} --spe ${Search_Path_Executable} ${File_Names}";
+batch_test_command = "python1.5 ${Source runtest.py Absolute} --noqmtest -p tar-gz -t -v ${SUBSTitute '\\.[CD][0-9]+$' '' ${VERsion}} -o ${Output} --aegis  --sp ${Search_Path} --spe ${Search_Path_Executable} ${File_Names}";
 
 new_test_filename = "test/CHANGETHIS.py";
 
 		body = "${read_file ${source template/test.py abs}}";
 	},
 ];
+
+/*
+ * Command for distributing changes from Aegis to all of the repositories
+ * we want to mirror the information.
+ *
+ * XXX Uncomment after upgrading to an Aegis version that supports this.
+
+integrate_pass_notify_command =
+        "$sh ${s bin/scons-cdist} -p $project $change";
+ *
+ */
                         variables_gen, variables_mod]
     b = env.Command(doc_output_files,
                     scons_doc_files,
-                    "python $SCONS_PROC_PY --sgml -b ${TARGETS[0]},${TARGETS[1]} -t ${TARGETS[2]},${TARGETS[3]} -v ${TARGETS[4]},${TARGETS[5]} $( $SOURCES $)")
+                    "$PYTHON $SCONS_PROC_PY --sgml -b ${TARGETS[0]},${TARGETS[1]} -t ${TARGETS[2]},${TARGETS[3]} -v ${TARGETS[4]},${TARGETS[5]} $( $SOURCES $)")
     env.Depends(b, "$SCONS_PROC_PY")
 
     env.Local(b)
                 if build_doc and ext == '.sgml':
                     env.Command(doc_s,
                                 base + '.in',
-                                "python $SCONSOUTPUT_PY $SOURCE > $TARGET")
+                                "$PYTHON $SCONSOUTPUT_PY $SOURCE > $TARGET")
                 orig_env.SCons_revision(build_s, doc_s)
             Local(build_s)
 
 man_intermediate_files = map(lambda x: os.path.join(build, 'man', x),
                              man_i_files)
 
-cmd = "python $SCONS_PROC_PY --man -b ${TARGETS[0]} -t ${TARGETS[1]} -v ${TARGETS[2]} $( $SOURCES $)"
+cmd = "$PYTHON $SCONS_PROC_PY --man -b ${TARGETS[0]} -t ${TARGETS[1]} -v ${TARGETS[2]} $( $SOURCES $)"
 man_intermediate_files = env.Command(man_intermediate_files,
                                      scons_doc_files,
                                      cmd)
 function.
 
 .TP
+.RI --cache-debug= file
+Print debug information about the
+.BR CacheDir ()
+derived-file caching
+to the specified
+.IR file .
+If
+.I file
+is
+.B \-
+(a hyphen),
+the debug information are printed to the standard output.
+The printed messages describe what signature file names are
+being looked for in, retrieved from, or written to the
+.BR CacheDir ()
+directory tree.
+
+.TP
 --cache-disable, --no-cache
 Disable the derived-file caching specified by
 .BR CacheDir ().
 See the section "Action Objects,"
 below, for a complete explanation of the arguments and behavior.
 
+Note that the 
+.BR env.Action ()
+form of the invocation will expand
+construction variables in any arguments strings,
+including the
+.I action
+argument,
+at the time it is called
+using the construction variables in the
+.B env
+construction environment through which
+.BR env.Action ()
+was called.
+The
+.BR Action ()
+form delays all variable expansion
+until the Action object is actually used.
+
 '\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
 .TP 
 .RI AddPostAction( target ", " action )
 See the section "Builder Objects,"
 below, for a complete explanation of the arguments and behavior.
 
+Note that the 
+.BR env.Builder ()
+form of the invocation will expand
+construction variables in any arguments strings,
+including the
+.I action
+argument,
+at the time it is called
+using the construction variables in the
+.B env
+construction environment through which
+.BR env.Builder ()
+was called.
+The
+.BR Builder ()
+form delays all variable expansion
+until after the Builder object is actually called.
+
 '\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
 .TP 
 .RI CacheDir( cache_dir )
 .RI Install( dir ", " source )
 .TP
 .RI env.Install( dir ", " source )
-Installs one or more files in a destination directory.
-The file names remain the same.
+Installs one or more source files or directories
+in a destination directory
+.IR dir .
+The names of the specified source files or directories
+remain the same within the destination directory.
 
 .ES
 env.Install(dir = '/usr/local/bin', source = ['foo', 'bar'])
 .RI InstallAs( target ", " source )
 .TP
 .RI env.InstallAs( target ", " source )
-Installs one or more files as specific file names,
-allowing changing a file name as part of the
-installation.
-It is an error if the target and source
-list different numbers of files.
+Installs one or more source files or directories
+to specific names,
+allowing changing a file or directory name
+as part of the installation.
+It is an error if the
+.I target
+and
+.I source
+arguments list different numbers of files or directories.
 
 .ES
 env.InstallAs(target = '/usr/local/bin/foo',
 
 '\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
 .TP
-.RI Value( value )
-.TP
-.RI env.Value( value )
+.RI Value( value ", [" built_value ])
+.TP
+.RI env.Value( value ", [" built_value ])
 Returns a Node object representing the specified Python value.  Value
-nodes can be used as dependencies of targets.  If the result of
+Nodes can be used as dependencies of targets.  If the result of
 calling
 .BR str( value )
 changes between SCons runs, any targets depending on
 .BR Value( value )
-will be rebuilt.  When using timestamp source signatures, Value nodes'
-timestamps are equal to the system time when the node is created.
+will be rebuilt.  When using timestamp source signatures, Value Nodes'
+timestamps are equal to the system time when the Node is created.
+
+The returned Value Node object has a
+.BR write ()
+method that can be used to "build" a Value Node
+by setting a new value.
+The optional
+.I built_value
+argument can be specified 
+when the Value Node is created
+to indicate the Node should already be considered
+"built."
+There is a corresponding
+.BR read ()
+method that will return the built value of the Node.
 
 .ES
 def create(target, source, env):
 env = Environment()
 env['BUILDERS']['Config'] = Builder(action = create)
 env.Config(target = 'package-config', source = Value(prefix))
+
+def build_value(target, source, env):
+    target[0].write(source[0].get_contents())
+
+output = env.Value('before')
+input = env.Value('after')
+