Commits

Anonymous committed 16adeda

Merged revisions 2136-2200,2202-2290,2292-2301 via svnmerge from
http://scons.tigris.org/svn/scons/branches/core

........
r2145 | stevenknight | 2007-07-17 09:15:12 -0500 (Tue, 17 Jul 2007) | 3 lines

Don't put null strings (from variable expansion) in a path list.
(They get turned into the current directory on later expansion.)
........
r2146 | stevenknight | 2007-07-17 10:47:39 -0500 (Tue, 17 Jul 2007) | 3 lines

Add support for optional arguments on command-line long options
by specifying nargs='?'.
........
r2149 | stevenknight | 2007-07-17 15:22:24 -0500 (Tue, 17 Jul 2007) | 2 lines

Remove left-over Optik mentions.
........
r2150 | stevenknight | 2007-07-17 15:39:34 -0500 (Tue, 17 Jul 2007) | 4 lines

Add a $SWIGPATH variable for finding SWIG dependencies, with
$SWIGINC{PREFIX,SUFFIX} for adding them to the command line.
........
r2154 | stevenknight | 2007-07-18 20:05:31 -0500 (Wed, 18 Jul 2007) | 2 lines

Fix variable misspellings in the doc added for $SWIGOUTPUT.
........
r2155 | stevenknight | 2007-07-18 20:07:28 -0500 (Wed, 18 Jul 2007) | 2 lines

Add the Python eggs info file to the RPM packaging build.
........
r2156 | stevenknight | 2007-07-18 20:15:08 -0500 (Wed, 18 Jul 2007) | 2 lines

Convert documentation from DocBook SGML to XML.
........
r2158 | stevenknight | 2007-07-19 17:16:19 -0500 (Thu, 19 Jul 2007) | 3 lines

Conditionally add the .egg-info the RPM file list only if the distutils
in the version of Python that rpmbuild will execute knows about them.
........
r2161 | stevenknight | 2007-07-19 19:12:29 -0500 (Thu, 19 Jul 2007) | 5 lines

Capture a test case (contributed by Tilo Prutz) where instantiation of
a private class causes javac to generate an additional anonymous inner
class file. (No solution yet, but there's no sense throwing away the
preparatory work.)
........
r2162 | stevenknight | 2007-07-20 11:29:56 -0500 (Fri, 20 Jul 2007) | 3 lines

Support passing a list of .java files as source to the Java() builder.
(Leanid Nazdrynau)
........
r2163 | garyo | 2007-07-20 12:00:35 -0500 (Fri, 20 Jul 2007) | 1 line

Fixed cut-n-paste error in Touch factory method doc in users guide.
........
r2167 | stevenknight | 2007-07-21 22:59:40 -0500 (Sat, 21 Jul 2007) | 2 lines

Don't execute the SWIGOUTDIR test if swig isn't installed.
........
r2168 | stevenknight | 2007-07-21 23:14:17 -0500 (Sat, 21 Jul 2007) | 2 lines

Fix the test's ability to run under a path name containing spaces.
........
r2171 | stevenknight | 2007-07-24 15:54:41 -0500 (Tue, 24 Jul 2007) | 2 lines

Handle white space in key file names in the packaging build.
........
r2172 | stevenknight | 2007-07-24 21:41:15 -0500 (Tue, 24 Jul 2007) | 2 lines

More efficient copying of construction environments.
........
r2173 | stevenknight | 2007-07-25 10:56:02 -0500 (Wed, 25 Jul 2007) | 2 lines

Update the SCons build for Subversion and general clean-up.
........
r2174 | stevenknight | 2007-07-25 11:35:16 -0500 (Wed, 25 Jul 2007) | 3 lines

Suppress the [brackets] around a node in the --tree=prune output if
the node is a source.
........
r2175 | stevenknight | 2007-07-25 12:52:18 -0500 (Wed, 25 Jul 2007) | 3 lines

Commonize the skip_test() method and make its behavior configurable
via a TESTCOMMON_PASS_SKIPS environment variable.
........
r2178 | stevenknight | 2007-07-25 21:43:47 -0500 (Wed, 25 Jul 2007) | 3 lines

Add $JAVACLASSPATH and $JAVASOURCEPATH construction variables. (Leanid
Nazdrynau)
........
r2182 | stevenknight | 2007-07-30 12:10:20 -0500 (Mon, 30 Jul 2007) | 3 lines

Refactor Builder suffix-adjusting into its own method, so we can
(potentially) re-use it for Builders with attached source Builders.
........
r2183 | stevenknight | 2007-07-30 14:51:53 -0500 (Mon, 30 Jul 2007) | 2 lines

More efficient source-builder suffix matching.
........
r2184 | stevenknight | 2007-07-30 16:01:42 -0500 (Mon, 30 Jul 2007) | 4 lines

Encapsulate initialization of the default FS object by an accessor
function in SCons.Node.FS. (This also gets rid of an unnecessary
reference to SCons.Node.FS.default_fs in the LaTeX scanner.)
........
r2193 | stevenknight | 2007-07-30 18:24:07 -0500 (Mon, 30 Jul 2007) | 3 lines

Fix interpretation of source arguments that have no suffix when the
called Builder has both a src_suffix and a src_builder.
........
r2194 | stevenknight | 2007-07-31 10:25:31 -0500 (Tue, 31 Jul 2007) | 2 lines

Increase the number of tries for random output from three to ten.
........
r2195 | stevenknight | 2007-07-31 10:52:28 -0500 (Tue, 31 Jul 2007) | 3 lines

Skip the test gracefully if the zipfile module can't read the file it
just wrote (which is the case for Python 2.1 on 64-bit systems).
........
r2196 | stevenknight | 2007-07-31 13:06:21 -0500 (Tue, 31 Jul 2007) | 2 lines

Move the "import zipfile" so it doesn't fail on Python <= 2.0.
........
r2197 | stevenknight | 2007-07-31 14:51:50 -0500 (Tue, 31 Jul 2007) | 3 lines

Commonize initialization of the various Java builders so they can be
hooked up into a multi-stage Builder chain. (Leanid Nazdrynau)
........
r2198 | stevenknight | 2007-07-31 16:15:18 -0500 (Tue, 31 Jul 2007) | 3 lines

Fix use of ${TARGET.dir} and ${SOURCE.dir} expansions in $FORTRANMODDIR
$JARCHDIR, $JARFLAGS, $LEXFLAGS, $SWIGFLAGS, $SWIGOUTDIR and $YACCFLAGS.
........
r2199 | stevenknight | 2007-07-31 16:25:48 -0500 (Tue, 31 Jul 2007) | 2 lines

Remove left-over Trace() call.
........
r2202 | stevenknight | 2007-08-01 12:31:48 -0500 (Wed, 01 Aug 2007) | 3 lines

Bail out via test.skip_test() if wix ("candle") isn't found.
Put the main body of code flush left instead of under an if: block.
........
r2203 | stevenknight | 2007-08-01 15:35:55 -0500 (Wed, 01 Aug 2007) | 5 lines

Fix Tool.packaging.rpm.package() so it doesn't always overwrite
$RPMFLAGS with -ta.
Set --buildroot in RPM packaging tests so they don't overwrite
each other when run simultaneously.
........
r2204 | stevenknight | 2007-08-01 15:37:36 -0500 (Wed, 01 Aug 2007) | 2 lines

Fix a nested scope issue with the internal build_sources() function.
........
r2205 | stevenknight | 2007-08-01 15:46:08 -0500 (Wed, 01 Aug 2007) | 5 lines

Normalize (X out) the CreationDate field inside embedded, compressed
PostScript streams within the generated PDF files. Also normalize
preceding Length field, since compression length is affected by different
patterns of input, including the variable CreationDate value.
........
r2211 | stevenknight | 2007-08-02 08:52:06 -0500 (Thu, 02 Aug 2007) | 2 lines

Add the new modules from branches/packaging to the SCons packaging build.
........
r2212 | stevenknight | 2007-08-02 19:59:01 -0500 (Thu, 02 Aug 2007) | 2 lines

Fix the JAVACLASSPATH test when javah isn't on the default $PATH.
........
r2214 | stevenknight | 2007-08-03 15:05:21 -0500 (Fri, 03 Aug 2007) | 4 lines

Hook up the Java builders into a multi-step chain underneath a Java()
pseudo-builder (wrapper) that examines its arguments and calls the
appropriate underlying file-or-dir builder.
........
r2215 | stevenknight | 2007-08-03 15:49:58 -0500 (Fri, 03 Aug 2007) | 2 lines

Fix for old Python versions: use apply() instead of *args, **kw.
........
r2216 | stevenknight | 2007-08-03 16:49:31 -0500 (Fri, 03 Aug 2007) | 2 lines

Hook up the SWIG builder as a source builder for .java files.
........
r2217 | stevenknight | 2007-08-03 17:28:19 -0500 (Fri, 03 Aug 2007) | 2 lines

Don't use .endswith(), which didn't appear until later Python versions.
........
r2218 | stevenknight | 2007-08-03 17:29:38 -0500 (Fri, 03 Aug 2007) | 2 lines

Replace tabs with spaces.
........
r2219 | stevenknight | 2007-08-04 08:06:23 -0500 (Sat, 04 Aug 2007) | 3 lines

Initialize a loop-invariant lambda for matching .java suffixes outside
the loop.
........
r2220 | stevenknight | 2007-08-07 15:06:13 -0500 (Tue, 07 Aug 2007) | 2 lines

Refactor parallel class-generation loops into one.
........
r2221 | stevenknight | 2007-08-07 16:04:06 -0500 (Tue, 07 Aug 2007) | 5 lines

Have the Java multi-step builder test actually check for generated files,
and fix the generation of .java and .class file names, and interaction
with the SWIG builder, so that the files are generated in the correct
place.
........
r2222 | stevenknight | 2007-08-07 16:45:05 -0500 (Tue, 07 Aug 2007) | 3 lines

Fix dependencies on SWIG-generated .java files so they don't have to
be built in multiple passes.
........
r2226 | stevenknight | 2007-08-07 18:00:22 -0500 (Tue, 07 Aug 2007) | 2 lines

Fix SWIG when used with BuildDir().
........
r2227 | stevenknight | 2007-08-07 22:15:55 -0500 (Tue, 07 Aug 2007) | 5 lines

User's guide updates:
- Make the multiple files example match its text.
- Expand a truncated sentence about being able to use Python function actions
in the Command() Builder.
........
r2228 | stevenknight | 2007-08-07 23:25:18 -0500 (Tue, 07 Aug 2007) | 3 lines

Don't generate an error if a #include file matches a same-named
directory in $CPPPATH (or $FORTRANPATH, etc.).
........
r2229 | stevenknight | 2007-08-07 23:40:00 -0500 (Tue, 07 Aug 2007) | 2 lines

Fix a code example. (Gary Oberbrunner)
........
r2230 | stevenknight | 2007-08-08 00:05:43 -0500 (Wed, 08 Aug 2007) | 3 lines

Capture a test case to make sure AddPostAction() doesn't interfere
with normal linking. (Matt Doar, Gary Oberbrunner)
........
r2233 | stevenknight | 2007-08-08 14:15:44 -0500 (Wed, 08 Aug 2007) | 2 lines

Fix documentation typo in a construction variable cross-reference.
........
r2234 | stevenknight | 2007-08-08 17:03:25 -0500 (Wed, 08 Aug 2007) | 2 lines

Changes to SCons packaging to support checkpoint releases.
........
r2235 | stevenknight | 2007-08-09 10:10:01 -0500 (Thu, 09 Aug 2007) | 2 lines

Sidestep false negatives on heavily loaded systems.
........
r2236 | garyo | 2007-08-09 11:16:26 -0500 (Thu, 09 Aug 2007) | 1 line

Allow unpackaged files (e.g. *.pyo) to exist in the build dir without being packaged in the RPM. Without this, on some systems the rpmbuild may error out.
........
r2237 | stevenknight | 2007-08-09 11:27:56 -0500 (Thu, 09 Aug 2007) | 5 lines

Fix test/SWIG/build-dir.py so it works on old Python versions without
distutils.sysconfig.
Instead of just cutting-and-pasting initialization code from other
SWIG tests, centralize it in some new TestSCons methods.
........
r2238 | garyo | 2007-08-09 11:30:58 -0500 (Thu, 09 Aug 2007) | 1 line

Use docbook 4.3 instead of 4.4 for the XML doctype since some older(?) jade parsers can't handle new 4-byte Unicode chars in the 4.4 version of isogrk4.ent.
........
r2240 | stevenknight | 2007-08-09 16:35:06 -0500 (Thu, 09 Aug 2007) | 2 lines

User's Guide updates (post packaging changes).
........
r2243 | stevenknight | 2007-08-10 10:31:51 -0500 (Fri, 10 Aug 2007) | 3 lines

Fix the User's Guide build to use openjade, and to accomodate a change
in the name of the main generated file (book1.html => index.html).
........
r2245 | stevenknight | 2007-08-10 11:09:16 -0500 (Fri, 10 Aug 2007) | 2 lines

Update the {CHANGES,RELEASE}.txt datestamp lines.
........
r2253 | stevenknight | 2007-08-10 16:21:54 -0500 (Fri, 10 Aug 2007) | 2 lines

Fix the wix Tool module's ability to handle null entries in $PATH.
........
r2261 | stevenknight | 2007-08-11 23:08:12 -0500 (Sat, 11 Aug 2007) | 3 lines

Remove unnecessary files (.svnt/*, .{ae,cvs}ignore, www/*) from the
scons-src packages.
........
r2262 | stevenknight | 2007-08-11 23:24:49 -0500 (Sat, 11 Aug 2007) | 2 lines

Add missing __revision__ lines.
........
r2263 | stevenknight | 2007-08-11 23:33:42 -0500 (Sat, 11 Aug 2007) | 2 lines

Skip the test if the MANIFEST file hasn't been built.
........
r2264 | stevenknight | 2007-08-11 23:36:30 -0500 (Sat, 11 Aug 2007) | 2 lines

Add recent compatibility modules to the relevant exceptions lists.
........
r2265 | stevenknight | 2007-08-11 23:39:00 -0500 (Sat, 11 Aug 2007) | 3 lines

Update __VERSION__ strings in the QMTest/*.py modules, so that packaging
tests (src/test_*.py) will pass after builds of checkpoint releases.
........
r2266 | stevenknight | 2007-08-12 07:36:19 -0500 (Sun, 12 Aug 2007) | 2 lines

Add a comment about why we construct the __VERSION__ string at run time.
........
r2267 | stevenknight | 2007-08-12 07:42:30 -0500 (Sun, 12 Aug 2007) | 2 lines

Avoid reading the MANIFEST file twice. (Courtesy review by Greg Noel.)
........
r2268 | stevenknight | 2007-08-12 08:14:53 -0500 (Sun, 12 Aug 2007) | 3 lines

Shift Install() and InstallAs() from being documented as functions
to being documented as Builders.
........
r2269 | garyo | 2007-08-13 08:49:52 -0500 (Mon, 13 Aug 2007) | 1 line

Tests: Skip some more Java tests if javac is not installed on the test machine so they don't get marked as failing.
........
r2270 | garyo | 2007-08-13 11:09:39 -0500 (Mon, 13 Aug 2007) | 1 line

Fixed typo in test (shows up on non-Linux platforms).
........
r2271 | garyo | 2007-08-13 14:09:05 -0500 (Mon, 13 Aug 2007) | 4 lines

Test portability fixes for Darwin/OSX and IRIX.
This does not make all the tests pass on those OSes,
but it takes care of some of the more obvious errors that
I have time for right now. More to come.
........
r2272 | stevenknight | 2007-08-13 15:33:29 -0500 (Mon, 13 Aug 2007) | 2 lines

Tab => space fix.
........
r2273 | stevenknight | 2007-08-13 15:33:52 -0500 (Mon, 13 Aug 2007) | 2 lines

Test for swig, too, which is used to build from the .i file.
........
r2277 | garyo | 2007-08-14 10:40:00 -0500 (Tue, 14 Aug 2007) | 8 lines

Test portability on IRIX: test/Actions/pre-post creates target file
before building target, then IRIX CC does not chmod +x afterwards.
I think this change is safe on all OSes.

test/AS/ml.py: I think this is only supposed to be run on win32
(not skipped only on win32); the sense of the skip test was backwards.
........
r2278 | stevenknight | 2007-08-14 11:04:40 -0500 (Tue, 14 Aug 2007) | 2 lines

Add -tt when running tests, to catch inconsistent tab usage.
........
r2279 | stevenknight | 2007-08-14 14:00:43 -0500 (Tue, 14 Aug 2007) | 2 lines

Minor refactor of logic in File.retrieve_from_cache().
........
r2280 | stevenknight | 2007-08-15 01:11:40 -0500 (Wed, 15 Aug 2007) | 2 lines

Refactor CacheDir support into its own module.
........
r2281 | stevenknight | 2007-08-15 07:24:51 -0500 (Wed, 15 Aug 2007) | 2 lines

Move the cachepath() method from FS.File to the CacheDir class.
........
r2282 | stevenknight | 2007-08-15 08:31:34 -0500 (Wed, 15 Aug 2007) | 2 lines

Python 1.5.2 fix in the new Null class.
........
r2283 | stevenknight | 2007-08-15 10:45:53 -0500 (Wed, 15 Aug 2007) | 5 lines

Refactor CacheDir unit tests to:
- restore functionality that was dropped in the transition;
- commonize creation of test Nodes and other (mock) objects
- separate CacheDir tests from tests of CacheDir through Node.FS.File.
........
r2284 | stevenknight | 2007-08-15 11:46:38 -0500 (Wed, 15 Aug 2007) | 3 lines

Replace the Executor.Null.NullEnvironment object with a real Null object,
so it will absorb the CacheDir method calls as well.
........
r2285 | stevenknight | 2007-08-15 11:52:57 -0500 (Wed, 15 Aug 2007) | 5 lines

Add a get_CacheDir() method to a construction environment, which will
be used to fetch per-environment CacheDir specifications. (Right now
all calls to it still just return the one attached to underlying default
FS object.)
........
r2286 | stevenknight | 2007-08-15 15:15:46 -0500 (Wed, 15 Aug 2007) | 2 lines

Support per-construction-environment configuration of CacheDir().
........
r2287 | stevenknight | 2007-08-15 15:33:04 -0500 (Wed, 15 Aug 2007) | 2 lines

Move the tests of CacheDir()-related command-line options into test/CacheDir.
........
r2293 | stevenknight | 2007-08-16 11:14:49 -0500 (Thu, 16 Aug 2007) | 3 lines

Add the Package() builder description to the documentation build,
fixing the XML so that it will build.
........
r2294 | stevenknight | 2007-08-16 12:51:19 -0500 (Thu, 16 Aug 2007) | 3 lines

Reorganize packaging documentation: alphabetize the variable definitions
(and function names), document Tag() as a function, not a builder.
........
r2296 | stevenknight | 2007-08-16 12:55:01 -0500 (Thu, 16 Aug 2007) | 2 lines

Add a build command.
........
r2300 | stevenknight | 2007-08-16 16:49:13 -0500 (Thu, 16 Aug 2007) | 2 lines

First cut at documenting packaging variables.
........
r2301 | stevenknight | 2007-08-16 16:51:21 -0500 (Thu, 16 Aug 2007) | 3 lines

Construct the .src.rpm and .arch.rpm file names independnetly, not
by trying to massage one into the other.
........

Comments (0)

Files changed (329)

 
 python = os.environ.get('PYTHON', sys.executable)
 
+build = [
+    '"%(python)s" bootstrap.py %%s' % locals()
+]
+
 cmd = '"%(python)s" runtest.py -q --noqmtest %%s' % locals()
 
 test_inputs = [

HOWTO/release.txt

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

HOWTO/subrelease.txt

         aecp SConstruct
         vi SConstruct
 
-        aecp rpm/scons.spec.in
-        vi rpm/scons.spec.in
-
-        aecp src/setup.py
-        vi src/setup.py
-
         aecp QMTest/TestSCons.py
         vi QMTest/TestSCons.py
 

QMTest/SConscript

 #
 
 import os.path
+import string
 
 Import('build_dir', 'env')
 
 def copy(target, source, env):
     t = str(target[0])
     s = str(source[0])
-    open(t, 'wb').write(open(s, 'rb').read())
+    c = open(s, 'rb').read()
+    # Note:  We construct the __ VERSION __ substitution string at
+    # run-time so it doesn't get replaced when this file gets copied
+    # into the tree for packaging.
+    c = string.replace(c, '__' + 'VERSION' + '__', env['VERSION'])
+    open(t, 'wb').write(c)
 
 for file in files:
     # Guarantee that real copies of these files always exist in

QMTest/TestCommon.py

             print self.stdout()
             self.diff(stderr, self.stderr(), 'STDERR ')
             raise TestFailed
+
+    def skip_test(self, message="Skipping test.\n"):
+        """Skips a test.
+
+        Proper test-skipping behavior is dependent on the external
+        TESTCOMMON_PASS_SKIPS environment variable.  If set, we treat
+        the skip as a PASS (exit 0), and otherwise treat it as NO RESULT.
+        In either case, we print the specified message as an indication
+        that the substance of the test was skipped.
+
+        (This was originally added to support development under Aegis.
+        Technically, skipping a test is a NO RESULT, but Aegis would
+        treat that as a test failure and prevent the change from going to
+        the next step.  Since we ddn't want to force anyone using Aegis
+        to have to install absolutely every tool used by the tests, we
+        would 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()
+        pass_skips = os.environ.get('TESTCOMMON_PASS_SKIPS')
+        if pass_skips in [None, 0, '0']:
+            # 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)
+        else:
+            # We're under the development directory for this change,
+            # so this is an Aegis invocation; pass the test (exit 0).
+            self.pass_test()

QMTest/TestRuntest.py

         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')

QMTest/TestSCons.py

 
 # 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.)
+# here provides some independent verification that what we packaged
+# conforms to what we expect.
 
-SConsVersion = '0.97'
+default_version = '0.97.0'
+
+SConsVersion = '__VERSION__'
+if SConsVersion == '__' + 'VERSION' + '__':
+    SConsVersion = default_version
 
 __all__.extend([ 'TestSCons',
                  'machine',
             kw['workdir'] = ''
         apply(TestCommon.__init__, [self], kw)
 
+        import SCons.Node.FS
+        if SCons.Node.FS.default_fs is None:
+            SCons.Node.FS.default_fs = SCons.Node.FS.FS()
+
     def Environment(self, ENV=None, *args, **kw):
         """
         Return a construction Environment that optionally overrides
         kw['match'] = self.match_re_dotall
         apply(self.run, [], kw)
 
-    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 diff_substr(self, expect, actual, prelen=20, postlen=40):
         i = 0
         for x, y in zip(expect, actual):
                    r'/CreationDate (D:XXXX)', s)
         s = re.sub(r'/ID \[<[0-9a-fA-F]*> <[0-9a-fA-F]*>\]',
                    r'/ID [<XXXX> <XXXX>]', s)
+        s = re.sub(r'/(BaseFont|FontName) /[A-Z]{6}',
+                   r'/\1 /XXXXXX', s)
+        s = re.sub(r'/Length \d+ *\n/Filter /FlateDecode\n',
+                   r'/Length XXXX\n/Filter /FlateDecode\n', s)
+
+
+        try:
+            import zlib
+        except ImportError:
+            pass
+        else:
+            begin_marker = '/FlateDecode\n>>\nstream\n'
+            end_marker = 'endstream\nendobj'
+
+            encoded = []
+            b = string.find(s, begin_marker, 0)
+            while b != -1:
+                b = b + len(begin_marker)
+                e = string.find(s, end_marker, b)
+                encoded.append((b, e))
+                b = string.find(s, begin_marker, e + len(end_marker))
+
+            x = 0
+            r = []
+            for b, e in encoded:
+                r.append(s[x:b])
+                d = zlib.decompress(s[b:e])
+                d = re.sub(r'%%CreationDate: [^\n]*\n',
+                           r'%%CreationDate: 1970 Jan 01 00:00:00\n', d)
+                d = re.sub(r'%DVIPSSource:  TeX output \d\d\d\d\.\d\d\.\d\d:\d\d\d\d',
+                           r'%DVIPSSource:  TeX output 1970.01.01:0000', d)
+                d = re.sub(r'/(BaseFont|FontName) /[A-Z]{6}',
+                           r'/\1 /XXXXXX', d)
+                r.append(d)
+                x = e
+            r.append(s[x:])
+            s = string.join(r, '')
+
         return s
 
     def java_ENV(self):
                 print "-----------------------------------------------------"
                 self.fail_test()
 
+    def get_python_version(self):
+        """
+        Returns the Python version (just so everyone doesn't have to
+        hand-code slicing the right number of characters).
+        """
+        # see also sys.prefix documentation
+        return sys.version[:3]
+
+    def get_platform_python(self):
+        """
+        Returns a path to a Python executable suitable for testing on
+        this platform.
+
+        Mac OS X has no static libpython for SWIG to link against,
+        so we have to link against Apple's framwork version.  However,
+        testing must use the executable version that corresponds to the
+        framework we link against, or else we get interpreter errors.
+        """
+        if sys.platform == 'darwin':
+            return '/System/Library/Frameworks/Python.framework/Versions/Current/bin/python'
+        else:
+            global python
+            return python
+
+    def get_quoted_platform_python(self):
+        """
+        Returns a quoted path to a Python executable suitable for testing on
+        this platform.
+
+        Mac OS X has no static libpython for SWIG to link against,
+        so we have to link against Apple's framwork version.  However,
+        testing must use the executable version that corresponds to the
+        framework we link against, or else we get interpreter errors.
+        """
+        if sys.platform == 'darwin':
+            return '"' + self.get_platform_python() + '"'
+        else:
+            global _python_
+            return _python_
+
+    def get_platform_sys_prefix(self):
+        """
+        Returns a "sys.prefix" value suitable for linking on this platform.
+
+        Mac OS X has a built-in Python but no static libpython,
+        so we must link to it using Apple's 'framework' scheme.
+        """
+        if sys.platform == 'darwin':
+            fmt = '/System/Library/Frameworks/Python.framework/Versions/%s/'
+            return fmt % self.get_python_version()
+        else:
+            return sys.prefix
+
+    def get_python_frameworks_flags(self):
+        """
+        Returns a FRAMEWORKSFLAGS value for linking with Python.
+
+        Mac OS X has a built-in Python but no static libpython,
+        so we must link to it using Apple's 'framework' scheme.
+        """
+        if sys.platform == 'darwin':
+            return '-framework Python'
+        else:
+            return ''
+
+    def get_python_inc(self):
+        """
+        Returns a path to the Python include directory.
+        """
+        try:
+            import distutils.sysconfig
+        except ImportError:
+            return os.path.join(self.get_platform_sys_prefix(),
+                                'include',
+                                'python' + self.get_python_version())
+        else:
+            return distutils.sysconfig.get_python_inc()
+
 # In some environments, $AR will generate a warning message to stderr
 # if the library doesn't previously exist and is being created.  One
 # way to fix this is to tell AR to be quiet (sometimes the 'c' flag),

QMTest/TestSCons_time.py

         self.write(python_name, profile_py % d)
         self.run(program = python_name, interpreter = sys.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_aegis_py(self, name):
         name = self.workpath(name)
         self.write(name, aegis_py)

QMTest/scons_tdb.py

         and fails otherwise. The program output is logged, but not validated."""
 
         command = RedirectedExecutable()
-        args = [context.get('python', sys.executable), self.script]
+        args = [context.get('python', sys.executable), '-tt', self.script]
         status = command.Run(args, os.environ)
         if not check_exit_status(result, 'Test.', self.script, status):
             # In case of failure record exit code, stdout, and stderr.
                 of lines in each
             --  a script for synchronizing the Aegis tree to SourceForge
             --  a prototype script for capturing sample SCons output
-                in sgml files
+                in xml files
             --  a script that can profile and time a packaging build of
                 SCons itself
             --  a copy of xml_export, which can retrieve project data
 #
 
 import distutils.util
+import fnmatch
 import os
 import os.path
 import re
-import socket
 import stat
 import string
 import sys
-import time
 
 project = 'scons'
-default_version = '0.97'
+default_version = '0.97.0'
 copyright = "Copyright (c) %s The SCons Foundation" % copyright_years
 
 SConsignFile()
     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')
+svn = 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']
+developer = ARGUMENTS.get('DEVELOPER')
+if not developer:
+    for variable in ['USERNAME', 'LOGNAME', 'USER']:
+        developer = os.environ.get(variable)
+        if developer:
+            break
 
-if ARGUMENTS.has_key('build_system'):
-    build_system = ARGUMENTS['build_system']
-else:
+build_system = ARGUMENTS.get('BUILD_SYSTEM')
+if not build_system:
+    import socket
     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]
+version = ARGUMENTS.get('VERSION', '')
+if not version:
+    version = default_version
+
+revision = ARGUMENTS.get('REVISION', '')
+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)
+
+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
+
+if svn:
+    svn_status = os.popen("%s status --verbose 2> /dev/null" % svn, "r").read()
+    svn_status_lines = svn_status[:-1].split('\n')
 else:
-    revision = default_version
+    svn_status_lines = []
 
-# 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
+build_id = ARGUMENTS.get('BUILD_ID')
+if build_id is None:
+    if revision:
+        build_id = 'r' + revision
+        if filter(lambda l: l[0] in 'ACDMR', svn_status_lines):
+            build_id = build_id + '[MODIFIED]'
+    else:
+        build_id = ''
 
 python_ver = sys.version[0:3]
 
 platform = distutils.util.get_platform()
 
 ENV = { 'PATH' : os.environ['PATH'] }
-for key in ['AEGIS_PROJECT', 'LOGNAME', 'PYTHONPATH']:
+for key in ['LOGNAME', 'PYTHONPATH']:
     if os.environ.has_key(key):
         ENV[key] = os.environ[key]
 
 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.  " +
+                        "This 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(build_dir, "test-deb")
     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 = packaging_flavors + [('doc', 'The SCons documentation.')]
+aliases.sort()
+
+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'
 
 #
                    COPYRIGHT           = copyright,
                    DATE                = date,
                    DEVELOPER           = developer,
+                   DISTDIR             = os.path.join(build_dir, 'dist'),
                    MONTH_YEAR          = month_year,
                    REVISION            = revision,
                    VERSION             = version,
                    BUILDERS            = { 'SCons_revision' : revbuilder,
                                            'SOElim' : soelimbuilder },
 
-                   PYTHON              = sys.executable,
+                   PYTHON              = '"%s"' % sys.executable,
                    PYTHONFLAGS         = '-tt',
                  )
 
 
         'buildermap'    : {},
 
+        'extra_rpm_files' : [],
+
         'explicit_deps' : {
                             'SCons/__init__.py' : Version_values,
                           },
 }
 
+# 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.  Check for whether or not to add it to the expected
+# RPM files by executing "python" in a subshell.
+
+cmd = "python -c 'import distutils.command.install_egg_info' > /dev/null 2>&1"
+import_egg_error = os.system(cmd)
+
+if not import_egg_error:
+    egg_info_file = 'scons-' + version + '.egg-info'
+    python_scons['extra_rpm_files'].append(egg_info_file)
+
 #
 # The original packaging scheme would have have required us to push
 # the Python version number into the package name (python1.5-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)
 
     #
 
     distutils_targets = [ win32_exe ]
 
-    install_targets = distutils_targets[:]
+    Local(env.Install('$DISTDIR', distutils_targets))
 
     if gzip:
 
         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)
 
         #
         # Unpack the tar.gz archive created by the distutils into
         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, [
+        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",
         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)
 
         #
         # Unpack the zip archive created by the distutils into
                                       os.path.join(u, pv, x),
                                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,
         ])
                 os.path.join(unpack_zip_dir, pkg_version, 'setup.py'),
         ])
 
-    if rpmbuild:
+    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')
 
             maintain multiple lists.
             """
             c = open(str(source[0]), 'rb').read()
-            c = string.replace(c, '__RPM_FILES__', env['RPM_FILES'])
+            c = string.replace(c, '__VERSION' + '__', env['VERSION'])
+            c = string.replace(c, '__RPM_FILES' + '__', env['RPM_FILES'])
             open(str(target[0]), 'wb').write(c)
 
         rpm_files.sort()
         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)
 
         dfiles = map(lambda x, d=test_rpm_dir: os.path.join(d, 'usr', x),
                      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.
+        # 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)
     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_dir, 'dist', "%s.tar.gz" % s_l_v)
-    local_zip = os.path.join(build_dir, '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)
 
     commands = [
         Delete(build_dir_local),
     Local(l)
 
     if gzip:
-        env.Command(local_tar_gz,
+        env.Command(dist_local_tar_gz,
                     local_targets,
                     "cd %s && tar czf $( ${TARGET.abspath} $) *" % build_dir_local)
 
                     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,
+        env.Command(dist_local_zip, local_targets, zipit,
                     CD = build_dir_local, PSV = '.')
 
         unpack_targets = map(lambda x, d=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_dir, 'dist'), install_targets)
-    Local(b_d_files)
-
 #
 #
 #
 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])
+if svn_status:
+    slines = filter(lambda l: l[0] in ' MA', svn_status_lines)
+    sentries = map(lambda l: l.split()[-1], slines)
+    sfiles = filter(os.path.isfile, sentries)
 
-    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())
+    remove_patterns = [
+        '.svnt/*',
+        '*.aeignore',
+        '*.cvsignore',
+        'www/*',
+    ]
+
+    for p in remove_patterns:
+        sfiles = filter(lambda s, p=p: not fnmatch.fnmatch(s, p), sfiles)
 
     if sfiles:
         ps = "%s-src" % project
                                     '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')),
                                     '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')),
                 ],
                 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'),
+    ])
 
 if test $# -eq 0; then
     for f in doc/user/*.in; do
-        sgml=doc/user/`basename $f .in`.sgml
+        xml=doc/user/`basename $f .in`.xml
         echo $f:
-        python bin/sconsoutput.py $f | diff $DIFFFLAGS $sgml -
+        python bin/sconsoutput.py $f | diff $DIFFFLAGS $xml -
     done
 else
     for a in $*; do
         f=doc/user/$a.in
-        sgml=doc/user/$a.sgml
+        xml=doc/user/$a.xml
         echo $f:
-        python bin/sconsoutput.py $f | diff $DIFFFLAGS $sgml -
+        python bin/sconsoutput.py $f | diff $DIFFFLAGS $xml -
     done
 fi
 
 if test $# -eq 0; then
     for f in doc/user/*.in; do
-        sgml=doc/user/`basename $f .in`.sgml
+        xml=doc/user/`basename $f .in`.xml
         echo $f:
         python bin/sconsoutput.py $f
     done
 else
     for a in $*; do
         f=doc/user/$a.in
-        sgml=doc/user/$a.sgml
+        xml=doc/user/$a.xml
         echo $f:
         python bin/sconsoutput.py $f
     done
 
 if test $# -eq 0; then
     for f in doc/user/*.in; do
-        sgml=doc/user/`basename $f .in`.sgml
+        xml=doc/user/`basename $f .in`.xml
         echo $f:
-        python bin/sconsoutput.py $f > $sgml
+        python bin/sconsoutput.py $f > $xml
     done
 else
     for a in $*; do
         f=doc/user/$a.in
-        sgml=doc/user/$a.sgml
+        xml=doc/user/$a.xml
         echo $f:
-        python bin/sconsoutput.py $f > $sgml
+        python bin/sconsoutput.py $f > $xml
     done
 fi
 #
 # A source file is anything under the src/engine/ or src/script/
 # directories that ends in '.py' but does NOT begin with 'test_'
-# or end in 'Tests.py'.  (We should probably ignore the stuff in
-# src/engine/SCons/Optik, since it doesn't originate with SCons, but
-# what the hell.)
+# or end in 'Tests.py'.
 #
 # We report the number of tests and sources, the total number of lines
 # in each category, the number of non-blank lines, and the number of

bin/scons-proc.py

 # construction variables documented in the specified XML files.
 #
 # Dependening on the options, the lists are output in either
-# DocBook-formatted generated SGML files containing the summary text
+# DocBook-formatted generated XML files containing the summary text
 # and/or .mod files contining the ENTITY definitions for each item,
 # or in man-page-formatted output.
 #
 base_sys_path = [os.getcwd() + '/build/test-tar-gz/lib/scons'] + sys.path
 
 helpstr = """\
-Usage: scons-proc.py [--man|--sgml]
+Usage: scons-proc.py [--man|--xml]
                      [-b file(s)] [-t file(s)] [-v file(s)] [infile ...]
 Options:
   -b file(s)        dump builder information to the specified file(s)
   -v file(s)        dump variable information to the specified file(s)
   --man             print info in man page format, each -[btv] argument
                     is a single file name
-  --sgml            (default) print info in SGML format, each -[btv] argument
+  --xml             (default) print info in SML format, each -[btv] argument
                     is a pair of comma-separated .gen,.mod file names
 """
 
 opts, args = getopt.getopt(sys.argv[1:],
                            "b:ht:v:",
                            ['builders=', 'help',
-                            'man', 'sgml', 'tools=', 'variables='])
+                            'man', 'xml', 'tools=', 'variables='])
 
 buildersfiles = None
-output_type = '--sgml'
+output_type = '--xml'
 toolsfiles = None
 variablesfiles = None
 
     elif o in ['-h', '--help']:
         sys.stdout.write(helpstr)
         sys.exit(0)
-    elif o in ['--man', '--sgml']:
+    elif o in ['--man', '--xml']:
         output_type = o
     elif o in ['-t', '--tools']:
         toolsfiles = a
             return sys.stdout
         return open(name, 'w')
 
-class SCons_XML_to_SGML(SCons_XML):
+class SCons_XML_to_XML(SCons_XML):
     def write(self, files):
         gen, mod = string.split(files, ',')
         g.write_gen(gen)
 
 if output_type == '--man':
     processor_class = SCons_XML_to_man
-elif output_type == '--sgml':
-    processor_class = SCons_XML_to_SGML
+elif output_type == '--xml':
+    processor_class = SCons_XML_to_XML
 else:
     sys.stderr.write("Unknown output type '%s'\n" % output_type)
     sys.exit(1)

bin/sconsoutput.py

 
 toollist = map(lambda t: apply(ToolSurrogate, t), toollist)
 
+toollist.append('install')
+
 def surrogate_spawn(sh, escape, cmd, args, env):
     pass
 
 if f is not sys.stdin:
     f.close()
 
+if data.startswith('<?xml '):
+    first_line, data = data.split('\n', 1)
+    sys.stdout.write(first_line + '\n')
+
 x = MySGML()
 for c in data:
     x.feed(c)
 .*.swp
 .consign
 .sconsign
-version.sgml
+version.xml
 #
 #
 #
-doc_tar_gz = os.path.join(build_dir,
-                          'dist',
-                          'scons-doc-%s.tar.gz' % env.Dictionary('VERSION'))
+dist_doc_tar_gz = '$DISTDIR/scons-doc-${VERSION}.tar.gz'
 
 #
 # We'll only try to build text files (for some documents)
 groff = whereis('groff')
 lynx = whereis('lynx')
 man2html = whereis('man2html')
-jade = whereis('jade')
+jade = whereis('openjade') or whereis('jade')
 jadetex = whereis('jadetex')
 pdfjadetex = whereis('pdfjadetex')
 jw = whereis('jw')
 format_re = re.compile(r'<(?:graphic|imagedata)\s+fileref="([^"]*)"(?:\s+format="([^"]*)")?')
 
 #
-# Find internal dependencies in .sgml files:
+# Find internal dependencies in .xml files:
 #
-#   <!entity bground SYSTEM "bground.sgml">
+#   <!entity bground SYSTEM "bground.xml">
 #   <graphic fileref="file.jpg">
 #   <imagedata fileref="file.jpg">
 #
 # defined as a SYSTEM entity is, in fact, a file included
 # somewhere in the document.
 #
-def scansgml(node, env, target):
+def scanxml(node, env, target):
     includes = []
 
     contents = node.get_contents()
 
     return includes
 
-s = Scanner(name = 'sgml', function = scansgml, skeys = ['.sgml', '.mod'])
+s = Scanner(name = 'xml', function = scanxml, skeys = ['.xml', '.mod'])
 
 orig_env = env
 env = orig_env.Clone(SCANNERS = [s],
 
 if jw:
     #
-    # Always create a version.sgml file containing the version information
+    # Always create a version.xml file containing the version information
     # for this run.  Ignore it for dependency purposes so we don't
     # rebuild all the docs every time just because the date changes.
     #
     date, ver, rev = env.Dictionary('DATE', 'VERSION', 'REVISION')
-    version_sgml = File(os.path.join(build, "version.sgml"))
-    #version_sgml = File("version.sgml")
-    verfile = str(version_sgml)
+    version_xml = File(os.path.join(build, "version.xml"))
+    #version_xml = File("version.xml")
+    verfile = str(version_xml)
     try:
         os.unlink(verfile)
     except OSError:
                         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 --xml -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)
     }
 
     #
-    # We have to tell SCons to scan the top-level SGML files which
-    # get included by the document SGML files in the subdirectories.
+    # We have to tell SCons to scan the top-level XML files which
+    # get included by the document XML files in the subdirectories.
     #
     manifest = File('MANIFEST').rstr()
     src_files = map(lambda x: x[:-1], open(manifest).readlines())
             if ext in ['.fig', '.jpg']:
                 orig_env.InstallAs(build_s, doc_s)
             else:
-                if build_doc and ext == '.sgml':
+                if build_doc and ext == '.xml':
                     env.Command(doc_s,
                                 base + '.in',
                                 "$PYTHON $SCONSOUTPUT_PY $SOURCE > $TARGET")
                 orig_env.SCons_revision(build_s, doc_s)
             Local(build_s)
 
-        main = os.path.join(build, doc, 'main.sgml')
+        main = os.path.join(build, doc, 'main.xml')
         out = 'main.out'
 
         # Hard-coding the scons-src path is a bit of a hack.  This can
         # be reworked when a better solution presents itself.
         scons_src_main = os.path.join(build_dir, 'scons-src', 'doc', main)
-        env.Ignore(scons_src_main, version_sgml)
+        env.Ignore(scons_src_main, version_xml)
 
         htmldir = os.path.join(build, 'HTML', 'scons-%s' % doc)
         htmlindex = os.path.join(htmldir, docs[doc]['htmlindex'])
         text = os.path.join(build, 'TEXT', 'scons-%s.txt' % doc)
 
         if docs[doc].get('html') and jade:
+            def copy_index_html(target, source, env):
+                # Older versions of DocBook|jw|jade|whatever would
+                # create a book1.html file, while newer versions create
+                # an index.html file (logically enough).  The scons.org
+                # web site links expect book1.html, so we're going to
+                # leave the target as is, and run this post-processing
+                # action function to check that the target really did
+                # get created, and if it didn't, copy it from index.html.
+                t = str(target[0])
+                if not os.path.exists(t):
+                    i = os.path.join(os.path.split(t)[0], 'index.html')
+                    open(t, 'w').write(open(i, 'r').read())
+                return None
+
             cmds = [
                 Delete("${TARGET.dir}/*.html"),
                 "jw -b html -o ${TARGET.dir} $SOURCES",
             ]
             if tidy:
                 cmds.append("tidy -m -q $TARGET || true")
+            cmds.append(Action(copy_index_html))
             env.Command(htmlindex, File(main), cmds)
             Local(htmlindex)
 
             env.Command(html, File(main), cmds)
             Local(html)
 
-            env.Ignore([html, htmlindex], version_sgml)
+            env.Ignore([html, htmlindex], version_xml)
 
             tar_deps.extend([html, htmlindex])
             tar_list.extend([html, htmldir])
             ])
             Local(ps)
 
-            env.Ignore(ps, version_sgml)
+            env.Ignore(ps, version_xml)
 
             tar_deps.append(ps)
             tar_list.append(ps)
             ])
             Local(pdf)
 
-            env.Ignore(pdf, version_sgml)
+            env.Ignore(pdf, version_xml)
 
             tar_deps.append(pdf)
             tar_list.append(pdf)
             env.Command(text, html, "lynx -dump ${SOURCE.abspath} > $TARGET")
             Local(text)
 
-            env.Ignore(text, version_sgml)
+            env.Ignore(text, version_xml)
 
             tar_deps.append(text)
             tar_list.append(text)
 if tar_deps:
     tar_list = string.join(map(lambda x, b=build+'/': string.replace(x, b, ''),
                            tar_list))
-    env.Command(doc_tar_gz, tar_deps,
+    t = env.Command(dist_doc_tar_gz, tar_deps,
                 "tar cf${TAR_HFLAG} - -C %s %s | gzip > $TARGET" % (build, tar_list))
-    Local(doc_tar_gz)
+    Local(t)
+    Alias('doc', t)
+else:
+    Alias('doc', os.path.join(build_dir, 'doc'))

doc/design/MANIFEST

-acks.sgml
-bground.sgml
-copyright.sgml
+acks.xml
+bground.xml
+copyright.xml
 engine.fig
 engine.jpg
-engine.sgml
-goals.sgml
-install.sgml
-intro.sgml
-issues.sgml
-main.sgml
-native.sgml
-overview.sgml
+engine.xml
+goals.xml
+install.xml
+intro.xml
+issues.xml
+main.xml
+native.xml
+overview.xml
 scons.mod

doc/design/acks.sgml

-<!--
-
-  Copyright (c) 2001, 2002, 2003 Steven Knight
-
-  Permission is hereby granted, free of charge, to any person obtaining
-  a copy of this software and associated documentation files (the
-  "Software"), to deal in the Software without restriction, including
-  without limitation the rights to use, copy, modify, merge, publish,
-  distribute, sublicense, and/or sell copies of the Software, and to
-  permit persons to whom the Software is furnished to do so, subject to
-  the following conditions:
-
-  The above copyright notice and this permission notice shall be included
-  in all copies or substantial portions of the Software.
-
-  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
-  KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
-  WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
--->
-
- <para>
-
-   I'm grateful to the following people
-   for their influence, knowing or not,
-   on the design of &SCons;:
-
- </para>
-
- <variablelist>
-  <varlistentry>
-   <term>Bob Sidebotham</term>
-   <listitem>
-     <para>
-
-     First, as the original author of &Cons;, Bob did the real heavy
-     lifting of creating the underlying model for dependency management
-     and software construction, as well as implementing it in Perl.
-     During the first years of &Cons;' existence, Bob did a skillful
-     job of integrating input and code from the first users, and
-     consequently is a source of practical wisdom and insight into the
-     problems of real-world software construction.  His continuing
-     advice has been invaluable.
-
-     </para>
-   </listitem>
-  </varlistentry>
-
-  <varlistentry>
-   <term>The &SCons; Development Team</term>
-   <listitem>
-     <para>
-
-     A big round of thanks go to those brave souls who have
-     gotten in on the ground floor:
-     David Abrahams,
-     Charles Crain,
-     Steven Leblanc.
-     Anthony Roach,
-     and
-     Steven Shaw.
-     Their contributions,
-     through their general knowledge of software build issues in general
-     Python in particular,
-     have made &SCons; what it is today.
-
-     </para>
-   </listitem>
-  </varlistentry>
-
-  <varlistentry>
-   <term>The &Cons; Community</term>
-   <listitem>
-     <para>
-
-     The real-world build problems that the users of &Cons;
-     share on the <command>cons-discuss</command> mailing list
-     have informed much of the thinking that
-     has gone into the &SCons; design.
-     In particular,
-     Rajesh Vaidheeswarran,
-     the current maintainer of &Cons;,
-     has been a very steady influence.
-     I've also picked up valuable insight from
-     mailing-list participants
-     Johan Holmberg,
-     Damien Neil,
-     Gary Oberbrunner,
-     Wayne Scott,
-     and Greg Spencer.
-
-     </para>
-   </listitem>
-  </varlistentry>
-
-  <varlistentry>
-   <term>Peter Miller</term>
-   <listitem>
-
-     <para>
-
-     Peter has indirectly
-     influenced two aspects of the &SCons; design:
-
-     </para>
-
-     <para>
-
-     Miller's influential paper
-     <citetitle>Recursive Make Considered Harmful</citetitle>
-     was what led me, indirectly, to my involvement with &Cons;
-     in the first place.
-     Experimenting with the single-Makefile approach he describes in
-     <citetitle>RMCH</citetitle> led me to conclude that while it worked
-     as advertised, it was not an extensible scheme.  This solidified
-     my frustration with Make and led me to try &Cons;, which at its
-     core shares the single-process, universal-DAG model of the "RMCH"
-     single-Makefile technique.
-
-     </para>
-
-     <para>
-
-     The testing framework that Miller created for his
-     Aegis change management system
-     changed the way I approach software development
-     by providing a framework for rigorous, repeatable
-     testing during development.
-     It was my success at using Aegis for personal projects
-     that led me to begin my involvement with &Cons;
-     by creating the <command>cons-test</command> regression suite.
-
-     </para>
-   </listitem>
-  </varlistentry>
-
-  <varlistentry>
-   <term>Stuart Stanley</term>
-   <listitem>
-     <para>
-
-     An experienced Python programmer,
-     Stuart provided valuable advice and insight
-     into some of the more useful Python idioms at my disposal
-     during the original <literal>ScCons</literal>; design
-     for the Software Carpentry contest.
-
-     </para>
-   </listitem>
-  </varlistentry>
-
-  <varlistentry>
-   <term>Gary Holt</term>
-   <listitem>
-     <para>
-
-     I don't know which came first,
-     the first-round Software Carpentry contest entry
-     or the tool itself,
-     but Gary's design for &Makepp;
-     showed me that it is possible to marry
-     the strengths of &Cons;-like dependency management
-     with backwards compatibility for &Makefile;s.
-     Striving to support both
-     &Makefile; compatibility and
-     a native Python interface
-     cleaned up the &SCons; design immeasurably
-     by factoring out the common elements
-     into the Build Engine.
-
-     </para>
-   </listitem>
-  </varlistentry>
- </variablelist>
-

doc/design/acks.xml

+<!--
+
+  Copyright (c) 2001, 2002, 2003 Steven Knight
+
+  Permission is hereby granted, free of charge, to any person obtaining
+  a copy of this software and associated documentation files (the
+  "Software"), to deal in the Software without restriction, including
+  without limitation the rights to use, copy, modify, merge, publish,
+  distribute, sublicense, and/or sell copies of the Software, and to
+  permit persons to whom the Software is furnished to do so, subject to
+  the following conditions:
+
+  The above copyright notice and this permission notice shall be included
+  in all copies or substantial portions of the Software.
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+  KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+  WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+-->
+
+ <para>
+
+   I'm grateful to the following people
+   for their influence, knowing or not,
+   on the design of &SCons;:
+
+ </para>
+
+ <variablelist>
+  <varlistentry>
+   <term>Bob Sidebotham</term>
+   <listitem>
+     <para>
+
+     First, as the original author of &Cons;, Bob did the real heavy
+     lifting of creating the underlying model for dependency management
+     and software construction, as well as implementing it in Perl.
+     During the first years of &Cons;' existence, Bob did a skillful
+     job of integrating input and code from the first users, and
+     consequently is a source of practical wisdom and insight into the
+     problems of real-world software construction.  His continuing
+     advice has been invaluable.
+
+     </para>
+   </listitem>
+  </varlistentry>
+
+  <varlistentry>
+   <term>The &SCons; Development Team</term>
+   <listitem>
+     <para>
+
+     A big round of thanks go to those brave souls who have
+     gotten in on the ground floor:
+     David Abrahams,
+     Charles Crain,
+     Steven Leblanc.
+     Anthony Roach,
+     and
+     Steven Shaw.
+     Their contributions,
+     through their general knowledge of software build issues in general
+     Python in particular,
+     have made &SCons; what it is today.
+
+     </para>
+   </listitem>
+  </varlistentry>
+
+  <varlistentry>
+   <term>The &Cons; Community</term>
+   <listitem>
+     <para>
+
+     The real-world build problems that the users of &Cons;
+     share on the <command>cons-discuss</command> mailing list
+     have informed much of the thinking that
+     has gone into the &SCons; design.
+     In particular,
+     Rajesh Vaidheeswarran,
+     the current maintainer of &Cons;,
+     has been a very steady influence.
+     I've also picked up valuable insight from
+     mailing-list participants
+     Johan Holmberg,
+     Damien Neil,
+     Gary Oberbrunner,
+     Wayne Scott,
+     and Greg Spencer.
+
+     </para>
+   </listitem>
+  </varlistentry>
+
+  <varlistentry>
+   <term>Peter Miller</term>
+   <listitem>
+
+     <para>
+
+     Peter has indirectly
+     influenced two aspects of the &SCons; design:
+
+     </para>
+
+     <para>
+
+     Miller's influential paper
+     <citetitle>Recursive Make Considered Harmful</citetitle>
+     was what led me, indirectly, to my involvement with &Cons;
+     in the first place.
+     Experimenting with the single-Makefile approach he describes in
+     <citetitle>RMCH</citetitle> led me to conclude that while it worked
+     as advertised, it was not an extensible scheme.  This solidified
+     my frustration with Make and led me to try &Cons;, which at its
+     core shares the single-process, universal-DAG model of the "RMCH"
+     single-Makefile technique.
+
+     </para>
+
+     <para>
+
+     The testing framework that Miller created for his
+     Aegis change management system
+     changed the way I approach software development
+     by providing a framework for rigorous, repeatable
+     testing during development.
+     It was my success at using Aegis for personal projects
+     that led me to begin my involvement with &Cons;