Merged revisions 2454-2525 via svnmerge from
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Thu, 13 Dec 2007 04:25:43 +0000 (04:25 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Thu, 13 Dec 2007 04:25:43 +0000 (04:25 +0000)
http://scons.tigris.org/svn/scons/branches/core

........
  r2455 | stevenknight | 2007-09-20 01:27:23 -0500 (Thu, 20 Sep 2007) | 2 lines

  Use ${TARGET.base} to make sure $TARGET attributes stay fixed.
........
  r2456 | stevenknight | 2007-09-25 11:52:30 -0500 (Tue, 25 Sep 2007) | 5 lines

  Issue 1734:  Avoid having content signature calculation of Alias Nodes
  consume excessive amounts of memory by having an Alias' "contents" be a
  concatenation of the children's signatures, not the children's contents.
  (Ken Deeter)
........
  r2457 | stevenknight | 2007-09-26 12:18:49 -0500 (Wed, 26 Sep 2007) | 2 lines

  Add an Options.UnknownOptions() method.
........
  r2458 | stevenknight | 2007-09-26 16:26:05 -0500 (Wed, 26 Sep 2007) | 2 lines

  Add a compatibility fnmatch.filter() function.
........
  r2459 | stevenknight | 2007-09-27 18:26:03 -0500 (Thu, 27 Sep 2007) | 3 lines

  Add a new Glob() function that matches in-memory Nodes as well as on-disk
  files (including matching repository and source directories).
........
  r2460 | stevenknight | 2007-09-28 15:01:37 -0500 (Fri, 28 Sep 2007) | 5 lines

  Issue 1020:  fix use of Clean() for files created by "side effect"
  in BuildDir() by removing the file by absolute path, not by what str()
  returns.  (It will think that the file is a source file and return a
  path to the source directory.)
........
  r2461 | stevenknight | 2007-09-29 05:39:09 -0500 (Sat, 29 Sep 2007) | 2 lines

  Update to TestCmd 0.28 modules.
........
  r2462 | stevenknight | 2007-09-29 05:49:29 -0500 (Sat, 29 Sep 2007) | 3 lines

  The RPM packaging can no longer take a "target" argument and produces
  an appropriate error message.  Update the test accordingly.
........
  r2463 | pscholl | 2007-09-30 08:57:01 -0500 (Sun, 30 Sep 2007) | 3 lines

  fix documentation issues (issue 1736 on the bugtracker)
........
  r2464 | pscholl | 2007-09-30 09:39:49 -0500 (Sun, 30 Sep 2007) | 3 lines

  fix target set up if multiple package builders are specified at once.
........
  r2465 | stevenknight | 2007-10-01 13:00:44 -0500 (Mon, 01 Oct 2007) | 3 lines

  Update to TestCmd 0.29, with new methods for searching for a list
  of lines in output.
........
  r2466 | stevenknight | 2007-10-01 13:58:41 -0500 (Mon, 01 Oct 2007) | 4 lines

  Issue 1737:  Fix use of Configure() contexts with the -c (clean) and -h
  (help) options by supporting the ability to *configure* whether or no
  configure context tests are executed during those modes.
........
  r2467 | stevenknight | 2007-10-01 16:58:21 -0500 (Mon, 01 Oct 2007) | 2 lines

  Update to TestCmd 0.30, with a new TestCmd.rmdir() method.
........
  r2468 | stevenknight | 2007-10-01 17:05:44 -0500 (Mon, 01 Oct 2007) | 4 lines

  Issue 1586:  Capture a test script for "ghost" entries in .sconsign files.
  Test cases by Morten Elo Peterson and Jason Orendorff, packaged by
  Gary Oberbrunner.
........
  r2469 | stevenknight | 2007-10-04 11:21:12 -0500 (Thu, 04 Oct 2007) | 4 lines

  When cloning a construction environment, have the clone record the
  re-binding of the methods that were added to the original construction
  environment, so that further clones have their methods re-bound as well.
........
  r2470 | stevenknight | 2007-10-05 13:02:34 -0500 (Fri, 05 Oct 2007) | 3 lines

  Refactor the Glob() code for efficiency and readability.  (Greg Noel)
  Refactor Glob() unit tests for platform-independence.
........
  r2471 | stevenknight | 2007-10-09 10:49:15 -0500 (Tue, 09 Oct 2007) | 2 lines

  Back out Glob() refactoring to avoid Repository breakage.
........
  r2472 | stevenknight | 2007-10-09 12:16:33 -0500 (Tue, 09 Oct 2007) | 3 lines

  Fix ToolInitializer-related infinite recursion when the BUILDERS dict
  and the environment attributes can get out of sync.
........
  r2473 | stevenknight | 2007-10-10 14:39:19 -0500 (Wed, 10 Oct 2007) | 4 lines

  Fix a race condition in the -j sub-test by using marker directories to
  make sure (?) that the two build scripts are actually executed in parallel
  (regardless of system load).
........
  r2474 | stevenknight | 2007-10-11 12:32:07 -0500 (Thu, 11 Oct 2007) | 4 lines

  Re-fix globbing on case-insensitive systems like Windows.  Slight
  efficiency improvements as well (avoiding unnecessary calls to
  fnmatch.filter()).
........
  r2475 | stevenknight | 2007-10-11 15:04:42 -0500 (Thu, 11 Oct 2007) | 3 lines

  Refactor the Node lookup logic to fix handling Windows drive letters
  after an initial '#'.
........
  r2476 | stevenknight | 2007-10-12 00:01:31 -0500 (Fri, 12 Oct 2007) | 2 lines

  Fix nested scope issues (for the benefit of older Python versions).
........
  r2477 | stevenknight | 2007-10-12 11:36:21 -0500 (Fri, 12 Oct 2007) | 2 lines

  Issue 1743:  Document '#' interpretation, with examples.
........
  r2478 | stevenknight | 2007-10-12 12:17:50 -0500 (Fri, 12 Oct 2007) | 3 lines

  Fix the ability of our default ActionFactory function to handle Nodes
  as input.
........
  r2479 | stevenknight | 2007-10-12 13:53:37 -0500 (Fri, 12 Oct 2007) | 4 lines

  Enhance Options() file execution to add the file's directory to sys.path
  (and remove it afterwards) and to add a __name__ variable that can be
  used for introspecting on the file's location.
........
  r2480 | stevenknight | 2007-10-14 17:57:09 -0500 (Sun, 14 Oct 2007) | 4 lines

  Remove unnecessary os.path.normpath() calls when looking up directories
  or files by checking for whether we can just tack on a single entry name
  to the already-normalized lookup path of the directory Node.
........
  r2481 | stevenknight | 2007-10-17 01:08:47 -0500 (Wed, 17 Oct 2007) | 2 lines

  Add a GetBuildFailures() function.
........
  r2482 | stevenknight | 2007-10-17 09:56:18 -0500 (Wed, 17 Oct 2007) | 2 lines

  Fix the GetBuildFailures() example in the man page.
........
  r2483 | stevenknight | 2007-10-17 10:54:21 -0500 (Wed, 17 Oct 2007) | 3 lines

  Use sys.exitfunc if there's no atexit module (Python 1.5.2).
  Sort the failure list for deterministic build output under system load.
........
  r2484 | stevenknight | 2007-10-20 12:33:07 -0500 (Sat, 20 Oct 2007) | 2 lines

  Use more efficient Decider() defaults instead of {Target,Source}Signatures().
........
  r2485 | stevenknight | 2007-10-20 17:42:27 -0500 (Sat, 20 Oct 2007) | 2 lines

  Windows portability in GetBuildFailures() test scripts.
........
  r2486 | stevenknight | 2007-10-20 20:46:26 -0500 (Sat, 20 Oct 2007) | 2 lines

  Windows portability:  rename internal copy.py script, use a stub instead of tar.
........
  r2487 | stevenknight | 2007-10-24 23:36:24 -0500 (Wed, 24 Oct 2007) | 4 lines

  Whenever a script configures SConsignFile(None), make sure it uses
  stub compiler and linker scripts, not the system ones, to avoid writing
  (or trying to write) .sconsign files in system directories.
........
  r2488 | stevenknight | 2007-10-26 13:57:38 -0500 (Fri, 26 Oct 2007) | 2 lines

  Issue 1764:  Fix test-script portability issues on Solaris.
........
  r2489 | stevenknight | 2007-10-27 07:37:37 -0500 (Sat, 27 Oct 2007) | 3 lines

  Issue 1757:  add a CheckTypeSize() call to Configure contexts
  (David Cournapeau).
........
  r2490 | stevenknight | 2007-10-28 07:58:30 -0500 (Sun, 28 Oct 2007) | 2 lines

  Python 1.5.2 compatibility:  no use of +=.
........
  r2491 | stevenknight | 2007-10-29 12:13:35 -0500 (Mon, 29 Oct 2007) | 3 lines

  Issue 1758:  Fix the SCons packaging build for use with shared-lib
  versions of Python and to avoid .egg-info naming issues.
........
  r2492 | stevenknight | 2007-10-29 14:09:57 -0500 (Mon, 29 Oct 2007) | 2 lines

  Document the "expect" argument to CheckTypeSize().  (David Cournapeau)
........
  r2493 | stevenknight | 2007-11-05 20:57:27 -0600 (Mon, 05 Nov 2007) | 2 lines

  Fix use of Glob() when a pattern is below an explicitly-named subdirectory.
........
  r2495 | stevenknight | 2007-11-12 22:58:12 -0600 (Mon, 12 Nov 2007) | 5 lines

  Add a get_sources() access method to avoid an O(n^2) problem when adding
  sources to an Executor object.  The old code weeded out duplicates
  whenever a new source was added; the new code only does that when the
  source is list going to be used.
........
  r2496 | stevenknight | 2007-11-15 12:19:55 -0600 (Thu, 15 Nov 2007) | 4 lines

  Redefine the $WINDOWSPROGMANIFESTSUFFIX and
  $WINDOWSSHLIBMANIFESTSUFFIX variables so they pick up changes to
  the underlying $SHLIBSUFFIX and $PROGSUFFIX variables.
........
  r2497 | stevenknight | 2007-11-18 17:11:52 -0600 (Sun, 18 Nov 2007) | 4 lines

  Support .status and .command attributes of BuildError exceptions.
  Change Action objects to return BuildError objects (not raise them)
  when an action fails.
........
  r2498 | stevenknight | 2007-11-19 07:27:16 -0600 (Mon, 19 Nov 2007) | 3 lines

  When converting .sconsign paths to Nodes, use the more efficient
  _lookup_abs() method.
........
  r2499 | stevenknight | 2007-11-25 00:18:20 -0600 (Sun, 25 Nov 2007) | 3 lines

  Move the reflection-checking is_under() logic from the .srcdir_list()
  method to the .srcdir_duplicate() method.
........
  r2500 | stevenknight | 2007-11-25 00:31:33 -0600 (Sun, 25 Nov 2007) | 2 lines

  Have the .srcnode() method use the .srcdir_list() method.
........
  r2501 | stevenknight | 2007-11-28 22:56:39 -0600 (Wed, 28 Nov 2007) | 3 lines

  Issue 1845:  Have single-source Builders (like Object()) return
  NodeList objects even when called with multiple files.
........
  r2502 | stevenknight | 2007-11-28 23:00:46 -0600 (Wed, 28 Nov 2007) | 2 lines

  Issue 1845:  Document the NodeList behavior w.r.t Python's += operator.
........
  r2503 | stevenknight | 2007-11-29 09:35:31 -0600 (Thu, 29 Nov 2007) | 3 lines

  Issue 1840:  Fix a lot of typos in the man page and Users' Guide.
  (Malte Helmert)
........
  r2504 | stevenknight | 2007-11-29 10:41:44 -0600 (Thu, 29 Nov 2007) | 3 lines

  Issue 1841:  Fix --implicit-cache spurious rebuilds and inefficiency
  when using Builders that produce multiple targets.  (Benoit Belley)
........
  r2505 | stevenknight | 2007-11-30 17:36:03 -0600 (Fri, 30 Nov 2007) | 3 lines

  Unit test fix for Python 1.5.2, which can't .extend() lists with
  UserList objects.
........
  r2506 | stevenknight | 2007-11-30 20:37:14 -0600 (Fri, 30 Nov 2007) | 2 lines

  Python 1.5.2 portability:  use string.join(), not ' '.join().
........
  r2507 | stevenknight | 2007-11-30 21:42:19 -0600 (Fri, 30 Nov 2007) | 3 lines

  When searching directory lists like $CPPPATH, don't make Dir Nodes for
  directories that don't exist on disk.
........
  r2508 | stevenknight | 2007-12-01 00:14:35 -0600 (Sat, 01 Dec 2007) | 2 lines

  Add a Requires() function for specifying order-only prerequisites.
........
  r2509 | stevenknight | 2007-12-01 07:32:24 -0600 (Sat, 01 Dec 2007) | 3 lines

  Handle absolute paths without infinite recursion in the new code that
  searches for implicit dependencies without creating unnecessary Dir Nodes.
........
  r2510 | stevenknight | 2007-12-03 15:11:56 -0600 (Mon, 03 Dec 2007) | 2 lines

  Restore the rel_path() method, for the benefit of SConscript files using it.
........
  r2511 | stevenknight | 2007-12-04 00:34:02 -0600 (Tue, 04 Dec 2007) | 4 lines

  User's Guide updates for the Big Signature refactoring, capturing mention
  of things that still need documenting, and other changes from re-running
  the examples through the latest code.
........
  r2512 | stevenknight | 2007-12-04 08:48:51 -0600 (Tue, 04 Dec 2007) | 3 lines

  Issue 1846:  allow building only part of the dependency graph when
  BuildDir(duplicate=0) is used.  (Benoit Belley)
........
  r2513 | stevenknight | 2007-12-06 05:02:56 -0600 (Thu, 06 Dec 2007) | 3 lines

  Have the code that avoids creating unnecessary Dir Nodes when searching
  $*PATH variables handle absolute paths with Windows drive letters.
........
  r2514 | stevenknight | 2007-12-08 07:44:15 -0600 (Sat, 08 Dec 2007) | 6 lines

  Issue 1852:  Make the default behavior of
  {Source,Target}Signatures('timestamp') equivalent to
  'timestamp-match', not 'timestamp-newer'.  Fix use of CacheDir with
  Decider('timestamp-newer') by updating the modification time when copying
  files from the cache.
........
  r2515 | stevenknight | 2007-12-08 08:23:02 -0600 (Sat, 08 Dec 2007) | 4 lines

  Update the mock compiler inin/sconsoutput to use $CPPPATH.
  Capture the ripple effect in the Troubleshooting appendix.
  Also add a -t option to the mock "touch" command.
........
  r2516 | stevenknight | 2007-12-08 09:16:11 -0600 (Sat, 08 Dec 2007) | 3 lines

  Update the Dependencies chapter for use of the Decider() function,
  and to now discourage use of SourceSignatures() and TargetSignatures().
........
  r2517 | stevenknight | 2007-12-08 12:31:10 -0600 (Sat, 08 Dec 2007) | 4 lines

  Issue 1721:  On Windows, wrap __builtin__.close() and __builtin__.file()
  to disable file handle inheritance on any files opened by SCons during
  the run.
........
  r2518 | stevenknight | 2007-12-11 17:33:20 -0600 (Tue, 11 Dec 2007) | 4 lines

  Prevent the _get_str() method from causing underlying stat() values
  to be cached if we're not yet saving the string representations of
  FS.Base() Nodes.
........
  r2519 | stevenknight | 2007-12-11 23:27:05 -0600 (Tue, 11 Dec 2007) | 4 lines

  Add a warning about the unreliability of -j if the pywin32 modules aren't
  available or are old and can't suppress file handle inheritance.
  Add a release note about the change to open() and file().
........
  r2520 | stevenknight | 2007-12-11 23:28:05 -0600 (Tue, 11 Dec 2007) | 2 lines

  Add an overlooked update, fix spelling.
........
  r2521 | stevenknight | 2007-12-12 09:12:42 -0600 (Wed, 12 Dec 2007) | 3 lines

  Use &TargetSignatures; (replace missing ampersand) in the title of
  that section.  Move the &Depends; section to before the &Ignore; section.
........
  r2522 | stevenknight | 2007-12-12 09:20:46 -0600 (Wed, 12 Dec 2007) | 3 lines

  Final documentation update for checkpoint release:  propagate .in changes
  to .xml files.
........
  r2523 | stevenknight | 2007-12-12 09:29:11 -0600 (Wed, 12 Dec 2007) | 2 lines

  Update release lines for new checkpoint release.
........

git-svn-id: http://scons.tigris.org/svn/scons/trunk@2527 fdb21ef1-2011-0410-befe-b5e4ea1792b1

138 files changed:
QMTest/TestCmd.py
QMTest/TestCommon.py
SConstruct
bin/sconsoutput.py
doc/man/scons.1
doc/scons.mod
doc/user/ENV.in
doc/user/ENV.xml
doc/user/builders-writing.in
doc/user/builders-writing.xml
doc/user/command-line.in
doc/user/command-line.xml
doc/user/depends.in
doc/user/depends.xml
doc/user/environments.in
doc/user/environments.xml
doc/user/factories.in
doc/user/factories.xml
doc/user/file-removal.in
doc/user/file-removal.xml
doc/user/help.in
doc/user/help.xml
doc/user/hierarchy.in
doc/user/hierarchy.xml
doc/user/install.in
doc/user/install.xml
doc/user/libraries.in
doc/user/libraries.xml
doc/user/main.in
doc/user/main.xml
doc/user/parseconfig.in
doc/user/parseconfig.xml
doc/user/repositories.in
doc/user/repositories.xml
doc/user/sconf.in
doc/user/sconf.xml
doc/user/simple.in
doc/user/simple.xml
doc/user/sourcecode.xml
doc/user/troubleshoot.in
doc/user/troubleshoot.xml
src/CHANGES.txt
src/RELEASE.txt
src/engine/SCons/Action.py
src/engine/SCons/ActionTests.py
src/engine/SCons/Builder.py
src/engine/SCons/BuilderTests.py
src/engine/SCons/CacheDir.py
src/engine/SCons/Conftest.py
src/engine/SCons/Defaults.py
src/engine/SCons/Environment.py
src/engine/SCons/EnvironmentTests.py
src/engine/SCons/Errors.py
src/engine/SCons/Executor.py
src/engine/SCons/ExecutorTests.py
src/engine/SCons/Node/Alias.py
src/engine/SCons/Node/AliasTests.py
src/engine/SCons/Node/FS.py
src/engine/SCons/Node/FSTests.py
src/engine/SCons/Node/__init__.py
src/engine/SCons/Options/OptionsTests.py
src/engine/SCons/Options/__init__.py
src/engine/SCons/Platform/win32.py
src/engine/SCons/SConf.py
src/engine/SCons/SConfTests.py
src/engine/SCons/Script/Main.py
src/engine/SCons/Script/__init__.py
src/engine/SCons/Taskmaster.py
src/engine/SCons/TaskmasterTests.py
src/engine/SCons/Tool/mslink.py
src/engine/SCons/Tool/packaging/__init__.py
src/engine/SCons/Util.py
src/engine/SCons/compat/__init__.py
test/AR/AR.py
test/AR/ARFLAGS.py
test/BuildDir/Clean.py [new file with mode: 0644]
test/BuildDir/File-create.py [new file with mode: 0644]
test/BuildDir/Sconscript-build_dir.py
test/CC/SHCCCOMSTR.py
test/CXX/CXX.py
test/CXX/SHCXX.py
test/CXX/SHCXXCOMSTR.py
test/CacheDir/timestamp-content.py [moved from test/CacheDir/timestamp.py with 75% similarity]
test/CacheDir/timestamp-match.py [new file with mode: 0644]
test/CacheDir/timestamp-newer.py [new file with mode: 0644]
test/CacheDir/timestamp-timestamp.py [new file with mode: 0644]
test/Chmod.py
test/Configure/clean.py
test/Configure/help.py
test/Copy.py
test/Errors/preparation.py
test/Fortran/SHF77COMSTR.py
test/Fortran/SHF90COMSTR.py
test/Fortran/SHF95COMSTR.py
test/Fortran/SHFORTRANCOMSTR.py
test/GetBuildFailures/option-k.py [new file with mode: 0644]
test/GetBuildFailures/parallel.py [new file with mode: 0644]
test/GetBuildFailures/serial.py [new file with mode: 0644]
test/Glob/BuildDir.py [new file with mode: 0644]
test/Glob/Repository.py [new file with mode: 0644]
test/Glob/basic.py [new file with mode: 0644]
test/Glob/source.py [new file with mode: 0644]
test/Glob/strings.py [new file with mode: 0644]
test/Glob/subdir.py [new file with mode: 0644]
test/Glob/subst.py [new file with mode: 0644]
test/Install/Clone.py [new file with mode: 0644]
test/LINK/SHLINKCOMSTR.py
test/Library.py
test/MSVC/multiple-pdb.py
test/Mkdir.py
test/Move.py
test/NodeOps.py
test/Object.py
test/Options/chdir.py [new file with mode: 0644]
test/Options/import.py [new file with mode: 0644]
test/Parallel/duplicate-target.py
test/Parallel/failed-build.py [new file with mode: 0644]
test/Repository/variants.py
test/Requires/basic.py [new file with mode: 0644]
test/Scanner/no-Dir-node.py [new file with mode: 0644]
test/Touch.py
test/implicit-cache/DualTargets.py [new file with mode: 0644]
test/no-global-dependencies.py [new file with mode: 0644]
test/option-j.py
test/option/debug-dtree.py
test/option/debug-includes.py
test/option/debug-stree.py
test/option/debug-tree.py
test/option/tree-all.py
test/option/tree-derived.py
test/packaging/multiple-packages-at-once.py
test/packaging/rpm/explicit-target.py
test/sconsign/ghost-entries.py [new file with mode: 0644]
test/sconsign/script/Configure.py
test/sconsign/script/SConsignFile.py
test/sconsign/script/Signatures.py
test/sconsign/script/no-SConsignFile.py
test/subdivide.py

index 08642cb848166382d0031649222740b91be84def..f5e1c71556a9da177746b4de1cc6da9f44e9c2ff 100644 (file)
@@ -48,6 +48,8 @@ things.  Here is an overview of them:
 
     test.subdir('subdir', ...)
 
+    test.rmdir('subdir', ...)
+
     test.write('file', "contents\n")
     test.write(['subdir', 'file'], "contents\n")
 
@@ -179,8 +181,8 @@ version.
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 
 __author__ = "Steven Knight <knight at baldmt dot com>"
-__revision__ = "TestCmd.py 0.26.D001 2007/08/20 21:58:58 knight"
-__version__ = "0.26"
+__revision__ = "TestCmd.py 0.30.D001 2007/10/01 16:53:55 knight"
+__version__ = "0.30"
 
 import os
 import os.path
@@ -690,6 +692,17 @@ class TestCmd:
             raise ValueError, "mode must begin with 'r'"
         return open(file, mode).read()
 
+    def rmdir(self, dir):
+        """Removes the specified dir name.
+        The dir name may be a list, in which case the elements are
+        concatenated with the os.path.join() method.  The dir is
+        assumed to be under the temporary working directory unless it
+        is an absolute path name.
+        The dir must be empty.
+        """
+        dir = self.canonicalize(dir)
+        os.rmdir(dir)
+
     def run(self, program = None,
                   interpreter = None,
                   arguments = None,
index f4c7c541dd364e990a05e6f3b08343c78c545d83..d6b21adf2dee7f6caa4dcbb98df36c24bad16eca 100644 (file)
@@ -36,12 +36,16 @@ provided by the TestCommon class:
 
     test.must_contain('file', 'required text\n')
 
+    test.must_contain_lines(lines, output)
+
     test.must_exist('file1', ['file2', ...])
 
     test.must_match('file', "expected contents\n")
 
     test.must_not_be_writable('file1', ['file2', ...])
 
+    test.must_not_contain_lines(lines, output)
+
     test.must_not_exist('file1', ['file2', ...])
 
     test.run(options = "options to be prepended to arguments",
@@ -80,8 +84,8 @@ The TestCommon module also provides the following variables
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 
 __author__ = "Steven Knight <knight at baldmt dot com>"
-__revision__ = "TestCommon.py 0.26.D001 2007/08/20 21:58:58 knight"
-__version__ = "0.26"
+__revision__ = "TestCommon.py 0.30.D001 2007/10/01 16:53:55 knight"
+__version__ = "0.30"
 
 import os
 import os.path
@@ -95,8 +99,6 @@ from TestCmd import *
 from TestCmd import __all__
 
 __all__.extend([ 'TestCommon',
-                 'TestFailed',
-                 'TestNoResult',
                  'exe_suffix',
                  'obj_suffix',
                  'shobj_suffix',
@@ -196,24 +198,16 @@ def separate_files(flist):
             missing.append(f)
     return existing, missing
 
-class TestFailed(Exception):
-    def __init__(self, args=None):
-        self.args = args
-
-class TestNoResult(Exception):
-    def __init__(self, args=None):
-        self.args = args
-
 if os.name == 'posix':
     def _failed(self, status = 0):
         if self.status is None or status is None:
             return None
-        if os.WIFSIGNALED(self.status):
-            return None
         return _status(self) != status
     def _status(self):
         if os.WIFEXITED(self.status):
             return os.WEXITSTATUS(self.status)
+        elif os.WIFSIGNALED(self.status):
+            return os.WTERMSIG(self.status)
         else:
             return None
 elif os.name == 'nt':
@@ -301,6 +295,19 @@ class TestCommon(TestCmd):
             print file_contents
             self.fail_test(not contains)
 
+    def must_contain_lines(self, lines, output, title=None):
+        if title is None:
+            title = 'output'
+
+        missing = filter(lambda l, o=output: string.find(o, l) == -1, lines)
+
+        if missing:
+            print "Missing lines from %s:" % title
+            print string.join(missing, '\n')
+            print "%s ============================================================" % title
+            print output
+            self.fail_test()
+
     def must_exist(self, *files):
         """Ensures that the specified file(s) must exist.  An individual
         file be specified as a list of directory names, in which case the
@@ -329,6 +336,19 @@ class TestCommon(TestCmd):
             self.diff(expect, file_contents, 'contents ')
             raise
 
+    def must_not_contain_lines(self, lines, output=None, title=None):
+        if title is None:
+            title = 'output'
+
+        unexpected = filter(lambda l, o=output: string.find(o, l) != -1, lines)
+
+        if unexpected:
+            print "Unexpected lines in %s:" % title
+            print string.join(unexpected, '\n')
+            print "%s ============================================================" % title
+            print output
+            self.fail_test()
+
     def must_not_exist(self, *files):
         """Ensures that the specified file(s) must not exist.
         An individual file be specified as a list of directory names, in
@@ -399,12 +419,18 @@ class TestCommon(TestCmd):
             apply(TestCmd.run, [self], kw)
         except KeyboardInterrupt:
             raise
-        except:
+        except Exception, e:
             print self.banner('STDOUT ')
-            print self.stdout()
+            try:
+                print self.stdout()
+            except IndexError:
+                pass
             print self.banner('STDERR ')
-            print self.stderr()
-            raise
+            try:
+                print self.stderr()
+            except IndexError:
+                pass
+            raise e
         if _failed(self, status):
             expect = ''
             if status != 0:
@@ -414,19 +440,19 @@ class TestCommon(TestCmd):
             print self.stdout()
             print self.banner('STDERR ')
             print self.stderr()
-            raise TestFailed
+            self.fail_test()
         if not stdout is None and not match(self.stdout(), stdout):
             self.diff(stdout, self.stdout(), 'STDOUT ')
             stderr = self.stderr()
             if stderr:
                 print self.banner('STDERR ')
                 print stderr
-            raise TestFailed
+            self.fail_test()
         if not stderr is None and not match(self.stderr(), stderr):
             print self.banner('STDOUT ')
             print self.stdout()
             self.diff(stderr, self.stderr(), 'STDERR ')
-            raise TestFailed
+            self.fail_test()
 
     def skip_test(self, message="Skipping test.\n"):
         """Skips a test.
index b35e3b0f146b2d70fbcca5c5ca94a844c43defe8..72e3e064fdb037a67e67a87db8614b1ea55fb840 100644 (file)
@@ -42,6 +42,7 @@ import re
 import stat
 import string
 import sys
+import tempfile
 
 project = 'scons'
 default_version = '0.97.0'
@@ -139,8 +140,11 @@ python_ver = sys.version[0:3]
 
 platform = distutils.util.get_platform()
 
+# Re-exporting LD_LIBRARY_PATH is necessary if the Python version was
+# built with the --enable-shared option.
+
 ENV = { 'PATH' : os.environ['PATH'] }
-for key in ['LOGNAME', 'PYTHONPATH']:
+for key in ['LOGNAME', 'PYTHONPATH', 'LD_LIBRARY_PATH']:
     if os.environ.has_key(key):
         ENV[key] = os.environ[key]
 
@@ -481,18 +485,54 @@ python_scons = {
                           },
 }
 
-# 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.
+# Figure out the name of a .egg-info file that might be generated
+# as part of the RPM package.  There are two complicating factors.
+#
+# First, the RPM spec file we generate will just execute "python", not
+# necessarily the one in sys.executable.  If *that* version of python has
+# a distutils that knows about Python eggs, then setup.py will generate a
+# .egg-info file, so we have to execute any distutils logic in a subshell.
+#
+# Second, we can't just have the subshell check for the existence of the
+# distutils.command.install_egg_info module and generate the expected
+# file name by hand, the way we used to, because different systems can
+# have slightly different .egg-info naming conventions.  (Specifically,
+# Ubuntu overrides the default behavior to remove the Python version
+# string from the .egg-info file name.)  The right way to do this is to
+# actually call into the install_egg_info() class to have it generate
+# the expected name for us.
+#
+# This is all complicated enough that we do it by writing an in-line
+# script to a temporary file and then feeding it to a separate invocation
+# of "python" to tell us the actual name of the generated .egg-info file.
 
-cmd = "python -c 'import distutils.command.install_egg_info' > /dev/null 2>&1"
-import_egg_error = os.system(cmd)
+print_egg_info_name = """
+try:
+    from distutils.dist import Distribution
+    from distutils.command.install_egg_info import install_egg_info
+except ImportError:
+    pass
+else:
+    dist = Distribution({'name' : "scons", 'version' : '%s'})
+    i = install_egg_info(dist)
+    i.finalize_options()
+    import os.path
+    print os.path.split(i.outputs[0])[1]
+""" % version
 
-if not import_egg_error:
-    egg_info_file = 'scons-' + version + '.egg-info'
-    python_scons['extra_rpm_files'].append(egg_info_file)
+try:
+    fd, tfname = tempfile.mkstemp()
+    tfp = os.fdopen(fd, "w")
+    tfp.write(print_egg_info_name)
+    tfp.close()
+    egg_info_file = os.popen("python %s" % tfname).read()[:-1]
+    if egg_info_file:
+        python_scons['extra_rpm_files'].append(egg_info_file)
+finally:
+    try:
+        os.unlink(tfname)
+    except EnvironmentError:
+        pass
 
 #
 # The original packaging scheme would have have required us to push
index 585d3c1b78d00b1bd7c475b720de34fbcf5f9ef5..6340bc1864110f94511f2cda13a684edd809cc68 100644 (file)
@@ -280,12 +280,23 @@ def Cat(target, source, env):
 
 def CCCom(target, source, env):
     target = str(target[0])
-    f = open(target, "wb")
+    fp = open(target, "wb")
+    def process(source_file, fp=fp):
+        for line in open(source_file, "rb").readlines():
+            m = re.match(r'#include\s[<"]([^<"]+)[>"]', line)
+            if m:
+                include = m.group(1)
+                for d in [str(env.Dir('$CPPPATH')), '.']:
+                    f = os.path.join(d, include)
+                    if os.path.exists(f):
+                        process(f)
+                        break
+            elif line[:11] != "STRIP CCCOM":
+                fp.write(line)
     for src in map(str, source):
-        lines = open(src, "rb").readlines()
-        lines = filter(lambda l: l[:11] != "STRIP CCCOM", lines)
-        f.write(string.join(lines))
-    f.close()
+        process(src)
+        fp.write('debug = ' + ARGUMENTS.get('debug', '0') + '\\n')
+    fp.close()
 
 public_class_re = re.compile('^public class (\S+)', re.MULTILINE)
 
@@ -432,13 +443,19 @@ def command_scons(args, c, test, dict):
     return lines
 
 def command_touch(args, c, test, dict):
-    time.sleep(1)
+    if args[0] == '-t':
+        t = int(time.mktime(time.strptime(args[1], '%Y%m%d%H%M')))
+        times = (t, t)
+        args = args[2:]
+    else:
+        time.sleep(1)
+        times = None
     for file in args:
         if not os.path.isabs(file):
             file = os.path.join(test.workpath('WORK'), file)
         if not os.path.exists(file):
             open(file, 'wb')
-        os.utime(file, None)
+        os.utime(file, times)
     return []
 
 def command_edit(args, c, test, dict):
@@ -728,6 +745,7 @@ class MySGML(sgmllib.SGMLParser):
             elif lines:
                 content = string.join(lines, '\n' + p)
             if content:
+                content = re.sub(' at 0x[0-9a-fA-F]*\>', ' at 0x700000&gt;', content)
                 content = string.replace(content, '<', '&lt;')
                 content = string.replace(content, '>', '&gt;')
                 sys.stdout.write(p + content + '\n')
index 6f87b203c3ac9bb2400670e2abf33b5e980a1a2f..526a5a3cdb246af08b91029d5002dedf3fe82b27 100644 (file)
@@ -264,8 +264,8 @@ scons foo bar
 in which case only the specified targets will be built
 (along with any derived files on which they depend).
 
-Specifying "cleanup" targets in SConscript files is not
-necessary.  The 
+Specifying "cleanup" targets in SConscript files is not usually necessary.
+The 
 .B -c
 flag removes all files
 necessary to build the specified target:
@@ -1271,7 +1271,7 @@ These warnings are disabled by default.
 Search the specified repository for any input and target
 files not found in the local directory hierarchy.  Multiple
 .B -Y
-options may specified, in which case the
+options may be specified, in which case the
 repositories are searched in the order specified.
 
 .SH CONFIGURATION FILE REFERENCE
@@ -1340,7 +1340,7 @@ env = Environment(platform = my_platform)
 
 Additionally, a specific set of tools
 with which to initialize the environment
-may specified as an optional keyword argument:
+may be specified as an optional keyword argument:
 
 .ES
 env = Environment(tools = ['msvc', 'lex'])
@@ -1530,10 +1530,12 @@ Build rules are specified by calling a construction
 environment's builder methods.
 The arguments to the builder methods are
 .B target
-(a list of target files)
+(a list of targets to be built,
+usually file names)
 and
 .B source
-(a list of source files).
+(a list of sources to be built,
+usually file names).
 
 Because long lists of file names
 can lead to a lot of quoting,
@@ -1569,6 +1571,58 @@ env.Program(target = 'bar', env.Split('bar.c foo.c'))
 env.Program('bar', source = string.split('bar.c foo.c'))
 .EE
 
+Target and source file names
+that are not absolute path names
+(that is, do not begin with
+.B /
+on POSIX systems
+or 
+.B \\
+on Windows systems,
+with or without
+an optional drive letter)
+are interpreted relative to the directory containing the
+.B SConscript
+file being read.
+An initial
+.B #
+(hash mark)
+on a path name means that the rest of the file name
+is interpreted relative to
+the directory containing
+the top-level
+.B SConstruct
+file,
+even if the
+.B #
+is followed by a directory separator character
+(slash or backslash).
+
+Examples:
+
+.ES
+# The comments describing the targets that will be built
+# assume these calls are in a SConscript file in the
+# a subdirectory named "subdir".
+
+# Builds the program "subdir/foo" from "subdir/foo.c":
+env.Program('foo', 'foo.c')
+
+# Builds the program "/tmp/bar" from "subdir/bar.c":
+env.Program('/tmp/bar', 'bar.c')
+
+# An initial '#' or '#/' are equivalent; the following
+# calls build the programs "foo" and "bar" (in the
+# top-level SConstruct directory) from "subdir/foo.c" and
+# "subdir/bar.c", respectively:
+env.Program('#foo', 'foo.c')
+env.Program('#/bar', 'bar.c')
+
+# Builds the program "other/foo" (relative to the top-level
+# SConstruct directory) from "subdir/foo.c":
+env.Program('#other/foo', 'foo.c')
+.EE
+
 When the target shares the same base name
 as the source and only the suffix varies,
 and if the builder method has a suffix defined for the target file type,
@@ -1665,15 +1719,16 @@ to the Python module:
 from SCons.Script import *
 .EE
 
-All builder methods return a list of Nodes
-that represent the target or targets that will be built.
+All builder methods return a list-like object
+containing Nodes that
+represent the target or targets that will be built.
 A
 .I Node
 is an internal SCons object
 which represents
 build targets or sources.
 
-The returned Node(s)
+The returned Node-list object
 can be passed to other builder methods as source(s)
 or passed to any SCons function or method
 where a filename would normally be accepted.
@@ -1712,7 +1767,7 @@ for object in objects:
 
 Or you can use the
 .BR Flatten ()
-supplied by scons
+function supplied by scons
 to create a list containing just the Nodes,
 which may be more convenient:
 
@@ -1724,6 +1779,40 @@ for object in objects:
     print str(object)
 .EE
 
+Note also that because Builder calls return
+a list-like object, not an actual Python list,
+you should
+.I not
+use the Python
+.B +=
+operator to append Builder results to a Python list.
+Because the list and the object are different types,
+Python will not update the original list in place,
+but will instead create a new Node-list object
+containing the concatenation of the list
+elements and the Builder results.
+This will cause problems for any other Python variables
+in your SCons configuration
+that still hold on to a reference to the original list.
+Instead, use the Python
+.B .extend()
+method to make sure the list is updated in-place.
+Example:
+
+.ES
+object_files = []
+
+# Do NOT use += as follows:
+#
+#    object_files += Object('bar.c')
+#
+# It will not update the object_files list in place.
+#
+# Instead, use the .extend() method:
+object_files.extend(Object('bar.c'))
+
+.EE
+
 The path name for a Node's file may be used
 by passing the Node to the Python-builtin
 .B str()
@@ -3303,14 +3392,14 @@ foo = env.FindFile('foo', ['dir1', 'dir2'])
 .RI FindInstalledFiles( )
 .TP
 .RI env.FindInstalledFiles( )
-Returns the list of targets setup by the
+Returns the list of targets set up by the
 .B Install()
 or
 .B InstallAs()
 builders.
 
 This function serves as a convenient method to select the contents of
-a Binary Package.
+a binary package.
 
 Example:
 
@@ -3338,7 +3427,7 @@ Returns the list of nodes which serve as the source of the built files.
 It does so by inspecting the dependency tree starting at the optional
 argument
 .B node
-which defaults to the '"."'-node. It will then return all leafs of
+which defaults to the '"."'-node. It will then return all leaves of
 .B node.
 These are all children which have no further children.
 
@@ -3452,6 +3541,111 @@ for object in Flatten(objects):
     print str(object)
 .EE
 
+'\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+.TP 
+.RI GetBuildFailures()
+Returns a list of exceptions for the
+actions that failed while
+attempting to build targets.
+Each element in the returned list is a
+.B BuildError
+object
+with the following attributes
+that record various aspects
+of the build failure:
+
+.B .node
+The node that was being built
+when the build failure occurred.
+
+.B .status
+The numeric exit status
+returned by the command or Python function
+that failed when trying to build the
+specified Node.
+
+.B .errstr
+The SCons error string
+describing the build failure.
+(This is often a generic
+message like "Error 2"
+to indicate that an executed
+command exited with a status of 2.)
+
+.B .filename
+The name of the file or
+directory that actually caused the failure.
+This may be different from the
+.B .node
+attribute.
+For example,
+if an attempt to build a target named
+.B sub/dir/target
+fails because the
+.B sub/dir
+directory could not be created,
+then the
+.B .node
+attribute will be
+.B sub/dir/target
+but the
+.B .filename
+attribute will be
+.BR sub/dir .
+
+.B .executor
+The SCons Executor object
+for the target Node
+being built.
+This can be used to retrieve
+the construction environment used
+for the failed action.
+
+.B .action
+The actual SCons Action object that failed.
+This will be one specific action
+out of the possible list of
+actions that would have been
+executed to build the target.
+
+.B .command
+The actual expanded command that was executed and failed,
+after expansion of
+.BR $TARGET ,
+.BR $SOURCE ,
+and other construction variables.
+
+Note that the
+.BR GetBuildFailures ()
+function
+will always return an empty list
+until any build failure has occurred,
+which means that
+.BR GetBuildFailures ()
+will always return an empty list
+while the
+.B SConscript
+files are being read.
+Its primary intended use is
+for functions that will be
+executed before SCons exits
+by passing them to the
+standard Python
+.BR atexit.register ()
+function.
+Example:
+
+.ES
+import atexit
+
+def print_build_failures():
+    from SCons.Script import GetBuildFailures
+    for bf in GetBuildFailures():
+        print "%s failed: %s" % (bf.node, bf.errstr)
+
+atexit.register(print_build_failures)
+.EE
+
 '\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
 .TP
 .RI GetBuildPath( file ", [" ... ])
@@ -3498,6 +3692,111 @@ options from a SConscript file. See
 .IR SetOption () 
 for a description of the options available.
 
+""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+.TP
+.RI Glob( pattern ", [" ondisk ", " source ", " strings ])
+.TP
+.RI env.Glob( pattern ", [" ondisk ", " source ", " strings ])
+Returns Nodes (or strings) that match the specified
+.IR pattern ,
+relative to the directory of the current
+.B SConscript
+file.
+The
+.BR env.Glob ()
+form performs string substition on
+.I pattern
+and returns whatever matches
+the resulting expanded pattern.
+
+The specified
+.I pattern
+uses Unix shell style metacharacters for matching:
+
+.ES
+  *       matches everything
+  ?       matches any single character
+  [seq]   matches any character in seq
+  [!seq]  matches any char not in seq
+.EE
+
+Character matches do
+.I not
+span directory separators.
+
+The
+.BR Glob ()
+knows about
+repositories
+(see the
+.BR Repository ()
+function)
+and source directories
+(see the
+.BR BuildDir ()
+function)
+and
+returns a Node (or string, if so configured)
+in the local (SConscript) directory
+if matching Node is found
+anywhere in a corresponding
+repository or source directory.
+
+The
+.B ondisk
+argument may be set to
+.B False
+(or any other non-true value)
+to disable the search for matches on disk,
+thereby only returning matches among
+already-configured File or Dir Nodes.
+The default behavior is to
+return corresponding Nodes
+for any on-disk matches found.
+
+The
+.B source
+argument may be set to
+.B True
+(or any equivalent value)
+to specify that,
+when the local directory is a
+.BR BuildDir (),
+the returned Nodes should be from the
+corresponding source directory,
+not the local directory.
+
+The
+.B strings
+argument may be set to
+.B True
+(or any equivalent value)
+to have the
+.BR Glob ()
+function return strings, not Nodes,
+that represent the matched files or directories.
+The returned strings will be relative to
+the local (SConscript) directory.
+(Note that This may make it easier to perform
+arbitrary manipulation of file names,
+but if the returned strings are
+passed to a different
+.B SConscript
+file,
+any Node translation will be relative
+to the other
+.B SConscript
+directory,
+not the original
+.B SConscript
+directory.)
+
+Example:
+
+.ES
+Program('foo', Glob('*.c'))
+.EE
+
 '\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
 '\".TP
 '\".RI GlobalBuilders( flag )
@@ -4265,6 +4564,29 @@ use the
 .B Local()
 method.
 
+'\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+.TP
+.RI Requires( target ", " prerequisite )
+.TP
+.RI env.Requires( target ", " prerequisite )
+Specifies an order-only relationship
+between the specified target file(s)
+and the specified prerequisite file(s).
+The prerequisite file(s)
+will be (re)built, if necessary,
+.I before
+the target file(s),
+but the target file(s) do not actually
+depend on the prerequisites
+and will not be rebuilt simply because
+the prerequisite file(s) change.
+
+Example:
+
+.ES
+env.Requires('foo', 'file-that-must-be-built-before-foo')
+.EE
+
 '\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
 .TP
 .RI Return([ vars "... , " stop= ])
@@ -4875,7 +5197,16 @@ source_nodes = env.subst('$EXPAND_TO_NODELIST',
 .RI SourceSignatures( type )
 .TP
 .RI env.SourceSignatures( type )
-This function tells
+Note:  Although it is not yet officially deprecated,
+use of this function is discouraged.
+See the
+.BR Decider ()
+function for a more flexible and straightforward way
+to configure SCons' decision-making.
+
+The
+.BR SourceSignatures ()
+function tells
 .B scons
 how to decide if a source file
 (a file that is not built from any other files)
@@ -4906,8 +5237,12 @@ the last time it was used to rebuild a particular target file.
 means
 .B scons
 decides that a source file has changed
-if its timestamp (modification time) is newer than
+if its timestamp (modification time) has changed since
 the last time it was used to rebuild a particular target file.
+(Note that although this is similar to the behavior of Make,
+by default it will also rebuild if the dependency is
+.I older
+than the last time it was used to rebuild the target file.)
 
 There is no different between the two behaviors
 for Python
@@ -4993,7 +5328,16 @@ Tag( 'file2.txt', DOC )
 .RI TargetSignatures( type )
 .TP
 .RI env.TargetSignatures( type )
-This function tells
+Note:  Although it is not yet officially deprecated,
+use of this function is discouraged.
+See the
+.BR Decider ()
+function for a more flexible and straightforward way
+to configure SCons' decision-making.
+
+The
+.BR TargetSignatures ()
+function tells
 .B scons
 how to decide if a target file
 (a file that
@@ -5040,8 +5384,12 @@ rebuilt with exactly the same contents as the last time.
 means
 .B scons
 decides that a target file has changed
-if its timestamp (modification time) is newer than
+if its timestamp (modification time) has changed since
 the last time it was used to rebuild some other target file.
+(Note that although this is similar to the behavior of Make,
+by default it will also rebuild if the dependency is
+.I older
+than the last time it was used to rebuild the target file.)
 
 .B "source"
 means
@@ -5476,7 +5824,7 @@ defined construction variables:
 '\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
 '\" BEGIN GENERATED CONSTRUCTION VARIABLE DESCRIPTIONS
 '\"
-'\" The descriptions below of the various SCons contruction variables
+'\" The descriptions below of the various SCons construction variables
 '\" are generated from the .xml files that live next to the various
 '\" Python modules in the build enginer library.  If you're reading
 '\" this [gnt]roff file with an eye towards patching this man page,
@@ -5491,7 +5839,7 @@ defined construction variables:
 '\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
 '\" END GENERATED CONSTRUCTION VARIABLE DESCRIPTIONS
 '\"
-'\" The descriptions above of the various SCons contruction variables
+'\" The descriptions above of the various SCons construction variables
 '\" are generated from the .xml files that live next to the various
 '\" Python modules in the build enginer library.  If you're reading
 '\" this [gnt]roff file with an eye towards patching this man page,
@@ -5554,9 +5902,9 @@ command line option.
 The following methods can be used to perform checks:
 
 .TP
-.RI Configure( env ", [" custom_tests ", " conf_dir ", " log_file ", " config_h ])
+.RI Configure( env ", [" custom_tests ", " conf_dir ", " log_file ", " config_h ", " clean ", " help])
 .TP
-.RI env.Configure([ custom_tests ", " conf_dir ", " log_file ", " config_h ])
+.RI env.Configure([ custom_tests ", " conf_dir ", " log_file ", " config_h ", " clean ", " help])
 This creates a configure context, which can be used to perform checks.
 .I env
 specifies the environment for building the tests.
@@ -5604,6 +5952,30 @@ and some target that depends on the
 .I config_h
 file is being built.
 
+The optional
+.B clean
+and
+.B help
+arguments can be used to suppress execution of the configuration
+tests when the
+.B -c/--clean
+or
+.B -H/-h/--help
+options are used, respectively.
+The default behavior is always to execute
+configure context tests,
+since the results of the tests may
+affect the list of targets to be cleaned
+or the help text.
+If the configure tests do not affect these,
+then you may add the
+.B clean=False
+or
+.B help=False
+arguments
+(or both)
+to avoid unnecessary test execution.
+
 .EE
 A created
 .B Configure
@@ -5843,6 +6215,42 @@ if conf.CheckLibWithHeader( 'qt', 'qapp.h', 'c++', 'QApplication qapp(0,0);' ):
 env = conf.Finish() 
 .EE
 
+.TP
+.RI Configure.CheckTypeSize( self ", " type_name ", [" header ", " language ", " expect ])
+Checks for the size of a type defined by
+.BR typedef .
+.I type_name
+specifies the typedef name to check for.
+The optional
+.I header
+argument is a string
+that will be
+placed at the top
+of the test file
+that will be compiled
+to check if the function exists;
+the default is empty.
+The optional
+.I language
+argument should be
+.B C
+or
+.B C++
+and selects the compiler to be used for the check;
+the default is "C".
+The optional
+.I expect
+argument should be an integer.
+If this argument is used,
+the function will only check whether the type
+given in type_name has the expected size (in bytes).
+For example,
+.B "CheckTypeSize('short', expect = 2)"
+will return success only if short is two bytes.
+
+.ES
+.EE
+
 .EE
 You can define your own custom checks. 
 in addition to the predefined checks.
@@ -5990,9 +6398,13 @@ written. For example, libraries needed for the build may be in non-standard
 locations, or site-specific compiler options may need to be passed to the
 compiler. 
 .B scons
-provides a mechanism for overridding construction variables from the
-command line or a text-based SConscript file through an Options
-object. To create an Options object, call the Options() function:
+provides an Options object for overridding construction variables
+on the command line:
+.ES
+$ scons VARIABLE=foo
+.EE
+The variable values can also be specified in a text-based SConscript file.
+To create an Options object, call the Options() function:
 
 .TP
 .RI Options([ files "], [" args ])
@@ -6091,8 +6503,17 @@ opt.AddOptions(
 .RI Update( env ", [" args ])
 This updates a construction environment
 .I env
-with the customized construction variables. Normally this method is not
-called directly, but is called indirectly by passing the Options object to
+with the customized construction variables.
+Any specified variables that are
+.I not
+configured for the Options object
+will be saved and may be
+retrieved with the
+.BR UnknownOptions ()
+method, below.
+
+Normally this method is not called directly,
+but is called indirectly by passing the Options object to
 the Environment() function:
 
 .ES
@@ -6105,12 +6526,28 @@ when the Options object was created
 are executed as Python scripts,
 and the values of (global) Python variables set in the file
 are added to the construction environment.
+
 Example:
 
 .ES
 CC = 'my_cc'
 .EE
 
+.TP
+.RI UnknownOptions( )
+Returns a dictionary containing any
+variables that were specified
+either in the files or the dictionary
+with which the Options object was intialized,
+but for which the Options object was
+not configured.
+
+.ES
+env = Environment(options=opts)
+for key, value in opts.UnknownOptions():
+    print "unknown variable:  %s=%s" % (key, value)
+.EE
+
 .TP
 .RI Save( filename ", " env )
 This saves the currently set options into a script file named  
@@ -8436,7 +8873,7 @@ prefix and suffix for the current platform
 (for example, 'liba.a' on POSIX systems,
 'a.lib' on Windows).
 
-.SS Customizing contruction variables from the command line.
+.SS Customizing construction variables from the command line.
 
 The following would allow the C compiler to be specified on the command
 line or in the file custom.py. 
index f3c1a86ee714429ed89bc1b32780486546b66308..c23e6ae04029687433d1179e02ab17642ff15f27 100644 (file)
 <!ENTITY Command "<function>Command</function>">
 <!ENTITY Configure "<function>Configure</function>">
 <!ENTITY Copy "<function>Copy</function>">
+<!ENTITY Decider "<function>Decider</function>">
 <!ENTITY Default "<function>Default</function>">
 <!ENTITY DefaultRules "<function>DefaultRules</function>">
 <!ENTITY Delete "<function>Delete</function>">
index 3c8cfd5b891ec2eedd2757a61973759562263423..3d8bc4b3504235221a207a1e882a1af8238b1832 100644 (file)
     The drawback is that the build can behave
     differently if it's run by people with
     different &PATH; values in their environment--for example,
-    both the <literal>/bin</literal> and
+    if both the <literal>/bin</literal> and
     <literal>/usr/local/bin</literal> directories
     have different &cc; commands,
     then which one will be used to compile programs
index d84327633bce78c2e651192e65dd9b8993b584e0..9fa2ec13d9844e0e851441cd772efdac85ed6bff 100644 (file)
     The drawback is that the build can behave
     differently if it's run by people with
     different &PATH; values in their environment--for example,
-    both the <literal>/bin</literal> and
+    if both the <literal>/bin</literal> and
     <literal>/usr/local/bin</literal> directories
     have different &cc; commands,
     then which one will be used to compile programs
index 7497277fab956522412f37234a5c0db3de638a92..2285ac8894472179b19b5e870c8af4a10948a30d 100644 (file)
@@ -263,7 +263,7 @@ This functionality could be invoked as in the following example:
 
     <para>
 
-    To be able use both our own defined &Builder; objects
+    To be able to use both our own defined &Builder; objects
     and the default &Builder; objects in the same &consenv;,
     you can either add to the &cv-BUILDERS; variable
     using the &Append; function:
index 50f6556e8dc01f6d61558481a967607a29702435..8ab4fcae1b82cc26bf18c8ae9a3073008e153d5d 100644 (file)
@@ -239,7 +239,7 @@ This functionality could be invoked as in the following example:
 
     <para>
 
-    To be able use both our own defined &Builder; objects
+    To be able to use both our own defined &Builder; objects
     and the default &Builder; objects in the same &consenv;,
     you can either add to the &cv-BUILDERS; variable
     using the &Append; function:
index b2cb8883c3a982fe70e58dabc2efc96a7d7fb783..dbbddaf72397c8c6661718c2d9ea87501b665199 100644 (file)
@@ -53,7 +53,7 @@
     <para>
 
     If, for example,
-    and you're using a POSIX shell that's
+    you're using a POSIX shell that's
     compatible with the Bourne shell,
     and you always want &SCons; to use the
     <literal>-Q</literal> option,
@@ -96,7 +96,7 @@
 
     <para>
 
-    Windows users may typically want to set this
+    Windows users may typically want to set the
     &SCONSFLAGS; in the appropriate tab of the
     <literal>System Properties</literal> window.
 
       to control an expected path name.
       If, for example, you need to
       define a variable in the preprocessor
-      that control the location of a
+      that controls the location of a
       configuration file:
 
       </para>
     </para>
 
   </section>
+
+  <!--
+
+  AddOption() function for things like - -prefix=, - -force
+
+  -->
index d7f3d7b60abece0a254d5e8a2b99558bb5a8583c..8675c1918dabbf8952d7cd8ba77c8ad591933d53 100644 (file)
@@ -53,7 +53,7 @@
     <para>
 
     If, for example,
-    and you're using a POSIX shell that's
+    you're using a POSIX shell that's
     compatible with the Bourne shell,
     and you always want &SCons; to use the
     <literal>-Q</literal> option,
@@ -89,7 +89,7 @@
 
     <para>
 
-    Windows users may typically want to set this
+    Windows users may typically want to set the
     &SCONSFLAGS; in the appropriate tab of the
     <literal>System Properties</literal> window.
 
       to control an expected path name.
       If, for example, you need to
       define a variable in the preprocessor
-      that control the location of a
+      that controls the location of a
       configuration file:
 
       </para>
     </para>
 
   </section>
+
+  <!--
+
+  AddOption() function for things like - -prefix=, - -force
+
+  -->
index e0d5e928d435ef5788908e3bdafff2646f8a1ab3..7c15c5229efa47832227ab9deef0f4920494b4dd 100644 (file)
   <para>
 
   So far we've seen how &SCons; handles one-time builds.
-  But the real point of a build tool like &SCons;
+  But one of the main functions of a build tool like &SCons;
   is to rebuild only the necessary things
   when source files change--or, put another way,
   &SCons; should <emphasis>not</emphasis>
   waste time rebuilding things that have already been built.
-  You can see this at work simply be re-invoking &SCons;
+  You can see this at work simply by re-invoking &SCons;
   after building our simple &hello; example:
 
   </para>
   </para>
 
   <section>
-  <title>Deciding When a Source File Has Changed:  the &SourceSignatures; Function</title>
+  <title>Deciding When an Input File Has Changed:  the &Decider; Function</title>
 
     <para>
 
-    The other side of avoiding unnecessary rebuilds
+    Another aspect of avoiding unnecessary rebuilds
     is the fundamental build tool behavior
     of <emphasis>rebuilding</emphasis>
-    things when a source file changes,
+    things when an input file changes,
     so that the built software is up to date.
-    &SCons; keeps track of this through a
-    &signature; for each source file,
-    and allows you to configure
-    whether you want to use the source
-    file contents or the modification time (timestamp)
-    as the signature.
+    By default,
+    &SCons; keeps track of this through an
+    MD5 &signature;, or checksum, of the contents of each file,
+    although you can easily configure
+    &SCons; to use the
+    modification times (or time stamps)
+    instead.
+    You can even specify your own Python function
+    for deciding if an input file has changed.
 
     </para>
 
     <section>
-    <title>MD5 Source File Signatures</title>
+    <title>Using MD5 Signatures to Decide if a File Has Changed</title>
 
       <para>
 
       By default,
-      &SCons; keeps track of whether a source file has changed
-      based on the file's contents,
-      not the modification time.
+      &SCons; keeps track of whether a file has changed
+      based on an MD5 checksum of the file's contents,
+      not the file's modification time.
       This means that you may be surprised by the
       default &SCons; behavior if you are used to the
       &Make; convention of forcing
       Note that you can, if you wish,
       specify this default behavior
       (MD5 signatures) explicitly
-      using the &SourceSignatures; function as follows:
+      using the &Decider; function as follows:
 
       </para>
 
       <sconstruct>
         Program('hello.c')
-        SourceSignatures('MD5')
+        Decider('MD5')
       </sconstruct>
 
+      <para>
+
+      You can also use the string <literal>'content'</literal>
+      as a synonym for <literal>'MD5'</literal>
+      when calling the &Decider; function.
+
+      </para>
+
+      <section>
+      <title>Ramifications of Using MD5 Signatures</title>
+
+        <para>
+
+        Using MD5 Signatures to decide if an input file has changed
+        has one surprising benefit:
+        if a source file has been changed
+        in such a way that the contents of the
+        rebuilt target file(s)
+        will be exactly the same as the last time
+        the file was built,
+        then any "downstream" target files
+        that depend on the rebuilt-but-not-changed target
+        file actually need not be rebuilt.
+
+        </para>
+
+        <para>
+
+        So if, for example,
+        a user were to only change a comment in a &hello_c; file,
+        then the rebuilt &hello_o; file
+        would be exactly the same as the one previously built
+        (assuming the compiler doesn't put any build-specific
+        information in the object file).
+        &SCons; would then realize that it would not
+        need to rebuild the &hello; program as follows:
+
+        </para>
+
+        <scons_output example="ex1" os="posix">
+           <scons_output_command>scons -Q hello</scons_output_command>
+           <scons_output_command output="  [CHANGE A COMMENT IN hello.c]" edit="STRIP CCCOM line">edit hello.c</scons_output_command>
+           <scons_output_command>scons -Q hello</scons_output_command>
+        </scons_output>
+
+        <para>
+
+        In essence, &SCons;
+        "short-circuits" any dependent builds
+        when it realizes that a target file
+        has been rebuilt to exactly the same file as the last build.
+        This does take some extra processing time
+        to read the contents of the target (&hello_o;) file,
+        but often saves time when the rebuild that was avoided
+        would have been time-consuming and expensive.
+
+        </para>
+
+      </section>
+
     </section>
 
     <section>
-    <title>Source File Time Stamps</title>
+    <title>Using Time Stamps to Decide If a File Has Changed</title>
 
       <para>
 
       If you prefer, you can
       configure &SCons; to use the modification time
-      of source files,
-      not the file contents,
-      when deciding if something needs to be rebuilt.
-      To do this, call the &SourceSignatures;
+      of a file, not the file contents,
+      when deciding if a target needs to be rebuilt.
+      &SCons; gives you two ways to use time stamps
+      to decide if an input file has changed
+      since the last time a target has been built.
+
+      </para>
+
+      <para>
+
+      The most familiar way to use time stamps
+      is the way &Make; does:
+      that is, have &SCons; decide
+      and target must be rebuilt if
+      if a source file's modification time is
+      <emphasis>newer</emphasis>
+      than the target file.
+      To do this, call the &Decider;
       function as follows:
 
       </para>
 
-      <scons_example name="ex2">
+      <scons_example name="newer">
         <file name="SConstruct" printme="1">
         Program('hello.c')
-        SourceSignatures('timestamp')
+        Decider('timestamp-newer')
         </file>
         <file name="hello.c">
         int main() { printf("Hello, world!\n"); }
 
       </para>
 
-      <scons_output example="ex2" os="posix">
+      <scons_output example="newer" os="posix">
          <scons_output_command>scons -Q hello</scons_output_command>
          <scons_output_command>touch hello.c</scons_output_command>
          <scons_output_command>scons -Q hello</scons_output_command>
       </scons_output>
 
-    </section>
+      <para>
 
-  </section>
+      And, in fact, because this behavior is the same
+      as the behavior of &Make;,
+      you can also use the string <literal>'make'</literal>
+      as a synonym for <literal>'timestamp-newer'</literal>
+      when calling the &Decider; function:
 
-  <section>
-  <title>Deciding When a Target File Has Changed:  the &TargetSignatures; Function</title>
+      </para>
 
-    <para>
+      <sconstruct>
+        Program('hello.c')
+        Decider('make')
+      </sconstruct>
 
-    As you've just seen,
-    &SCons; uses signatures to decide whether a
-    target file is up to date or must be rebuilt.
-    When a target file depends on another target file,
-    &SCons; allows you to configure separately
-    how the signatures of "intermediate" target files
-    are used when deciding if a dependent target file
-    must be rebuilt.
+      <para>
 
-    </para>
+      One drawback to using times stamps exactly like &Make;
+      is that if an input file's modification time suddenly
+      becomes <emphasis>older</emphasis> than a target file,
+      the target file will not be rebuilt.
+      This can happen if an old copy of a source file is restored
+      from a backup archive, for example.
+      The contents of the restored file will likely be different
+      than they were the last time a dependent target was built,
+      but the target won't be rebuilt
+      because the modification time of the source file
+      is not newer than the target.
 
-    <section>
-    <title>Build Signatures</title>
+      </para>
 
       <para>
 
-      Modifying a source file
-      will cause not only its direct target file to be rebuilt,
-      but also the target file(s)
-      that depend on that direct target file.
-      In our example,
-      changing the contents of the &hello_c; file causes
-      the &hello_o; file to be rebuilt,
-      which in turn causes the
-      &hello; program to be rebuilt:
+      Because &SCons; actually stores information
+      about the source files' time stamps whenever a target is built,
+      it can handle this situation by checking for
+      an exact match of the source file time stamp,
+      instead of just whether or not the source file
+      is newer than the target file.
+      To do this, specify the argument
+      <literal>'timestamp-match'</literal>
+      when calling the &Decider; function:
 
       </para>
 
-      <scons_output example="ex1" os="posix">
+      <scons_example name="match">
+        <file name="SConstruct" printme="1">
+        Program('hello.c')
+        Decider('timestamp-match')
+        </file>
+        <file name="hello.c">
+        int main() { printf("Hello, world!\n"); }
+        </file>
+      </scons_example>
+
+      <para>
+
+      When configured this way,
+      &SCons; will rebuild a target whenever
+      a source file's modification time has changed.
+      So if we use the <literal>touch -t</literal>
+      option to change the modification time of
+      &hello_c; to an old date (January 1, 1989),
+      &SCons; will still rebuild the target file:
+
+      </para>
+
+      <scons_output example="match" os="posix">
          <scons_output_command>scons -Q hello</scons_output_command>
-         <scons_output_command output="    [CHANGE THE CONTENTS OF hello.c]">edit hello.c</scons_output_command>
+         <scons_output_command>touch -t 198901010000 hello.c</scons_output_command>
          <scons_output_command>scons -Q hello</scons_output_command>
       </scons_output>
 
       <para>
 
-      What's not obvious, though,
-      is that &SCons; internally handles the signature of
-      the target file(s)
-      (&hello_o; in the above example)
-      differently from the signature of the source file
-      (&hello_c;).
-      By default,
-      &SCons; tracks whether a target file must be rebuilt
-      by using a &buildsignature;
-      that consists of the combined
-      signatures of all the files
-      that go into making the target file.
-      This is efficient because
-      the accumulated signatures
-      actually give &SCons; all of the
-      information it needs
-      to decide if the target file is out of date.
+      In general, the only reason to prefer
+      <literal>timestamp-newer</literal>
+      instead of
+      <literal>timestamp-match</literal>,
+      would be if you have some specific reason
+      to require this &Make;-like behavior of 
+      not rebuilding a target when an otherwise-modified
+      source file is older.
 
       </para>
 
+    </section>
+
+    <section>
+    <title>Deciding If a File Has Changed Using Both MD Signatures and Time Stamps</title>
+
       <para>
 
-      If you wish, you can
-      specify this default behavior
-      (build signatures) explicitly
-      using the &TargetSignatures; function:
+      As a performance enhancement,
+      &SCons; provides a way to use
+      MD5 checksums of file contents
+      but to only read the contents
+      whenever the file's timestamp has changed.
+      To do this, call the &Decider;
+      function with <literal>'MD5-timestamp'</literal>
+      argument as follows:
 
       </para>
 
-      <sconstruct>
+      <scons_example name="MD5-timestamp">
+        <file name="SConstruct" printme="1">
         Program('hello.c')
-        TargetSignatures('build')
-      </sconstruct>
+        Decider('MD5-timestamp')
+        </file>
+        <file name="hello.c">
+        int main() { printf("Hello, world!\n"); }
+        </file>
+      </scons_example>
+
+      <para>
+
+      So configured, &SCons will still behave like
+      it does when using <literal>Decider('MD5')</literal>:
+
+      </para>
+
+      <scons_output example="MD5-timestamp" os="posix">
+         <scons_output_command>scons -Q hello</scons_output_command>
+         <scons_output_command>touch hello.c</scons_output_command>
+         <scons_output_command>scons -Q hello</scons_output_command>
+       <scons_output_command output="    [CHANGE THE CONTENTS OF hello.c]">edit hello.c</scons_output_command>
+         <scons_output_command>scons -Q hello</scons_output_command>
+      </scons_output>
+
+      <para>
+
+      However, the second call to &SCons; in the above output,
+      when the build is up-to-date,
+      will have been performed by simply looking at the
+      modification time of the &hello_c; file,
+      not by opening it and performing
+      an MD5 checksum calcuation on its contents.
+      This can significantly speed up many up-to-date builds.
+
+      </para>
+
+      <para>
+
+      The only drawback to using
+      <literal>Decider('MD5-timestamp')</literal>
+      is that &SCons; will <emphasis>not</emphasis>
+      rebuild a target file if a source file was modified
+      within one second of the last time &SCons; built the file.
+      While most developers are programming,
+      this isn't a problem in practice,
+      since it's unlikely that someone will have built
+      and then thought quickly enought to make a substantive
+      change to a source file within one second.
+      Certain build scripts or
+      continuous integration tools may, however,
+      rely on the ability to applying changes to files
+      automatically and then rebuild as quickly as possible,
+      in which case use of
+      <literal>Decider('MD5-timestamp')</literal>
+      may not be appropriate.
+
+      </para>
 
     </section>
 
     <section>
-    <title>File Contents</title>
+    <title>Writing Your Own Custom &Decider; Function</title>
 
       <para>
 
-      Sometimes a source file can be changed
-      in such a way that the contents of the
-      rebuilt target file(s)
-      will be exactly the same as the last time
-      the file was built.
-      If so, then any other target files
-      that depend on such a built-but-not-changed target
-      file actually need not be rebuilt.
-      You can make &SCons;
-      realize that it does not need to rebuild
-      a dependent target file in this situation
-      using the &TargetSignatures; function as follows:
+      The different string values that we've passed to
+      the &Decider; function are essentially used by &SCons;
+      to pick one of several specific internal functions
+      that implement various ways of deciding if a dependency
+      (usually a source file)
+      has changed since a target file has been built.
+      As it turns out,
+      you can also supply your own function
+      to decide if a dependency has changed.
 
       </para>
 
-      <scons_example name="ex3">
+      <para>
+
+      For example, suppose we have an input file
+      that contains a lot of data,
+      in some specific regular format,
+      that is used to rebuild a lot of different target files,
+      but each target file really only depends on
+      one particular section of the input file.
+      We'd like to have each target file depend on
+      only its section of the input file.
+      However, since the input file may contain a lot of data,
+      we only want to open the input file if its timestamp has changed.
+      This could done with a custom
+      &Decider; function that might look something like this:
+
+      </para>
+
+      <scons_example name="function">
         <file name="SConstruct" printme="1">
         Program('hello.c')
-        TargetSignatures('content')
+        def decide_if_changed(dependency, target, prev_ni):
+            if self.get_timestamp() != prev_ni.timestamp:
+                dep = str(dependency)
+                tgt = str(target)
+                if specific_part_of_file_has_changed(dep, tgt):
+                    return True
+            return False
+        Decider(decide_if_changed)
         </file>
         <file name="hello.c">
         int main() { printf("Hello, world!\n"); }
 
       <para>
 
-      So if, for example,
-      a user were to only change a comment in a C file,
-      then the rebuilt &hello_o; file
-      would be exactly the same as the one previously built
-      (assuming the compiler doesn't put any build-specific
-      information in the object file).
-      &SCons; would then realize that it would not
-      need to rebuild the &hello; program as follows:
+      Note that in the function definition,
+      the <literal>dependency</literal>
+      (input file) is the first argument,
+      and then the <literal>target</literal>.
+      Both of these are passed to the functions as
+      SCons &Node; objects,
+      which we convert to strings using the Python
+      <function>str()</function>.
+      The third argument, <literal>prev_ni</literal>,
+      is an object that holds the
+      signature or timestamp information
+      that was recorded about the dependency
+      the last time the target was built.
 
       </para>
 
-      <scons_output example="ex3" os="posix">
-         <scons_output_command>scons -Q hello</scons_output_command>
-         <scons_output_command output="  [CHANGE A COMMENT IN hello.c]" edit="STRIP CCCOM line">edit hello.c</scons_output_command>
-         <scons_output_command>scons -Q hello</scons_output_command>
+      <para>
+
+      Note that ignoring some of the arguments
+      in your custom &Decider; function
+      is a perfectly normal thing to do,
+      if they don't impact the way you want to
+      decide if the dependency file has changed.
+
+      </para>
+
+    </section>
+
+    <section>
+    <title>Mixing Different Ways of Deciding If a File Has Changed</title>
+
+      <para>
+
+      The previous examples have all demonstrated calling
+      the global &Decider; function
+      to configure all dependency decisions that &SCons; makes.
+      Sometimes, however, you want to be able to configure
+      different decision-making for different targets.
+      When that's necessary, you can use the
+      <function>env.Decider</function>
+      method to affect only the configuration
+      decisions for targets built with a
+      specific construction environment.
+
+      </para>
+
+      <para>
+
+      For example, if we arbitrarily want to build
+      one program using MD5 checkums
+      and another use file modification times
+      from the same source
+      we might configure it this way:
+
+      </para>
+
+      <scons_example name="mixing">
+        <file name="SConstruct" printme="1">
+        env1 = Environment(CPPPATH = ['.'])
+        env2 = env1.Clone()
+        env2.Decider('timestamp-match')
+        env1.Program('prog-MD5', 'program1.c')
+        env2.Program('prog-timestamp', 'program2.c')
+        </file>
+        <file name="program1.c">
+        #include "inc.h"
+        int main() { printf("Hello, world!\n"); }
+        </file>
+        <file name="program2.c">
+        #include "inc.h"
+        int main() { printf("Hello, world!\n"); }
+        </file>
+        <file name="inc.h">
+        #define INC     1
+        </file>
+      </scons_example>
+
+      <para>
+
+      If both of the programs include the same
+      <filename>inc.h</filename> file,
+      then updating the modification time of
+      <filename>inc.h</filename>
+      (using the &touch; command)
+      will cause only <filename>prog-timestamp</filename>
+      to be rebuilt:
+
+      </para>
+
+      <scons_output example="mixing" os="posix">
+         <scons_output_command>scons -Q</scons_output_command>
+         <scons_output_command>touch inc.h</scons_output_command>
+         <scons_output_command>scons -Q</scons_output_command>
       </scons_output>
 
+    </section>
+
+  </section>
+
+  <section>
+  <title>Older Functions for Deciding When an Input File Has Changed</title>
+
+    <para>
+
+    &SCons; still supports two functions that used to be the
+    primary methods for configuring the
+    decision about whether or not an input file has changed.
+    Although they're not officially deprecated yet,
+    their use is discouraged,
+    mainly because they rely on a somewhat
+    confusing distinction between how
+    source files and target files are handled.
+    These functions are documented here mainly in case you
+    encounter them in existing &SConscript; files.
+
+    </para>
+  
+    <section>
+    <title>The &SourceSignatures; Function</title>
+
       <para>
 
-      In essence, &SCons; has
-      "short-circuited" any dependent builds
-      when it realizes that a target file
-      has been rebuilt to exactly the same file as the last build.
-      So configured,
-      &SCons; does take some extra processing time
-      to scan the contents of the target (&hello_o;) file,
-      but this may save time
-      if the rebuild that was avoided
-      would have been very time-consuming and expensive.
+      The &SourceSignatures; function is fairly straightforward,
+      and supports two different argument values
+      to configure whether source file changes should be decided
+      using MD5 signatures:
+
+      </para>
+
+      <sconstruct>
+        Program('hello.c')
+        SourceSignatures('MD5')
+      </sconstruct>
+
+      <para>
+
+      Or using time stamps:
+
+      </para>
+
+      <sconstruct>
+        Program('hello.c')
+        SourceSignatures('timestamp')
+      </sconstruct>
+
+      <para>
+
+      These are roughly equivalent to specifying
+      <function>Decider('MD5')</function>
+      or
+      <function>Decider('timestamp-match')</function>,
+      respectively,
+      although it only affects how SCons makes
+      decisions about dependencies on
+      <emphasis>source</emphasis> files--that is,
+      files that are not built from any other files.
+
+      </para>
+
+    </section>
+
+    <section>
+    <title>The &TargetSignatures; Function</title>
+
+      <para>
+
+      The &TargetSignatures; function
+      specifies how &SCons; decides
+      when a target file has changed
+      <emphasis>when it is used as a
+      dependency of (input to) another target</emphasis>--that is,
+      the &TargetSignatures; function configures
+      how the signatures of "intermediate" target files
+      are used when deciding if a "downstream" target file
+      must be rebuilt.
+      <footnote><para>
+      This easily-overlooked distinction between
+      how &SCons; decides if the target itself must be rebuilt
+      and how the target is then used to decide if a different
+      target must be rebuilt is one of the confusing
+      things that has led to the &TargetSignatures;
+      and &SourceSignatures; functions being
+      replaced by the simpler &Decider; function.
+      </para></footnote>
+
+      </para>
+
+      <para>
+
+      The &TargetSignatures; function supports the same
+      <literal>'MD5'</literal> and <literal>'timestamp'</literal>
+      argument values that are supported by the &SourceSignatures;,
+      with the same meanings, but applied to target files.
+      That is, in the example:
+
+      </para>
+
+      <sconstruct>
+        Program('hello.c')
+        TargetSignatures('MD5')
+      </sconstruct>
+
+      <para>
+
+      The MD5 checksum of the &hello_o; target file
+      will be used to decide if it has changed since the last
+      time the "downstream" &hello; target file was built.
+      And in the example:
+      
+      </para>
+
+      <sconstruct>
+        Program('hello.c')
+        TargetSignatures('timestamp')
+      </sconstruct>
+
+      <para>
+
+      The modification time of the &hello_o; target file
+      will be used to decide if it has changed since the last
+      time the "downstream" &hello; target file was built.
+
+      </para>
+
+      <para>
+
+      The &TargetSignatures; function supports
+      two additional argument values:
+      <literal>'source'</literal> and <literal>'build'</literal>.
+      The <literal>'source'</literal> argument
+      specifies that decisions involving
+      whether target files have changed
+      since a previous build
+      should use the same behavior
+      for the decisions configured for source files
+      (using the &SourceSignatures; function).
+      So in the example:
+
+      </para>
+
+      <sconstruct>
+        Program('hello.c')
+        TargetSignatures('source')
+        SourceSignatures('timestamp')
+      </sconstruct>
+
+      <para>
+
+      All files, both targets and sources,
+      will use modification times
+      when deciding if an input file
+      has changed since the last
+      time a target was built.
+
+      </para>
+
+      <para>
+
+      Lastly, the <literal>'build'</literal> argument
+      specifies that &SCons; should examine
+      the build status of a target file
+      and always rebuild a "downstream" target
+      if the target file was itself rebuilt,
+      without re-examining the contents or timestamp
+      of the newly-built target file.
+      If the target file was not rebuilt during
+      this &scons; invocation,
+      then the target file will be examined
+      the same way as configured by
+      the &SourceSignature; call
+      to decide if it has changed.
+
+      </para>
+
+      <para>
+
+      This mimics the behavior of
+      <literal>build signatures</literal>
+      in earlier versions of &SCons;.
+      A &buildsignature; re-combined
+      signatures of all the input files
+      that went into making the target file,
+      so that the target file itself
+      did not need to have its contents read
+      to compute an MD5 signature.
+      This can improve performance for some configurations,
+      but is generally not as effective as using
+      <literal>Decider('MD5-timestamp')</literal>.
 
       </para>
 
     <para>
 
     Now suppose that our "Hello, World!" program
-    actually has a <literal>#include</literal> line
+    actually has an <literal>#include</literal> line
     to include the &hello_h; file in the compilation:
 
     </para>
 
-    <scons_example name="ex4">
+    <scons_example name="include">
       <file name="SConstruct">
        Program('hello.c', CPPPATH = '.')
       </file>
 
     </para>
 
-    <scons_example_file example="ex4"  name="hello.h">
+    <scons_example_file example="include"  name="hello.h">
     </scons_example_file>
 
     <para>
 
     </para>
 
-    <scons_example_file example="ex4"  name="SConstruct">
+    <scons_example_file example="include"  name="SConstruct">
     </scons_example_file>
 
     <para>
 
     </para>
 
-    <scons_output example="ex4" os="posix">
+    <scons_output example="include" os="posix">
        <scons_output_command>scons -Q hello</scons_output_command>
        <scons_output_command>scons -Q hello</scons_output_command>
        <scons_output_command output="    [CHANGE THE CONTENTS OF hello.h]">edit hello.h</scons_output_command>
     the &cv-CPPPATH; variable
     may be a list of directories,
     or a string separated by
-    the system-specific path separate character
+    the system-specific path separation character
     (':' on POSIX/Linux, ';' on Windows).
     Either way, &SCons; creates the
     right command-line options
   </section>
 
   <section>
-  <title>Ignoring Dependencies:  the &Ignore; Method</title>
+  <title>Explicit Dependencies:  the &Depends; Function</title>
+
+    <para>
+
+    Sometimes a file depends on another file
+    that is not detected by an &SCons; scanner.
+    For this situation,
+    &SCons; allows you to specific explicitly that one file
+    depends on another file,
+    and must be rebuilt whenever that file changes.
+    This is specified using the &Depends; method:
+
+    </para>
+
+    <programlisting>
+       hello = Program('hello.c')
+       Depends(hello, 'other_file')
+    </programlisting>
+
+    <!-- XXX mention that you can use arrays for target and source? -->
+
+    <screen>
+       % <userinput>scons -Q hello</userinput>
+       cc -c hello.c -o hello.o
+       cc -o hello hello.o
+       % <userinput>scons -Q hello</userinput>
+       scons: `hello' is up to date.
+       % <userinput>edit other_file</userinput>
+           [CHANGE THE CONTENTS OF other_file]
+       % <userinput>scons -Q hello</userinput>
+       cc -c hello.c -o hello.o
+       cc -o hello hello.o
+    </screen>
+
+  </section>
+
+  <section>
+  <title>Ignoring Dependencies:  the &Ignore; Function</title>
 
     <para>
 
 
     Now, the above example is a little contrived,
     because it's hard to imagine a real-world situation
-    where you wouldn't to rebuild &hello;
+    where you wouldn't want to rebuild &hello;
     if the &hello_h; file changed.
     A more realistic example
     might be if the &hello;
   </section>
 
   <section>
-  <title>Explicit Dependencies:  the &Depends; Method</title>
-
-    <para>
-
-    On the other hand,
-    sometimes a file depends on another file
-    that is not detected by an &SCons; scanner.
-    For this situation,
-    &SCons; allows you to specific explicitly that one file
-    depends on another file,
-    and must be rebuilt whenever that file changes.
-    This is specified using the &Depends; method:
-
-    </para>
-
-    <programlisting>
-       hello = Program('hello.c')
-       Depends(hello, 'other_file')
-    </programlisting>
-
-    <!-- XXX mention that you can use arrays for target and source? -->
-
-    <screen>
-       % <userinput>scons -Q hello</userinput>
-       cc -c hello.c -o hello.o
-       cc -o hello hello.o
-       % <userinput>scons -Q hello</userinput>
-       scons: `hello' is up to date.
-       % <userinput>edit other_file</userinput>
-           [CHANGE THE CONTENTS OF other_file]
-       % <userinput>scons -Q hello</userinput>
-       cc -c hello.c -o hello.o
-       cc -o hello hello.o
-    </screen>
-
-  </section>
-
-  <section>
-  <title>The &AlwaysBuild; Method</title>
+  <title>The &AlwaysBuild; Function</title>
 
     <para>
 
index 9e055ee1c04250e7eef4c15a7c1408ce43d9376f..936e4b2acaf9f3251cf4a8332869f1e2961deb4f 100644 (file)
   <para>
 
   So far we've seen how &SCons; handles one-time builds.
-  But the real point of a build tool like &SCons;
+  But one of the main functions of a build tool like &SCons;
   is to rebuild only the necessary things
   when source files change--or, put another way,
   &SCons; should <emphasis>not</emphasis>
   waste time rebuilding things that have already been built.
-  You can see this at work simply be re-invoking &SCons;
+  You can see this at work simply by re-invoking &SCons;
   after building our simple &hello; example:
 
   </para>
   </para>
 
   <section>
-  <title>Deciding When a Source File Has Changed:  the &SourceSignatures; Function</title>
+  <title>Deciding When an Input File Has Changed:  the &Decider; Function</title>
 
     <para>
 
-    The other side of avoiding unnecessary rebuilds
+    Another aspect of avoiding unnecessary rebuilds
     is the fundamental build tool behavior
     of <emphasis>rebuilding</emphasis>
-    things when a source file changes,
+    things when an input file changes,
     so that the built software is up to date.
-    &SCons; keeps track of this through a
-    &signature; for each source file,
-    and allows you to configure
-    whether you want to use the source
-    file contents or the modification time (timestamp)
-    as the signature.
+    By default,
+    &SCons; keeps track of this through an
+    MD5 &signature;, or checksum, of the contents of each file,
+    although you can easily configure
+    &SCons; to use the
+    modification times (or time stamps)
+    instead.
+    You can even specify your own Python function
+    for deciding if an input file has changed.
 
     </para>
 
     <section>
-    <title>MD5 Source File Signatures</title>
+    <title>Using MD5 Signatures to Decide if a File Has Changed</title>
 
       <para>
 
       By default,
-      &SCons; keeps track of whether a source file has changed
-      based on the file's contents,
-      not the modification time.
+      &SCons; keeps track of whether a file has changed
+      based on an MD5 checksum of the file's contents,
+      not the file's modification time.
       This means that you may be surprised by the
       default &SCons; behavior if you are used to the
       &Make; convention of forcing
       Note that you can, if you wish,
       specify this default behavior
       (MD5 signatures) explicitly
-      using the &SourceSignatures; function as follows:
+      using the &Decider; function as follows:
 
       </para>
 
       <programlisting>
         Program('hello.c')
-        SourceSignatures('MD5')
+        Decider('MD5')
       </programlisting>
 
+      <para>
+
+      You can also use the string <literal>'content'</literal>
+      as a synonym for <literal>'MD5'</literal>
+      when calling the &Decider; function.
+
+      </para>
+
+      <section>
+      <title>Ramifications of Using MD5 Signatures</title>
+
+        <para>
+
+        Using MD5 Signatures to decide if an input file has changed
+        has one surprising benefit:
+        if a source file has been changed
+        in such a way that the contents of the
+        rebuilt target file(s)
+        will be exactly the same as the last time
+        the file was built,
+        then any "downstream" target files
+        that depend on the rebuilt-but-not-changed target
+        file actually need not be rebuilt.
+
+        </para>
+
+        <para>
+
+        So if, for example,
+        a user were to only change a comment in a &hello_c; file,
+        then the rebuilt &hello_o; file
+        would be exactly the same as the one previously built
+        (assuming the compiler doesn't put any build-specific
+        information in the object file).
+        &SCons; would then realize that it would not
+        need to rebuild the &hello; program as follows:
+
+        </para>
+
+        <screen>
+           % <userinput>scons -Q hello</userinput>
+           cc -o hello.o -c hello.c
+           cc -o hello hello.o
+           % <userinput>edit hello.c</userinput>
+             [CHANGE A COMMENT IN hello.c]
+           % <userinput>scons -Q hello</userinput>
+           cc -o hello.o -c hello.c
+           scons: `hello' is up to date.
+        </screen>
+
+        <para>
+
+        In essence, &SCons;
+        "short-circuits" any dependent builds
+        when it realizes that a target file
+        has been rebuilt to exactly the same file as the last build.
+        This does take some extra processing time
+        to read the contents of the target (&hello_o;) file,
+        but often saves time when the rebuild that was avoided
+        would have been time-consuming and expensive.
+
+        </para>
+
+      </section>
+
     </section>
 
     <section>
-    <title>Source File Time Stamps</title>
+    <title>Using Time Stamps to Decide If a File Has Changed</title>
 
       <para>
 
       If you prefer, you can
       configure &SCons; to use the modification time
-      of source files,
-      not the file contents,
-      when deciding if something needs to be rebuilt.
-      To do this, call the &SourceSignatures;
+      of a file, not the file contents,
+      when deciding if a target needs to be rebuilt.
+      &SCons; gives you two ways to use time stamps
+      to decide if an input file has changed
+      since the last time a target has been built.
+
+      </para>
+
+      <para>
+
+      The most familiar way to use time stamps
+      is the way &Make; does:
+      that is, have &SCons; decide
+      and target must be rebuilt if
+      if a source file's modification time is
+      <emphasis>newer</emphasis>
+      than the target file.
+      To do this, call the &Decider;
       function as follows:
 
       </para>
 
       <programlisting>
         Program('hello.c')
-        SourceSignatures('timestamp')
+        Decider('timestamp-newer')
       </programlisting>
 
       <para>
          cc -o hello hello.o
       </screen>
 
-    </section>
+      <para>
 
-  </section>
+      And, in fact, because this behavior is the same
+      as the behavior of &Make;,
+      you can also use the string <literal>'make'</literal>
+      as a synonym for <literal>'timestamp-newer'</literal>
+      when calling the &Decider; function:
 
-  <section>
-  <title>Deciding When a Target File Has Changed:  the &TargetSignatures; Function</title>
+      </para>
 
-    <para>
+      <programlisting>
+        Program('hello.c')
+        Decider('make')
+      </programlisting>
 
-    As you've just seen,
-    &SCons; uses signatures to decide whether a
-    target file is up to date or must be rebuilt.
-    When a target file depends on another target file,
-    &SCons; allows you to configure separately
-    how the signatures of "intermediate" target files
-    are used when deciding if a dependent target file
-    must be rebuilt.
+      <para>
 
-    </para>
+      One drawback to using times stamps exactly like &Make;
+      is that if an input file's modification time suddenly
+      becomes <emphasis>older</emphasis> than a target file,
+      the target file will not be rebuilt.
+      This can happen if an old copy of a source file is restored
+      from a backup archive, for example.
+      The contents of the restored file will likely be different
+      than they were the last time a dependent target was built,
+      but the target won't be rebuilt
+      because the modification time of the source file
+      is not newer than the target.
+
+      </para>
+
+      <para>
+
+      Because &SCons; actually stores information
+      about the source files' time stamps whenever a target is built,
+      it can handle this situation by checking for
+      an exact match of the source file time stamp,
+      instead of just whether or not the source file
+      is newer than the target file.
+      To do this, specify the argument
+      <literal>'timestamp-match'</literal>
+      when calling the &Decider; function:
+
+      </para>
+
+      <programlisting>
+        Program('hello.c')
+        Decider('timestamp-match')
+      </programlisting>
+
+      <para>
+
+      When configured this way,
+      &SCons; will rebuild a target whenever
+      a source file's modification time has changed.
+      So if we use the <literal>touch -t</literal>
+      option to change the modification time of
+      &hello_c; to an old date (January 1, 1989),
+      &SCons; will still rebuild the target file:
+
+      </para>
+
+      <screen>
+         % <userinput>scons -Q hello</userinput>
+         cc -o hello.o -c hello.c
+         cc -o hello hello.o
+         % <userinput>touch -t 198901010000 hello.c</userinput>
+         % <userinput>scons -Q hello</userinput>
+         cc -o hello.o -c hello.c
+         scons: `hello' is up to date.
+      </screen>
+
+      <para>
+
+      In general, the only reason to prefer
+      <literal>timestamp-newer</literal>
+      instead of
+      <literal>timestamp-match</literal>,
+      would be if you have some specific reason
+      to require this &Make;-like behavior of 
+      not rebuilding a target when an otherwise-modified
+      source file is older.
+
+      </para>
+
+    </section>
 
     <section>
-    <title>Build Signatures</title>
+    <title>Deciding If a File Has Changed Using Both MD Signatures and Time Stamps</title>
+
+      <para>
+
+      As a performance enhancement,
+      &SCons; provides a way to use
+      MD5 checksums of file contents
+      but to only read the contents
+      whenever the file's timestamp has changed.
+      To do this, call the &Decider;
+      function with <literal>'MD5-timestamp'</literal>
+      argument as follows:
+
+      </para>
+
+      <programlisting>
+        Program('hello.c')
+        Decider('MD5-timestamp')
+      </programlisting>
 
       <para>
 
-      Modifying a source file
-      will cause not only its direct target file to be rebuilt,
-      but also the target file(s)
-      that depend on that direct target file.
-      In our example,
-      changing the contents of the &hello_c; file causes
-      the &hello_o; file to be rebuilt,
-      which in turn causes the
-      &hello; program to be rebuilt:
+      So configured, &SCons; will still behave like
+      it does when using <literal>Decider('MD5')</literal>:
 
       </para>
 
          % <userinput>scons -Q hello</userinput>
          cc -o hello.o -c hello.c
          cc -o hello hello.o
+         % <userinput>touch hello.c</userinput>
+         % <userinput>scons -Q hello</userinput>
+         scons: `hello' is up to date.
          % <userinput>edit hello.c</userinput>
              [CHANGE THE CONTENTS OF hello.c]
          % <userinput>scons -Q hello</userinput>
-         cc -o hello.o -c hello.c
-         cc -o hello hello.o
+         scons: `hello' is up to date.
       </screen>
 
       <para>
 
-      What's not obvious, though,
-      is that &SCons; internally handles the signature of
-      the target file(s)
-      (&hello_o; in the above example)
-      differently from the signature of the source file
-      (&hello_c;).
-      By default,
-      &SCons; tracks whether a target file must be rebuilt
-      by using a &buildsignature;
-      that consists of the combined
-      signatures of all the files
-      that go into making the target file.
-      This is efficient because
-      the accumulated signatures
-      actually give &SCons; all of the
-      information it needs
-      to decide if the target file is out of date.
+      However, the second call to &SCons; in the above output,
+      when the build is up-to-date,
+      will have been performed by simply looking at the
+      modification time of the &hello_c; file,
+      not by opening it and performing
+      an MD5 checksum calcuation on its contents.
+      This can significantly speed up many up-to-date builds.
 
       </para>
 
       <para>
 
-      If you wish, you can
-      specify this default behavior
-      (build signatures) explicitly
-      using the &TargetSignatures; function:
+      The only drawback to using
+      <literal>Decider('MD5-timestamp')</literal>
+      is that &SCons; will <emphasis>not</emphasis>
+      rebuild a target file if a source file was modified
+      within one second of the last time &SCons; built the file.
+      While most developers are programming,
+      this isn't a problem in practice,
+      since it's unlikely that someone will have built
+      and then thought quickly enought to make a substantive
+      change to a source file within one second.
+      Certain build scripts or
+      continuous integration tools may, however,
+      rely on the ability to applying changes to files
+      automatically and then rebuild as quickly as possible,
+      in which case use of
+      <literal>Decider('MD5-timestamp')</literal>
+      may not be appropriate.
+
+      </para>
+
+    </section>
+
+    <section>
+    <title>Writing Your Own Custom &Decider; Function</title>
+
+      <para>
+
+      The different string values that we've passed to
+      the &Decider; function are essentially used by &SCons;
+      to pick one of several specific internal functions
+      that implement various ways of deciding if a dependency
+      (usually a source file)
+      has changed since a target file has been built.
+      As it turns out,
+      you can also supply your own function
+      to decide if a dependency has changed.
+
+      </para>
+
+      <para>
+
+      For example, suppose we have an input file
+      that contains a lot of data,
+      in some specific regular format,
+      that is used to rebuild a lot of different target files,
+      but each target file really only depends on
+      one particular section of the input file.
+      We'd like to have each target file depend on
+      only its section of the input file.
+      However, since the input file may contain a lot of data,
+      we only want to open the input file if its timestamp has changed.
+      This could done with a custom
+      &Decider; function that might look something like this:
 
       </para>
 
       <programlisting>
         Program('hello.c')
-        TargetSignatures('build')
+        def decide_if_changed(dependency, target, prev_ni):
+            if self.get_timestamp() != prev_ni.timestamp:
+                dep = str(dependency)
+                tgt = str(target)
+                if specific_part_of_file_has_changed(dep, tgt):
+                    return True
+            return False
+        Decider(decide_if_changed)
       </programlisting>
 
+      <para>
+
+      Note that in the function definition,
+      the <literal>dependency</literal>
+      (input file) is the first argument,
+      and then the <literal>target</literal>.
+      Both of these are passed to the functions as
+      SCons &Node; objects,
+      which we convert to strings using the Python
+      <function>str()</function>.
+      The third argument, <literal>prev_ni</literal>,
+      is an object that holds the
+      signature or timestamp information
+      that was recorded about the dependency
+      the last time the target was built.
+
+      </para>
+
+      <para>
+
+      Note that ignoring some of the arguments
+      in your custom &Decider; function
+      is a perfectly normal thing to do,
+      if they don't impact the way you want to
+      decide if the dependency file has changed.
+
+      </para>
+
     </section>
 
     <section>
-    <title>File Contents</title>
+    <title>Mixing Different Ways of Deciding If a File Has Changed</title>
+
+      <para>
+
+      The previous examples have all demonstrated calling
+      the global &Decider; function
+      to configure all dependency decisions that &SCons; makes.
+      Sometimes, however, you want to be able to configure
+      different decision-making for different targets.
+      When that's necessary, you can use the
+      <function>env.Decider</function>
+      method to affect only the configuration
+      decisions for targets built with a
+      specific construction environment.
+
+      </para>
 
       <para>
 
-      Sometimes a source file can be changed
-      in such a way that the contents of the
-      rebuilt target file(s)
-      will be exactly the same as the last time
-      the file was built.
-      If so, then any other target files
-      that depend on such a built-but-not-changed target
-      file actually need not be rebuilt.
-      You can make &SCons;
-      realize that it does not need to rebuild
-      a dependent target file in this situation
-      using the &TargetSignatures; function as follows:
+      For example, if we arbitrarily want to build
+      one program using MD5 checkums
+      and another use file modification times
+      from the same source
+      we might configure it this way:
 
       </para>
 
       <programlisting>
-        Program('hello.c')
-        TargetSignatures('content')
+        env1 = Environment(CPPPATH = ['.'])
+        env2 = env1.Clone()
+        env2.Decider('timestamp-match')
+        env1.Program('prog-MD5', 'program1.c')
+        env2.Program('prog-timestamp', 'program2.c')
       </programlisting>
 
       <para>
 
-      So if, for example,
-      a user were to only change a comment in a C file,
-      then the rebuilt &hello_o; file
-      would be exactly the same as the one previously built
-      (assuming the compiler doesn't put any build-specific
-      information in the object file).
-      &SCons; would then realize that it would not
-      need to rebuild the &hello; program as follows:
+      If both of the programs include the same
+      <filename>inc.h</filename> file,
+      then updating the modification time of
+      <filename>inc.h</filename>
+      (using the &touch; command)
+      will cause only <filename>prog-timestamp</filename>
+      to be rebuilt:
 
       </para>
 
       <screen>
-         % <userinput>scons -Q hello</userinput>
-         cc -o hello.o -c hello.c
-         cc -o hello hello.o
-         % <userinput>edit hello.c</userinput>
-           [CHANGE A COMMENT IN hello.c]
-         % <userinput>scons -Q hello</userinput>
-         cc -o hello.o -c hello.c
-         scons: `hello' is up to date.
+         % <userinput>scons -Q</userinput>
+         cc -o program1.o -c -I. program1.c
+         cc -o prog-MD5 program1.o
+         cc -o program2.o -c -I. program2.c
+         cc -o prog-timestamp program2.o
+         % <userinput>touch inc.h</userinput>
+         % <userinput>scons -Q</userinput>
+         cc -o program2.o -c -I. program2.c
+         cc -o prog-timestamp program2.o
       </screen>
 
+    </section>
+
+  </section>
+
+  <section>
+  <title>Older Functions for Deciding When an Input File Has Changed</title>
+
+    <para>
+
+    &SCons; still supports two functions that used to be the
+    primary methods for configuring the
+    decision about whether or not an input file has changed.
+    Although they're not officially deprecated yet,
+    their use is discouraged,
+    mainly because they rely on a somewhat
+    confusing distinction between how
+    source files and target files are handled.
+    These functions are documented here mainly in case you
+    encounter them in existing &SConscript; files.
+
+    </para>
+  
+    <section>
+    <title>The &SourceSignatures; Function</title>
+
+      <para>
+
+      The &SourceSignatures; function is fairly straightforward,
+      and supports two different argument values
+      to configure whether source file changes should be decided
+      using MD5 signatures:
+
+      </para>
+
+      <programlisting>
+        Program('hello.c')
+        SourceSignatures('MD5')
+      </programlisting>
+
+      <para>
+
+      Or using time stamps:
+
+      </para>
+
+      <programlisting>
+        Program('hello.c')
+        SourceSignatures('timestamp')
+      </programlisting>
+
       <para>
 
-      In essence, &SCons; has
-      "short-circuited" any dependent builds
-      when it realizes that a target file
-      has been rebuilt to exactly the same file as the last build.
-      So configured,
-      &SCons; does take some extra processing time
-      to scan the contents of the target (&hello_o;) file,
-      but this may save time
-      if the rebuild that was avoided
-      would have been very time-consuming and expensive.
+      These are roughly equivalent to specifying
+      <function>Decider('MD5')</function>
+      or
+      <function>Decider('timestamp-match')</function>,
+      respectively,
+      although it only affects how SCons makes
+      decisions about dependencies on
+      <emphasis>source</emphasis> files--that is,
+      files that are not built from any other files.
+
+      </para>
+
+    </section>
+
+    <section>
+    <title>The &TargetSignatures; Function</title>
+
+      <para>
+
+      The &TargetSignatures; function
+      specifies how &SCons; decides
+      when a target file has changed
+      <emphasis>when it is used as a
+      dependency of (input to) another target</emphasis>--that is,
+      the &TargetSignatures; function configures
+      how the signatures of "intermediate" target files
+      are used when deciding if a "downstream" target file
+      must be rebuilt.
+      <footnote><para>
+      This easily-overlooked distinction between
+      how &SCons; decides if the target itself must be rebuilt
+      and how the target is then used to decide if a different
+      target must be rebuilt is one of the confusing
+      things that has led to the &TargetSignatures;
+      and &SourceSignatures; functions being
+      replaced by the simpler &Decider; function.
+      </para></footnote>
+
+      </para>
+
+      <para>
+
+      The &TargetSignatures; function supports the same
+      <literal>'MD5'</literal> and <literal>'timestamp'</literal>
+      argument values that are supported by the &SourceSignatures;,
+      with the same meanings, but applied to target files.
+      That is, in the example:
+
+      </para>
+
+      <programlisting>
+        Program('hello.c')
+        TargetSignatures('MD5')
+      </programlisting>
+
+      <para>
+
+      The MD5 checksum of the &hello_o; target file
+      will be used to decide if it has changed since the last
+      time the "downstream" &hello; target file was built.
+      And in the example:
+      
+      </para>
+
+      <programlisting>
+        Program('hello.c')
+        TargetSignatures('timestamp')
+      </programlisting>
+
+      <para>
+
+      The modification time of the &hello_o; target file
+      will be used to decide if it has changed since the last
+      time the "downstream" &hello; target file was built.
+
+      </para>
+
+      <para>
+
+      The &TargetSignatures; function supports
+      two additional argument values:
+      <literal>'source'</literal> and <literal>'build'</literal>.
+      The <literal>'source'</literal> argument
+      specifies that decisions involving
+      whether target files have changed
+      since a previous build
+      should use the same behavior
+      for the decisions configured for source files
+      (using the &SourceSignatures; function).
+      So in the example:
+
+      </para>
+
+      <programlisting>
+        Program('hello.c')
+        TargetSignatures('source')
+        SourceSignatures('timestamp')
+      </programlisting>
+
+      <para>
+
+      All files, both targets and sources,
+      will use modification times
+      when deciding if an input file
+      has changed since the last
+      time a target was built.
+
+      </para>
+
+      <para>
+
+      Lastly, the <literal>'build'</literal> argument
+      specifies that &SCons; should examine
+      the build status of a target file
+      and always rebuild a "downstream" target
+      if the target file was itself rebuilt,
+      without re-examining the contents or timestamp
+      of the newly-built target file.
+      If the target file was not rebuilt during
+      this &scons; invocation,
+      then the target file will be examined
+      the same way as configured by
+      the &SourceSignature; call
+      to decide if it has changed.
+
+      </para>
+
+      <para>
+
+      This mimics the behavior of
+      <literal>build signatures</literal>
+      in earlier versions of &SCons;.
+      A &buildsignature; re-combined
+      signatures of all the input files
+      that went into making the target file,
+      so that the target file itself
+      did not need to have its contents read
+      to compute an MD5 signature.
+      This can improve performance for some configurations,
+      but is generally not as effective as using
+      <literal>Decider('MD5-timestamp')</literal>.
 
       </para>
 
     <para>
 
     Now suppose that our "Hello, World!" program
-    actually has a <literal>#include</literal> line
+    actually has an <literal>#include</literal> line
     to include the &hello_h; file in the compilation:
 
     </para>
     the &cv-CPPPATH; variable
     may be a list of directories,
     or a string separated by
-    the system-specific path separate character
+    the system-specific path separation character
     (':' on POSIX/Linux, ';' on Windows).
     Either way, &SCons; creates the
     right command-line options
   </section>
 
   <section>
-  <title>Ignoring Dependencies:  the &Ignore; Method</title>
+  <title>Explicit Dependencies:  the &Depends; Function</title>
+
+    <para>
+
+    Sometimes a file depends on another file
+    that is not detected by an &SCons; scanner.
+    For this situation,
+    &SCons; allows you to specific explicitly that one file
+    depends on another file,
+    and must be rebuilt whenever that file changes.
+    This is specified using the &Depends; method:
+
+    </para>
+
+    <programlisting>
+       hello = Program('hello.c')
+       Depends(hello, 'other_file')
+    </programlisting>
+
+    <!-- XXX mention that you can use arrays for target and source? -->
+
+    <screen>
+       % <userinput>scons -Q hello</userinput>
+       cc -c hello.c -o hello.o
+       cc -o hello hello.o
+       % <userinput>scons -Q hello</userinput>
+       scons: `hello' is up to date.
+       % <userinput>edit other_file</userinput>
+           [CHANGE THE CONTENTS OF other_file]
+       % <userinput>scons -Q hello</userinput>
+       cc -c hello.c -o hello.o
+       cc -o hello hello.o
+    </screen>
+
+  </section>
+
+  <section>
+  <title>Ignoring Dependencies:  the &Ignore; Function</title>
 
     <para>
 
 
     Now, the above example is a little contrived,
     because it's hard to imagine a real-world situation
-    where you wouldn't to rebuild &hello;
+    where you wouldn't want to rebuild &hello;
     if the &hello_h; file changed.
     A more realistic example
     might be if the &hello;
   </section>
 
   <section>
-  <title>Explicit Dependencies:  the &Depends; Method</title>
-
-    <para>
-
-    On the other hand,
-    sometimes a file depends on another file
-    that is not detected by an &SCons; scanner.
-    For this situation,
-    &SCons; allows you to specific explicitly that one file
-    depends on another file,
-    and must be rebuilt whenever that file changes.
-    This is specified using the &Depends; method:
-
-    </para>
-
-    <programlisting>
-       hello = Program('hello.c')
-       Depends(hello, 'other_file')
-    </programlisting>
-
-    <!-- XXX mention that you can use arrays for target and source? -->
-
-    <screen>
-       % <userinput>scons -Q hello</userinput>
-       cc -c hello.c -o hello.o
-       cc -o hello hello.o
-       % <userinput>scons -Q hello</userinput>
-       scons: `hello' is up to date.
-       % <userinput>edit other_file</userinput>
-           [CHANGE THE CONTENTS OF other_file]
-       % <userinput>scons -Q hello</userinput>
-       cc -c hello.c -o hello.o
-       cc -o hello hello.o
-    </screen>
-
-  </section>
-
-  <section>
-  <title>The &AlwaysBuild; Method</title>
+  <title>The &AlwaysBuild; Function</title>
 
     <para>
 
index 504ef675d659e7763f8fa90e4460a1867b3cce5b..3fcec02fa0a4673cadf3e7fa8961311eed06ec96 100644 (file)
@@ -391,7 +391,7 @@ environment undisturbed.
    enabled on the command line,
    or different executable programs need to be linked
    with different libraries.
-   &SCons; accomodates these different build
+   &SCons; accommodates these different build
    requirements by allowing you to create and
    configure multiple &consenvs;
    that control how the software is built.
@@ -416,7 +416,7 @@ environment undisturbed.
 
  <para>
 
-   By default, &SCons; intializes every
+   By default, &SCons; initializes every
    new construction environment
    with a set of &consvars;
    based on the tools that it finds on your system,
@@ -845,7 +845,7 @@ environment undisturbed.
    (Note that because we're not expanding this
    in the context of building something
    there are no target or source files
-   for &cv-link-TARGET; and &cv-link-SOURCES; to expand.
+   for &cv-link-TARGET; and &cv-link-SOURCES; to expand.)
 
    </para>
 
index fbcef9926025d9fb97789371ef3b6dac3ae70f66..01ae2ed50844a42d1388a96859852063c900ace5 100644 (file)
@@ -391,7 +391,7 @@ environment undisturbed.
    enabled on the command line,
    or different executable programs need to be linked
    with different libraries.
-   &SCons; accomodates these different build
+   &SCons; accommodates these different build
    requirements by allowing you to create and
    configure multiple &consenvs;
    that control how the software is built.
@@ -416,7 +416,7 @@ environment undisturbed.
 
  <para>
 
-   By default, &SCons; intializes every
+   By default, &SCons; initializes every
    new construction environment
    with a set of &consvars;
    based on the tools that it finds on your system,
@@ -842,7 +842,7 @@ environment undisturbed.
    (Note that because we're not expanding this
    in the context of building something
    there are no target or source files
-   for &cv-link-TARGET; and &cv-link-SOURCES; to expand.
+   for &cv-link-TARGET; and &cv-link-SOURCES; to expand.)
 
    </para>
 
index d0c5313057901d43b142b3e0762a6025bc6cb719..222f02a26e4574e0c722b07c8cc51c979aa0de8f 100644 (file)
     The &Move; factory
     allows you to rename a file or directory.
     For example, if we don't want to copy the temporary file,
-    we could:
+    we could use:
 
     </para>
 
     a file in a temporary directory
     in which the processing tool
     will create other files that we don't care about, 
-    you could:
+    you could use:
 
     </para>
 
index 7c09e4bbcde9702c59891f21b081b845a1d42bd5..6d0de02d30b6e36eeb8b0a4ffe3523246a96dd89 100644 (file)
     The &Move; factory
     allows you to rename a file or directory.
     For example, if we don't want to copy the temporary file,
-    we could:
+    we could use:
 
     </para>
 
     a file in a temporary directory
     in which the processing tool
     will create other files that we don't care about, 
-    you could:
+    you could use:
 
     </para>
 
index adc5b5fe0e224d93c181971d281dab8bde079921..1d1b60455c7fae38caba33ff71a107b51d925c9e 100644 (file)
 
     By default, &SCons; removes all built targets when invoked
     with the <literal>-c</literal> option to clean a source tree
-    of built tragets.
+    of built targets.
     Sometimes, however, this is not what you want.
     For example, you may want to remove only intermediate generated files
     (such as object files),
index f64d3942457a9b7aae4fb04fa947eb8f23c15ca9..bfec7ac741f39133e05909252a879c3416103165 100644 (file)
@@ -94,7 +94,7 @@
 
     By default, &SCons; removes all built targets when invoked
     with the <literal>-c</literal> option to clean a source tree
-    of built tragets.
+    of built targets.
     Sometimes, however, this is not what you want.
     For example, you may want to remove only intermediate generated files
     (such as object files),
index 4356d18c29c2bdef7cea312be215d300a59e9514..5ab7e83bfdcf5230b61fadf3039cc4d341f34691 100644 (file)
 
    <para>
 
-   Will display the completely help text on Windows:
+   Will display the complete help text on Windows:
 
    </para>
 
index ca44a40f241a4c97af29bf113d173504f290260a..ed2b5ded1f13104e11a07348a8416e059c527152 100644 (file)
 
    <para>
 
-   Will display the completely help text on Windows:
+   Will display the complete help text on Windows:
 
    </para>
 
index e0d6b00643306ed7c7f3863237d5317fe5a183c0..4286556f335b6905c6a68e103ea0190e861a97ea 100644 (file)
@@ -518,7 +518,7 @@ make no difference to the build.
       so that it may be used by other &SConscript; files.
       First, you can call the &Export;
       function with a list of variables,
-      or a string white-space separated variable names.
+      or a string of white-space separated variable names.
       Each call to &Export; adds one
       or more variables to a global list
       of variables that are available for import
@@ -770,6 +770,10 @@ make no difference to the build.
         <scons_output_command>scons -Q</scons_output_command>
       </scons_output>
 
+      <!--
+      XXX Return(stop=False)
+      -->
+
     </section>
 
   </section>
index 4c84d5b9fb9930edd677ff1baa18ee16b9c0c044..2e2941c7c33c98e316bfc02a11c3e5a6be5a736c 100644 (file)
@@ -482,7 +482,7 @@ make no difference to the build.
       so that it may be used by other &SConscript; files.
       First, you can call the &Export;
       function with a list of variables,
-      or a string white-space separated variable names.
+      or a string of white-space separated variable names.
       Each call to &Export; adds one
       or more variables to a global list
       of variables that are available for import
@@ -722,6 +722,10 @@ make no difference to the build.
         ranlib libprog.a
       </screen>
 
+      <!--
+      XXX Return(stop=False)
+      -->
+
     </section>
 
   </section>
index ac9510f2110bcbefc43f33f147dc47637c0a370b..ba179e795c93861947784fa223c1344ca8a26bcd 100644 (file)
     you can either call the &InstallAs; function
     multiple times, or as a shorthand,
     you can supply same-length lists
-    for the both the target and source arguments:
+    for both the target and source arguments:
 
     </para>
 
index 2a6d1b89107dbc6fbce2b5b1a227e82c7877051a..dd79153b48d683abf4b24f08179e9a138e3dc0e1 100644 (file)
     you can either call the &InstallAs; function
     multiple times, or as a shorthand,
     you can supply same-length lists
-    for the both the target and source arguments:
+    for both the target and source arguments:
 
     </para>
 
index 1ccb1c3e8f8a7eb9ba537a1ac6d348adc66e1ffb..73ba6c92c758060b284560c77783f32846604058 100644 (file)
     the library will be found in the 
     &cv-link-LIBPATH; construction variable:
 
+    <!-- In the preceding paragraph, the "$" notation for
+         LIBS, LIBPATH etc. is used for the first time.
+         Maybe some words of explanation would be nice. -->
+
     </para>
 
     <scons_example name="ex2">
     <scons_output example="ex3" os="win32">
       <scons_output_command>scons -Q</scons_output_command>
     </scons_output>
+    <!-- The link command is too wide in the PDF version. 
+         There are some other examples of this throughout the document. -->
 
     <para>
 
index 035ebd3c4452a442d947a03757e7c6fefa16f3e8..da7d835f7d76e6635e5ed4475460a83be42dd498 100644 (file)
     the library will be found in the 
     &cv-link-LIBPATH; construction variable:
 
+    <!-- In the preceding paragraph, the "$" notation for
+         LIBS, LIBPATH etc. is used for the first time.
+         Maybe some words of explanation would be nice. -->
+
     </para>
 
     <programlisting>
       cl /nologo /c prog.c /Foprog.obj
       link /nologo /OUT:prog.exe /LIBPATH:\usr\lib /LIBPATH:\usr\local\lib m.lib prog.obj
     </screen>
+    <!-- The link command is too wide in the PDF version. 
+         There are some other examples of this throughout the document. -->
 
     <para>
 
index c3cf2c2ec7d7e0f657187fdec2a512b241c2dc50..4095e6b2601289f3dccdf90fc9eb98121e4a0d9a 100644 (file)
   XXX GetBuildPath()
   XXX GetLaunchDir()
 
+  XXX ParseConfig()
   XXX MergeFlags()
   XXX ParseFlags()
 
   XXX GetOption('num_jobs')
   XXX SetOption('num_jobs')
 
+  XXX Options.UnknownOption()
+
+  XXX GetBuildFailures()
+
+  XXX Requires()
+
+  XXX CheckTypeSize()
+
+  XXX Glob()
+
+  XXX Progress()
+
+  XXX AddMethod()
+
   XXX - - diskcheck=
 
   XXX site_scons
index c3cf2c2ec7d7e0f657187fdec2a512b241c2dc50..4095e6b2601289f3dccdf90fc9eb98121e4a0d9a 100644 (file)
   XXX GetBuildPath()
   XXX GetLaunchDir()
 
+  XXX ParseConfig()
   XXX MergeFlags()
   XXX ParseFlags()
 
   XXX GetOption('num_jobs')
   XXX SetOption('num_jobs')
 
+  XXX Options.UnknownOption()
+
+  XXX GetBuildFailures()
+
+  XXX Requires()
+
+  XXX CheckTypeSize()
+
+  XXX Glob()
+
+  XXX Progress()
+
+  XXX AddMethod()
+
   XXX - - diskcheck=
 
   XXX site_scons
index d3f563c52ac512793e3c35127fd40bdce1f29a27..45b58cc8466cad8605b8b6fd8b5fe71971c54ba6 100644 (file)
@@ -50,7 +50,7 @@
  <scons_example name="ParseConfig1">
    <file name="SConstruct" printme="1">
     env = Environment()
-    env.ParseConfig("pkg-config")
+    env.ParseConfig("pkg-config x11")
    </file>
    <file name="f1.c">
    int f1() { }
index 067ef37544d1ea4b39b7f47ad4e110adb8df91bb..25ea12c82d71093b9ce744c8ba8962d7c67bacf9 100644 (file)
@@ -49,7 +49,7 @@
 
  <programlisting>
     env = Environment()
-    env.ParseConfig("pkg-config")
+    env.ParseConfig("pkg-config x11")
  </programlisting>
 
  <para>
@@ -62,7 +62,6 @@
  <screen>
     % <userinput>scons -Q</userinput>
     scons: `.' is up to date.
-    Must specify package names on the command line
  </screen>
 
  <para>
index 9065593815e44852f9580cde45191556c54af888..d4ed418c974130421304afd602de8873637741ee 100644 (file)
@@ -487,8 +487,8 @@ coming into existence.)
     Usually, this would be done by a build integrator
     who would run &SCons; in the repository
     to create all of its derived files and &sconsign; files,
-    or who would &SCons; in a separate build directory
-    and copying the resulting tree to the desired repository:
+    or who would run &SCons; in a separate build directory
+    and copy the resulting tree to the desired repository:
 
     </para>
 
@@ -552,7 +552,7 @@ coming into existence.)
     <para>
 
     Notice that &SCons; realizes that it does not need to
-    rebuild local copies file1.o and file2.o files,
+    rebuild local copies <filename>file1.o</filename> and <filename>file2.o</filename> files,
     but instead uses the already-compiled files
     from the repository.
 
index f22611b4ceba26e45b12cf2990debca78bb1f096..766d8bc82e163bc84a32941fd95ee1df5b4d34d3 100644 (file)
@@ -457,8 +457,8 @@ coming into existence.)
     Usually, this would be done by a build integrator
     who would run &SCons; in the repository
     to create all of its derived files and &sconsign; files,
-    or who would &SCons; in a separate build directory
-    and copying the resulting tree to the desired repository:
+    or who would run &SCons; in a separate build directory
+    and copy the resulting tree to the desired repository:
 
     </para>
 
@@ -511,7 +511,7 @@ coming into existence.)
     <para>
 
     Notice that &SCons; realizes that it does not need to
-    rebuild local copies file1.o and file2.o files,
+    rebuild local copies <filename>file1.o</filename> and <filename>file2.o</filename> files,
     but instead uses the already-compiled files
     from the repository.
 
index 0165dddcf611e44b0cf55992a673223ab15fb8eb..265b720b447a98b6c527d60f85bcdec17292119d 100644 (file)
 
     Note that you can choose to terminate
     the build if a given header file doesn't exist,
-    or you can modify the contstruction environment
+    or you can modify the construction environment
     based on the existence of a header file.
 
     </para>
index df530fefeabff61d745e66f92389964f05978488..163db3b61831668b109b2015cd442dd2777f454f 100644 (file)
 
     Note that you can choose to terminate
     the build if a given header file doesn't exist,
-    or you can modify the contstruction environment
+    or you can modify the construction environment
     based on the existence of a header file.
 
     </para>
index b468f9a6f6a3ee41cdd8919d0da719b01f204623..3382f867383532d57d2e13ab0d972447211b33d4 100644 (file)
 
    Because we want this User's Guide to focus
    on what &SCons; is actually doing,
-   we're going use the &Q; option
+   we're going to use the &Q; option
    to remove these messages from the
    output of all the remaining examples in this Guide.
 
index c5ef430c67a282fce7036cb7398741515bac80db..4e4ff0ebf349fcc4c6e8e8632c9e50747c8633dc 100644 (file)
 
    Because we want this User's Guide to focus
    on what &SCons; is actually doing,
-   we're going use the &Q; option
+   we're going to use the &Q; option
    to remove these messages from the
    output of all the remaining examples in this Guide.
 
index 6cb41626bc81db637818713532a4911c9346d511..1818d8eaaf18be76f97efd307ca092e25b88d781 100644 (file)
@@ -50,7 +50,6 @@
 
    <screen>
      % <userinput>scons -Q</userinput>
-     bk get -
      bk get hello.c
      cc -o hello.o -c hello.c
      cc -o hello hello.o
@@ -75,7 +74,6 @@
 
    <screen>
      % <userinput>scons -Q</userinput>
-     cvs -d /usr/local/CVS co -
      cvs -d /usr/local/CVS co hello.c
      cc -o hello.o -c hello.c
      cc -o hello hello.o
 
    <screen>
      % <userinput>scons -Q</userinput>
-     co -
      co hello.c
      cc -o hello.o -c hello.c
      cc -o hello hello.o
 
    <screen>
      % <userinput>scons -Q</userinput>
-     sccs get -
      sccs get hello.c
      cc -o hello.o -c hello.c
      cc -o hello hello.o
index 11f44ddc61027e3f5c0e11d0660aa63eb8379387..3e0bfdb356cda9cfc106296225be501eb9d84548 100644 (file)
     Although this is usually helpful and what you want,
     it might be frustrating if &SCons;
     doesn't set certain variables that you
-    expect to be sit.
+    expect to be set.
     In situations like this,
     it's sometimes helpful to use the
     construction environment &Dump; method
     In a real-life situation,
     the construction environments will
     likely contain a great many more variables.
+    Also note that we've massaged the example output above
+    to make the memory address of all objects a constant 0x700000.
+    In reality, you would see a different hexadecimal
+    number for each object.
 
     </para>
 
index 6ec6185724368c3cc9046242b4b92d1c3093a0b0..ca5ace81f0754bbaff6cb2e52c0fbbd868b2c26a 100644 (file)
     Although this is usually helpful and what you want,
     it might be frustrating if &SCons;
     doesn't set certain variables that you
-    expect to be sit.
+    expect to be set.
     In situations like this,
     it's sometimes helpful to use the
     construction environment &Dump; method
     <screen>
       % <userinput>scons</userinput>
       scons: Reading SConscript files ...
-      { 'BUILDERS': {'InstallAs': &lt;function InstallAsBuilderWrapper at 0xb23a28&gt;, 'Install': &lt;function InstallBuilderWrapper at 0xb1b7d0&gt;},
+      { 'BUILDERS': {'InstallAs': &lt;function InstallAsBuilderWrapper at 0x700000&gt;, 'Install': &lt;function InstallBuilderWrapper at 0x700000&gt;},
         'CONFIGUREDIR': '#/.sconf_temp',
         'CONFIGURELOG': '#/config.log',
         'CPPSUFFIXES': [ '.c',
                          '.spp',
                          '.SPP'],
         'DSUFFIXES': ['.d'],
-        'Dir': &lt;SCons.Defaults.Variable_Method_Caller instance at 0xa1eb48&gt;,
-        'Dirs': &lt;SCons.Defaults.Variable_Method_Caller instance at 0xa1eb90&gt;,
+        'Dir': &lt;SCons.Defaults.Variable_Method_Caller instance at 0x700000&gt;,
+        'Dirs': &lt;SCons.Defaults.Variable_Method_Caller instance at 0x700000&gt;,
         'ENV': {'PATH': '/usr/local/bin:/opt/bin:/bin:/usr/bin'},
-        'ESCAPE': &lt;function escape at 0xb1fa28&gt;,
-        'File': &lt;SCons.Defaults.Variable_Method_Caller instance at 0xa1ebd8&gt;,
+        'ESCAPE': &lt;function escape at 0x700000&gt;,
+        'File': &lt;SCons.Defaults.Variable_Method_Caller instance at 0x700000&gt;,
         'IDLSUFFIXES': ['.idl', '.IDL'],
-        'INSTALL': &lt;function copyFunc at 0xb23aa0&gt;,
+        'INSTALL': &lt;function copyFunc at 0x700000&gt;,
         'LATEXSUFFIXES': ['.tex', '.ltx', '.latex'],
         'LIBPREFIX': 'lib',
         'LIBPREFIXES': '$LIBPREFIX',
         'PLATFORM': 'posix',
         'PROGPREFIX': '',
         'PROGSUFFIX': '',
-        'PSPAWN': &lt;function piped_env_spawn at 0xb23230&gt;,
-        'RDirs': &lt;SCons.Defaults.Variable_Method_Caller instance at 0xa1ec20&gt;,
+        'PSPAWN': &lt;function piped_env_spawn at 0x700000&gt;,
+        'RDirs': &lt;SCons.Defaults.Variable_Method_Caller instance at 0x700000&gt;,
         'SCANNERS': [],
         'SHELL': 'sh',
         'SHLIBPREFIX': '$LIBPREFIX',
         'SHLIBSUFFIX': '.so',
         'SHOBJPREFIX': '$OBJPREFIX',
         'SHOBJSUFFIX': '$OBJSUFFIX',
-        'SPAWN': &lt;function spawnvpe_spawn at 0xb1f7d0&gt;,
-        'TEMPFILE': &lt;class SCons.Platform.TempFileMunge at 0xa4e170&gt;,
+        'SPAWN': &lt;function spawnvpe_spawn at 0x700000&gt;,
+        'TEMPFILE': &lt;class SCons.Platform.TempFileMunge at 0x700000&gt;,
         'TEMPFILEPREFIX': '@',
         'TOOLS': ['install', 'install'],
         '_CPPDEFFLAGS': '${_defines(CPPDEFPREFIX, CPPDEFINES, CPPDEFSUFFIX, __env__)}',
         '_LIBDIRFLAGS': '$( ${_concat(LIBDIRPREFIX, LIBPATH, LIBDIRSUFFIX, __env__, RDirs, TARGET, SOURCE)} $)',
         '_LIBFLAGS': '${_concat(LIBLINKPREFIX, LIBS, LIBLINKSUFFIX, __env__)}',
         '__RPATH': '$_RPATH',
-        '_concat': &lt;function _concat at 0xa3d398&gt;,
-        '_defines': &lt;function _defines at 0xa3d500&gt;,
-        '_stripixes': &lt;function _stripixes at 0xa3d488&gt;}
+        '_concat': &lt;function _concat at 0x700000&gt;,
+        '_defines': &lt;function _defines at 0x700000&gt;,
+        '_stripixes': &lt;function _stripixes at 0x700000&gt;}
       scons: done reading SConscript files.
       scons: Building targets ...
       scons: `.' is up to date.
     <screen>
       C:\><userinput>scons</userinput>
       scons: Reading SConscript files ...
-      { 'BUILDERS': {'RES': &lt;SCons.Builder.BuilderBase instance at 0xb39518&gt;, 'Object': &lt;SCons.Builder.CompositeBuilder instance at 0xb4a710&gt;, 'InstallAs': &lt;function InstallAsBuilderWrapper at 0xb45c08&gt;, 'PCH': &lt;SCons.Builder.BuilderBase instance at 0xb1cef0&gt;, 'Install': &lt;function InstallBuilderWrapper at 0xb1b7d0&gt;, 'SharedObject': &lt;SCons.Builder.CompositeBuilder instance at 0xb4aa28&gt;, 'StaticObject': &lt;SCons.Builder.CompositeBuilder instance at 0xb4a710&gt;},
+      { 'BUILDERS': {'RES': &lt;SCons.Builder.BuilderBase instance at 0x700000&gt;, 'Object': &lt;SCons.Builder.CompositeBuilder instance at 0x700000&gt;, 'InstallAs': &lt;function InstallAsBuilderWrapper at 0x700000&gt;, 'PCH': &lt;SCons.Builder.BuilderBase instance at 0x700000&gt;, 'Install': &lt;function InstallBuilderWrapper at 0x700000&gt;, 'SharedObject': &lt;SCons.Builder.CompositeBuilder instance at 0x700000&gt;, 'StaticObject': &lt;SCons.Builder.CompositeBuilder instance at 0x700000&gt;},
         'CC': 'cl',
-        'CCCOM': &lt;SCons.Action.FunctionAction instance at 0xb4c290&gt;,
+        'CCCOM': &lt;SCons.Action.FunctionAction instance at 0x700000&gt;,
         'CCCOMFLAGS': '$CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS /c $SOURCES /Fo$TARGET $CCPCHFLAGS $CCPDBFLAGS',
         'CCFLAGS': ['/nologo'],
         'CCPCHFLAGS': ['${(PCH and "/Yu%s /Fp%s"%(PCHSTOP or "",File(PCH))) or ""}'],
         'CXXFILESUFFIX': '.cc',
         'CXXFLAGS': ['$CCFLAGS', '$(', '/TP', '$)'],
         'DSUFFIXES': ['.d'],
-        'Dir': &lt;SCons.Defaults.Variable_Method_Caller instance at 0xa1eb48&gt;,
-        'Dirs': &lt;SCons.Defaults.Variable_Method_Caller instance at 0xa1eb90&gt;,
+        'Dir': &lt;SCons.Defaults.Variable_Method_Caller instance at 0x700000&gt;,
+        'Dirs': &lt;SCons.Defaults.Variable_Method_Caller instance at 0x700000&gt;,
         'ENV': { 'INCLUDE': 'C:\\Program Files\\Microsoft Visual Studio/VC98\\include',
                  'LIB': 'C:\\Program Files\\Microsoft Visual Studio/VC98\\lib',
                  'PATH': 'C:\\Program Files\\Microsoft Visual Studio\\Common\\tools\\WIN95;C:\\Program Files\\Microsoft Visual Studio\\Common\\MSDev98\\bin;C:\\Program Files\\Microsoft Visual Studio\\Common\\tools;C:\\Program Files\\Microsoft Visual Studio/VC98\\bin',
                  'PATHEXT': '.COM;.EXE;.BAT;.CMD',
                  'SystemRoot': 'C:/WINDOWS'},
-        'ESCAPE': &lt;function escape at 0xb24848&gt;,
-        'File': &lt;SCons.Defaults.Variable_Method_Caller instance at 0xa1ebd8&gt;,
+        'ESCAPE': &lt;function escape at 0x700000&gt;,
+        'File': &lt;SCons.Defaults.Variable_Method_Caller instance at 0x700000&gt;,
         'IDLSUFFIXES': ['.idl', '.IDL'],
         'INCPREFIX': '/I',
         'INCSUFFIX': '',
-        'INSTALL': &lt;function copyFunc at 0xb45c80&gt;,
+        'INSTALL': &lt;function copyFunc at 0x700000&gt;,
         'LATEXSUFFIXES': ['.tex', '.ltx', '.latex'],
         'LIBPREFIX': '',
         'LIBPREFIXES': ['$LIBPREFIX'],
         'PLATFORM': 'win32',
         'PROGPREFIX': '',
         'PROGSUFFIX': '.exe',
-        'PSPAWN': &lt;function piped_spawn at 0xb20488&gt;,
+        'PSPAWN': &lt;function piped_spawn at 0x700000&gt;,
         'RC': 'rc',
         'RCCOM': '$RC $_CPPDEFFLAGS $_CPPINCFLAGS $RCFLAGS /fo$TARGET $SOURCES',
         'RCFLAGS': [],
-        'RDirs': &lt;SCons.Defaults.Variable_Method_Caller instance at 0xa1ec20&gt;,
+        'RDirs': &lt;SCons.Defaults.Variable_Method_Caller instance at 0x700000&gt;,
         'SCANNERS': [],
         'SHCC': '$CC',
-        'SHCCCOM': &lt;SCons.Action.FunctionAction instance at 0xb4c320&gt;,
+        'SHCCCOM': &lt;SCons.Action.FunctionAction instance at 0x700000&gt;,
         'SHCCFLAGS': ['$CCFLAGS'],
         'SHCFLAGS': ['$CFLAGS'],
         'SHCXX': '$CXX',
         'SHLIBSUFFIX': '.dll',
         'SHOBJPREFIX': '$OBJPREFIX',
         'SHOBJSUFFIX': '$OBJSUFFIX',
-        'SPAWN': &lt;function spawn at 0xb247d0&gt;,
+        'SPAWN': &lt;function spawn at 0x700000&gt;,
         'STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME': 1,
-        'TEMPFILE': &lt;class SCons.Platform.TempFileMunge at 0xa4e170&gt;,
+        'TEMPFILE': &lt;class SCons.Platform.TempFileMunge at 0x700000&gt;,
         'TEMPFILEPREFIX': '@',
         'TOOLS': ['msvc', 'install', 'install'],
         '_CPPDEFFLAGS': '${_defines(CPPDEFPREFIX, CPPDEFINES, CPPDEFSUFFIX, __env__)}',
         '_CPPINCFLAGS': '$( ${_concat(INCPREFIX, CPPPATH, INCSUFFIX, __env__, RDirs, TARGET, SOURCE)} $)',
         '_LIBDIRFLAGS': '$( ${_concat(LIBDIRPREFIX, LIBPATH, LIBDIRSUFFIX, __env__, RDirs, TARGET, SOURCE)} $)',
         '_LIBFLAGS': '${_concat(LIBLINKPREFIX, LIBS, LIBLINKSUFFIX, __env__)}',
-        '_concat': &lt;function _concat at 0xa3d398&gt;,
-        '_defines': &lt;function _defines at 0xa3d500&gt;,
-        '_stripixes': &lt;function _stripixes at 0xa3d488&gt;}
+        '_concat': &lt;function _concat at 0x700000&gt;,
+        '_defines': &lt;function _defines at 0x700000&gt;,
+        '_stripixes': &lt;function _stripixes at 0x700000&gt;}
       scons: done reading SConscript files.
       scons: Building targets ...
       scons: `.' is up to date.
     In a real-life situation,
     the construction environments will
     likely contain a great many more variables.
+    Also note that we've massaged the example output above
+    to make the memory address of all objects a constant 0x700000.
+    In reality, you would see a different hexadecimal
+    number for each object.
 
     </para>
 
       cc -o f3.o -c -I. f3.c
       cc -o prog f1.o f2.o f3.o
       +-.
-        +--
         +-SConstruct
         +-f1.c
         +-f1.o
                H = no cache
       
       [E b      ]+-.
-      [         ]  +--
-      [E        ]  +-SConstruct
-      [E        ]  +-f1.c
+      [E     C  ]  +-SConstruct
+      [E     C  ]  +-f1.c
       [E B   C  ]  +-f1.o
-      [E        ]  | +-f1.c
-      [E        ]  | +-inc.h
-      [E        ]  +-f2.c
+      [E     C  ]  | +-f1.c
+      [E     C  ]  | +-inc.h
+      [E     C  ]  +-f2.c
       [E B   C  ]  +-f2.o
-      [E        ]  | +-f2.c
-      [E        ]  | +-inc.h
-      [E        ]  +-f3.c
+      [E     C  ]  | +-f2.c
+      [E     C  ]  | +-inc.h
+      [E     C  ]  +-f3.c
       [E B   C  ]  +-f3.o
-      [E        ]  | +-f3.c
-      [E        ]  | +-inc.h
-      [E        ]  +-inc.h
+      [E     C  ]  | +-f3.c
+      [E     C  ]  | +-inc.h
+      [E     C  ]  +-inc.h
       [E B   C  ]  +-prog
       [E B   C  ]    +-f1.o
-      [E        ]    | +-f1.c
-      [E        ]    | +-inc.h
+      [E     C  ]    | +-f1.c
+      [E     C  ]    | +-inc.h
       [E B   C  ]    +-f2.o
-      [E        ]    | +-f2.c
-      [E        ]    | +-inc.h
+      [E     C  ]    | +-f2.c
+      [E     C  ]    | +-inc.h
       [E B   C  ]    +-f3.o
-      [E        ]      +-f3.c
-      [E        ]      +-inc.h
+      [E     C  ]      +-f3.c
+      [E     C  ]      +-inc.h
     </screen>
 
     <para>
       cc -o f3.o -c -I. f3.c
       cc -o prog f1.o f2.o f3.o
       +-.
+        +-f1.o
+        +-f2.o
+        +-f3.o
+        +-prog
+          +-f1.o
+          +-f2.o
+          +-f3.o
     </screen>
 
     <para>
       cc -o prog2.o -c -I. prog2.c
       cc -o prog2 prog2.o -L. -lfoo
       +-.
-        +--
         +-SConstruct
         +-f1.c
         +-f1.o
       cc -o prog2.o -c -I. prog2.c
       cc -o prog2 prog2.o -L. -lfoo
       +-.
-        +--
         +-SConstruct
         +-f1.c
         +-f1.o
       scons: *** Source `prog.c' not found, needed by target `prog.o'.  Stop.
       scons: internal stack trace:
         File "bootstrap/src/engine/SCons/Job.py", line 114, in start
+        File "bootstrap/src/engine/SCons/Script/Main.py", line 157, in prepare
         File "bootstrap/src/engine/SCons/Taskmaster.py", line 169, in prepare
-        File "bootstrap/src/engine/SCons/Node/FS.py", line 2220, in prepare
-        File "bootstrap/src/engine/SCons/Node/__init__.py", line 819, in prepare
+        File "bootstrap/src/engine/SCons/Node/FS.py", line 2568, in prepare
+        File "bootstrap/src/engine/SCons/Node/__init__.py", line 349, in prepare
     </screen>
 
     <para>
       % <userinput>scons -Q --taskmastertrace=- prog</userinput>
       Taskmaster: 'prog': children:
           ['prog.o']
-          waiting on unstarted children:
+          waiting on unfinished children:
           ['prog.o']
+      Taskmaster: 'prog.o': children:
+          ['inc.h', 'prog.c']
+          waiting on unfinished children:
+          ['inc.h', 'prog.c']
+      Taskmaster: 'prog.c': evaluating prog.c
+      Taskmaster: 'inc.h': evaluating inc.h
       Taskmaster: 'prog.o': children:
           ['inc.h', 'prog.c']
           evaluating prog.o
           ['prog.o']
           evaluating prog
       cc -o prog prog.o
-      Taskmaster: 'prog': already handled (executed)
     </screen>
 
     <para>
index 8886204a6c622cd3d30455f5a1f0aee7375608ff..c8dc7d3f733b9df94ee8174d2c337d966f043b44 100644 (file)
@@ -8,7 +8,106 @@
 
 
 
-RELEASE 0.XX - XXX
+RELEASE 0.97.0d20071212 - Wed, 12 Dec 2007 09:29:32 -0600
+
+  From Benoit Belley:
+
+  - Fix occasional spurious rebuilds and inefficiency when using
+    --implicit-cache and Builders that produce multiple targets.
+
+  - Allow SCons to not have to know about the builders of generated
+    files when BuildDir(duplicate=0) is used, potentially allowing some
+    SConscript files to be ignored for smaller builds.
+
+  From David Cournapeau:
+
+  - Add a CheckTypeSize() call to configure contexts.
+
+  From Ken Deeter:
+
+  - Make the "contents" of Alias Nodes a concatenation of the children's
+    content signatures (MD5 checksums), not a concatenation of the
+    children's contents, to avoid using large amounts of memory during
+    signature calculation.
+
+  From Malte Helmert:
+
+  - Fix a lot of typos in the man page and User's Guide.
+
+  From Geoffrey Irving:
+
+  - Speed up conversion of paths in .sconsign files to File or Dir Nodes.
+
+  From Steven Knight:
+
+  - Add an Options.UnknownOptions() method that returns any settings
+    (from the command line, or whatever dictionary was passed in)
+    that aren't known to the Options object.
+
+  - Add a Glob() function.
+
+  - When removing targets with the -c option, use the absolute path (to
+    avoid problems interpreting BuildDir() when the top-level directory
+    is the source directory).
+
+  - Fix problems with Install() and InstallAs() when called through a
+    clone (of a clone, ...) of a cloned construction environment.
+
+  - When executing a file containing Options() settings, add the file's
+    directory to sys.path (so modules can be imported from there) and
+    explicity set __name__ to the name of the file so the statement's
+    in the file can deduce the location if they need to.
+
+  - Fix an O(n^2) performance problem when adding sources to a target
+    through calls to a multi Builder (including Aliases).
+
+  - Redefine the $WINDOWSPROGMANIFESTSUFFIX and
+    $WINDOWSSHLIBMANIFESTSUFFIX variables so they pick up changes to
+    the underlying $SHLIBSUFFIX and $PROGSUFFIX variables.
+
+  - Add a GetBuildFailures() function that can be called from functions
+    registered with the Python atexit module to print summary information
+    about any failures encountered while building.
+
+  - Return a NodeList object, not a Python list, when a single_source
+    Builder like Object() is called with more than one file.
+
+  - When searching for implicit dependency files in the directories
+    in a $*PATH list, don't create Dir Nodes for directories that
+    don't actually exist on-disk.
+
+  - Add a Requires() function to allow the specification of order-only
+    prerequisites, which will be updated before specified "downstream"
+    targets but which don't actually cause the target to be rebuilt.
+
+  - Restore the FS.{Dir,File,Entry}.rel_path() method.
+
+  - Make the default behavior of {Source,Target}Signatures('timestamp')
+    be equivalent to 'timestamp-match', not 'timestamp-newer'.
+
+  - Fix use of CacheDir with Decider('timestamp-newer') by updating
+    the modification time when copying files from the cache.
+
+  - Fix random issues with parallel (-j) builds on Windows when Python
+    holds open file handles (especially for SCons temporary files,
+    or targets built by Python function actions) across process creation.
+
+  From Maxim Kartashev:
+
+  - Fix test scripts when run on Solaris.
+
+  From Gary Oberbrunner:
+
+  - Fix Glob() when a pattern is in an explicitly-named subdirectory.
+
+  From Philipp Scholl:
+
+  - Fix setting up targets if multiple Package builders are specified
+    at once.
+
+
+
+RELEASE 0.97.0d20070918 - Tue, 18 Sep 2007 10:51:27 -0500
 
   From Steven Knight:
 
index 23c563847ec17d99af2bbc861a26e705707d99e4..ca01607bffdcd570d9afa4aea8ff162d94972e9c 100644 (file)
@@ -20,11 +20,39 @@ more effectively, please sign up for the scons-users mailing list at:
 
 
 
-RELEASE 0.97.0d200709XX - XXX
+RELEASE 0.97.0d20071212 - Wed, 12 Dec 2007 09:29:32 -0600
 
   This is the eighth beta release of SCons.  Please consult the
   CHANGES.txt file for a list of specific changes since last release.
 
+  Please note the following important changes since release 0.97.0d20070918:
+
+    --  SCons REDEFINES PYTHON open() AND file() ON Windows TO NOT PASS
+        ON OPEN FILE HANDLES TO CREATED PROCESSES
+
+        On Windows systems, SCons now redefines the Python open()
+        and file() functions so that, if the Python Win32 extensions
+        are available, the file handles for any opened files will *not*
+        be inherited by subprocesses, such as the spawned compilers and
+        other tools invoked to build the software.
+
+        This prevents certain race conditions where a file handle for
+        a file opened by Python (either in a Python function action,
+        or directly in a SConscript file) could be inherited and help
+        open by a subprocess, interfering with the ability of other
+        processes to create or modify the file.
+
+        In general, this should not cause problems for the vast majority
+        of configurations.  The only time this would be a problem would be
+        in the unlikely event that a process spawned by SCons specifically
+        *expected* to use an inherited file handle opened by SCons.
+
+        If the Python Win32 extensions are not installed or are an
+        earlier version that does not have the ability to disable file
+        handle inheritance, SCons will print a warning message when the
+        -j option is used.  The warning message may be suppressed by
+        specifying --warn=no-parallel-support.
+
   Please note the following important changes since release 0.97.0d20070809:
 
     --  "content" SIGNATURES ARE NOW THE DEFAULT BEHAVIOR
@@ -433,9 +461,9 @@ RELEASE 0.97.0d200709XX - XXX
 
         You can guarantee that a list will be updated in place regardless
         of which SConscript file defines it and which adds to it by
-        using the list append() method as follows:
+        using the list extend() method as follows:
 
-                obj.append(env.Object('foo.c'))
+                obj.extend(env.Object('foo.c'))
 
   Please note the following important changes since release 0.96.1:
 
index bdedc99b8bd6ab834a4b36aa7db6b18455e2acb2..c2c11581ab376c18116172a04d4706e6b6231825 100644 (file)
@@ -330,7 +330,14 @@ class _ActionAction(ActionBase):
                 os.chdir(chdir)
             try:
                 stat = self.execute(target, source, env)
-                stat = exitstatfunc(stat)
+                if isinstance(stat, SCons.Errors.BuildError):
+                    s = exitstatfunc(stat.status)
+                    if s:
+                        stat.status = s
+                    else:
+                        stat = s
+                else:
+                    stat = exitstatfunc(stat)
             finally:
                 if save_cwd:
                     os.chdir(save_cwd)
@@ -478,7 +485,11 @@ class CommandAction(_ActionAction):
             cmd_line = escape_list(cmd_line, escape)
             result = spawn(shell, escape, cmd_line[0], cmd_line, ENV)
             if not ignore and result:
-                return result
+                msg = "Error %s" % result
+                return SCons.Errors.BuildError(errstr=msg,
+                                               status=result,
+                                               action=self,
+                                               command=cmd_line)
         return 0
 
     def get_contents(self, target, source, env):
@@ -689,9 +700,19 @@ class FunctionAction(_ActionAction):
             # target file will appear).
             try: filename = e.filename
             except AttributeError: filename = None
-            raise SCons.Errors.BuildError(node=target,
-                                          errstr=e.strerror,
-                                          filename=filename)
+            result = SCons.Errors.BuildError(node=target,
+                                             errstr=e.strerror,
+                                             status=1,
+                                             filename=filename,
+                                             action=self,
+                                             command=self.strfunction(target, source, env))
+        else:
+            if result:
+                msg = "Error %s" % result
+                result = SCons.Errors.BuildError(errstr=msg,
+                                                 status=result,
+                                                 action=self,
+                                                 command=self.strfunction(target, source, env))
         return result
 
     def get_contents(self, target, source, env):
@@ -822,8 +843,9 @@ class ActionCaller:
         # was called by using this hard-coded value as a special return.
         if s == '$__env__':
             return env
-        else:
+        elif SCons.Util.is_String(s):
             return env.subst(s, 0, target, source)
+        return self.parent.convert(s)
     def subst_args(self, target, source, env):
         return map(lambda x, self=self, t=target, s=source, e=env:
                           self.subst(x, t, s, e),
@@ -853,9 +875,10 @@ class ActionFactory:
     called with and give them to the ActionCaller object we create,
     so it can hang onto them until it needs them.
     """
-    def __init__(self, actfunc, strfunc):
+    def __init__(self, actfunc, strfunc, convert=lambda x: x):
         self.actfunc = actfunc
         self.strfunc = strfunc
+        self.convert = convert
     def __call__(self, *args, **kw):
         ac = ActionCaller(self, args, kw)
         action = Action(ac, strfunction=ac.strfunction)
index 01d0992297a1667e95a3e43e53d26275a494b643..06030e3bb4a9575b9b9a6a2e8d4b437108a8331f 100644 (file)
@@ -430,7 +430,7 @@ class _ActionActionTestCase(unittest.TestCase):
             sio = StringIO.StringIO()
             sys.stdout = sio
             result = a("out", "in", env)
-            assert result == 7, result
+            assert result.status == 7, result
             s = sio.getvalue()
             assert s == 'execfunc(["out"], ["in"])\n', s
 
@@ -440,14 +440,14 @@ class _ActionActionTestCase(unittest.TestCase):
             sio = StringIO.StringIO()
             sys.stdout = sio
             result = a("out", "in", env)
-            assert result == 7, result
+            assert result.status == 7, result.status
             s = sio.getvalue()
             assert s == expect % (repr('xyz'), repr(test.workpath())), s
 
             sio = StringIO.StringIO()
             sys.stdout = sio
             result = a("out", "in", env, chdir='sub')
-            assert result == 7, result
+            assert result.status == 7, result.status
             s = sio.getvalue()
             assert s == expect % (repr('sub'), repr(test.workpath())), s
 
@@ -456,7 +456,7 @@ class _ActionActionTestCase(unittest.TestCase):
             sio = StringIO.StringIO()
             sys.stdout = sio
             result = b("out", "in", env)
-            assert result == 7, result
+            assert result.status == 7, result.status
             s = sio.getvalue()
             assert s == 'firstfunc(["out"], ["in"])\nexecfunc(["out"], ["in"])\n', s
 
@@ -482,35 +482,35 @@ class _ActionActionTestCase(unittest.TestCase):
             sio = StringIO.StringIO()
             sys.stdout = sio
             result = a("out", "in", env)
-            assert result == 7, result
+            assert result.status == 7, result.status
             s = sio.getvalue()
             assert s == 'Building out with action:\n  execfunc(target, source, env)\nexecfunc(["out"], ["in"])\n', s
 
             sio = StringIO.StringIO()
             sys.stdout = sio
             result = a("out", "in", env, presub=0)
-            assert result == 7, result
+            assert result.status == 7, result.status
             s = sio.getvalue()
             assert s == 'execfunc(["out"], ["in"])\n', s
 
             sio = StringIO.StringIO()
             sys.stdout = sio
             result = a("out", "in", env, presub=1)
-            assert result == 7, result
+            assert result.status == 7, result.status
             s = sio.getvalue()
             assert s == 'Building out with action:\n  execfunc(target, source, env)\nexecfunc(["out"], ["in"])\n', s
 
             sio = StringIO.StringIO()
             sys.stdout = sio
             result = b(["out"], "in", env, presub=1)
-            assert result == 7, result
+            assert result.status == 7, result.status
             s = sio.getvalue()
             assert s == 'Building out with action:\n  firstfunc(target, source, env)\nfirstfunc(["out"], ["in"])\nBuilding out with action:\n  execfunc(target, source, env)\nexecfunc(["out"], ["in"])\n', s
 
             sio = StringIO.StringIO()
             sys.stdout = sio
             result = b(["out", "list"], "in", env, presub=1)
-            assert result == 7, result
+            assert result.status == 7, result.status
             s = sio.getvalue()
             assert s == 'Building out and list with action:\n  firstfunc(target, source, env)\nfirstfunc(["out", "list"], ["in"])\nBuilding out and list with action:\n  execfunc(target, source, env)\nexecfunc(["out", "list"], ["in"])\n', s
 
@@ -519,14 +519,14 @@ class _ActionActionTestCase(unittest.TestCase):
             sio = StringIO.StringIO()
             sys.stdout = sio
             result = a2("out", "in", env)
-            assert result == 7, result
+            assert result.status == 7, result.status
             s = sio.getvalue()
             assert s == 'Building out with action:\n  execfunc(target, source, env)\nexecfunc(["out"], ["in"])\n', s
 
             sio = StringIO.StringIO()
             sys.stdout = sio
             result = a2("out", "in", env, presub=0)
-            assert result == 7, result
+            assert result.status == 7, result.status
             s = sio.getvalue()
             assert s == 'execfunc(["out"], ["in"])\n', s
 
@@ -542,7 +542,7 @@ class _ActionActionTestCase(unittest.TestCase):
             sio = StringIO.StringIO()
             sys.stdout = sio
             result = a("out", "in", env, presub=0, execute=1, show=0)
-            assert result == 7, result
+            assert result.status == 7, result.status
             s = sio.getvalue()
             assert s == '', s
 
@@ -558,7 +558,7 @@ class _ActionActionTestCase(unittest.TestCase):
             assert exitstatfunc_result == [], exitstatfunc_result
 
             result = a("out", "in", env, execute=1, exitstatfunc=exitstatfunc)
-            assert result == 7, result
+            assert result.status == 7, result.status
             assert exitstatfunc_result == [7], exitstatfunc_result
 
             SCons.Action.execute_actions = 1
@@ -709,7 +709,7 @@ class CommandActionTestCase(unittest.TestCase):
             m = 'Invalid command display variable'
             assert string.find(s, m) != -1, 'Unexpected string:  %s' % s
         else:
-            raise "did not catch expected UserError"
+            raise Exception, "did not catch expected UserError"
 
     def test___str__(self):
         """Test fetching the pre-substitution string for command Actions
@@ -1014,26 +1014,26 @@ class CommandActionTestCase(unittest.TestCase):
         # Test that a nonexistent command returns 127
         act = SCons.Action.CommandAction(python + "_no_such_command_")
         r = act([], [], env.Clone(out = outfile))
-        assert r == expect_nonexistent, "r == %d" % r
+        assert r.status == expect_nonexistent, r.status
 
         # Test that trying to execute a directory returns 126
         dir, tail = os.path.split(python)
         act = SCons.Action.CommandAction(dir)
         r = act([], [], env.Clone(out = outfile))
-        assert r == expect_nonexecutable, "r == %d" % r
+        assert r.status == expect_nonexecutable, r.status
 
         # Test that trying to execute a non-executable file returns 126
         act = SCons.Action.CommandAction(outfile)
         r = act([], [], env.Clone(out = outfile))
-        assert r == expect_nonexecutable, "r == %d" % r
+        assert r.status == expect_nonexecutable, r.status
 
         act = SCons.Action.CommandAction('%s %s 1' % (_python_, exit_py))
         r = act([], [], env)
-        assert r == 1, r
+        assert r.status == 1, r.status
 
         act = SCons.Action.CommandAction('@%s %s 1' % (_python_, exit_py))
         r = act([], [], env)
-        assert r == 1, r
+        assert r.status == 1, r.status
 
         act = SCons.Action.CommandAction('@-%s %s 1' % (_python_, exit_py))
         r = act([], [], env)
@@ -1045,7 +1045,7 @@ class CommandActionTestCase(unittest.TestCase):
 
         act = SCons.Action.CommandAction('@ %s %s 1' % (_python_, exit_py))
         r = act([], [], env)
-        assert r == 1, r
+        assert r.status == 1, r.status
 
         act = SCons.Action.CommandAction('@- %s %s 1' % (_python_, exit_py))
         r = act([], [], env)
@@ -1441,13 +1441,10 @@ class FunctionActionTestCase(unittest.TestCase):
             return 1
 
         act = SCons.Action.FunctionAction(function1)
-        r = None
-        try:
-            r = act(target = [outfile, outfile2], source=[], env=Environment())
-        except SCons.Errors.BuildError:
-            pass
-        assert r == 1
-        assert count == 1
+        r = act(target = [outfile, outfile2], source=[], env=Environment())
+        assert r.status == 1, r.status
+
+        assert count == 1, count
         c = test.read(outfile, 'r')
         assert c == "function1\n", c
         c = test.read(outfile2, 'r')
@@ -1459,7 +1456,7 @@ class FunctionActionTestCase(unittest.TestCase):
 
         act = SCons.Action.FunctionAction(class1a)
         r = act([], [], Environment(out = outfile))
-        assert r.__class__ == class1a
+        assert isinstance(r.status, class1a), r.status
         c = test.read(outfile, 'r')
         assert c == "class1a\n", c
 
@@ -1470,7 +1467,7 @@ class FunctionActionTestCase(unittest.TestCase):
 
         act = SCons.Action.FunctionAction(class1b())
         r = act([], [], Environment(out = outfile))
-        assert r == 2
+        assert r.status == 2, r.status
         c = test.read(outfile, 'r')
         assert c == "class1b\n", c
 
@@ -1611,7 +1608,7 @@ class ListActionTestCase(unittest.TestCase):
                 open(env['out'], 'a').write("class2b\n")
         act = SCons.Action.ListAction([cmd2, function2, class2a(), class2b])
         r = act([], [], Environment(out = outfile))
-        assert r.__class__ == class2b
+        assert isinstance(r.status, class2b), r.status
         c = test.read(outfile, 'r')
         assert c == "act.py: 'syzygy'\nfunction2\nclass2a\nclass2b\n", c
 
index 6164a55edf58dcb058c648844c7e891f7f3a861e..4021f2b94d894366d06259399daddf2e1a8c5b4a 100644 (file)
@@ -554,7 +554,7 @@ class BuilderBase:
                 if not tgt is None: tgt = [tgt]
                 if not src is None: src = [src]
                 result.extend(self._execute(env, tgt, src, overwarn))
-            return result
+            return SCons.Node.NodeList(result)
 
         overwarn.warn()
 
index bc4c52ddef0682d566f31482f7fbb7404e3da182..cf13025c5d73f4bd8246beff0c6aea9f934b620e 100644 (file)
@@ -697,14 +697,32 @@ class BuilderTestCase(unittest.TestCase):
                                         single_source = 1, suffix='.out')
         env['CNT'] = [0]
         tgt = builder(env, target=outfiles[0], source=infiles[0])[0]
+        s = str(tgt)
+        assert s == test.workpath('0.out'), s
         tgt.prepare()
         tgt.build()
         assert env['CNT'][0] == 1, env['CNT'][0]
         tgt = builder(env, outfiles[1], infiles[1])[0]
+        s = str(tgt)
+        assert s == test.workpath('1.out'), s
         tgt.prepare()
         tgt.build()
         assert env['CNT'][0] == 2
         tgts = builder(env, None, infiles[2:4])
+        try:
+            [].extend(UserList.UserList())
+        except TypeError:
+            # Old Python version (1.5.2) that can't handle extending
+            # a list with list-like objects.  That means the return
+            # value from the builder call is a real list with Nodes,
+            # and doesn't have a __str__() method that stringifies
+            # the individual elements.  Since we're gong to drop 1.5.2
+            # support anyway, don't bother trying to test for it.
+            pass
+        else:
+            s = str(tgts)
+            expect = str([test.workpath('2.out'), test.workpath('3.out')])
+            assert s == expect, s
         for t in tgts: t.prepare()
         tgts[0].build()
         tgts[1].build()
index e3730a4caddeeaffdfeb68c819a4c1dbdf15f8e6..9b2b4b4b8776574845353d08fd04e31f51386057 100644 (file)
@@ -51,7 +51,7 @@ def CacheRetrieveFunc(target, source, env):
         if fs.islink(cachefile):
             fs.symlink(fs.readlink(cachefile), t.path)
         else:
-            fs.copy2(cachefile, t.path)
+            env.copy_from_cache(cachefile, t.path)
         st = fs.stat(cachefile)
         fs.chmod(t.path, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
     return 0
index bb3be56263522d425018d0e1ae7ef0f47ac46daf..fcf8c5a75bbb714397fad409b4e81fac4e747b5c 100644 (file)
@@ -318,6 +318,102 @@ int main() {
 
     return ret
 
+def CheckTypeSize(context, type_name, header = None, language = None, expect = None):
+    """This check can be used to get the size of a given type, or to check whether
+    the type is of expected size.
+
+    Arguments:
+        - type : str
+            the type to check
+        - includes : sequence
+            list of headers to include in the test code before testing the type
+        - language : str
+            'C' or 'C++'
+        - expect : int
+            if given, will test wether the type has the given number of bytes.
+            If not given, will automatically find the size.
+
+        Returns:
+            status : int
+                0 if the check failed, or the found size of the type if the check succeeded."""
+    
+    # Include "confdefs.h" first, so that the header can use HAVE_HEADER_H.
+    if context.headerfilename:
+        includetext = '#include "%s"' % context.headerfilename
+    else:
+        includetext = ''
+
+    if not header:
+        header = ""
+
+    lang, suffix, msg = _lang2suffix(language)
+    if msg:
+        context.Display("Cannot check for %s type: %s\n" % (type_name, msg))
+        return msg
+
+    src = includetext + header 
+    if not expect is None:
+        # Only check if the given size is the right one
+        context.Display('Checking %s is %d bytes... ' % (type_name, expect))
+
+        # test code taken from autoconf: this is a pretty clever hack to find that
+        # a type is of a given size using only compilation. This speeds things up
+        # quite a bit compared to straightforward code using TryRun
+        src = src + r"""
+typedef %s scons_check_type;
+
+int main()
+{
+    static int test_array[1 - 2 * !(((long int) (sizeof(scons_check_type))) == %d)];
+    test_array[0] = 0;
+
+    return 0;
+}
+"""
+
+        # XXX: Try* vs CompileProg ?
+        st = context.TryCompile(src % (type_name, expect), suffix)
+        if st:
+            _Have(context, "SIZEOF_" + type_name, str(expect))
+            context.Display("yes\n")
+            return expect
+        else:
+            context.Display("no\n")
+            _LogFailed(context, src, st)
+            return 0
+    else:
+        # Only check if the given size is the right one
+        context.Message('Checking size of %s ... ' % type_name)
+
+        # We have to be careful with the program we wish to test here since
+        # compilation will be attempted using the current environment's flags.
+        # So make sure that the program will compile without any warning. For
+        # example using: 'int main(int argc, char** argv)' will fail with the
+        # '-Wall -Werror' flags since the variables argc and argv would not be
+        # used in the program...
+        #
+        src = src + """
+#include <stdlib.h>
+#include <stdio.h>
+int main() {
+    printf("%d", (int)sizeof(""" + type_name + """));
+    return 0;
+}
+    """
+        ret = context.TryRun(src, suffix)
+        st = ret[0]
+        try:
+            size = int(ret[1])
+            _Have(context, "SIZEOF_" + type_name, str(size))
+            context.Display("%d\n" % size)
+        except ValueError:
+            size = 0
+            _LogFailed(context, src, st)
+            context.Display(" Failed !\n")
+        if st:
+            return size
+        else:
+            return 0
 
 def CheckLib(context, libs, func_name = None, header = None,
                  extra_libs = None, call = None, language = None, autoadd = 1):
index 9308051b15fc2b591e7fdedd618ba4b5781625e1..c3d30cb2e20cf9f5fbca4380696a91ebd3af78f2 100644 (file)
@@ -87,11 +87,10 @@ def DefaultEnvironment(*args, **kw):
     if not _default_env:
         import SCons.Util
         _default_env = apply(SCons.Environment.Environment, args, kw)
-        _default_env.TargetSignatures('source')
         if SCons.Util.md5:
-            _default_env.SourceSignatures('MD5')
+            _default_env.Decider('MD5')
         else:
-            _default_env.SourceSignatures('timestamp')
+            _default_env.Decider('timestamp-match')
         global DefaultEnvironment
         DefaultEnvironment = _fetch_DefaultEnvironment
         _default_env._CacheDir = SCons.CacheDir.Null()
@@ -158,7 +157,10 @@ LdModuleLinkAction = SCons.Action.Action("$LDMODULECOM", "$LDMODULECOMSTR")
 # ways by creating ActionFactory instances.
 ActionFactory = SCons.Action.ActionFactory
 
-Chmod = ActionFactory(os.chmod,
+def chmod_func(path, mode):
+    return os.chmod(str(path), mode)
+
+Chmod = ActionFactory(chmod_func,
                       lambda dest, mode: 'Chmod("%s", 0%o)' % (dest, mode))
 
 def copy_func(dest, src):
@@ -172,9 +174,11 @@ def copy_func(dest, src):
         return shutil.copytree(src, dest, 1)
 
 Copy = ActionFactory(copy_func,
-                     lambda dest, src: 'Copy("%s", "%s")' % (dest, src))
+                     lambda dest, src: 'Copy("%s", "%s")' % (dest, src),
+                     convert=str)
 
 def delete_func(entry, must_exist=0):
+    entry = str(entry)
     if not must_exist and not os.path.exists(entry):
         return None
     if not os.path.exists(entry) or os.path.isfile(entry):
@@ -188,12 +192,15 @@ def delete_strfunc(entry, must_exist=0):
 Delete = ActionFactory(delete_func, delete_strfunc)
 
 Mkdir = ActionFactory(os.makedirs,
-                      lambda dir: 'Mkdir("%s")' % dir)
+                      lambda dir: 'Mkdir("%s")' % dir,
+                      convert=str)
 
 Move = ActionFactory(lambda dest, src: os.rename(src, dest),
-                     lambda dest, src: 'Move("%s", "%s")' % (dest, src))
+                     lambda dest, src: 'Move("%s", "%s")' % (dest, src),
+                     convert=str)
 
 def touch_func(file):
+    file = str(file)
     mtime = int(time.time())
     if os.path.exists(file):
         atime = os.path.getatime(file)
index 2f4c34e669fd516681cc614a7fc30ce927b433ed..cf2d0eb3cd72e88e4bd43af17f24f0cd63c2f048 100644 (file)
@@ -216,6 +216,9 @@ class BuilderWrapper(MethodWrapper):
     def __repr__(self):
         return '<BuilderWrapper %s>' % repr(self.name)
 
+    def __str__(self):
+        return self.__repr__()
+
     def __getattr__(self, name):
         if name == 'env':
             return self.object
@@ -259,6 +262,12 @@ class BuilderDict(UserDict):
         return self.__class__(self.data, self.env)
 
     def __setitem__(self, item, val):
+        try:
+            method = getattr(self.env, item).method
+        except AttributeError:
+            pass
+        else:
+            self.env.RemoveMethod(method)
         UserDict.__setitem__(self, item, val)
         BuilderWrapper(self.env, val, item)
 
@@ -773,6 +782,10 @@ def default_decide_target(dependency, target, prev_ni):
     f = SCons.Defaults.DefaultEnvironment().decide_target
     return f(dependency, target, prev_ni)
 
+def default_copy_from_cache(src, dst):
+    f = SCons.Defaults.DefaultEnvironment().copy_from_cache
+    return f(src, dst)
+
 class Base(SubstitutionEnvironment):
     """Base class for "real" construction Environments.  These are the
     primary objects used to communicate dependency and construction
@@ -836,6 +849,8 @@ class Base(SubstitutionEnvironment):
         self.decide_target = default_decide_target
         self.decide_source = default_decide_source
 
+        self.copy_from_cache = default_copy_from_cache
+
         self._dict['BUILDERS'] = BuilderDict(self._dict['BUILDERS'], self)
 
         if platform is None:
@@ -1137,7 +1152,7 @@ class Base(SubstitutionEnvironment):
 
         clone.added_methods = []
         for mw in self.added_methods:
-            mw.clone(clone)
+            clone.added_methods.append(mw.clone(clone))
 
         clone._memo = {}
 
@@ -1185,7 +1200,14 @@ class Base(SubstitutionEnvironment):
     def _changed_timestamp_match(self, dependency, target, prev_ni):
         return dependency.changed_timestamp_match(target, prev_ni)
 
+    def _copy_from_cache(self, src, dst):
+        return self.fs.copy(src, dst)
+
+    def _copy2_from_cache(self, src, dst):
+        return self.fs.copy2(src, dst)
+
     def Decider(self, function):
+        copy_function = self._copy2_from_cache
         if function in ('MD5', 'content'):
             if not SCons.Util.md5:
                 raise UserError, "MD5 signatures are not available in this version of Python."
@@ -1194,6 +1216,7 @@ class Base(SubstitutionEnvironment):
             function = self._changed_timestamp_then_content
         elif function in ('timestamp-newer', 'make'):
             function = self._changed_timestamp_newer
+            copy_function = self._copy_from_cache
         elif function == 'timestamp-match':
             function = self._changed_timestamp_match
         elif not callable(function):
@@ -1205,6 +1228,8 @@ class Base(SubstitutionEnvironment):
         self.decide_target = function
         self.decide_source = function
 
+        self.copy_from_cache = copy_function
+
     def Detect(self, progs):
         """Return the first available program in progs.
         """
@@ -1706,7 +1731,11 @@ class Base(SubstitutionEnvironment):
         """Directly execute an action through an Environment
         """
         action = apply(self.Action, (action,) + args, kw)
-        return action([], [], self)
+        result = action([], [], self)
+        if isinstance(result, SCons.Errors.BuildError):
+            return result.status
+        else:
+            return result
 
     def File(self, name, *args, **kw):
         """
@@ -1728,6 +1757,9 @@ class Base(SubstitutionEnvironment):
         else:
             return result[0]
 
+    def Glob(self, pattern, ondisk=True, source=False, strings=False):
+        return self.fs.Glob(self.subst(pattern), ondisk, source, strings)
+
     def Ignore(self, target, dependency):
         """Ignore a dependency."""
         tlist = self.arg2nodes(target, self.fs.Entry)
@@ -1763,6 +1795,16 @@ class Base(SubstitutionEnvironment):
         dirs = self.arg2nodes(list(dirs), self.fs.Dir)
         apply(self.fs.Repository, dirs, kw)
 
+    def Requires(self, target, prerequisite):
+        """Specify that 'prerequisite' must be built before 'target',
+        (but 'target' does not actually depend on 'prerequisite'
+        and need not be rebuilt if it changes)."""
+        tlist = self.arg2nodes(target, self.fs.Entry)
+        plist = self.arg2nodes(prerequisite, self.fs.Entry)
+        for t in tlist:
+            t.add_prerequisite(plist)
+        return tlist
+
     def Scanner(self, *args, **kw):
         nargs = []
         for arg in args:
@@ -1810,7 +1852,7 @@ class Base(SubstitutionEnvironment):
                 raise UserError, "MD5 signatures are not available in this version of Python."
             self.decide_source = self._changed_content
         elif type == 'timestamp':
-            self.decide_source = self._changed_timestamp_newer
+            self.decide_source = self._changed_timestamp_match
         else:
             raise UserError, "Unknown source signature type '%s'" % type
 
@@ -1840,7 +1882,7 @@ class Base(SubstitutionEnvironment):
                 raise UserError, "MD5 signatures are not available in this version of Python."
             self.decide_target = self._changed_content
         elif type == 'timestamp':
-            self.decide_target = self._changed_timestamp_newer
+            self.decide_target = self._changed_timestamp_match
         elif type == 'build':
             self.decide_target = self._changed_build
         elif type == 'source':
index 630f5948ef4b6e9bb4f5cdacccdb5b9bf5ff8a5d..3f64d43cf855316b2aa40356f94813814cd3c73c 100644 (file)
@@ -674,6 +674,21 @@ sys.exit(1)
         r = env.func('-yyy')
         assert r == 'func2-foo-yyy', r
 
+        # Test that clones of clones correctly re-bind added methods.
+        env1 = Environment(FOO = '1')
+        env1.AddMethod(func2)
+        env2 = env1.Clone(FOO = '2')
+        env3 = env2.Clone(FOO = '3')
+        env4 = env3.Clone(FOO = '4')
+        r = env1.func2()
+        assert r == 'func2-1', r
+        r = env2.func2()
+        assert r == 'func2-2', r
+        r = env3.func2()
+        assert r == 'func2-3', r
+        r = env4.func2()
+        assert r == 'func2-4', r
+
     def test_Override(self):
         "Test overriding construction variables"
         env = SubstitutionEnvironment(ONE=1, TWO=2, THREE=3, FOUR=4)
index 3ee7ff4242d5cd2f9f27493ccbf563bd71445c27..fc55cf45aaa1128a23b20ce63925579821ccec8e 100644 (file)
@@ -33,10 +33,16 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
 
 class BuildError(Exception):
-    def __init__(self, node=None, errstr="Unknown error", filename=None, *args):
+    def __init__(self, node=None, errstr="Unknown error", status=0,
+                       filename=None, executor=None, action=None, command=None,
+                       *args):
         self.node = node
         self.errstr = errstr
+        self.status = status
         self.filename = filename
+        self.executor = executor
+        self.action = action
+        self.command = command
         apply(Exception.__init__, (self,) + args)
 
 class InternalError(Exception):
index 88a46cca9bde853a7ec83aa1730b72b6cc09942d..1cb0cf977248cbbd5cd6474c7266b783257c286c 100644 (file)
@@ -33,6 +33,7 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 import string
 
 from SCons.Debug import logInstanceCreation
+import SCons.Errors
 import SCons.Memoize
 
 
@@ -59,6 +60,7 @@ class Executor:
         self.overridelist = overridelist
         self.targets = targets
         self.sources = sources[:]
+        self.sources_need_sorting = False
         self.builder_kw = builder_kw
         self._memo = {}
 
@@ -110,7 +112,7 @@ class Executor:
             cwd = self.targets[0].cwd
         except (IndexError, AttributeError):
             cwd = None
-        return scanner.path(env, cwd, self.targets, self.sources)
+        return scanner.path(env, cwd, self.targets, self.get_sources())
 
     def get_kw(self, kw={}):
         result = self.builder_kw.copy()
@@ -126,9 +128,13 @@ class Executor:
         kw = self.get_kw(kw)
         status = 0
         for act in self.get_action_list():
-            status = apply(act, (self.targets, self.sources, env), kw)
-            if status:
-                break
+            status = apply(act, (self.targets, self.get_sources(), env), kw)
+            if isinstance(status, SCons.Errors.BuildError):
+                status.executor = self
+                raise status
+            elif status:
+                msg = "Error %s" % status
+                raise SCons.Errors.BuildError(errstr=msg, executor=self, action=act)
         return status
 
     # use extra indirection because with new-style objects (Python 2.2
@@ -145,8 +151,14 @@ class Executor:
         """Add source files to this Executor's list.  This is necessary
         for "multi" Builders that can be called repeatedly to build up
         a source file list for a given target."""
-        slist = filter(lambda x, s=self.sources: x not in s, sources)
-        self.sources.extend(slist)
+        self.sources.extend(sources)
+        self.sources_need_sorting = True
+
+    def get_sources(self):
+        if self.sources_need_sorting:
+            self.sources = SCons.Util.uniquer_hashables(self.sources)
+            self.sources_need_sorting = False
+        return self.sources
 
     def add_pre_action(self, action):
         self.pre_actions.append(action)
@@ -158,7 +170,7 @@ class Executor:
 
     def my_str(self):
         env = self.get_build_env()
-        get = lambda action, t=self.targets, s=self.sources, e=env: \
+        get = lambda action, t=self.targets, s=self.get_sources(), e=env: \
                      action.genstring(t, s, e)
         return string.join(map(get, self.get_action_list()), "\n")
 
@@ -183,7 +195,7 @@ class Executor:
         except KeyError:
             pass
         env = self.get_build_env()
-        get = lambda action, t=self.targets, s=self.sources, e=env: \
+        get = lambda action, t=self.targets, s=self.get_sources(), e=env: \
                      action.get_contents(t, s, e)
         result = string.join(map(get, self.get_action_list()), "")
         self._memo['get_contents'] = result
@@ -201,7 +213,7 @@ class Executor:
 
     def scan_sources(self, scanner):
         if self.sources:
-            self.scan(scanner, self.sources)
+            self.scan(scanner, self.get_sources())
 
     def scan(self, scanner, node_list):
         """Scan a list of this Executor's files (targets or sources) for
@@ -241,7 +253,7 @@ class Executor:
     def get_missing_sources(self):
         """
         """
-        return filter(lambda s: s.missing(), self.sources)
+        return filter(lambda s: s.missing(), self.get_sources())
 
     def _get_unignored_sources_key(self, ignore=()):
         return tuple(ignore)
@@ -261,9 +273,12 @@ class Executor:
             except KeyError:
                 pass
 
-        sourcelist = self.sources
+        sourcelist = self.get_sources()
         if ignore:
-            sourcelist = filter(lambda s, i=ignore: not s in i, sourcelist)
+            idict = {}
+            for i in ignore:
+                idict[i] = 1
+            sourcelist = filter(lambda s, i=idict: not i.has_key(s), sourcelist)
 
         memo_dict[ignore] = sourcelist
 
@@ -299,7 +314,7 @@ class Executor:
         result = []
         build_env = self.get_build_env()
         for act in self.get_action_list():
-            result.extend(act.get_implicit_deps(self.targets, self.sources, build_env))
+            result.extend(act.get_implicit_deps(self.targets, self.get_sources(), build_env))
         return result
 
 
index 59deca571346ac7ec84845f2dca30f4be47adb72..368e034b673a43c0c4e2c2542522ca886d276da1 100644 (file)
@@ -236,7 +236,12 @@ class ExecutorTestCase(unittest.TestCase):
         x = SCons.Executor.Executor(a, env, [], t, ['s1', 's2'])
         x.add_pre_action(pre_err)
         x.add_post_action(post)
-        x(t)
+        try:
+            x(t)
+        except SCons.Errors.BuildError:
+            pass
+        else:
+            raise Exception, "Did not catch expected BuildError"
         assert result == ['pre_err'], result
         del result[:]
 
@@ -265,8 +270,19 @@ class ExecutorTestCase(unittest.TestCase):
         x = SCons.Executor.Executor('b', 'e', 'o', 't', ['s1', 's2'])
         assert x.sources == ['s1', 's2'], x.sources
         x.add_sources(['s1', 's2'])
+        assert x.sources == ['s1', 's2', 's1', 's2'], x.sources
+        x.add_sources(['s3', 's1', 's4'])
+        assert x.sources == ['s1', 's2', 's1', 's2', 's3', 's1', 's4'], x.sources
+
+    def test_get_sources(self):
+        """Test getting sources from an Executor"""
+        x = SCons.Executor.Executor('b', 'e', 'o', 't', ['s1', 's2'])
+        assert x.sources == ['s1', 's2'], x.sources
+        x.add_sources(['s1', 's2'])
+        x.get_sources()
         assert x.sources == ['s1', 's2'], x.sources
         x.add_sources(['s3', 's1', 's4'])
+        x.get_sources()
         assert x.sources == ['s1', 's2', 's3', 's4'], x.sources
 
     def test_add_pre_action(self):
index 15de6644e176b7b9c214b196bdfa8b8e53a5d459..bb23d3f670762ffc92da6ad6374ac180d5e50ad0 100644 (file)
@@ -91,9 +91,9 @@ class Alias(SCons.Node.Node):
 
     def get_contents(self):
         """The contents of an alias is the concatenation
-        of all the contents of its sources"""
-        contents = map(lambda n: n.get_contents(), self.children())
-        return string.join(contents, '')
+        of the content signatures of all its sources."""
+        childsigs = map(lambda n: n.get_csig(), self.children())
+        return string.join(childsigs, '')
 
     def sconsign(self):
         """An Alias is not recorded in .sconsign files"""
@@ -133,7 +133,7 @@ class Alias(SCons.Node.Node):
             return self.ninfo.csig
         except AttributeError:
             pass
-           
+
         contents = self.get_contents()
         csig = SCons.Util.MD5signature(contents)
         self.get_ninfo().csig = csig
index 755cf7503ce53f5e0e3afc3ed8d4c0e5fdb269b2..02488f00bf0a38d31d11aaf4d19854d1793e60a7 100644 (file)
@@ -54,6 +54,8 @@ class AliasTestCase(unittest.TestCase):
         class DummyNode:
             def __init__(self, contents):
                 self.contents = contents
+            def get_csig(self):
+                return self.contents
             def get_contents(self):
                 return self.contents
 
index 964af62dddaffd5eb81c0176745bb9e8a39c760b..d0843d1fddea33c7ec3555d774ea61918269b2f2 100644 (file)
@@ -35,8 +35,10 @@ that can be used by scripts or modules looking for the canonical default.
 
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
+import fnmatch
 import os
 import os.path
+import re
 import shutil
 import stat
 import string
@@ -84,6 +86,42 @@ def save_strings(val):
     global Save_Strings
     Save_Strings = val
 
+#
+# Avoid unnecessary function calls by recording a Boolean value that
+# tells us whether or not os.path.splitdrive() actually does anything
+# on this system, and therefore whether we need to bother calling it
+# when looking up path names in various methods below.
+# 
+
+do_splitdrive = None
+
+def initialize_do_splitdrive():
+    global do_splitdrive
+    drive, path = os.path.splitdrive('X:/foo')
+    do_splitdrive = not not drive
+
+initialize_do_splitdrive()
+
+#
+
+needs_normpath_check = None
+
+def initialize_normpath_check():
+    """
+    Initialize the normpath_check regular expression.
+
+    This function is used by the unit tests to re-initialize the pattern
+    when testing for behavior with different values of os.sep.
+    """
+    global needs_normpath_check
+    if os.sep == '/':
+        pattern = r'.*/|\.$|\.\.$'
+    else:
+        pattern = r'.*[/%s]|\.$|\.\.$' % re.escape(os.sep)
+    needs_normpath_check = re.compile(pattern)
+
+initialize_normpath_check()
+
 #
 # SCons.Action objects for interacting with the outside world.
 #
@@ -544,9 +582,29 @@ class Base(SCons.Node.Node):
         return result
 
     def _get_str(self):
+        global Save_Strings
         if self.duplicate or self.is_derived():
             return self.get_path()
-        return self.srcnode().get_path()
+        srcnode = self.srcnode()
+        if srcnode.stat() is None and not self.stat() is None:
+            result = self.get_path()
+        else:
+            result = srcnode.get_path()
+        if not Save_Strings:
+            # We're not at the point where we're saving the string string
+            # representations of FS Nodes (because we haven't finished
+            # reading the SConscript files and need to have str() return
+            # things relative to them).  That also means we can't yet
+            # cache values returned (or not returned) by stat(), since
+            # Python code in the SConscript files might still create
+            # or otherwise affect the on-disk file.  So get rid of the
+            # values that the underlying stat() method saved.
+            try: del self._memo['stat']
+            except KeyError: pass
+            if not self is srcnode:
+                try: del srcnode._memo['stat']
+                except KeyError: pass
+        return result
 
     rstr = __str__
 
@@ -607,15 +665,11 @@ class Base(SCons.Node.Node):
         corresponding to its source file.  Otherwise, return
         ourself.
         """
-        dir=self.dir
-        name=self.name
-        while dir:
-            if dir.srcdir:
-                srcnode = dir.srcdir.Entry(name)
-                srcnode.must_be_same(self.__class__)
-                return srcnode
-            name = dir.name + os.sep + name
-            dir = dir.up()
+        srcdir_list = self.dir.srcdir_list()
+        if srcdir_list:
+            srcnode = srcdir_list[0].Entry(self.name)
+            srcnode.must_be_same(self.__class__)
+            return srcnode
         return self
 
     def get_path(self, dir=None):
@@ -673,7 +727,7 @@ class Base(SCons.Node.Node):
     def target_from_source(self, prefix, suffix, splitext=SCons.Util.splitext):
         """
 
-       Generates a target entry that corresponds to this entry (usually
+        Generates a target entry that corresponds to this entry (usually
         a source file) with the specified prefix and suffix.
 
         Note that this method can be overridden dynamically for generated
@@ -745,6 +799,9 @@ class Base(SCons.Node.Node):
         self._memo['rentry'] = result
         return result
 
+    def _glob1(self, pattern, ondisk=True, source=False, strings=False):
+        return []
+
 class Entry(Base):
     """This is the class for generic Node.FS entries--that is, things
     that could be a File or a Dir, but we're just not sure yet.
@@ -845,11 +902,11 @@ class Entry(Base):
         directory."""
         return self.disambiguate().exists()
 
-#    def rel_path(self, other):
-#        d = self.disambiguate()
-#        if d.__class__ == Entry:
-#            raise "rel_path() could not disambiguate File/Dir"
-#        return d.rel_path(other)
+    def rel_path(self, other):
+        d = self.disambiguate()
+        if d.__class__ == Entry:
+            raise "rel_path() could not disambiguate File/Dir"
+        return d.rel_path(other)
 
     def new_ninfo(self):
         return self.disambiguate().new_ninfo()
@@ -857,6 +914,9 @@ class Entry(Base):
     def changed_since_last_build(self, target, prev_ni):
         return self.disambiguate().changed_since_last_build(target, prev_ni)
 
+    def _glob1(self, pattern, ondisk=True, source=False, strings=False):
+        return self.disambiguate()._glob1(pattern, ondisk, source, strings)
+
 # This is for later so we can differentiate between Entry the class and Entry
 # the method of the FS class.
 _classEntry = Entry
@@ -885,6 +945,8 @@ class LocalFS:
     #    return os.chdir(path)
     def chmod(self, path, mode):
         return os.chmod(path, mode)
+    def copy(self, src, dst):
+        return shutil.copy(src, dst)
     def copy2(self, src, dst):
         return shutil.copy2(src, dst)
     def exists(self, path):
@@ -975,8 +1037,8 @@ class FS(LocalFS):
         self.Top.tpath = '.'
         self._cwd = self.Top
 
-        DirNodeInfo.top = self.Top
-        FileNodeInfo.top = self.Top
+        DirNodeInfo.fs = self
+        FileNodeInfo.fs = self
     
     def set_SConstruct_dir(self, dir):
         self.SConstruct_dir = dir
@@ -1028,11 +1090,16 @@ class FS(LocalFS):
 
         This translates arbitrary input into a canonical Node.FS object
         of the specified fsclass.  The general approach for strings is
-        to turn it into a normalized absolute path and then call the
-        root directory's lookup_abs() method for the heavy lifting.
+        to turn it into a fully normalized absolute path and then call
+        the root directory's lookup_abs() method for the heavy lifting.
 
         If the path name begins with '#', it is unconditionally
-        interpreted relative to the top-level directory of this FS.
+        interpreted relative to the top-level directory of this FS.  '#'
+        is treated as a synonym for the top-level SConstruct directory,
+        much like '~' is treated as a synonym for the user's home
+        directory in a UNIX shell.  So both '#foo' and '#/foo' refer
+        to the 'foo' subdirectory underneath the top-level SConstruct
+        directory.
 
         If the path name is relative, then the path is looked up relative
         to the specified directory, or the current directory (self._cwd,
@@ -1046,33 +1113,53 @@ class FS(LocalFS):
             return p
         # str(p) in case it's something like a proxy object
         p = str(p)
-        drive, p = os.path.splitdrive(p)
+
+        initial_hash = (p[0:1] == '#')
+        if initial_hash:
+            # There was an initial '#', so we strip it and override
+            # whatever directory they may have specified with the
+            # top-level SConstruct directory.
+            p = p[1:]
+            directory = self.Top
+
+        if directory and not isinstance(directory, Dir):
+            directory = self.Dir(directory)
+
+        if do_splitdrive:
+            drive, p = os.path.splitdrive(p)
+        else:
+            drive = ''
         if drive and not p:
-            # A drive letter without a path...
+            # This causes a naked drive letter to be treated as a synonym
+            # for the root directory on that drive.
             p = os.sep
-            root = self.get_root(drive)
-        elif os.path.isabs(p):
-            # An absolute path...
+        absolute = os.path.isabs(p)
+
+        needs_normpath = needs_normpath_check.match(p)
+
+        if initial_hash or not absolute:
+            # This is a relative lookup, either to the top-level
+            # SConstruct directory (because of the initial '#') or to
+            # the current directory (the path name is not absolute).
+            # Add the string to the appropriate directory lookup path,
+            # after which the whole thing gets normalized.
+            if not directory:
+                directory = self._cwd
+            if p:
+                p = directory.labspath + '/' + p
+            else:
+                p = directory.labspath
+
+        if needs_normpath:
             p = os.path.normpath(p)
+
+        if drive or absolute:
             root = self.get_root(drive)
         else:
-            if p[0:1] == '#':
-                # A top-relative path...
-                directory = self.Top
-                offset = 1
-                if p[1:2] in(os.sep, '/'):
-                    offset = 2
-                p = p[offset:]
-            else:
-                # A relative path...
-                if not directory:
-                    # ...to the current (SConscript) directory.
-                    directory = self._cwd
-                elif not isinstance(directory, Dir):
-                    # ...to the specified directory.
-                    directory = self.Dir(directory)
-            p = os.path.normpath(directory.labspath + '/' + p)
+            if not directory:
+                directory = self._cwd
             root = directory.root
+
         if os.sep != '/':
             p = string.replace(p, os.sep, '/')
         return root._lookup_abs(p, fsclass, create)
@@ -1098,7 +1185,7 @@ class FS(LocalFS):
         """
         return self._lookup(name, directory, File, create)
 
-    def Dir(self, name, directory = None, create = 1):
+    def Dir(self, name, directory = None, create = True):
         """Lookup or create a Dir node with the specified name.  If
         the name is a relative path (begins with ./, ../, or a file name),
         then it is looked up relative to the supplied directory node,
@@ -1160,24 +1247,41 @@ class FS(LocalFS):
             message = fmt % string.join(map(str, targets))
         return targets, message
 
+    def Glob(self, pathname, ondisk=True, source=True, strings=False, cwd=None):
+        """
+        Globs
+
+        This is mainly a shim layer 
+        """
+        if cwd is None:
+            cwd = self.getcwd()
+        return cwd.glob(pathname, ondisk, source, strings)
+
 class DirNodeInfo(SCons.Node.NodeInfoBase):
     # This should get reset by the FS initialization.
     current_version_id = 1
 
-    top = None
+    fs = None
 
     def str_to_node(self, s):
-        top = self.top
-        if os.path.isabs(s):
-            n = top.fs._lookup(s, top, Entry)
-        else:
+        top = self.fs.Top
+        root = top.root
+        if do_splitdrive:
+            drive, s = os.path.splitdrive(s)
+            if drive:
+                root = self.fs.get_root(drive)
+        if not os.path.isabs(s):
             s = top.labspath + '/' + s
-            n = top.root._lookup_abs(s, Entry)
-        return n
+        return root._lookup_abs(s, Entry)
 
 class DirBuildInfo(SCons.Node.BuildInfoBase):
     current_version_id = 1
 
+glob_magic_check = re.compile('[*?[]')
+
+def has_glob_magic(s):
+    return glob_magic_check.search(s) is not None
+
 class Dir(Base):
     """A class for directories in a file system.
     """
@@ -1252,12 +1356,12 @@ class Dir(Base):
         """
         return self.fs.Entry(name, self)
 
-    def Dir(self, name):
+    def Dir(self, name, create=True):
         """
         Looks up or creates a directory node named 'name' relative to
         this directory.
         """
-        dir = self.fs.Dir(name, self)
+        dir = self.fs.Dir(name, self, create)
         return dir
 
     def File(self, name):
@@ -1313,7 +1417,10 @@ class Dir(Base):
         while dir:
             for rep in dir.getRepositories():
                 result.append(rep.Dir(fname))
-            fname = dir.name + os.sep + fname
+            if fname == '.':
+                fname = dir.name
+            else:
+                fname = dir.name + os.sep + fname
             dir = dir.up()
 
         self._memo['get_all_rdirs'] = result
@@ -1329,66 +1436,68 @@ class Dir(Base):
     def up(self):
         return self.entries['..']
 
-# This complicated method, which constructs relative paths between
-# arbitrary Node.FS objects, is no longer used.  It was introduced to
-# store dependency paths in .sconsign files relative to the target, but
-# that ended up being significantly inefficient.  We're leaving the code
-# here, commented out, because it would be too easy for someone to decide
-# to re-invent this wheel in the future (if it becomes necessary) because
-# they didn't know this code was buried in some source-code change from
-# the distant past...
-#
-#    def _rel_path_key(self, other):
-#        return str(other)
-#
-#    memoizer_counters.append(SCons.Memoize.CountDict('rel_path', _rel_path_key))
-#
-#    def rel_path(self, other):
-#        """Return a path to "other" relative to this directory.
-#        """
-#        try:
-#            memo_dict = self._memo['rel_path']
-#        except KeyError:
-#            memo_dict = {}
-#            self._memo['rel_path'] = memo_dict
-#        else:
-#            try:
-#                return memo_dict[other]
-#            except KeyError:
-#                pass
-#
-#        if self is other:
-#
-#            result = '.'
-#
-#        elif not other in self.path_elements:
-#
-#            try:
-#                other_dir = other.get_dir()
-#            except AttributeError:
-#                result = str(other)
-#            else:
-#                if other_dir is None:
-#                    result = other.name
-#                else:
-#                    dir_rel_path = self.rel_path(other_dir)
-#                    if dir_rel_path == '.':
-#                        result = other.name
-#                    else:
-#                        result = dir_rel_path + os.sep + other.name
-#
-#        else:
-#
-#            i = self.path_elements.index(other) + 1
-#
-#            path_elems = ['..'] * (len(self.path_elements) - i) \
-#                         + map(lambda n: n.name, other.path_elements[i:])
-#             
-#            result = string.join(path_elems, os.sep)
-#
-#        memo_dict[other] = result
-#
-#        return result
+    def _rel_path_key(self, other):
+        return str(other)
+
+    memoizer_counters.append(SCons.Memoize.CountDict('rel_path', _rel_path_key))
+
+    def rel_path(self, other):
+        """Return a path to "other" relative to this directory.
+        """
+
+       # This complicated and expensive method, which constructs relative
+       # paths between arbitrary Node.FS objects, is no longer used
+       # by SCons itself.  It was introduced to store dependency paths
+       # in .sconsign files relative to the target, but that ended up
+       # being significantly inefficient.
+        #
+       # We're continuing to support the method because some SConstruct
+       # files out there started using it when it was available, and
+       # we're all about backwards compatibility..
+
+        try:
+            memo_dict = self._memo['rel_path']
+        except KeyError:
+            memo_dict = {}
+            self._memo['rel_path'] = memo_dict
+        else:
+            try:
+                return memo_dict[other]
+            except KeyError:
+                pass
+
+        if self is other:
+
+            result = '.'
+
+        elif not other in self.path_elements:
+
+            try:
+                other_dir = other.get_dir()
+            except AttributeError:
+                result = str(other)
+            else:
+                if other_dir is None:
+                    result = other.name
+                else:
+                    dir_rel_path = self.rel_path(other_dir)
+                    if dir_rel_path == '.':
+                        result = other.name
+                    else:
+                        result = dir_rel_path + os.sep + other.name
+
+        else:
+
+            i = self.path_elements.index(other) + 1
+
+            path_elems = ['..'] * (len(self.path_elements) - i) \
+                         + map(lambda n: n.name, other.path_elements[i:])
+             
+            result = string.join(path_elems, os.sep)
+
+        memo_dict[other] = result
+
+        return result
 
     def get_env_scanner(self, env, kw={}):
         import SCons.Defaults
@@ -1575,13 +1684,7 @@ class Dir(Base):
         dir = self
         while dir:
             if dir.srcdir:
-                d = dir.srcdir.Dir(dirname)
-                if d.is_under(dir):
-                    # Shouldn't source from something in the build path:
-                    # build_dir is probably under src_dir, in which case
-                    # we are reflecting.
-                    break
-                result.append(d)
+                result.append(dir.srcdir.Dir(dirname))
             dirname = dir.name + os.sep + dirname
             dir = dir.up()
 
@@ -1591,6 +1694,11 @@ class Dir(Base):
 
     def srcdir_duplicate(self, name):
         for dir in self.srcdir_list():
+            if self.is_under(dir):
+                # We shouldn't source from something in the build path;
+                # build_dir is probably under src_dir, in which case
+                # we are reflecting.
+                break
             if dir.entry_exists_on_disk(name):
                 srcnode = dir.Entry(name).disambiguate()
                 if self.duplicate:
@@ -1693,6 +1801,118 @@ class Dir(Base):
         for dirname in filter(select_dirs, names):
             entries[dirname].walk(func, arg)
 
+    def glob(self, pathname, ondisk=True, source=False, strings=False):
+        """
+        Returns a list of Nodes (or strings) matching a specified
+        pathname pattern.
+
+        Pathname patterns follow UNIX shell semantics:  * matches
+        any-length strings of any characters, ? matches any character,
+        and [] can enclose lists or ranges of characters.  Matches do
+        not span directory separators.
+
+        The matches take into account Repositories, returning local
+        Nodes if a corresponding entry exists in a Repository (either
+        an in-memory Node or something on disk).
+
+        By defafult, the glob() function matches entries that exist
+        on-disk, in addition to in-memory Nodes.  Setting the "ondisk"
+        argument to False (or some other non-true value) causes the glob()
+        function to only match in-memory Nodes.  The default behavior is
+        to return both the on-disk and in-memory Nodes.
+
+        The "source" argument, when true, specifies that corresponding
+        source Nodes must be returned if you're globbing in a build
+        directory (initialized with BuildDir()).  The default behavior
+        is to return Nodes local to the BuildDir().
+
+        The "strings" argument, when true, returns the matches as strings,
+        not Nodes.  The strings are path names relative to this directory.
+
+        The underlying algorithm is adapted from the glob.glob() function
+        in the Python library (but heavily modified), and uses fnmatch()
+        under the covers.
+        """
+        dirname, basename = os.path.split(pathname)
+        if not dirname:
+            return self._glob1(basename, ondisk, source, strings)
+        if has_glob_magic(dirname):
+            list = self.glob(dirname, ondisk, source, strings=False)
+        else:
+            list = [self.Dir(dirname, create=True)]
+        result = []
+        for dir in list:
+            r = dir._glob1(basename, ondisk, source, strings)
+            if strings:
+                r = map(lambda x, d=str(dir): os.path.join(d, x), r)
+            result.extend(r)
+        return result
+
+    def _glob1(self, pattern, ondisk=True, source=False, strings=False):
+        """
+        Globs for and returns a list of entry names matching a single
+        pattern in this directory.
+
+        This searches any repositories and source directories for
+        corresponding entries and returns a Node (or string) relative
+        to the current directory if an entry is found anywhere.
+
+        TODO: handle pattern with no wildcard
+        """
+        search_dir_list = self.get_all_rdirs()
+        for srcdir in self.srcdir_list():
+            search_dir_list.extend(srcdir.get_all_rdirs())
+
+        names = []
+        for dir in search_dir_list:
+            # We use the .name attribute from the Node because the keys of
+            # the dir.entries dictionary are normalized (that is, all upper
+            # case) on case-insensitive systems like Windows.
+            #node_names = [ v.name for k, v in dir.entries.items() if k not in ('.', '..') ]
+            entry_names = filter(lambda n: n not in ('.', '..'), dir.entries.keys())
+            node_names = map(lambda n, e=dir.entries: e[n].name, entry_names)
+            names.extend(node_names)
+            if ondisk:
+                try:
+                    disk_names = os.listdir(dir.abspath)
+                except os.error:
+                    pass
+                else:
+                    names.extend(disk_names)
+                    if not strings:
+                        # We're going to return corresponding Nodes in
+                        # the local directory, so we need to make sure
+                        # those Nodes exist.  We only want to create
+                        # Nodes for the entries that will match the
+                        # specified pattern, though, which means we
+                        # need to filter the list here, even though
+                        # the overall list will also be filtered later,
+                        # after we exit this loop.
+                        if pattern[0] != '.':
+                            #disk_names = [ d for d in disk_names if d[0] != '.' ]
+                            disk_names = filter(lambda x: x[0] != '.', disk_names)
+                        disk_names = fnmatch.filter(disk_names, pattern)
+                        rep_nodes = map(dir.Entry, disk_names)
+                        #rep_nodes = [ n.disambiguate() for n in rep_nodes ]
+                        rep_nodes = map(lambda n: n.disambiguate(), rep_nodes)
+                        for node, name in zip(rep_nodes, disk_names):
+                            n = self.Entry(name)
+                            if n.__class__ != node.__class__:
+                                n.__class__ = node.__class__
+                                n._morph()
+
+        names = set(names)
+        if pattern[0] != '.':
+            #names = [ n for n in names if n[0] != '.' ]
+            names = filter(lambda x: x[0] != '.', names)
+        names = fnmatch.filter(names, pattern)
+
+        if strings:
+            return names
+
+        #return [ self.entries[_my_normcase(n)] for n in names ]
+        return map(lambda n, e=self.entries:  e[_my_normcase(n)], names)
+
 class RootDir(Dir):
     """A class for the root directory of a file system.
 
@@ -1817,16 +2037,18 @@ class FileNodeInfo(SCons.Node.NodeInfoBase):
     field_list = ['csig', 'timestamp', 'size']
 
     # This should get reset by the FS initialization.
-    top = None
+    fs = None
 
     def str_to_node(self, s):
-        top = self.top
-        if os.path.isabs(s):
-            n = top.fs._lookup(s, top, Entry)
-        else:
+        top = self.fs.Top
+        root = top.root
+        if do_splitdrive:
+            drive, s = os.path.splitdrive(s)
+            if drive:
+                root = self.fs.get_root(drive)
+        if not os.path.isabs(s):
             s = top.labspath + '/' + s
-            n = top.root._lookup_abs(s, Entry)
-        return n
+        return root._lookup_abs(s, Entry)
 
 class FileBuildInfo(SCons.Node.BuildInfoBase):
     current_version_id = 1
@@ -1925,10 +2147,10 @@ class File(Base):
         the SConscript directory of this file."""
         return self.cwd.Entry(name)
 
-    def Dir(self, name):
+    def Dir(self, name, create=True):
         """Create a directory node named 'name' relative to
         the SConscript directory of this file."""
-        return self.cwd.Dir(name)
+        return self.cwd.Dir(name, create)
 
     def Dirs(self, pathlist):
         """Create a list of directories relative to the SConscript
@@ -2171,8 +2393,8 @@ class File(Base):
         try: return binfo.bimplicit
         except AttributeError: return None
 
-#    def rel_path(self, other):
-#        return self.dir.rel_path(other)
+    def rel_path(self, other):
+        return self.dir.rel_path(other)
 
     def _get_found_includes_key(self, env, scanner, path):
         return (id(env), id(scanner), path)
@@ -2329,7 +2551,9 @@ class File(Base):
 
     def _rmv_existing(self):
         self.clear_memoized_values()
-        Unlink(self, [], None)
+        e = Unlink(self, [], None)
+        if isinstance(e, SCons.Errors.BuildError):
+            raise e
 
     #
     # Taskmaster interface subsystem
@@ -2367,13 +2591,9 @@ class File(Base):
 
     def do_duplicate(self, src):
         self._createDir()
-        try:
-            Unlink(self, None, None)
-        except SCons.Errors.BuildError:
-            pass
-        try:
-            Link(self, src, None)
-        except SCons.Errors.BuildError, e:
+        Unlink(self, None, None)
+        e = Link(self, src, None)
+        if isinstance(e, SCons.Errors.BuildError):
             desc = "Cannot duplicate `%s' in `%s': %s." % (src.path, self.dir.path, e.errstr)
             raise SCons.Errors.StopError, desc
         self.linked = 1
@@ -2440,7 +2660,7 @@ class File(Base):
             # which can be the case if they've disabled disk checks,
             # or if an action with a File target actually happens to
             # create a same-named directory by mistake.
-            csig = None
+            csig = ''
         else:
             csig = SCons.Util.MD5signature(contents)
 
@@ -2511,7 +2731,9 @@ class File(Base):
                     # ...and it's even up-to-date...
                     if self._local:
                         # ...and they'd like a local copy.
-                        LocalCopy(self, r, None)
+                        e = LocalCopy(self, r, None)
+                        if isinstance(e, SCons.Errors.BuildError):
+                            raise 
                         self.store_info()
                     if T: Trace(' 1\n')
                     return 1
@@ -2610,6 +2832,39 @@ class FileFinder:
     def __init__(self):
         self._memo = {}
 
+    def filedir_lookup(self, p, fd=None):
+        """
+        A helper method for find_file() that looks up a directory for
+        a file we're trying to find.  This only creates the Dir Node if
+        it exists on-disk, since if the directory doesn't exist we know
+        we won't find any files in it...  :-)
+
+        It would be more compact to just use this as a nested function
+        with a default keyword argument (see the commented-out version
+        below), but that doesn't work unless you have nested scopes,
+        so we define it here just this works work under Python 1.5.2.
+        """
+        if fd is None:
+            fd = self.default_filedir
+        dir, name = os.path.split(fd)
+        drive, d = os.path.splitdrive(dir)
+        if d in ('/', os.sep):
+            return p
+        if dir:
+            p = self.filedir_lookup(p, dir)
+            if not p:
+                return None
+        norm_name = _my_normcase(name)
+        try:
+            node = p.entries[norm_name]
+        except KeyError:
+            return p.dir_on_disk(name)
+        # Once we move to Python 2.2 we can do:
+        #if isinstance(node, (Dir, Entry)):
+        if isinstance(node, Dir) or isinstance(node, Entry):
+            return node
+        return None
+
     def _find_file_key(self, filename, paths, verbose=None):
         return (filename, paths)
         
@@ -2655,14 +2910,35 @@ class FileFinder:
 
         filedir, filename = os.path.split(filename)
         if filedir:
-            def filedir_lookup(p, fd=filedir):
-                try:
-                    return p.Dir(fd)
-                except TypeError:
-                    # We tried to look up a Dir, but it seems there's
-                    # already a File (or something else) there.  No big.
-                    return None
-            paths = filter(None, map(filedir_lookup, paths))
+            # More compact code that we can't use until we drop
+            # support for Python 1.5.2:
+            #
+            #def filedir_lookup(p, fd=filedir):
+            #    """
+            #    A helper function that looks up a directory for a file
+            #    we're trying to find.  This only creates the Dir Node
+            #    if it exists on-disk, since if the directory doesn't
+            #    exist we know we won't find any files in it...  :-)
+            #    """
+            #    dir, name = os.path.split(fd)
+            #    if dir:
+            #        p = filedir_lookup(p, dir)
+            #        if not p:
+            #            return None
+            #    norm_name = _my_normcase(name)
+            #    try:
+            #        node = p.entries[norm_name]
+            #    except KeyError:
+            #        return p.dir_on_disk(name)
+            #    # Once we move to Python 2.2 we can do:
+            #    #if isinstance(node, (Dir, Entry)):
+            #    if isinstance(node, Dir) or isinstance(node, Entry):
+            #        return node
+            #    return None
+            #paths = filter(None, map(filedir_lookup, paths))
+
+            self.default_filedir = filedir
+            paths = filter(None, map(self.filedir_lookup, paths))
 
         result = None
         for dir in paths:
index 225226deb66beff4eadfa2aa44a539a16868b1f7..b698e876a23d1d5f59dafb0d1baedfb739b36530 100644 (file)
@@ -288,8 +288,9 @@ class BuildDirTestCase(unittest.TestCase):
 
         assert not f7.exists()
         assert f7.rexists()
-        assert f7.rfile().path == os.path.normpath(test.workpath('rep1/build/var1/test2.out')),\
-               f7.rfile().path
+        r = f7.rfile().path
+        expect = os.path.normpath(test.workpath('rep1/build/var1/test2.out'))
+        assert r == expect, (repr(r), repr(expect))
 
         assert not f8.exists()
         assert f8.rexists()
@@ -534,6 +535,8 @@ class BuildDirTestCase(unittest.TestCase):
                 'work/src/b1/b2',
                 'work/src/b1/b2/b1',
                 'work/src/b1/b2/b1/b2',
+                'work/src/b1/b2/b1/b2/b1',
+                'work/src/b1/b2/b1/b2/b1/b2',
         ]
 
         srcnode_map = {
@@ -543,6 +546,10 @@ class BuildDirTestCase(unittest.TestCase):
                 'work/src/b1/b2/b1/f' : 'work/src/b1/f',
                 'work/src/b1/b2/b1/b2' : 'work/src/b1/b2',
                 'work/src/b1/b2/b1/b2/f' : 'work/src/b1/b2/f',
+                'work/src/b1/b2/b1/b2/b1' : 'work/src/b1/b2/b1',
+                'work/src/b1/b2/b1/b2/b1/f' : 'work/src/b1/b2/b1/f',
+                'work/src/b1/b2/b1/b2/b1/b2' : 'work/src/b1/b2/b1/b2',
+                'work/src/b1/b2/b1/b2/b1/b2/f' : 'work/src/b1/b2/b1/b2/f',
         }
 
         alter_map = {
@@ -910,43 +917,47 @@ class FSTestCase(_tempdirTestCase):
 
         drive, path = os.path.splitdrive(os.getcwd())
 
+        def _do_Dir_test(lpath, path_, abspath_, up_path_, sep, fileSys=fs, drive=drive):
+            dir = fileSys.Dir(string.replace(lpath, '/', sep))
+
+            if os.sep != '/':
+                path_ = string.replace(path_, '/', os.sep)
+                abspath_ = string.replace(abspath_, '/', os.sep)
+                up_path_ = string.replace(up_path_, '/', os.sep)
+
+            def strip_slash(p, drive=drive):
+                if p[-1] == os.sep and len(p) > 1:
+                    p = p[:-1]
+                if p[0] == os.sep:
+                    p = drive + p
+                return p
+            path = strip_slash(path_)
+            abspath = strip_slash(abspath_)
+            up_path = strip_slash(up_path_)
+            name = string.split(abspath, os.sep)[-1]
+
+            assert dir.name == name, \
+                   "dir.name %s != expected name %s" % \
+                   (dir.name, name)
+            assert dir.path == path, \
+                   "dir.path %s != expected path %s" % \
+                   (dir.path, path)
+            assert str(dir) == path, \
+                   "str(dir) %s != expected path %s" % \
+                   (str(dir), path)
+            assert dir.get_abspath() == abspath, \
+                   "dir.abspath %s != expected absolute path %s" % \
+                   (dir.get_abspath(), abspath)
+            assert dir.up().path == up_path, \
+                   "dir.up().path %s != expected parent path %s" % \
+                   (dir.up().path, up_path)
+
         for sep in seps:
 
-            def Dir_test(lpath, path_, abspath_, up_path_, fileSys=fs, s=sep, drive=drive):
-                dir = fileSys.Dir(string.replace(lpath, '/', s))
-
-                if os.sep != '/':
-                    path_ = string.replace(path_, '/', os.sep)
-                    abspath_ = string.replace(abspath_, '/', os.sep)
-                    up_path_ = string.replace(up_path_, '/', os.sep)
-
-                def strip_slash(p, drive=drive):
-                    if p[-1] == os.sep and len(p) > 1:
-                        p = p[:-1]
-                    if p[0] == os.sep:
-                        p = drive + p
-                    return p
-                path = strip_slash(path_)
-                abspath = strip_slash(abspath_)
-                up_path = strip_slash(up_path_)
-                name = string.split(abspath, os.sep)[-1]
-
-                assert dir.name == name, \
-                       "dir.name %s != expected name %s" % \
-                       (dir.name, name)
-                assert dir.path == path, \
-                       "dir.path %s != expected path %s" % \
-                       (dir.path, path)
-                assert str(dir) == path, \
-                       "str(dir) %s != expected path %s" % \
-                       (str(dir), path)
-                assert dir.get_abspath() == abspath, \
-                       "dir.abspath %s != expected absolute path %s" % \
-                       (dir.get_abspath(), abspath)
-                assert dir.up().path == up_path, \
-                       "dir.up().path %s != expected parent path %s" % \
-                       (dir.up().path, up_path)
+            def Dir_test(lpath, path_, abspath_, up_path_, sep=sep, func=_do_Dir_test):
+                return func(lpath, path_, abspath_, up_path_, sep)
 
+            Dir_test('',            './',          sub_dir,           sub)
             Dir_test('foo',         'foo/',        sub_dir_foo,       './')
             Dir_test('foo/bar',     'foo/bar/',    sub_dir_foo_bar,   'foo/')
             Dir_test('/foo',        '/foo/',       '/foo/',           '/')
@@ -1374,6 +1385,109 @@ class FSTestCase(_tempdirTestCase):
                f.get_string(0)
         assert f.get_string(1) == 'baz', f.get_string(1)
 
+    def test_drive_letters(self):
+        """Test drive-letter look-ups"""
+
+        test = self.test
+
+        test.subdir('sub', ['sub', 'dir'])
+
+        def drive_workpath(drive, dirs, test=test):
+            x = apply(test.workpath, dirs)
+            drive, path = os.path.splitdrive(x)
+            return 'X:' + path
+
+        wp              = drive_workpath('X:', [''])
+
+        if wp[-1] in (os.sep, '/'):
+            tmp         = os.path.split(wp[:-1])[0]
+        else:
+            tmp         = os.path.split(wp)[0]
+
+        parent_tmp      = os.path.split(tmp)[0]
+        if parent_tmp == 'X:':
+            parent_tmp = 'X:' + os.sep
+
+        tmp_foo         = os.path.join(tmp, 'foo')
+
+        foo             = drive_workpath('X:', ['foo'])
+        foo_bar         = drive_workpath('X:', ['foo', 'bar'])
+        sub             = drive_workpath('X:', ['sub', ''])
+        sub_dir         = drive_workpath('X:', ['sub', 'dir', ''])
+        sub_dir_foo     = drive_workpath('X:', ['sub', 'dir', 'foo', ''])
+        sub_dir_foo_bar = drive_workpath('X:', ['sub', 'dir', 'foo', 'bar', ''])
+        sub_foo         = drive_workpath('X:', ['sub', 'foo', ''])
+
+        fs = SCons.Node.FS.FS()
+
+        seps = [os.sep]
+        if os.sep != '/':
+            seps = seps + ['/']
+
+        def _do_Dir_test(lpath, path_, up_path_, sep, fileSys=fs):
+            dir = fileSys.Dir(string.replace(lpath, '/', sep))
+
+            if os.sep != '/':
+                path_ = string.replace(path_, '/', os.sep)
+                up_path_ = string.replace(up_path_, '/', os.sep)
+
+            def strip_slash(p):
+                if p[-1] == os.sep and len(p) > 3:
+                    p = p[:-1]
+                return p
+            path = strip_slash(path_)
+            up_path = strip_slash(up_path_)
+            name = string.split(path, os.sep)[-1]
+
+            assert dir.name == name, \
+                   "dir.name %s != expected name %s" % \
+                   (dir.name, name)
+            assert dir.path == path, \
+                   "dir.path %s != expected path %s" % \
+                   (dir.path, path)
+            assert str(dir) == path, \
+                   "str(dir) %s != expected path %s" % \
+                   (str(dir), path)
+            assert dir.up().path == up_path, \
+                   "dir.up().path %s != expected parent path %s" % \
+                   (dir.up().path, up_path)
+
+        save_os_path = os.path
+        save_os_sep = os.sep
+        try:
+            import ntpath
+            os.path = ntpath
+            os.sep = '\\'
+            SCons.Node.FS.initialize_do_splitdrive()
+            SCons.Node.FS.initialize_normpath_check()
+
+            for sep in seps:
+
+                def Dir_test(lpath, path_, up_path_, sep=sep, func=_do_Dir_test):
+                    return func(lpath, path_, up_path_, sep)
+
+                Dir_test('#X:',         wp,             tmp)
+                Dir_test('X:foo',       foo,            wp)
+                Dir_test('X:foo/bar',   foo_bar,        foo)
+                Dir_test('X:/foo',      'X:/foo',       'X:/')
+                Dir_test('X:/foo/bar',  'X:/foo/bar/',  'X:/foo/')
+                Dir_test('X:..',        tmp,            parent_tmp)
+                Dir_test('X:foo/..',    wp,             tmp)
+                Dir_test('X:../foo',    tmp_foo,        tmp)
+                Dir_test('X:.',         wp,             tmp)
+                Dir_test('X:./.',       wp,             tmp)
+                Dir_test('X:foo/./bar', foo_bar,        foo)
+                Dir_test('#X:../foo',   tmp_foo,        tmp)
+                Dir_test('#X:/../foo',  tmp_foo,        tmp)
+                Dir_test('#X:foo/bar',  foo_bar,        foo)
+                Dir_test('#X:/foo/bar', foo_bar,        foo)
+                Dir_test('#X:/',        wp,             tmp)
+        finally:
+            os.path = save_os_path
+            os.sep = save_os_sep
+            SCons.Node.FS.initialize_do_splitdrive()
+            SCons.Node.FS.initialize_normpath_check()
+
     def test_target_from_source(self):
         """Test the method for generating target nodes from sources"""
         fs = self.fs
@@ -1426,13 +1540,7 @@ class FSTestCase(_tempdirTestCase):
         above_path = apply(os.path.join, ['..']*len(dirs) + ['above'])
         above = d2.Dir(above_path)
 
-    # Note that the rel_path() method is not used right now, but we're
-    # leaving it commented out and disabling the unit here because
-    # it would be a shame to have to recreate the logic (or remember
-    # that it's buried in a long-past code checkin) if we ever need to
-    # resurrect it.
-
-    def DO_NOT_test_rel_path(self):
+    def test_rel_path(self):
         """Test the rel_path() method"""
         test = self.test
         fs = self.fs
@@ -1669,10 +1777,10 @@ class DirTestCase(_tempdirTestCase):
         check(s, ['src/b1'])
 
         s = b1_b2_b1_b2.srcdir_list()
-        check(s, [])
+        check(s, ['src/b1/b2'])
 
         s = b1_b2_b1_b2_sub.srcdir_list()
-        check(s, [])
+        check(s, ['src/b1/b2/sub'])
 
     def test_srcdir_duplicate(self):
         """Test the Dir.srcdir_duplicate() method
@@ -1978,6 +2086,291 @@ class FileTestCase(_tempdirTestCase):
 
 
 
+class GlobTestCase(_tempdirTestCase):
+    def setUp(self):
+        _tempdirTestCase.setUp(self)
+
+        fs = SCons.Node.FS.FS()
+        self.fs = fs
+
+        # Make entries on disk that will not have Nodes, so we can verify
+        # the behavior of looking for things on disk.
+        self.test.write('disk-aaa', "disk-aaa\n")
+        self.test.write('disk-bbb', "disk-bbb\n")
+        self.test.write('disk-ccc', "disk-ccc\n")
+        self.test.subdir('disk-sub')
+        self.test.write(['disk-sub', 'disk-ddd'], "disk-sub/disk-ddd\n")
+        self.test.write(['disk-sub', 'disk-eee'], "disk-sub/disk-eee\n")
+        self.test.write(['disk-sub', 'disk-fff'], "disk-sub/disk-fff\n")
+
+        # Make some entries that have both Nodes and on-disk entries,
+        # so we can verify what we do with
+        self.test.write('both-aaa', "both-aaa\n")
+        self.test.write('both-bbb', "both-bbb\n")
+        self.test.write('both-ccc', "both-ccc\n")
+        self.test.subdir('both-sub1')
+        self.test.write(['both-sub1', 'both-ddd'], "both-sub1/both-ddd\n")
+        self.test.write(['both-sub1', 'both-eee'], "both-sub1/both-eee\n")
+        self.test.write(['both-sub1', 'both-fff'], "both-sub1/both-fff\n")
+        self.test.subdir('both-sub2')
+        self.test.write(['both-sub2', 'both-ddd'], "both-sub2/both-ddd\n")
+        self.test.write(['both-sub2', 'both-eee'], "both-sub2/both-eee\n")
+        self.test.write(['both-sub2', 'both-fff'], "both-sub2/both-fff\n")
+
+        self.both_aaa = fs.File('both-aaa')
+        self.both_bbb = fs.File('both-bbb')
+        self.both_ccc = fs.File('both-ccc')
+        self.both_sub1 = fs.Dir('both-sub1')
+        self.both_sub1_both_ddd = self.both_sub1.File('both-ddd')
+        self.both_sub1_both_eee = self.both_sub1.File('both-eee')
+        self.both_sub1_both_fff = self.both_sub1.File('both-fff')
+        self.both_sub2 = fs.Dir('both-sub2')
+        self.both_sub2_both_ddd = self.both_sub2.File('both-ddd')
+        self.both_sub2_both_eee = self.both_sub2.File('both-eee')
+        self.both_sub2_both_fff = self.both_sub2.File('both-fff')
+
+        # Make various Nodes (that don't have on-disk entries) so we
+        # can verify how we match them.
+        self.ggg = fs.File('ggg')
+        self.hhh = fs.File('hhh')
+        self.iii = fs.File('iii')
+        self.subdir1 = fs.Dir('subdir1')
+        self.subdir1_jjj = self.subdir1.File('jjj')
+        self.subdir1_kkk = self.subdir1.File('kkk')
+        self.subdir1_lll = self.subdir1.File('lll')
+        self.subdir2 = fs.Dir('subdir2')
+        self.subdir2_jjj = self.subdir2.File('jjj')
+        self.subdir2_kkk = self.subdir2.File('kkk')
+        self.subdir2_lll = self.subdir2.File('lll')
+        self.sub = fs.Dir('sub')
+        self.sub_dir3 = self.sub.Dir('dir3')
+        self.sub_dir3_jjj = self.sub_dir3.File('jjj')
+        self.sub_dir3_kkk = self.sub_dir3.File('kkk')
+        self.sub_dir3_lll = self.sub_dir3.File('lll')
+
+
+    def do_cases(self, cases, **kwargs):
+
+        # First, execute all of the cases with string=True and verify
+        # that we get the expected strings returned.  We do this first
+        # so the Glob() calls don't add Nodes to the self.fs file system
+        # hierarchy.
+
+        import copy
+        strings_kwargs = copy.copy(kwargs)
+        strings_kwargs['strings'] = True
+        for input, string_expect, node_expect in cases:
+            r = apply(self.fs.Glob, (input,), strings_kwargs)
+            r.sort()
+            assert r == string_expect, "Glob(%s, strings=True) expected %s, got %s" % (input, string_expect, r)
+
+        # Now execute all of the cases without string=True and look for
+        # the expected Nodes to be returned.  If we don't have a list of
+        # actual expected Nodes, that means we're expecting a search for
+        # on-disk-only files to have returned some newly-created nodes.
+        # Verify those by running the list through str() before comparing
+        # them with the expected list of strings.
+        for input, string_expect, node_expect in cases:
+            r = apply(self.fs.Glob, (input,), kwargs)
+            if node_expect:
+                r.sort(lambda a,b: cmp(a.path, b.path))
+                result = node_expect
+            else:
+                r = map(str, r)
+                r.sort()
+                result = string_expect
+            assert r == result, "Glob(%s) expected %s, got %s" % (input, map(str, result), map(str, r))
+
+    def test_exact_match(self):
+        """Test globbing for exact Node matches"""
+        join = os.path.join
+
+        cases = (
+            ('ggg',         ['ggg'],                    [self.ggg]),
+
+            ('subdir1',     ['subdir1'],                [self.subdir1]),
+
+            ('subdir1/jjj', [join('subdir1', 'jjj')],   [self.subdir1_jjj]),
+
+            ('disk-aaa',    ['disk-aaa'],               None),
+
+            ('disk-sub',    ['disk-sub'],               None),
+
+            ('both-aaa',    ['both-aaa'],               []),
+        )
+
+        self.do_cases(cases)
+
+    def test_subdir_matches(self):
+        """Test globbing for exact Node matches in subdirectories"""
+        join = os.path.join
+
+        cases = (
+            ('*/jjj',
+             [join('subdir1', 'jjj'), join('subdir2', 'jjj')],
+             [self.subdir1_jjj, self.subdir2_jjj]),
+
+            ('*/disk-ddd',
+             [join('disk-sub', 'disk-ddd')],
+             None),
+        )
+
+        self.do_cases(cases)
+
+    def test_asterisk(self):
+        """Test globbing for simple asterisk Node matches"""
+        cases = (
+            ('h*',
+             ['hhh'],
+             [self.hhh]),
+
+            ('*',
+             ['both-aaa', 'both-bbb', 'both-ccc',
+              'both-sub1', 'both-sub2',
+              'ggg', 'hhh', 'iii',
+              'sub', 'subdir1', 'subdir2'],
+             [self.both_aaa, self.both_bbb, self.both_ccc,
+              self.both_sub1, self.both_sub2,
+              self.ggg, self.hhh, self.iii,
+              self.sub, self.subdir1, self.subdir2]),
+        )
+
+        self.do_cases(cases, ondisk=False)
+
+        cases = (
+            ('disk-b*',
+             ['disk-bbb'],
+             None),
+
+            ('*',
+             ['both-aaa', 'both-bbb', 'both-ccc', 'both-sub1', 'both-sub2',
+              'disk-aaa', 'disk-bbb', 'disk-ccc', 'disk-sub',
+              'ggg', 'hhh', 'iii',
+              'sub', 'subdir1', 'subdir2'],
+             None),
+        )
+
+        self.do_cases(cases)
+
+    def test_question_mark(self):
+        """Test globbing for simple question-mark Node matches"""
+        join = os.path.join
+
+        cases = (
+            ('ii?',
+             ['iii'],
+             [self.iii]),
+
+            ('both-sub?/both-eee',
+             [join('both-sub1', 'both-eee'), join('both-sub2', 'both-eee')],
+             [self.both_sub1_both_eee, self.both_sub2_both_eee]),
+
+            ('subdir?/jjj',
+             [join('subdir1', 'jjj'), join('subdir2', 'jjj')],
+             [self.subdir1_jjj, self.subdir2_jjj]),
+
+            ('disk-cc?',
+             ['disk-ccc'],
+             None),
+        )
+
+        self.do_cases(cases)
+
+    def test_does_not_exist(self):
+        """Test globbing for things that don't exist"""
+
+        cases = (
+            ('does_not_exist',  [], []),
+            ('no_subdir/*',     [], []),
+            ('subdir?/no_file', [], []),
+        )
+
+        self.do_cases(cases)
+
+    def test_subdir_asterisk(self):
+        """Test globbing for asterisk Node matches in subdirectories"""
+        join = os.path.join
+
+        cases = (
+            ('*/k*',
+             [join('subdir1', 'kkk'), join('subdir2', 'kkk')],
+             [self.subdir1_kkk, self.subdir2_kkk]),
+
+            ('both-sub?/*',
+             [join('both-sub1', 'both-ddd'),
+              join('both-sub1', 'both-eee'),
+              join('both-sub1', 'both-fff'),
+              join('both-sub2', 'both-ddd'),
+              join('both-sub2', 'both-eee'),
+              join('both-sub2', 'both-fff')],
+             [self.both_sub1_both_ddd, self.both_sub1_both_eee, self.both_sub1_both_fff,
+              self.both_sub2_both_ddd, self.both_sub2_both_eee, self.both_sub2_both_fff],
+             ),
+
+            ('subdir?/*',
+             [join('subdir1', 'jjj'),
+              join('subdir1', 'kkk'),
+              join('subdir1', 'lll'),
+              join('subdir2', 'jjj'),
+              join('subdir2', 'kkk'),
+              join('subdir2', 'lll')],
+             [self.subdir1_jjj, self.subdir1_kkk, self.subdir1_lll,
+              self.subdir2_jjj, self.subdir2_kkk, self.subdir2_lll]),
+
+            ('sub/*/*',
+             [join('sub', 'dir3', 'jjj'),
+              join('sub', 'dir3', 'kkk'),
+              join('sub', 'dir3', 'lll')],
+             [self.sub_dir3_jjj, self.sub_dir3_kkk, self.sub_dir3_lll]),
+
+            ('*/k*',
+             [join('subdir1', 'kkk'), join('subdir2', 'kkk')],
+             None),
+
+            ('subdir?/*',
+             [join('subdir1', 'jjj'),
+              join('subdir1', 'kkk'),
+              join('subdir1', 'lll'),
+              join('subdir2', 'jjj'),
+              join('subdir2', 'kkk'),
+              join('subdir2', 'lll')],
+             None),
+
+            ('sub/*/*',
+             [join('sub', 'dir3', 'jjj'),
+              join('sub', 'dir3', 'kkk'),
+              join('sub', 'dir3', 'lll')],
+             None),
+        )
+
+        self.do_cases(cases)
+
+    def test_subdir_question(self):
+        """Test globbing for question-mark Node matches in subdirectories"""
+        join = os.path.join
+
+        cases = (
+            ('*/?kk',
+             [join('subdir1', 'kkk'), join('subdir2', 'kkk')],
+             [self.subdir1_kkk, self.subdir2_kkk]),
+
+            ('subdir?/l?l',
+             [join('subdir1', 'lll'), join('subdir2', 'lll')],
+             [self.subdir1_lll, self.subdir2_lll]),
+
+            ('*/disk-?ff',
+             [join('disk-sub', 'disk-fff')],
+             None),
+
+            ('subdir?/l?l',
+             [join('subdir1', 'lll'), join('subdir2', 'lll')],
+             None),
+        )
+
+        self.do_cases(cases)
+
+
+
 class RepositoryTestCase(_tempdirTestCase):
 
     def setUp(self):
@@ -2379,7 +2772,7 @@ class StringDirTestCase(unittest.TestCase):
         fs = SCons.Node.FS.FS(test.workpath(''))
 
         d = fs.Dir('sub', '.')
-        assert str(d) == 'sub'
+        assert str(d) == 'sub', str(d)
         assert d.exists()
         f = fs.File('file', 'sub')
         assert str(f) == os.path.join('sub', 'file')
@@ -2913,6 +3306,7 @@ if __name__ == "__main__":
         FileBuildInfoTestCase,
         FileNodeInfoTestCase,
         FSTestCase,
+        GlobTestCase,
         RepositoryTestCase,
     ]
     for tclass in tclasses:
index 7ddca37e979918aa03c9045e0cd7f147b92a904d..f252151547d2040187bdea48d4f0e15ed238e18d 100644 (file)
@@ -207,6 +207,7 @@ class Node:
         self.depends_dict = {}
         self.ignore = []        # dependencies to ignore
         self.ignore_dict = {}
+        self.prerequisites = SCons.Util.UniqueList()
         self.implicit = None    # implicit (scanned) dependencies (None means not scanned yet)
         self.waiting_parents = {}
         self.waiting_s_e = {}
@@ -361,11 +362,11 @@ class Node:
         in built().
 
         """
-        executor = self.get_executor()
-        stat = apply(executor, (self,), kw)
-        if stat:
-            msg = "Error %d" % stat
-            raise SCons.Errors.BuildError(node=self, errstr=msg)
+        try:
+            apply(self.get_executor(), (self,), kw)
+        except SCons.Errors.BuildError, e:
+            e.node = self
+            raise
 
     def built(self):
         """Called just after this node is successfully built."""
@@ -614,6 +615,7 @@ class Node:
             return
 
         build_env = self.get_build_env()
+        executor = self.get_executor()
 
         # Here's where we implement --implicit-cache.
         if implicit_cache and not implicit_deps_changed:
@@ -623,7 +625,14 @@ class Node:
                 # stored .sconsign entry to have already been converted
                 # to Nodes for us.  (We used to run them through a
                 # source_factory function here.)
-                self._add_child(self.implicit, self.implicit_dict, implicit)
+
+                # Update all of the targets with them.  This
+                # essentially short-circuits an N*M scan of the
+                # sources for each individual target, which is a hell
+                # of a lot more efficient.
+                for tgt in executor.targets:
+                    tgt.add_to_implicit(implicit)
+
                 if implicit_deps_unchanged or self.is_up_to_date():
                     return
                 # one of this node's sources has changed,
@@ -633,8 +642,6 @@ class Node:
                 self._children_reset()
                 self.del_binfo()
 
-        executor = self.get_executor()
-
         # Have the executor scan the sources.
         executor.scan_sources(self.builder.source_scanner)
 
@@ -825,6 +832,11 @@ class Node:
                 s = str(e)
             raise SCons.Errors.UserError("attempted to add a non-Node dependency to %s:\n\t%s is a %s, not a Node" % (str(self), s, type(e)))
 
+    def add_prerequisite(self, prerequisite):
+        """Adds prerequisites"""
+        self.prerequisites.extend(prerequisite)
+        self._children_reset()
+
     def add_ignore(self, depend):
         """Adds dependencies to ignore."""
         try:
@@ -1200,19 +1212,18 @@ class Node:
             lines = ["%s:\n" % preamble] + lines
             return string.join(lines, ' '*11)
 
-l = [1]
-ul = UserList.UserList([2])
 try:
-    l.extend(ul)
+    [].extend(UserList.UserList([]))
 except TypeError:
+    # Python 1.5.2 doesn't allow a list to be extended by list-like
+    # objects (such as UserList instances), so just punt and use
+    # real lists.
     def NodeList(l):
         return l
 else:
     class NodeList(UserList.UserList):
         def __str__(self):
             return str(map(str, self.data))
-del l
-del ul
 
 def get_children(node, parent): return node.children()
 def ignore_cycle(node, stack): pass
index 95bd1cdf6b29d78dedc6ccbd2762286e72595cbf..5ec9d7a01cbed30c9595f7ac66cc258b4b01cad1 100644 (file)
@@ -516,9 +516,37 @@ B 42 54 b - alpha test ['B']
         assert text == expectAlpha, text
 
 
-    
+
+class UnknownOptionsTestCase(unittest.TestCase):
+
+    def test_unknown(self):
+        """Test the UnknownOptions() method"""
+        opts = SCons.Options.Options()
         
+        opts.Add('ANSWER',
+                 'THE answer to THE question',
+                 "42")
+
+        args = {
+            'ANSWER'    : 'answer',
+            'UNKNOWN'   : 'unknown',
+        }
+
+        env = Environment()
+        opts.Update(env, args)
+
+        r = opts.UnknownOptions()
+        assert r == {'UNKNOWN' : 'unknown'}, r
+        assert env['ANSWER'] == 'answer', env['ANSWER']
+
+
+
 if __name__ == "__main__":
-    suite = unittest.makeSuite(OptionsTestCase, 'test_')
+    suite = unittest.TestSuite()
+    tclasses = [ OptionsTestCase,
+                 UnknownOptionsTestCase ]
+    for tclass in tclasses:
+        names = unittest.getTestCaseNames(tclass, 'test_')
+        suite.addTests(map(tclass, names))
     if not unittest.TextTestRunner().run(suite).wasSuccessful():
         sys.exit(1)
index e2ad80f74273be2a9a9d11241b049c0bd6bb6415..3dc7772d22b96f55d7779910488132fa50ea2262 100644 (file)
@@ -29,8 +29,11 @@ customizable variables to an SCons build.
 
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
+import SCons.compat
+
 import os.path
 import string
+import sys
 
 import SCons.Errors
 import SCons.Util
@@ -64,6 +67,7 @@ class Options:
             else:
                 files = []
         self.files = files
+        self.unknown = {}
 
         # create the singleton instance
         if is_global:
@@ -158,16 +162,29 @@ class Options:
         # next set the value specified in the options file
         for filename in self.files:
             if os.path.exists(filename):
-                execfile(filename, values)
-
-        # finally set the values specified on the command line
+                dir = os.path.split(os.path.abspath(filename))[0]
+                if dir:
+                    sys.path.insert(0, dir)
+                try:
+                    values['__name__'] = filename
+                    execfile(filename, {}, values)
+                finally:
+                    if dir:
+                        del sys.path[0]
+                    del values['__name__']
+
+        # set the values specified on the command line
         if args is None:
             args = self.args
 
         for arg, value in args.items():
-          for option in self.options:
-            if arg in option.aliases + [ option.key ]:
-              values[option.key]=value
+            added = False
+            for option in self.options:
+                if arg in option.aliases + [ option.key ]:
+                    values[option.key] = value
+                    added = True
+            if not added:
+                self.unknown[arg] = value
 
         # put the variables in the environment:
         # (don't copy over variables that are not declared as options)
@@ -195,6 +212,13 @@ class Options:
             if option.validator and values.has_key(option.key):
                 option.validator(option.key, env.subst('${%s}'%option.key), env)
 
+    def UnknownOptions(self):
+        """
+        Returns any options in the specified arguments lists that
+        were not known, declared options in this object.
+        """
+        return self.unknown
+
     def Save(self, filename, env):
         """
         Saves all the options in the given file.  This file can
index 8d35a8d1fc4412e780fed132093b77947ebfd971..2fa30cc5a65a0b91aeccfbcbcc961de793cede45 100644 (file)
@@ -40,13 +40,53 @@ import tempfile
 
 from SCons.Platform.posix import exitvalmap
 from SCons.Platform import TempFileMunge
-
-# XXX See note below about why importing SCons.Action should be
-# eventually refactored.
-import SCons.Action
 import SCons.Util
 
 
+
+try:
+    import msvcrt
+    import win32api
+    import win32con
+
+    msvcrt.get_osfhandle
+    win32api.SetHandleInformation
+    win32con.HANDLE_FLAG_INHERIT
+except ImportError:
+    parallel_msg = \
+        "you do not seem to have the pywin32 extensions installed;\n" + \
+        "\tparallel (-j) builds may not work reliably with open Python files."
+except AttributeError:
+    parallel_msg = \
+        "your pywin32 extensions do not support file handle operations;\n" + \
+        "\tparallel (-j) builds may not work reliably with open Python files."
+else:
+    parallel_msg = None
+
+    import __builtin__
+
+    _builtin_file = __builtin__.file
+    _builtin_open = __builtin__.open
+
+    def _scons_file(*args, **kw):
+        fp = apply(_builtin_file, args, kw)
+        win32api.SetHandleInformation(msvcrt.get_osfhandle(fp.fileno()),
+                                      win32con.HANDLE_FLAG_INHERIT,
+                                      0)
+        return fp
+
+    def _scons_open(*args, **kw):
+        fp = apply(_builtin_open, args, kw)
+        win32api.SetHandleInformation(msvcrt.get_osfhandle(fp.fileno()),
+                                      win32con.HANDLE_FLAG_INHERIT,
+                                      0)
+        return fp
+
+    __builtin__.file = _scons_file
+    __builtin__.open = _scons_open
+
+
+
 # The upshot of all this is that, if you are using Python 1.5.2,
 # you had better have cmd or command.com in your PATH when you run
 # scons.
index 47a552adb8ef40add80cd23a04351d29c23bb34d..ae3a77eb174b007da361b0294f6a51739ab66618 100644 (file)
@@ -54,6 +54,14 @@ from SCons.Debug import Trace
 SCons.Conftest.LogInputFiles = 0
 SCons.Conftest.LogErrorMessages = 0
 
+# Set
+build_type = None
+build_types = ['clean', 'help']
+
+def SetBuildType(type):
+    global build_type
+    build_type = type
+
 # to be set, if we are in dry-run mode
 dryrun = 0
 
@@ -354,7 +362,7 @@ class SConfBuildTask(SCons.Taskmaster.Task):
                     sconsign.set_entry(t.name, sconsign_entry)
                     sconsign.merge()
 
-class SConf:
+class SConfBase:
     """This is simply a class to represent a configure context. After
     creating a SConf object, you can call any tests. After finished with your
     tests, be sure to call the Finish() method, which returns the modified
@@ -395,6 +403,7 @@ class SConf:
         default_tests = {
                  'CheckFunc'          : CheckFunc,
                  'CheckType'          : CheckType,
+                 'CheckTypeSize'      : CheckTypeSize,
                  'CheckHeader'        : CheckHeader,
                  'CheckCHeader'       : CheckCHeader,
                  'CheckCXXHeader'     : CheckCXXHeader,
@@ -603,7 +612,7 @@ class SConf:
     def AddTest(self, test_name, test_instance):
         """Adds test_class to this SConf instance. It can be called with
         self.test_name(...)"""
-        setattr(self, test_name, SConf.TestWrapper(test_instance, self))
+        setattr(self, test_name, SConfBase.TestWrapper(test_instance, self))
 
     def AddTests(self, tests):
         """Adds all the tests given in the tests dictionary to this SConf
@@ -815,6 +824,19 @@ class CheckContext:
     #### End of stuff used by Conftest.py.
 
 
+def SConf(*args, **kw):
+    if kw.get(build_type, True):
+        kw['_depth'] = kw.get('_depth', 0) + 1
+        for bt in build_types:
+            try:
+                del kw[bt]
+            except KeyError:
+                pass
+        return apply(SConfBase, args, kw)
+    else:
+        return SCons.Util.Null()
+
+
 def CheckFunc(context, function_name, header = None, language = None):
     res = SCons.Conftest.CheckFunc(context, function_name, header = header, language = language)
     context.did_show_result = 1
@@ -826,6 +848,13 @@ def CheckType(context, type_name, includes = "", language = None):
     context.did_show_result = 1
     return not res
 
+def CheckTypeSize(context, type_name, includes = "", language = None, expect = None):
+    res = SCons.Conftest.CheckTypeSize(context, type_name,
+                                       header = includes, language = language, 
+                                       expect = expect)
+    context.did_show_result = 1
+    return res
+
 def createIncludesFromHeaders(headers, leaveLast, include_quotes = '""'):
     # used by CheckHeader and CheckLibWithHeader to produce C - #include
     # statements from the specified header (list)
index 2c35730596e5ae50842ea08a9bbe29aad661956c..601c5eb433dd2d7290bba823ccbf7471deffcdef 100644 (file)
@@ -95,7 +95,7 @@ class SConfTestCase(unittest.TestCase):
         # 'TryLink'), so we are aware of reloading modules.
         
         def checks(self, sconf, TryFuncString):
-            TryFunc = self.SConf.SConf.__dict__[TryFuncString]
+            TryFunc = self.SConf.SConfBase.__dict__[TryFuncString]
             res1 = TryFunc( sconf, "int main() { return 0; }\n", ".c" )
             res2 = TryFunc( sconf,
                             '#include "no_std_header.h"\nint main() {return 0; }\n',
@@ -497,6 +497,40 @@ int main() {
         finally:
             sconf.Finish()
 
+    def test_CheckTypeSize(self):
+        """Test SConf.CheckTypeSize()
+        """
+        self._resetSConfState()
+        sconf = self.SConf.SConf(self.scons_env,
+                                 conf_dir=self.test.workpath('config.tests'),
+                                 log_file=self.test.workpath('config.log'))
+        try:
+            # CheckTypeSize()
+
+            # In ANSI C, sizeof(char) == 1.
+            r = sconf.CheckTypeSize('char', expect = 1)
+            assert r == 1, "sizeof(char) != 1 ??"
+            r = sconf.CheckTypeSize('char', expect = 0)
+            assert r == 0, "sizeof(char) == 0 ??"
+            r = sconf.CheckTypeSize('char', expect = 2)
+            assert r == 0, "sizeof(char) == 2 ??"
+            r = sconf.CheckTypeSize('char')
+            assert r == 1, "sizeof(char) != 1 ??"
+            r = sconf.CheckTypeSize('const unsigned char')
+            assert r == 1, "sizeof(const unsigned char) != 1 ??"
+
+            # Checking C++
+            r = sconf.CheckTypeSize('const unsigned char', language = 'C++')
+            assert r == 1, "sizeof(const unsigned char) != 1 ??"
+
+            # Checking Non-existing type
+            r = sconf.CheckTypeSize('thistypedefhasnotchancetosexist_scons')
+            assert r == 0, \
+                   "Checking size of thistypedefhasnotchancetosexist_scons succeeded ?"
+
+        finally:
+            sconf.Finish()
+
     def test_(self):
         """Test SConf.CheckType()
         """
index 36dc21e1e41007089f800f020930e4604c3bcc5c..97e0b190f455c3c113b817e9b1b7bdc48e548f30 100644 (file)
@@ -139,6 +139,12 @@ def Progress(*args, **kw):
 
 # Task control.
 #
+
+_BuildFailures = []
+
+def GetBuildFailures():
+    return _BuildFailures
+
 class BuildTask(SCons.Taskmaster.Task):
     """An SCons build task."""
     progress = ProgressObject
@@ -174,6 +180,7 @@ class BuildTask(SCons.Taskmaster.Task):
                 display("scons: `%s' is up to date." % str(self.node))
 
     def do_failed(self, status=2):
+        _BuildFailures.append(self.exception[1])
         global exit_status
         if self.options.ignore_errors:
             SCons.Taskmaster.Task.executed(self)
@@ -276,34 +283,31 @@ class BuildTask(SCons.Taskmaster.Task):
 
 class CleanTask(SCons.Taskmaster.Task):
     """An SCons clean task."""
-    def dir_index(self, directory):
-        dirname = lambda f, d=directory: os.path.join(d, f)
-        files = map(dirname, os.listdir(directory))
-
-        # os.listdir() isn't guaranteed to return files in any specific order,
-        # but some of the test code expects sorted output.
-        files.sort()
-        return files
-
-    def fs_delete(self, path, remove=1):
+    def fs_delete(self, path, pathstr, remove=1):
         try:
             if os.path.exists(path):
                 if os.path.isfile(path):
                     if remove: os.unlink(path)
-                    display("Removed " + path)
+                    display("Removed " + pathstr)
                 elif os.path.isdir(path) and not os.path.islink(path):
                     # delete everything in the dir
-                    for p in self.dir_index(path):
+                    entries = os.listdir(path)
+                    # Sort for deterministic output (os.listdir() Can
+                    # return entries in a random order).
+                    entries.sort()
+                    for e in entries:
+                        p = os.path.join(path, e)
+                        s = os.path.join(pathstr, e)
                         if os.path.isfile(p):
                             if remove: os.unlink(p)
-                            display("Removed " + p)
+                            display("Removed " + s)
                         else:
-                            self.fs_delete(p, remove)
+                            self.fs_delete(p, s, remove)
                     # then delete dir itself
                     if remove: os.rmdir(path)
-                    display("Removed directory " + path)
+                    display("Removed directory " + pathstr)
         except (IOError, OSError), e:
-            print "scons: Could not remove '%s':" % str(path), e.strerror
+            print "scons: Could not remove '%s':" % pathstr, e.strerror
 
     def show(self):
         target = self.targets[0]
@@ -314,7 +318,7 @@ class CleanTask(SCons.Taskmaster.Task):
         if SCons.Environment.CleanTargets.has_key(target):
             files = SCons.Environment.CleanTargets[target]
             for f in files:
-                self.fs_delete(str(f), 0)
+                self.fs_delete(f.abspath, str(f), 0)
 
     def remove(self):
         target = self.targets[0]
@@ -335,7 +339,7 @@ class CleanTask(SCons.Taskmaster.Task):
         if SCons.Environment.CleanTargets.has_key(target):
             files = SCons.Environment.CleanTargets[target]
             for f in files:
-                self.fs_delete(str(f))
+                self.fs_delete(f.abspath, str(f))
 
     execute = remove
 
@@ -726,6 +730,7 @@ def version_string(label, module):
                   module.__buildsys__)
 
 def _main(parser):
+    import SCons
     global exit_status
 
     options = parser.values
@@ -836,12 +841,10 @@ def _main(parser):
         CleanTask.execute = CleanTask.show
     if options.question:
         SCons.SConf.dryrun = 1
-    if options.clean or options.help:
-        # If they're cleaning targets or have asked for help, replace
-        # the whole SCons.SConf module with a Null object so that the
-        # Configure() calls when reading the SConscript files don't
-        # actually do anything.
-        SCons.SConf.SConf = SCons.Util.Null
+    if options.clean:
+        SCons.SConf.SetBuildType('clean')
+    if options.help:
+        SCons.SConf.SetBuildType('help')
     SCons.SConf.SetCacheMode(options.config)
     SCons.SConf.SetProgressDisplay(progress_display)
 
@@ -1067,7 +1070,6 @@ def _main(parser):
             """Leave the order of dependencies alone."""
             return dependencies
 
-    progress_display("scons: " + opening_message)
     if options.taskmastertrace_file == '-':
         tmtrace = sys.stdout
     elif options.taskmastertrace_file:
@@ -1083,15 +1085,22 @@ def _main(parser):
     global num_jobs
     num_jobs = options.num_jobs
     jobs = SCons.Job.Jobs(num_jobs, taskmaster)
-    if num_jobs > 1 and jobs.num_jobs == 1:
-        msg = "parallel builds are unsupported by this version of Python;\n" + \
-              "\tignoring -j or num_jobs option.\n"
-        SCons.Warnings.warn(SCons.Warnings.NoParallelSupportWarning, msg)
+    if num_jobs > 1:
+        msg = None
+        if jobs.num_jobs == 1:
+            msg = "parallel builds are unsupported by this version of Python;\n" + \
+                  "\tignoring -j or num_jobs option.\n"
+        elif sys.platform == 'win32':
+            import SCons.Platform.win32
+            msg = SCons.Platform.win32.parallel_msg
+        if msg:
+            SCons.Warnings.warn(SCons.Warnings.NoParallelSupportWarning, msg)
 
     memory_stats.append('before building targets:')
     count_stats.append(('pre-', 'build'))
 
     try:
+        progress_display("scons: " + opening_message)
         jobs.run()
     finally:
         jobs.cleanup()
index d1a115a2d49791e2f101f1028eef83988288ea9f..44f01b827cdf501f7e60790626873d795275439f 100644 (file)
@@ -111,6 +111,7 @@ AddOption               = Main.AddOption
 GetOption               = Main.GetOption
 SetOption               = Main.SetOption
 Progress                = Main.Progress
+GetBuildFailures        = Main.GetBuildFailures
 
 #keep_going_on_error     = Main.keep_going_on_error
 #print_dtree             = Main.print_dtree
@@ -302,6 +303,7 @@ GlobalDefaultEnvironmentFunctions = [
     'FindSourceFiles',
     'Flatten',
     'GetBuildPath',
+    'Glob',
     'Ignore',
     'Install',
     'InstallAs',
@@ -310,6 +312,7 @@ GlobalDefaultEnvironmentFunctions = [
     'ParseDepends',
     'Precious',
     'Repository',
+    'Requires',
     'SConsignFile',
     'SideEffect',
     'SourceCode',
index 598566e43e58d45ccc90d1e1d75f3802d74fff38..3bb4225d742636c2e884151eda4401bd1c138047 100644 (file)
@@ -535,7 +535,7 @@ class Taskmaster:
             node.set_state(SCons.Node.pending)
 
             try:
-                children = node.children()
+                children = node.children() + node.prerequisites
             except SystemExit:
                 exc_value = sys.exc_info()[1]
                 e = SCons.Errors.ExplicitExit(node, exc_value.code)
index c79fb9313cba5992c563fd742437dccb10a163ce..9a7969b31045e8a82b22404ff458f6bb5ee41610 100644 (file)
@@ -46,6 +46,7 @@ class Node:
         self.scanned = 0
         self.scanner = None
         self.targets = [self]
+        self.prerequisites = []
         class Builder:
             def targets(self, node):
                 return node.targets
index c071aa7eed2652c4c88844a8ce490d87fd567758..25f356469825fe35420ca1b86ae0470b43762a1a 100644 (file)
@@ -186,9 +186,9 @@ def generate(env):
     env['WINDOWSEXPSUFFIX']      = '${WIN32EXPSUFFIX}'
 
     env['WINDOWSSHLIBMANIFESTPREFIX'] = ''
-    env['WINDOWSSHLIBMANIFESTSUFFIX'] = env['SHLIBSUFFIX'] + '.manifest'
+    env['WINDOWSSHLIBMANIFESTSUFFIX'] = '${SHLIBSUFFIX}.manifest'
     env['WINDOWSPROGMANIFESTPREFIX']  = ''
-    env['WINDOWSPROGMANIFESTSUFFIX']  = env['PROGSUFFIX'] + '.manifest'
+    env['WINDOWSPROGMANIFESTSUFFIX']  = '${PROGSUFFIX}.manifest'
 
     env['REGSVRACTION'] = regServerCheck
     env['REGSVR'] = os.path.join(SCons.Platform.win32.get_system_root(),'System32','regsvr32')
index 72bbff0026ad68c9fea827b818ac9df160327e67..79cd4abc0319361ccaef5d5627e3d98b9bfab17e 100644 (file)
@@ -86,14 +86,17 @@ def Tag(env, target, source, *more_tags, **kw_tags):
 def Package(env, target=None, source=None, **kw):
     """ Entry point for the package tool.
     """
-    # first check some arguments
+    # check if we need to find the source files ourself
     if not source:
         source = env.FindInstalledFiles()
 
     if len(source)==0:
         raise UserError, "No source for Package() given"
 
-    # has the option for this Tool been set?
+    # decide which types of packages shall be built. Can be defined through
+    # four mechanisms: command line argument, keyword argument,
+    # environment argument and default selection( zip or tar.gz ) in that
+    # order.
     try: kw['PACKAGETYPE']=env['PACKAGETYPE']
     except KeyError: pass
 
@@ -108,12 +111,12 @@ def Package(env, target=None, source=None, **kw):
             kw['PACKAGETYPE']='zip'
         else:
             raise UserError, "No type for Package() given"
+
     PACKAGETYPE=kw['PACKAGETYPE']
     if not is_List(PACKAGETYPE):
-        #PACKAGETYPE=PACKAGETYPE.split(',')
         PACKAGETYPE=string.split(PACKAGETYPE, ',')
 
-    # now load the needed packagers.
+    # load the needed packagers.
     def load_packager(type):
         try:
             file,path,desc=imp.find_module(type, __path__)
@@ -123,23 +126,24 @@ def Package(env, target=None, source=None, **kw):
 
     packagers=map(load_packager, PACKAGETYPE)
 
-    # now try to setup the default_target and the default PACKAGEROOT
-    # arguments.
+    # set up targets and the PACKAGEROOT
     try:
         # fill up the target list with a default target name until the PACKAGETYPE
         # list is of the same size as the target list.
-        if target==None or target==[]:
-            target=["%(NAME)s-%(VERSION)s"%kw]
+        if not target: target = []
+
+        size_diff      = len(PACKAGETYPE)-len(target)
+        default_name   = "%(NAME)s-%(VERSION)s"
 
-        size_diff=len(PACKAGETYPE)-len(target)
         if size_diff>0:
-            target.extend([target]*size_diff)
+            default_target = default_name%kw
+            target.extend( [default_target]*size_diff )
 
         if not kw.has_key('PACKAGEROOT'):
-            kw['PACKAGEROOT']="%(NAME)s-%(VERSION)s"%kw
+            kw['PACKAGEROOT'] = default_name%kw
 
     except KeyError, e:
-        raise SCons.Errors.UserError( "Missing PackageTag '%s'"%e.args[0] )
+        raise SCons.Errors.UserError( "Missing Packagetag '%s'"%e.args[0] )
 
     # setup the source files
     source=env.arg2nodes(source, env.fs.Entry)
@@ -148,11 +152,14 @@ def Package(env, target=None, source=None, **kw):
     targets=[]
     try:
         for packager in packagers:
-            t=apply(packager.package, [env,target,source], kw)
+            t=[target.pop(0)]
+            t=apply(packager.package, [env,t,source], kw)
             targets.extend(t)
 
+        assert( len(target) == 0 )
+
     except KeyError, e:
-        raise SCons.Errors.UserError( "Missing PackageTag '%s' for %s packager"\
+        raise SCons.Errors.UserError( "Missing Packagetag '%s' for %s packager"\
                                       % (e.args[0],packager.__name__) )
     except TypeError, e:
         # this exception means that a needed argument for the packager is
@@ -170,10 +177,10 @@ def Package(env, target=None, source=None, **kw):
         if len(args)==0:
             raise # must be a different error, so reraise
         elif len(args)==1:
-            raise SCons.Errors.UserError( "Missing PackageTag '%s' for %s packager"\
+            raise SCons.Errors.UserError( "Missing Packagetag '%s' for %s packager"\
                                           % (args[0],packager.__name__) )
         else:
-            raise SCons.Errors.UserError( "Missing PackageTags '%s' for %s packager"\
+            raise SCons.Errors.UserError( "Missing Packagetags '%s' for %s packager"\
                                           % (", ".join(args),packager.__name__) )
 
     target=env.arg2nodes(target, env.fs.Entry)
index 04d263b815989b0ef33eba19755eecda1085f3bb..258de0f1fb3fb66bd1c3788b3cc3d62485e039de 100644 (file)
@@ -1051,6 +1051,99 @@ class LogicalLines:
 
 
 
+class UniqueList(UserList):
+    def __init__(self, seq = []):
+        UserList.__init__(self, seq)
+        self.unique = True
+    def __make_unique(self):
+        if not self.unique:
+            self.data = uniquer_hashables(self.data)
+            self.unique = True
+    def __lt__(self, other):
+        self.__make_unique()
+        return UserList.__lt__(self, other)
+    def __le__(self, other):
+        self.__make_unique()
+        return UserList.__le__(self, other)
+    def __eq__(self, other):
+        self.__make_unique()
+        return UserList.__eq__(self, other)
+    def __ne__(self, other):
+        self.__make_unique()
+        return UserList.__ne__(self, other)
+    def __gt__(self, other):
+        self.__make_unique()
+        return UserList.__gt__(self, other)
+    def __ge__(self, other):
+        self.__make_unique()
+        return UserList.__ge__(self, other)
+    def __cmp__(self, other):
+        self.__make_unique()
+        return UserList.__cmp__(self, other)
+    def __len__(self):
+        self.__make_unique()
+        return UserList.__len__(self)
+    def __getitem__(self, i):
+        self.__make_unique()
+        return UserList.__getitem__(self, i)
+    def __setitem__(self, i, item):
+        UserList.__setitem__(self, i, item)
+        self.unique = False
+    def __getslice__(self, i, j):
+        self.__make_unique()
+        return UserList.__getslice__(self, i, j)
+    def __setslice__(self, i, j, other):
+        UserList.__setslice__(self, i, j, other)
+        self.unique = False
+    def __add__(self, other):
+        result = UserList.__add__(self, other)
+        result.unique = False
+        return result
+    def __radd__(self, other):
+        result = UserList.__radd__(self, other)
+        result.unique = False
+        return result
+    def __iadd__(self, other):
+        result = UserList.__iadd__(self, other)
+        result.unique = False
+        return result
+    def __mul__(self, other):
+        result = UserList.__mul__(self, other)
+        result.unique = False
+        return result
+    def __rmul__(self, other):
+        result = UserList.__rmul__(self, other)
+        result.unique = False
+        return result
+    def __imul__(self, other):
+        result = UserList.__imul__(self, other)
+        result.unique = False
+        return result
+    def append(self, item):
+        UserList.append(self, item)
+        self.unique = False
+    def insert(self, i):
+        UserList.insert(self, i)
+        self.unique = False
+    def count(self, item):
+        self.__make_unique()
+        return UserList.count(self, item)
+    def index(self, item):
+        self.__make_unique()
+        return UserList.index(self, item)
+    def reverse(self):
+        self.__make_unique()
+        UserList.reverse(self)
+    def sort(self, *args, **kwds):
+        self.__make_unique()
+        #return UserList.sort(self, *args, **kwds)
+        return apply(UserList.sort, (self,)+args, kwds)
+    def extend(self, other):
+        UserList.extend(self, other)
+        self.unique = False
+
+
+
 class Unbuffered:
     """
     A proxy class that wraps a file object, flushing after every write,
index 5e095d1cb43989869366e197cbda386858c880c3..47ae3bea2abf5957ae095d7a87e1e40a77f87bdc 100644 (file)
@@ -111,6 +111,35 @@ except NameError:
     import sets
     __builtin__.set = sets.Set
 
+import fnmatch
+try:
+    fnmatch.filter
+except AttributeError:
+    # Pre-2.2 Python has no fnmatch.filter() function.
+    def filter(names, pat):
+        """Return the subset of the list NAMES that match PAT"""
+        import os,posixpath
+        result=[]
+        pat = os.path.normcase(pat)
+        if not fnmatch._cache.has_key(pat):
+            import re
+            res = fnmatch.translate(pat)
+            fnmatch._cache[pat] = re.compile(res)
+        match = fnmatch._cache[pat].match
+        if os.path is posixpath:
+            # normcase on posix is NOP. Optimize it away from the loop.
+            for name in names:
+                if match(name):
+                    result.append(name)
+        else:
+            for name in names:
+                if match(os.path.normcase(name)):
+                    result.append(name)
+        return result
+    fnmatch.filter = filter
+    del filter
+   
+
 # If we need the compatibility version of textwrap, it  must be imported
 # before optparse, which uses it.
 try:
index cb11175b59285e67213cc18d9faca3767c290376..404834001b4600a2819206ee1bb8fdd09b5f2429 100644 (file)
@@ -79,6 +79,9 @@ test.write('main.c', r"""
 #include <stdio.h>
 #include <stdlib.h>
 
+extern void
+library_function(void);
+
 int
 main(int argc, char *argv[])
 {
index d4d122e2d3c6c66830bf25c8352e08a50e1d59a0..fbca0ff98821818aae22f74084f6c10c107e7f12 100644 (file)
@@ -78,6 +78,9 @@ library_function(void)
 test.write('main.c', r"""
 #include <stdlib.h>
 
+extern void
+library_function();
+
 int
 main(int argc, char *argv[])
 {
diff --git a/test/BuildDir/Clean.py b/test/BuildDir/Clean.py
new file mode 100644 (file)
index 0000000..f4a8c48
--- /dev/null
@@ -0,0 +1,71 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# 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.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that we can Clean() files in a BuildDir() that's underneath us.
+(At one point this didn't work because we were using str() instead of
+abspath to remove the files, which would interfere with the removal by
+returning a path relative to the BuildDir(), not the top-level SConstruct
+directory, if the source directory was the top-level directory.)
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+BuildDir('build0', '.', duplicate=0)
+BuildDir('build1', '.', duplicate=1)
+
+def build_sample(target, source, env):
+    targetdir = str(target[0].dir)
+    target = str(target[0])
+    open(target, 'wb').write(open(str(source[0]), 'rb').read())
+    open(targetdir+'/sample.junk', 'wb').write('Side effect!\\n')
+
+t0 = Command("build0/sample.out", "sample.in", build_sample)
+t1 = Command("build1/sample.out", "sample.in", build_sample)
+
+Clean(t0, 'build0/sample.junk')
+Clean(t1, 'build1/sample.junk')
+""")
+
+test.write('sample.in', "sample.in\n")
+
+test.run(arguments = '.')
+
+test.must_match(['build0', 'sample.out'], "sample.in\n")
+test.must_exist(['build0', 'sample.junk'])
+
+test.must_match(['build1', 'sample.out'], "sample.in\n")
+test.must_exist(['build1', 'sample.junk'])
+
+test.run(arguments = '-c .')
+
+test.must_not_exist(['build', 'sample.out'])
+test.must_not_exist(['build', 'sample.junk'])
+
+test.pass_test()
diff --git a/test/BuildDir/File-create.py b/test/BuildDir/File-create.py
new file mode 100644 (file)
index 0000000..0a838be
--- /dev/null
@@ -0,0 +1,70 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# 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.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that explicit use of File() Nodes in a BuildDir, followed by
+*direct* creation of the file by Python in the SConscript file itself,
+works correctly, with both duplicate=0 and duplicate=1.
+
+Right now it only works if you explicitly str() the Node before the file
+is created on disk, but we at least want to make sure that continues
+to work.  The non-str() case, which doesn't currently work, is captured
+here but commented out.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.subdir('src')
+
+test.write('SConstruct', """\
+SConscript('src/SConscript', build_dir='build0', chdir=1, duplicate=0)
+SConscript('src/SConscript', build_dir='build1', chdir=1, duplicate=1)
+""")
+
+test.write(['src', 'SConscript'], """\
+#f1_in = File('f1.in')
+#Command('f1.out', f1_in, Copy('$TARGET', '$SOURCE'))
+#open('f1.in', 'wb').write("f1.in\\n")
+
+f2_in = File('f2.in')
+str(f2_in)
+Command('f2.out', f2_in, Copy('$TARGET', '$SOURCE'))
+open('f2.in', 'wb').write("f2.in\\n")
+""")
+
+test.run(arguments = '--tree=all .')
+
+#test.must_match(['build0', 'f1.out'], "f1.in\n")
+test.must_match(['build0', 'f2.out'], "f2.in\n")
+
+#test.must_match(['build1', 'f1.out'], "f1.in\n")
+test.must_match(['build1', 'f2.out'], "f2.in\n")
+
+test.up_to_date(arguments = '.')
+
+test.pass_test()
index 685011d961d02990d5e11b964cf9cd526f440078..50e2c4f2d4d698320ae2ddfaa71cf460867064f1 100644 (file)
@@ -225,6 +225,9 @@ test.write(['test2', 'foo.c'], r"""
 #include <stdio.h>
 #include <stdlib.h>
 
+extern void
+bar(void);
+
 int
 main(int argc, char *argv[]) {
         bar();
index 4d240ae04211d78df89914b51e5b857f2b64145f..562961a726fffa146069a706921145ccbefc6ecd 100644 (file)
@@ -58,6 +58,7 @@ else:
 test.write('SConstruct', """
 env = Environment(SHCCCOM = r'%(_python_)s mycc.py $TARGET $SOURCE',
                   SHCCCOMSTR = 'Building $TARGET from $SOURCE',
+                  SHOBJPREFIX='',
                   SHOBJSUFFIX='.obj')
 env.SharedObject(target = 'test1', source = 'test1.c')
 env.SharedObject(target = 'test2', source = 'test2%(alt_c_suffix)s')
index a1b4c2a31cf2dd8d2dcfcce5069a61d4304486b5..f537ee8712a67b18ba74a0879de4e5e91e40d7f3 100644 (file)
@@ -199,7 +199,7 @@ test.write('foo.cxx', r"""
 int
 main(int argc, char *argv[])
 {
-        argv[argc++] = "--";
+        argv[argc++] = (char *)"--";
         printf("foo.cxx\n");
         exit (0);
 }
@@ -211,7 +211,7 @@ test.write('bar.cxx', r"""
 int
 main(int argc, char *argv[])
 {
-        argv[argc++] = "--";
+        argv[argc++] = (char *)"--";
         printf("foo.cxx\n");
         exit (0);
 }
index c02086b4932b671c2e4c4908a45041fa007605f9..abfc19df4798de71c58f3622630a48bb7f5d37f8 100644 (file)
@@ -55,7 +55,7 @@ test.write('foo.cpp', r"""
 int
 main(int argc, char *argv[])
 {
-        argv[argc++] = "--";
+        argv[argc++] = (char *)"--";
         printf("foo.c\n");
         exit (0);
 }
@@ -67,7 +67,7 @@ test.write('bar.cpp', r"""
 int
 main(int argc, char *argv[])
 {
-        argv[argc++] = "--";
+        argv[argc++] = (char *)"--";
         printf("foo.c\n");
         exit (0);
 }
index 33beae7c8c00a4ab697dfd5183adcb52e722ce24..8e55441765179a02894c8023bf3836bdd49b2918 100644 (file)
@@ -58,7 +58,7 @@ else:
 test.write('SConstruct', """
 env = Environment(SHCXXCOM = r'%(_python_)s mycc.py $TARGET $SOURCE',
                   SHCXXCOMSTR = 'Building shared object $TARGET from $SOURCE',
-                  SHOBJSUFFIX='.obj')
+                  SHOBJPREFIX='', SHOBJSUFFIX='.obj')
 env.SharedObject(target = 'test1', source = 'test1.cpp')
 env.SharedObject(target = 'test2', source = 'test2.cc')
 env.SharedObject(target = 'test3', source = 'test3.cxx')
similarity index 75%
rename from test/CacheDir/timestamp.py
rename to test/CacheDir/timestamp-content.py
index b0d45b63030ebf76fa2665ba9d3402bbc258a7e7..6434d0cee67eeac9ff6711f5feebc4a9686e9a5c 100644 (file)
@@ -25,7 +25,8 @@
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
 """
-Verify that CacheDir() works even when using timestamp signatures.
+Verify that CacheDir() works when using SourceSignatures('timestamp')
+and TargetSignatures 'content'.
 """
 
 import TestSCons
@@ -41,8 +42,20 @@ Command('file.out', 'file.in', Copy('$TARGET', '$SOURCE'))
 
 test.write('file.in', "file.in\n")
 
-test.run()
+test.run(arguments = '.')
 
 test.must_match('file.out', "file.in\n")
 
+test.up_to_date(options = '--cache-show --debug=explain', arguments = '.')
+
+test.sleep()
+
+test.touch('file.in')
+
+test.not_up_to_date(options = '--cache-show --debug=explain', arguments = '.')
+
+test.up_to_date(options = '--cache-show --debug=explain', arguments = '.')
+
+test.up_to_date(options = '--cache-show --debug=explain', arguments = '.')
+
 test.pass_test()
diff --git a/test/CacheDir/timestamp-match.py b/test/CacheDir/timestamp-match.py
new file mode 100644 (file)
index 0000000..0e0074d
--- /dev/null
@@ -0,0 +1,59 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# 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.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that CAcheDir() works when using 'timestamp-match' decisions.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write(['SConstruct'], """\
+Decider('timestamp-match')
+CacheDir('cache')
+Command('file.out', 'file.in', Copy('$TARGET', '$SOURCE'))
+""")
+
+test.write('file.in', "file.in\n")
+
+test.run(arguments = '--cache-show --debug=explain .')
+
+test.must_match('file.out', "file.in\n")
+
+test.up_to_date(options = '--cache-show --debug=explain', arguments = '.')
+
+test.sleep()
+
+test.touch('file.in')
+
+test.not_up_to_date(options = '--cache-show --debug=explain', arguments = '.')
+
+test.up_to_date(options = '--cache-show --debug=explain', arguments = '.')
+
+test.up_to_date(options = '--cache-show --debug=explain', arguments = '.')
+
+test.pass_test()
diff --git a/test/CacheDir/timestamp-newer.py b/test/CacheDir/timestamp-newer.py
new file mode 100644 (file)
index 0000000..36267f8
--- /dev/null
@@ -0,0 +1,59 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# 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.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that CAcheDir() works when using 'timestamp-newer' decisions.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write(['SConstruct'], """\
+Decider('timestamp-newer')
+CacheDir('cache')
+Command('file.out', 'file.in', Copy('$TARGET', '$SOURCE'))
+""")
+
+test.write('file.in', "file.in\n")
+
+test.run(arguments = '--cache-show --debug=explain .')
+
+test.must_match('file.out', "file.in\n")
+
+test.up_to_date(options = '--cache-show --debug=explain', arguments = '.')
+
+test.sleep()
+
+test.touch('file.in')
+
+test.not_up_to_date(options = '--cache-show --debug=explain', arguments = '.')
+
+test.up_to_date(options = '--cache-show --debug=explain', arguments = '.')
+
+test.up_to_date(options = '--cache-show --debug=explain', arguments = '.')
+
+test.pass_test()
diff --git a/test/CacheDir/timestamp-timestamp.py b/test/CacheDir/timestamp-timestamp.py
new file mode 100644 (file)
index 0000000..2bef1cd
--- /dev/null
@@ -0,0 +1,61 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# 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.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that CacheDir() works when using both SourceSignatures()
+and TargetSignatures values of 'timestamp'.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write(['SConstruct'], """\
+SourceSignatures('timestamp')
+TargetSignatures('timestamp')
+CacheDir('cache')
+Command('file.out', 'file.in', Copy('$TARGET', '$SOURCE'))
+""")
+
+test.write('file.in', "file.in\n")
+
+test.run(arguments = '--cache-show --debug=explain .')
+
+test.must_match('file.out', "file.in\n")
+
+test.up_to_date(options = '--cache-show --debug=explain', arguments = '.')
+
+test.sleep()
+
+test.touch('file.in')
+
+test.not_up_to_date(options = '--cache-show --debug=explain', arguments = '.')
+
+test.up_to_date(options = '--cache-show --debug=explain', arguments = '.')
+
+test.up_to_date(options = '--cache-show --debug=explain', arguments = '.')
+
+test.pass_test()
index eb0b6c149301cf1d82e2f1dac69d8f6bd4835293..92fa6394b14125e23245f796fa83176487b83166 100644 (file)
@@ -41,7 +41,9 @@ test = TestSCons.TestSCons()
 # oscillate between those values.
 test.write('SConstruct', """
 Execute(Chmod('f1', 0666))
+Execute(Chmod(('f1-File'), 0666))
 Execute(Chmod('d2', 0777))
+Execute(Chmod(Dir('d2-Dir'), 0777))
 def cat(env, source, target):
     target = str(target[0])
     source = map(str, source)
@@ -62,8 +64,11 @@ env.Command('f7.out', 'f7.in', [Cat,
 """)
 
 test.write('f1', "f1\n")
+test.write('f1-File', "f1-File\n")
 test.subdir('d2')
 test.write(['d2', 'file'], "d2/file\n")
+test.subdir('d2-Dir')
+test.write(['d2-Dir', 'file'], "d2-Dir/file\n")
 test.write('bar.in', "bar.in\n")
 test.write('f3', "f3\n")
 test.subdir('d4')
@@ -75,14 +80,21 @@ test.write('Chmod-f7.in', "Chmod-f7.in\n")
 test.write('f7.out-Chmod', "f7.out-Chmod\n")
 
 os.chmod(test.workpath('f1'), 0444)
+os.chmod(test.workpath('f1-File'), 0444)
 os.chmod(test.workpath('d2'), 0555)
+os.chmod(test.workpath('d2-Dir'), 0555)
 os.chmod(test.workpath('f3'), 0444)
 os.chmod(test.workpath('d4'), 0555)
 os.chmod(test.workpath('f5'), 0444)
 os.chmod(test.workpath('Chmod-f7.in'), 0444)
 os.chmod(test.workpath('f7.out-Chmod'), 0444)
 
-expect = test.wrap_stdout(read_str = 'Chmod("f1", 0666)\nChmod("d2", 0777)\n',
+expect = test.wrap_stdout(read_str = """\
+Chmod("f1", 0666)
+Chmod("f1-File", 0666)
+Chmod("d2", 0777)
+Chmod("d2-Dir", 0777)
+""",
                           build_str = """\
 cat(["bar.out"], ["bar.in"])
 Chmod("f3", 0666)
@@ -97,8 +109,12 @@ test.run(options = '-n', arguments = '.', stdout = expect)
 
 s = stat.S_IMODE(os.stat(test.workpath('f1'))[stat.ST_MODE])
 test.fail_test(s != 0444)
+s = stat.S_IMODE(os.stat(test.workpath('f1-File'))[stat.ST_MODE])
+test.fail_test(s != 0444)
 s = stat.S_IMODE(os.stat(test.workpath('d2'))[stat.ST_MODE])
 test.fail_test(s != 0555)
+s = stat.S_IMODE(os.stat(test.workpath('d2-Dir'))[stat.ST_MODE])
+test.fail_test(s != 0555)
 test.must_not_exist('bar.out')
 s = stat.S_IMODE(os.stat(test.workpath('f3'))[stat.ST_MODE])
 test.fail_test(s != 0444)
@@ -117,8 +133,12 @@ test.run()
 
 s = stat.S_IMODE(os.stat(test.workpath('f1'))[stat.ST_MODE])
 test.fail_test(s != 0666)
+s = stat.S_IMODE(os.stat(test.workpath('f1-File'))[stat.ST_MODE])
+test.fail_test(s != 0666)
 s = stat.S_IMODE(os.stat(test.workpath('d2'))[stat.ST_MODE])
 test.fail_test(s != 0777)
+s = stat.S_IMODE(os.stat(test.workpath('d2-Dir'))[stat.ST_MODE])
+test.fail_test(s != 0777)
 test.must_match('bar.out', "bar.in\n")
 s = stat.S_IMODE(os.stat(test.workpath('f3'))[stat.ST_MODE])
 test.fail_test(s != 0666)
index 5b8e43d0022677f99c1ec258a9aea242c21462f0..a6ba7c1002471bf061fd76a24a90ed4eca8b97cc 100644 (file)
@@ -39,7 +39,7 @@ test.write('SConstruct', """\
 env = Environment()
 import os
 env.AppendENVPath('PATH', os.environ['PATH'])
-conf = Configure(env)
+conf = Configure(env, clean=int(ARGUMENTS['clean']))
 r1 = conf.CheckCHeader( 'math.h' )
 r2 = conf.CheckCHeader( 'no_std_c_header.h' ) # leads to compile error
 env = conf.Finish()
@@ -65,32 +65,16 @@ lines = [
     "Checking for C header file no_std_c_header.h... "
 ]
 
-unexpected = []
+test.run(arguments = '-c clean=0')
+test.must_not_contain_lines(lines, test.stdout())
 
-test.run(arguments = '-c')
+test.run(arguments = '-c clean=1')
+test.must_contain_lines(lines, test.stdout())
 
-for line in lines:
-    if string.find(test.stdout(), line) != -1:
-        unexpected.append(line)
+test.run(arguments = '--clean clean=0')
+test.must_not_contain_lines(lines, test.stdout())
 
-if unexpected:
-    print "Unexpected lines in standard output:"
-    print string.join(unexpected, '\n')
-    print "STDOUT ============================================================"
-    print test.stdout()
-    test.fail_test()
-
-test.run(arguments = '--clean')
-
-for line in lines:
-    if string.find(test.stdout(), line) != -1:
-        unexpected.append(line)
-
-if unexpected:
-    print "Unexpected lines in standard output:"
-    print string.join(unexpected, '\n')
-    print "STDOUT ============================================================"
-    print test.stdout()
-    test.fail_test()
+test.run(arguments = '--clean clean=1')
+test.must_contain_lines(lines, test.stdout())
 
 test.pass_test()
index f79831ca6e7e8b79a82fe140a8eec1903beeead0..32c68eb53c7ceed1b246a7b6219302a1a5fe9fce 100644 (file)
@@ -39,7 +39,7 @@ test.write('SConstruct', """\
 env = Environment()
 import os
 env.AppendENVPath('PATH', os.environ['PATH'])
-conf = Configure(env)
+conf = Configure(env, help=int(ARGUMENTS['help']))
 r1 = conf.CheckCHeader( 'math.h' )
 r2 = conf.CheckCHeader( 'no_std_c_header.h' ) # leads to compile error
 env = conf.Finish()
@@ -65,45 +65,26 @@ lines = [
     "Checking for C header file no_std_c_header.h... "
 ]
 
-unexpected = []
+# The help setting should have no effect on -H, so the -H output
+# should never contain the lines.
+test.run(arguments = '-H help=0')
+test.must_not_contain_lines(lines, test.stdout())
 
-test.run(arguments = '-H')
+test.run(arguments = '-H help=1')
+test.must_not_contain_lines(lines, test.stdout())
 
-for line in lines:
-    if string.find(test.stdout(), line) != -1:
-        unexpected.append(line)
+# For -h and --help, the lines appear or not depending on how Configure()
+# is initialized.
+test.run(arguments = '-h help=0')
+test.must_not_contain_lines(lines, test.stdout())
 
-if unexpected:
-    print "Unexpected lines in standard output:"
-    print string.join(unexpected, '\n')
-    print "STDOUT ============================================================"
-    print test.stdout()
-    test.fail_test()
+test.run(arguments = '-h help=1')
+test.must_contain_lines(lines, test.stdout())
 
-test.run(arguments = '-h')
+test.run(arguments = '--help help=0')
+test.must_not_contain_lines(lines, test.stdout())
 
-for line in lines:
-    if string.find(test.stdout(), line) != -1:
-        unexpected.append(line)
-
-if unexpected:
-    print "Unexpected lines in standard output:"
-    print string.join(unexpected, '\n')
-    print "STDOUT ============================================================"
-    print test.stdout()
-    test.fail_test()
-
-test.run(arguments = '--help')
-
-for line in lines:
-    if string.find(test.stdout(), line) != -1:
-        unexpected.append(line)
-
-if unexpected:
-    print "Unexpected lines in standard output:"
-    print string.join(unexpected, '\n')
-    print "STDOUT ============================================================"
-    print test.stdout()
-    test.fail_test()
+test.run(arguments = '--help help=1')
+test.must_contain_lines(lines, test.stdout())
 
 test.pass_test()
index 6659d934c44ccc1ff2fa55d33279452e8f364783..827b9120c155be213bfd5096d0c24f9fdc1d5514 100644 (file)
@@ -36,8 +36,8 @@ test = TestSCons.TestSCons()
 
 test.write('SConstruct', """
 Execute(Copy('f1.out', 'f1.in'))
-Execute(Copy('d2.out', 'd2.in'))
-Execute(Copy('d3.out', 'f3.in'))
+Execute(Copy(File('d2.out'), 'd2.in'))
+Execute(Copy('d3.out', File('f3.in')))
 def cat(env, source, target):
     target = str(target[0])
     source = map(str, source)
index 9857f99093bc922e316b92df0e5768c13602a104..597216aacc25e8db921099ac581c50cc42b59109 100644 (file)
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
 """
-Verify that we print a useful message (and exit non-zero) if an external
-error occurs while deciding if a Node is current or not.
+A currently disabled test that used to verify that we print a useful
+message (and exit non-zero) if an external error occurs while deciding
+if a Node is current or not.
+
+This behavior changed when the Big Signature Refactoring changed when
+signature calculation happens to *after* a Node has been visited (and
+therefore visiting source Nodes in turn).  Creating an analogous situation
+in the new code isn't obvious, and It's not clear whether we need it
+anyway, so we're going to leave this checked in but disabled for now.
 """
 
 import sys
@@ -35,6 +42,8 @@ import TestSCons
 
 test = TestSCons.TestSCons()
 
+test.skip_test('Test not useful with current code; skipping.\n')
+
 work_file_out = test.workpath('work', 'file.out')
 
 test.subdir('install', 'work')
index 908557027ae79a09a1c6ad85ddecd52372ea7b1a..2bedb4898889ba5919ce2c143213415d48b64c77 100644 (file)
@@ -56,7 +56,7 @@ env = Environment(SHF77COM = r'%(_python_)s myfc.py f77 $TARGET $SOURCES',
                   SHF77COMSTR = 'Building f77 $TARGET from $SOURCES',
                   SHF77PPCOM = r'%(_python_)s myfc.py f77pp $TARGET $SOURCES',
                   SHF77PPCOMSTR = 'Building f77pp $TARGET from $SOURCES',
-                  SHOBJSUFFIX='.shobj')
+                  SHOBJPREFIX='', SHOBJSUFFIX='.shobj')
 env.SharedObject(source = 'test01.f')
 env.SharedObject(source = 'test02.F')
 env.SharedObject(source = 'test03.for')
index 9633d451d756bd48ccaedc236a6d997d142e30d3..08208fa469448ce9f6413a46dde4474f3f554df2 100644 (file)
@@ -56,7 +56,7 @@ env = Environment(SHF90COM = r'%(_python_)s myfc.py f90 $TARGET $SOURCES',
                   SHF90COMSTR = 'Building f90 $TARGET from $SOURCES',
                   SHF90PPCOM = r'%(_python_)s myfc.py f90pp $TARGET $SOURCES',
                   SHF90PPCOMSTR = 'Building f90pp $TARGET from $SOURCES',
-                  SHOBJSUFFIX='.shobj')
+                  SHOBJPREFIX='', SHOBJSUFFIX='.shobj')
 env.SharedObject(source = 'test01.f90')
 env.SharedObject(source = 'test02.F90')
 """ % locals())
index eaa24ae75914ceefc17e66c0e9bff87fbbcc0df4..71a5627135ea5adc320673d36df2dc91a893fcaa 100644 (file)
@@ -56,7 +56,7 @@ env = Environment(SHF95COM = r'%(_python_)s myfc.py f95 $TARGET $SOURCES',
                   SHF95COMSTR = 'Building f95 $TARGET from $SOURCES',
                   SHF95PPCOM = r'%(_python_)s myfc.py f95pp $TARGET $SOURCES',
                   SHF95PPCOMSTR = 'Building f95pp $TARGET from $SOURCES',
-                  SHOBJSUFFIX='.shobj')
+                  SHOBJPREFIX='', SHOBJSUFFIX='.shobj')
 env.SharedObject(source = 'test01.f95')
 env.SharedObject(source = 'test02.F95')
 """ % locals())
index 69a1eba723fd882718af4e7bce5241486eacc9ab..52b20c229d3baa6392a0bca6288bc31e8dbfece1 100644 (file)
@@ -56,7 +56,7 @@ env = Environment(SHFORTRANCOM = r'%(_python_)s myfc.py fortran $TARGET $SOURCES
                   SHFORTRANCOMSTR = 'Building fortran $TARGET from $SOURCES',
                   SHFORTRANPPCOM = r'%(_python_)s myfc.py fortranpp $TARGET $SOURCES',
                   SHFORTRANPPCOMSTR = 'Building fortranpp $TARGET from $SOURCES',
-                  SHOBJSUFFIX='.shobj')
+                  SHOBJPREFIX='', SHOBJSUFFIX='.shobj')
 env.SharedObject(source = 'test01.f')
 env.SharedObject(source = 'test02.F')
 env.SharedObject(source = 'test03.for')
diff --git a/test/GetBuildFailures/option-k.py b/test/GetBuildFailures/option-k.py
new file mode 100644 (file)
index 0000000..5246f31
--- /dev/null
@@ -0,0 +1,112 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# 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.
+#
+
+"""
+Verify that a failed build action with -j works as expected.
+"""
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import TestSCons
+
+_python_ = TestSCons._python_
+
+try:
+    import threading
+except ImportError:
+    # if threads are not supported, then
+    # there is nothing to test
+    TestCmd.no_result()
+    sys.exit()
+
+
+test = TestSCons.TestSCons()
+
+contents = r"""\
+import sys
+if sys.argv[0] == 'mypass.py':
+    open(sys.argv[3], 'wb').write(open(sys.argv[4], 'rb').read())
+    exit_value = 0
+elif sys.argv[0] == 'myfail.py':
+    exit_value = 1
+sys.exit(exit_value)
+"""
+
+test.write('mypass.py', contents)
+test.write('myfail.py', contents)
+
+test.write('SConstruct', """\
+Command('f3', 'f3.in', r'@%(_python_)s mypass.py -  f3 $TARGET $SOURCE')
+Command('f4', 'f4.in', r'@%(_python_)s myfail.py f3 f4 $TARGET $SOURCE')
+Command('f5', 'f5.in', r'@%(_python_)s myfail.py f4 f5 $TARGET $SOURCE')
+Command('f6', 'f6.in', r'@%(_python_)s mypass.py f5 -  $TARGET $SOURCE')
+
+def print_build_failures():
+    from SCons.Script import GetBuildFailures
+    bf_list = GetBuildFailures()
+    bf_list.sort(lambda a,b: cmp(a.filename, b.filename))
+    for bf in bf_list:
+        print "%%s failed:  %%s" %% (bf.node, bf.errstr)
+
+try:
+    import atexit
+except ImportError:
+    import sys
+    sys.exitfunc = print_build_failures
+else:
+    atexit.register(print_build_failures)
+""" % locals())
+
+test.write('f3.in', "f3.in\n")
+test.write('f4.in', "f4.in\n")
+test.write('f5.in', "f5.in\n")
+test.write('f6.in', "f6.in\n")
+
+expect_stdout = """\
+scons: Reading SConscript files ...
+scons: done reading SConscript files.
+scons: Building targets ...
+scons: done building targets (errors occurred during build).
+f4 failed:  Error 1
+f5 failed:  Error 1
+""" % locals()
+
+expect_stderr = """\
+scons: *** [f4] Error 1
+scons: *** [f5] Error 1
+"""
+
+test.run(arguments = '-k .',
+         status = 2,
+         stdout = expect_stdout,
+         stderr = expect_stderr)
+
+test.must_match(test.workpath('f3'), 'f3.in\n')
+test.must_not_exist(test.workpath('f4'))
+test.must_not_exist(test.workpath('f5'))
+test.must_match(test.workpath('f6'), 'f6.in\n') 
+
+
+
+test.pass_test()
diff --git a/test/GetBuildFailures/parallel.py b/test/GetBuildFailures/parallel.py
new file mode 100644 (file)
index 0000000..cfadd0d
--- /dev/null
@@ -0,0 +1,127 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# 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.
+#
+
+"""
+Verify that a failed build action with -j works as expected.
+"""
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import TestSCons
+
+_python_ = TestSCons._python_
+
+try:
+    import threading
+except ImportError:
+    # if threads are not supported, then
+    # there is nothing to test
+    TestCmd.no_result()
+    sys.exit()
+
+
+test = TestSCons.TestSCons()
+
+# We want to verify that -j 4 starts all four jobs, the first and last of
+# which fail and the second and third of which succeed, and then stops
+# processing due to the build failures.  To try to control the timing,
+# the created build scripts use marker directories to avoid doing their
+# processing until the previous script has finished.
+
+contents = r"""\
+import os.path
+import sys
+import time
+wait_marker = sys.argv[1] + '.marker'
+write_marker = sys.argv[2] + '.marker'
+if wait_marker != '-.marker':
+    while not os.path.exists(wait_marker):
+        time.sleep(1)
+if sys.argv[0] == 'mypass.py':
+    open(sys.argv[3], 'wb').write(open(sys.argv[4], 'rb').read())
+    exit_value = 0
+elif sys.argv[0] == 'myfail.py':
+    exit_value = 1
+if write_marker != '-.marker':
+    os.mkdir(write_marker)
+sys.exit(exit_value)
+"""
+
+test.write('mypass.py', contents)
+test.write('myfail.py', contents)
+
+test.write('SConstruct', """\
+Command('f3', 'f3.in', r'@%(_python_)s mypass.py -  f3 $TARGET $SOURCE')
+Command('f4', 'f4.in', r'@%(_python_)s myfail.py f3 f4 $TARGET $SOURCE')
+Command('f5', 'f5.in', r'@%(_python_)s myfail.py f4 f5 $TARGET $SOURCE')
+Command('f6', 'f6.in', r'@%(_python_)s mypass.py f5 -  $TARGET $SOURCE')
+
+def print_build_failures():
+    from SCons.Script import GetBuildFailures
+    bf_list = GetBuildFailures()
+    bf_list.sort(lambda a,b: cmp(a.filename, b.filename))
+    for bf in bf_list:
+        print "%%s failed:  %%s" %% (bf.node, bf.errstr)
+
+try:
+    import atexit
+except ImportError:
+    import sys
+    sys.exitfunc = print_build_failures
+else:
+    atexit.register(print_build_failures)
+""" % locals())
+
+test.write('f3.in', "f3.in\n")
+test.write('f4.in', "f4.in\n")
+test.write('f5.in', "f5.in\n")
+test.write('f6.in', "f6.in\n")
+
+expect_stdout = """\
+scons: Reading SConscript files ...
+scons: done reading SConscript files.
+scons: Building targets ...
+scons: building terminated because of errors.
+f4 failed:  Error 1
+f5 failed:  Error 1
+""" % locals()
+
+expect_stderr = """\
+scons: *** [f4] Error 1
+scons: *** [f5] Error 1
+"""
+
+test.run(arguments = '-j 4 .',
+         status = 2,
+         stdout = expect_stdout,
+         stderr = expect_stderr)
+
+test.must_match(test.workpath('f3'), 'f3.in\n')
+test.must_not_exist(test.workpath('f4'))
+test.must_not_exist(test.workpath('f5'))
+test.must_match(test.workpath('f6'), 'f6.in\n') 
+
+
+
+test.pass_test()
diff --git a/test/GetBuildFailures/serial.py b/test/GetBuildFailures/serial.py
new file mode 100644 (file)
index 0000000..c8205ed
--- /dev/null
@@ -0,0 +1,115 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# 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.
+#
+
+"""
+Verify that the GetBuildFailures() function returns a list of
+BuildError exceptions.  Also verify printing the BuildError
+attributes we expect to be most commonly used.
+"""
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import TestSCons
+
+_python_ = TestSCons._python_
+
+try:
+    import threading
+except ImportError:
+    # if threads are not supported, then
+    # there is nothing to test
+    TestCmd.no_result()
+    sys.exit()
+
+
+test = TestSCons.TestSCons()
+
+contents = r"""\
+import sys
+if sys.argv[0] == 'mypass.py':
+    open(sys.argv[3], 'wb').write(open(sys.argv[4], 'rb').read())
+    exit_value = 0
+elif sys.argv[0] == 'myfail.py':
+    exit_value = 1
+sys.exit(exit_value)
+"""
+
+test.write('mypass.py', contents)
+test.write('myfail.py', contents)
+
+test.write('SConstruct', """\
+Command('f3', 'f3.in', r'@%(_python_)s mypass.py -  f3 $TARGET $SOURCE')
+Command('f4', 'f4.in', r'@%(_python_)s myfail.py f3 f4 $TARGET $SOURCE')
+Command('f5', 'f5.in', r'@%(_python_)s myfail.py f4 f5 $TARGET $SOURCE')
+Command('f6', 'f6.in', r'@%(_python_)s mypass.py f5 -  $TARGET $SOURCE')
+
+def print_build_failures():
+    from SCons.Script import GetBuildFailures
+    import string
+    bf_list = GetBuildFailures()
+    bf_list.sort(lambda a,b: cmp(a.filename, b.filename))
+    for bf in bf_list:
+        print "%%s failed (%%s):  %%s" %% (bf.node, bf.status, bf.errstr)
+        print "    %%s" %% string.join(bf.command)
+
+try:
+    import atexit
+except ImportError:
+    import sys
+    sys.exitfunc = print_build_failures
+else:
+    atexit.register(print_build_failures)
+""" % locals())
+
+test.write('f3.in', "f3.in\n")
+test.write('f4.in', "f4.in\n")
+test.write('f5.in', "f5.in\n")
+test.write('f6.in', "f6.in\n")
+
+expect_stdout = """\
+scons: Reading SConscript files ...
+scons: done reading SConscript files.
+scons: Building targets ...
+scons: building terminated because of errors.
+f4 failed (1):  Error 1
+    %(_python_)s myfail.py f3 f4 "f4" "f4.in"
+""" % locals()
+
+expect_stderr = """\
+scons: *** [f4] Error 1
+"""
+
+test.run(arguments = '.',
+         status = 2,
+         stdout = expect_stdout,
+         stderr = expect_stderr)
+
+test.must_match(test.workpath('f3'), 'f3.in\n')
+test.must_not_exist(test.workpath('f4'))
+test.must_not_exist(test.workpath('f5'))
+test.must_not_exist(test.workpath('f6'))
+
+
+
+test.pass_test()
diff --git a/test/Glob/BuildDir.py b/test/Glob/BuildDir.py
new file mode 100644 (file)
index 0000000..274ca49
--- /dev/null
@@ -0,0 +1,71 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# 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.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that default use of the Glob() function within a BuildDir()
+finds the local file Nodes.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.subdir('src')
+
+test.write('SConstruct', """\
+BuildDir('var1', 'src')
+BuildDir('var2', 'src')
+
+SConscript('var1/SConscript')
+SConscript('var2/SConscript')
+""")
+
+test.write(['src', 'SConscript'], """\
+env = Environment() 
+
+def concatenate(target, source, env):
+    fp = open(str(target[0]), 'wb')
+    for s in source:
+        fp.write(open(str(s), 'rb').read())
+    fp.close()
+
+env['BUILDERS']['Concatenate'] = Builder(action=concatenate)
+
+f_in = Glob('f*.in')
+f_in.sort(lambda a,b: cmp(a.name, b.name))
+env.Concatenate('f.out', f_in)
+""")
+
+test.write(['src', 'f1.in'], "src/f1.in\n")
+test.write(['src', 'f2.in'], "src/f2.in\n")
+test.write(['src', 'f3.in'], "src/f3.in\n")
+
+test.run(arguments = '.')
+
+test.must_match(['var1', 'f.out'], "src/f1.in\nsrc/f2.in\nsrc/f3.in\n")
+test.must_match(['var2', 'f.out'], "src/f1.in\nsrc/f2.in\nsrc/f3.in\n")
+
+test.pass_test()
diff --git a/test/Glob/Repository.py b/test/Glob/Repository.py
new file mode 100644 (file)
index 0000000..6cef443
--- /dev/null
@@ -0,0 +1,117 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# 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.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that the Glob() function finds files in repositories.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.subdir('work',
+            'repository',
+            ['repository', 'src'],
+            ['repository', 'src', 'sub1'],
+            ['repository', 'src', 'sub2'])
+
+work_aaa = test.workpath('work', 'aaa')
+work_bbb = test.workpath('work', 'bbb')
+work_ccc = test.workpath('work', 'ccc')
+work_src_xxx = test.workpath('work', 'src', 'xxx')
+work_src_yyy = test.workpath('work', 'src', 'yyy')
+
+opts = "-Y " + test.workpath('repository')
+
+test.write(['repository', 'SConstruct'], """\
+def cat(env, source, target):
+    target = str(target[0])
+    source = map(str, source)
+    f = open(target, "wb")
+    for src in source:
+        f.write(open(src, "rb").read())
+    f.close()
+
+env = Environment(BUILDERS={'Build':Builder(action=cat)})
+env.Build('aaa.out', Glob('a*.in'))
+env.Build('bbb.out', Glob('b*.in'))
+env.Build('ccc.out', Glob('c*.in'))
+SConscript('src/SConscript', "env")
+""")
+
+test.write(['repository', 'aaa.in'], "repository/aaa.in\n")
+test.write(['repository', 'bbb.in'], "repository/bbb.in\n")
+test.write(['repository', 'ccc.in'], "repository/ccc.in\n")
+
+test.write(['repository', 'src', 'SConscript'], """
+Import("env")
+env.Build('xxx.out', Glob('x*.in'))
+env.Build('yyy.out', Glob('yy?.in'))
+zzz_in = Glob('*/zzz.in')
+zzz_in.sort(lambda a,b: cmp(a.abspath, b.abspath))
+env.Build('zzz.out', zzz_in)
+""")
+
+test.write(['repository', 'src', 'xxx.in'], "repository/src/xxx.in\n")
+test.write(['repository', 'src', 'yyy.in'], "repository/src/yyy.in\n")
+test.write(['repository', 'src', 'sub1', 'zzz.in'], "repository/src/sub1/zzz.in\n")
+test.write(['repository', 'src', 'sub2', 'zzz.in'], "repository/src/sub2/zzz.in\n")
+
+#
+# Make the repository non-writable,
+# so we'll detect if we try to write into it accidentally.
+test.writable('repository', 0)
+
+#
+test.run(chdir = 'work', options = opts, arguments = 'aaa.out')
+
+test.must_match(['work', 'aaa.out'], "repository/aaa.in\n")
+test.must_not_exist(test.workpath('work', 'bbb.out'))
+test.must_not_exist(test.workpath('work', 'ccc.out'))
+test.must_not_exist(test.workpath('work', 'src', 'xxx.out'))
+test.must_not_exist(test.workpath('work', 'src', 'yyy.out'))
+
+test.run(chdir = 'work', options = opts, arguments = 'bbb.out src')
+
+test.must_match(['work', 'bbb.out'], "repository/bbb.in\n")
+test.must_not_exist(test.workpath('work', 'ccc.out'))
+test.must_match(['work', 'src', 'xxx.out'], "repository/src/xxx.in\n")
+test.must_match(['work', 'src', 'yyy.out'], "repository/src/yyy.in\n")
+
+expect = """\
+repository/src/sub1/zzz.in
+repository/src/sub2/zzz.in
+"""
+
+test.must_match(['work', 'src', 'zzz.out'], expect)
+
+#
+test.run(chdir = 'work', options = opts, arguments = '.')
+
+test.must_match(['work', 'ccc.out'], "repository/ccc.in\n")
+
+#
+test.pass_test()
diff --git a/test/Glob/basic.py b/test/Glob/basic.py
new file mode 100644 (file)
index 0000000..2e7427a
--- /dev/null
@@ -0,0 +1,59 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# 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.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify basic operation of the Glob() function.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+env = Environment() 
+
+def concatenate(target, source, env):
+    fp = open(str(target[0]), 'wb')
+    for s in source:
+        fp.write(open(str(s), 'rb').read())
+    fp.close()
+
+env['BUILDERS']['Concatenate'] = Builder(action=concatenate)
+
+f_in = Glob('f*.in')
+f_in.sort(lambda a,b: cmp(a.name, b.name))
+env.Concatenate('f.out', f_in)
+""")
+
+test.write('f1.in', "f1.in\n")
+test.write('f2.in', "f2.in\n")
+test.write('f3.in', "f3.in\n")
+
+test.run(arguments = '.')
+
+test.must_match('f.out', "f1.in\nf2.in\nf3.in\n")
+
+test.pass_test()
diff --git a/test/Glob/source.py b/test/Glob/source.py
new file mode 100644 (file)
index 0000000..b82e1d9
--- /dev/null
@@ -0,0 +1,88 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# 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.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that use of the Glob() function within a BuildDir() returns the
+file Nodes in the source directory when the source= keyword argument is
+specified (and duplicate=0 is specified for the BuildDir()).
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.subdir('src', 'var1', 'var2')
+
+test.write('SConstruct', """\
+env = Environment() 
+
+def concatenate(target, source, env):
+    fp = open(str(target[0]), 'wb')
+    for s in source:
+        fp.write(open(str(s), 'rb').read())
+    fp.close()
+
+env['BUILDERS']['Concatenate'] = Builder(action=concatenate)
+
+Export("env")
+
+BuildDir('var1', 'src', duplicate=0)
+BuildDir('var2', 'src', duplicate=0)
+
+SConscript('var1/SConscript')
+SConscript('var2/SConscript')
+""")
+
+test.write(['var1', 'SConscript'], """\
+Import("env")
+
+f_in = Glob('f[45].in', source=True)
+f_in.sort(lambda a,b: cmp(a.name, b.name))
+env.Concatenate('f.out', f_in)
+""")
+
+test.write(['var2', 'SConscript'], """\
+Import("env")
+
+f_in = Glob('f[67].in')
+f_in.sort(lambda a,b: cmp(a.name, b.name))
+env.Concatenate('f.out', f_in)
+""")
+
+test.write(['src', 'f1.in'], "src/f1.in\n")
+test.write(['src', 'f2.in'], "src/f2.in\n")
+
+test.write(['src', 'f4.in'], "src/f4.in\n")
+test.write(['src', 'f5.in'], "src/f5.in\n")
+test.write(['src', 'f6.in'], "src/f6.in\n")
+test.write(['src', 'f7.in'], "src/f7.in\n")
+
+test.run(arguments = '.')
+
+test.must_match(['var1', 'f.out'], "src/f4.in\nsrc/f5.in\n")
+test.must_match(['var2', 'f.out'], "src/f6.in\nsrc/f7.in\n")
+
+test.pass_test()
diff --git a/test/Glob/strings.py b/test/Glob/strings.py
new file mode 100644 (file)
index 0000000..d9f8ff1
--- /dev/null
@@ -0,0 +1,72 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# 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.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that use of the Glob() function with the strings= option works
+when they're used in the same SConscript file (and therefore the same
+directory) as input to a Builder call.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.subdir('src')
+
+test.write('SConstruct', """\
+BuildDir('var1', 'src')
+BuildDir('var2', 'src')
+
+SConscript('var1/SConscript')
+SConscript('var2/SConscript')
+""")
+
+test.write(['src', 'SConscript'], """\
+env = Environment() 
+
+def concatenate(target, source, env):
+    fp = open(str(target[0]), 'wb')
+    for s in source:
+        fp.write(open(str(s), 'rb').read())
+    fp.close()
+
+env['BUILDERS']['Concatenate'] = Builder(action=concatenate)
+
+f_in = Glob('f*.in', strings=True)
+f_in.sort()
+env.Concatenate('f.out', f_in)
+""")
+
+test.write(['src', 'f1.in'], "src/f1.in\n")
+test.write(['src', 'f2.in'], "src/f2.in\n")
+test.write(['src', 'f3.in'], "src/f3.in\n")
+
+test.run(arguments = '.')
+
+test.must_match(['var1', 'f.out'], "src/f1.in\nsrc/f2.in\nsrc/f3.in\n")
+test.must_match(['var2', 'f.out'], "src/f1.in\nsrc/f2.in\nsrc/f3.in\n")
+
+test.pass_test()
diff --git a/test/Glob/subdir.py b/test/Glob/subdir.py
new file mode 100644 (file)
index 0000000..b4e89e8
--- /dev/null
@@ -0,0 +1,60 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# 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.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that Glob() works to find Nodes underneath an explicitly-
+named subdirectory.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.subdir('subdir')
+
+test.write('SConstruct', """\
+env = Environment() 
+
+def concatenate(target, source, env):
+    fp = open(str(target[0]), 'wb')
+    for s in source:
+        fp.write(open(str(s), 'rb').read())
+    fp.close()
+
+env['BUILDERS']['Concatenate'] = Builder(action=concatenate)
+
+f_in = Glob('subdir/*.in')
+f_in.sort(lambda a,b: cmp(a.name, b.name))
+env.Concatenate('f.out', f_in)
+""")
+
+test.write(['subdir', 'file.in'], "subdir/file.in\n")
+
+test.run(arguments = '.')
+
+test.must_match('f.out', "subdir/file.in\n")
+
+test.pass_test()
diff --git a/test/Glob/subst.py b/test/Glob/subst.py
new file mode 100644 (file)
index 0000000..e6aa3ca
--- /dev/null
@@ -0,0 +1,60 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# 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.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify the ability to Glob() using a pattern from a construction variable
+expansion.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+env = Environment(PATTERN = 'f*.in')
+
+def copy(target, source, env):
+    fp = open(str(target[0]), 'wb')
+    for s in source:
+        fp.write(open(str(s), 'rb').read())
+    fp.close()
+
+env['BUILDERS']['Copy'] = Builder(action=copy)
+
+f_in = env.Glob('$PATTERN')
+f_in.sort(lambda a,b: cmp(a.name, b.name))
+env.Copy('f.out', f_in)
+""")
+
+test.write('f1.in', "f1.in\n")
+test.write('f2.in', "f2.in\n")
+test.write('f3.in', "f3.in\n")
+
+test.run(arguments = '.')
+
+test.must_match('f.out', "f1.in\nf2.in\nf3.in\n")
+
+test.pass_test()
diff --git a/test/Install/Clone.py b/test/Install/Clone.py
new file mode 100644 (file)
index 0000000..65770d2
--- /dev/null
@@ -0,0 +1,69 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# 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.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that we can Install() and InstallAs() from a construction
+environment cloned from a clone.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """
+env1 = Environment(DESTDIR='sub1', tools=[])
+
+# Call env1.Install() but not env1.InstallAs() *before* we clone it.
+# This is to verify that re-initializing the Install() attribute on the
+# construction environment doesn't mess up the environment settings in
+# a way that leaves the InstallAs() intializer in place, which leads to
+# infinite recursion.
+env1.Install('$DESTDIR', 'foo.in')
+
+env2 = env1.Clone(DESTDIR='sub2')
+env3 = env2.Clone(DESTDIR='sub3')
+
+env2.Install('$DESTDIR', 'foo.in')
+env3.Install('$DESTDIR', 'foo.in')
+
+env1.InstallAs('$DESTDIR/foo.out', 'foo.in')
+env2.InstallAs('$DESTDIR/foo.out', 'foo.in')
+env3.InstallAs('$DESTDIR/foo.out', 'foo.in')
+""")
+
+test.write('foo.in', "foo.in\n")
+
+test.run(arguments = '.')
+
+test.must_match(['sub1', 'foo.in'], "foo.in\n")
+test.must_match(['sub2', 'foo.in'], "foo.in\n")
+test.must_match(['sub3', 'foo.in'], "foo.in\n")
+
+test.must_match(['sub1', 'foo.out'], "foo.in\n")
+test.must_match(['sub2', 'foo.out'], "foo.in\n")
+test.must_match(['sub3', 'foo.out'], "foo.in\n")
+
+test.pass_test()
index cf318133ef0e9e8c8b0a0e0cfaa184bf9462a28a..94740b89f2347fb1d28408215543096e00480fbb 100644 (file)
@@ -64,6 +64,7 @@ test.write('SConstruct', """
 env = Environment(SHCCCOM = r'%(_python_)s mycc.py $TARGET $SOURCES',
                   SHLINKCOM = r'%(_python_)s mylink.py $TARGET $SOURCES',
                   SHLINKCOMSTR = 'Linking shared $TARGET from $SOURCES',
+                  SHOBJPREFIX = '',
                   SHOBJSUFFIX = '.obj',
                   SHLIBPREFIX = '',
                   SHLIBSUFFIX = '.dll')
index 3a04b9c67801fc97684cc9c0ef6f23f0588099ce..4bcb2c7deba2d606f49e3fb20f9c60536303439e 100644 (file)
@@ -114,7 +114,7 @@ void f3c(void);
 int
 main(int argc, char *argv[])
 {
-        argv[argc++] = "--";
+        argv[argc++] = (char *)"--";
         f1();
         f2a();
         f2b();
index f359fb97b3932e14c3ca806639b63e9af5115f4d..feb3fd97bd584ab3d2add7ca2c85bbe36e0dfe14 100644 (file)
@@ -46,7 +46,7 @@ if sys.platform != 'win32':
     test.skip_test(msg)\r
 \r
 test.write('SConstruct', """\\r
-env = Environment(PDB = '${TARGET}.pdb')\r
+env = Environment(PDB = '${TARGET.base}.pdb')\r
 env.Program('test1.cpp')\r
 env.Program('test2.cpp')\r
 """)\r
@@ -75,9 +75,9 @@ main(int argc, char *argv)
 \r
 test.run(arguments = '.')\r
 \r
-test.must_exist('test1%s'     % _exe)\r
-test.must_exist('test1%s.pdb' % _exe)\r
-test.must_exist('test2%s'     % _exe)\r
-test.must_exist('test2%s.pdb' % _exe)\r
+test.must_exist('test1%s' % _exe)\r
+test.must_exist('test1.pdb')\r
+test.must_exist('test2%s' % _exe)\r
+test.must_exist('test2.pdb')\r
 \r
 test.pass_test()\r
index cbf465ec1e25408dc8c0151e6347a0c069752794..00b222b804cf046a0348ef2d7e1e0e3748599298 100644 (file)
@@ -38,6 +38,7 @@ test.subdir('work1', 'work2')
 
 test.write(['work1', 'SConstruct'], """
 Execute(Mkdir('d1'))
+Execute(Mkdir(Dir('#d1-Dir')))
 def cat(env, source, target):
     target = str(target[0])
     source = map(str, source)
@@ -64,7 +65,7 @@ test.write(['work1', 'f2.in'], "f2.in\n")
 test.write(['work1', 'f5.in'], "f5.in\n")
 test.write(['work1', 'f6.in'], "f6.in\n")
 
-expect = test.wrap_stdout(read_str = 'Mkdir("d1")\n',
+expect = test.wrap_stdout(read_str = 'Mkdir("d1")\nMkdir("d1-Dir")\n',
                           build_str = """\
 cat(["f2.out"], ["f2.in"])
 Mkdir("d3")
@@ -79,6 +80,7 @@ Touch("%s")
 test.run(chdir = 'work1', options = '-n', arguments = '.', stdout = expect)
 
 test.must_not_exist(['work1', 'd1'])
+test.must_not_exist(['work1', 'd1-Dir'])
 test.must_not_exist(['work1', 'f2.out'])
 test.must_not_exist(['work1', 'd3'])
 test.must_not_exist(['work1', 'd4'])
@@ -90,6 +92,7 @@ test.must_not_exist(['work1', 'f6.out-Mkdir'])
 test.run(chdir = 'work1')
 
 test.must_exist(['work1', 'd1'])
+test.must_exist(['work1', 'd1-Dir'])
 test.must_match(['work1', 'f2.out'], "f2.in\n")
 test.must_exist(['work1', 'd3'])
 test.must_exist(['work1', 'd4'])
index c1cdcfdf7758b34db767cb11d25a0779b1d0628d..ba5558196b128b1b64243b4e1ef872d61a7a84c6 100644 (file)
@@ -34,6 +34,7 @@ test = TestSCons.TestSCons()
 
 test.write('SConstruct', """
 Execute(Move('f1.out', 'f1.in'))
+Execute(Move('File-f1.out', File('f1.in-File')))
 def cat(env, source, target):
     target = str(target[0])
     source = map(str, source)
@@ -50,6 +51,7 @@ env.Command('f6.out', 'f6.in', [Cat, Move("Move-$TARGET", "$SOURCE-Move")])
 """)
 
 test.write('f1.in', "f1.in\n")
+test.write('f1.in-File', "f1.in-File\n")
 test.write('f2.in', "f2.in\n")
 test.write('f3.in', "f3.in\n")
 test.write('f4.in', "f4.in\n")
@@ -57,7 +59,10 @@ test.write('f5.in', "f5.in\n")
 test.write('f6.in', "f6.in\n")
 test.write('f6.in-Move', "f6.in-Move\n")
 
-expect = test.wrap_stdout(read_str = 'Move("f1.out", "f1.in")\n',
+expect = test.wrap_stdout(read_str = """\
+Move("f1.out", "f1.in")
+Move("File-f1.out", "f1.in-File")
+""",
                           build_str = """\
 cat(["f2.out"], ["f2.in"])
 Move("f3.out", "f3.in")
@@ -69,6 +74,7 @@ Move("Move-f6.out", "f6.in-Move")
 test.run(options = '-n', arguments = '.', stdout = expect)
 
 test.must_not_exist('f1.out')
+test.must_not_exist('File-f1.out')
 test.must_not_exist('f2.out')
 test.must_not_exist('f3.out')
 test.must_not_exist('f4.out')
@@ -79,6 +85,7 @@ test.must_not_exist('Move-f6.out')
 test.run()
 
 test.must_match('f1.out', "f1.in\n")
+test.must_match('File-f1.out', "f1.in-File\n")
 test.must_match('f2.out', "f2.in\n")
 test.must_not_exist('f3.in')
 test.must_match('f3.out', "f3.in\n")
index 7e656f7ec6c8f3cd11f6ae09da80715b3cf8d146..b23a8d435cdf6c05f6a3e85c2517462cdc823313 100644 (file)
@@ -59,8 +59,8 @@ barflags = e['SHCXXFLAGS'] + ' -DBAR'
 test.subdir('bld', 'src', ['src', 'subsrcdir'])
 
 sconstruct = r"""
-foo = Environment(SHCXXFLAGS = '%(fooflags)s', WINDOWS_INSERT_DEF=1)
-bar = Environment(SHCXXFLAGS = '%(barflags)s', WINDOWS_INSERT_DEF=1)
+foo = Environment(SHOBJPREFIX='', SHCXXFLAGS = '%(fooflags)s', WINDOWS_INSERT_DEF=1)
+bar = Environment(SHOBJPREFIX='', SHCXXFLAGS = '%(barflags)s', WINDOWS_INSERT_DEF=1)
 src = Dir('src')
 BuildDir('bld', src, duplicate=1)
 Nodes=[]
index da945e35fdfb2279e834ea5231726809972885da..108960da103e47b1498934393c25533965ecd91b 100644 (file)
@@ -100,7 +100,7 @@ extern "C" void f3(void);
 int
 main(int argc, char *argv[])
 {
-        argv[argc++] = "--";
+        argv[argc++] = (char *)"--";
         f1();
         f2();
         f3();
diff --git a/test/Options/chdir.py b/test/Options/chdir.py
new file mode 100644 (file)
index 0000000..7ba85ea
--- /dev/null
@@ -0,0 +1,71 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# 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.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that we can chdir() to the directory in which an Options
+file lives by using the __name__ value.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.subdir('bin', 'subdir')
+
+test.write('SConstruct', """\
+opts = Options('../bin/opts.cfg', ARGUMENTS)
+opts.Add('VARIABLE')
+Export("opts")
+SConscript('subdir/SConscript')
+""")
+
+SConscript_contents = """\
+Import("opts")
+env = Environment()
+opts.Update(env)
+print "VARIABLE =", repr(env['VARIABLE'])
+"""
+
+test.write(['bin', 'opts.cfg'], """\
+import os
+import os.path
+os.chdir(os.path.split(__name__)[0])
+execfile('opts2.cfg')
+""")
+
+test.write(['bin', 'opts2.cfg'], """\
+VARIABLE = 'opts2.cfg value'
+""")
+
+test.write(['subdir', 'SConscript'], SConscript_contents)
+
+expect = """\
+VARIABLE = 'opts2.cfg value'
+"""
+
+test.run(arguments = '-q -Q .', stdout=expect)
+
+test.pass_test()
diff --git a/test/Options/import.py b/test/Options/import.py
new file mode 100644 (file)
index 0000000..0a3d367
--- /dev/null
@@ -0,0 +1,69 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# 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.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that an Options file in a different directory can import
+a module in that directory.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+workpath = test.workpath('')
+
+test.subdir('bin', 'subdir')
+
+test.write('SConstruct', """\
+opts = Options('../bin/opts.cfg', ARGUMENTS)
+opts.Add('VARIABLE')
+Export("opts")
+SConscript('subdir/SConscript')
+""")
+
+SConscript_contents = """\
+Import("opts")
+env = Environment()
+opts.Update(env)
+print "VARIABLE =", env.get('VARIABLE')
+"""
+
+test.write(['bin', 'opts.cfg'], """\
+import sys
+from local_options import VARIABLE
+""" % locals())
+
+test.write(['bin', 'local_options.py'], """\
+VARIABLE = 'bin/local_options.py'
+""")
+
+test.write(['subdir', 'SConscript'], SConscript_contents)
+
+expect = "VARIABLE = bin/local_options.py\n"
+
+test.run(arguments = '-q -Q .', stdout = expect)
+
+test.pass_test()
index 5d4f5e1b16d958d0d484a9ead32c699ce8ba7e60..43015fe894a9a5933e6c5db185dc8c2eb9bd16d4 100644 (file)
@@ -41,34 +41,58 @@ _python_ = TestSCons._python_
 
 test = TestSCons.TestSCons()
 
-test.subdir('work')
+test.subdir('work', ['work', 'sub'])
 
 tar_output = test.workpath('work.tar')
 
-test.write(['work', 'copy.py'], """\
+test.write(['work', 'mycopy.py'], """\
 import sys
 import time
 time.sleep(int(sys.argv[1]))
 open(sys.argv[2], 'wb').write(open(sys.argv[3], 'rb').read())
 """)
 
+test.write(['work', 'mytar.py'], """\
+import sys
+import os.path
+
+def visit(arg, dirname, fnames):
+    fnames.sort()
+    for fn in fnames:
+        p = os.path.join(dirname, fn)
+        if os.path.isfile(p):
+            arg.write(open(p, 'rb').read())
+
+fp = open(sys.argv[1], 'wb')
+for s in sys.argv[2:]:
+    os.path.walk(s, visit, fp)
+""")
+
 test.write(['work', 'SConstruct'], """\
 env = Environment()
-out1 = File('f1.out')
-out2 = File('f2.out')
-env.Command([out1, out1], 'f1.in', r'%(_python_)s copy.py 3 $TARGET $SOURCE')
-env.Command([out2, out2], 'f2.in', r'%(_python_)s copy.py 3 $TARGET $SOURCE')
+out1 = File('sub/f1.out')
+out2 = File('sub/f2.out')
+env.Command([out1, out1], 'sub/f1.in',
+            r'%(_python_)s mycopy.py 3 $TARGET $SOURCE')
+env.Command([out2, out2], 'sub/f2.in',
+            r'%(_python_)s mycopy.py 3 $TARGET $SOURCE')
 
-env.Tar(r'%(tar_output)s', Dir('.'))
+env.Command(r'%(tar_output)s', Dir('sub'),
+            r'%(_python_)s mytar.py $TARGET $SOURCE')
 """ % locals())
 
-test.write(['work', 'f1.in'], "work/f1.in\n")
-test.write(['work', 'f2.in'], "work/f2.in\n")
+test.write(['work', 'sub', 'f1.in'], "work/sub/f1.in\n")
+test.write(['work', 'sub', 'f2.in'], "work/sub/f2.in\n")
 
 test.run(chdir = 'work', arguments = tar_output + ' -j2')
 
-test.must_match(['work', 'f1.out'], "work/f1.in\n")
-test.must_match(['work', 'f2.out'], "work/f2.in\n")
-test.must_exist(tar_output)
+test.must_match(['work', 'sub', 'f1.out'], "work/sub/f1.in\n")
+test.must_match(['work', 'sub', 'f2.out'], "work/sub/f2.in\n")
+test.must_match(tar_output, """\
+work/sub/f1.in
+work/sub/f1.in
+work/sub/f2.in
+work/sub/f2.in
+""")
 
 test.pass_test()
diff --git a/test/Parallel/failed-build.py b/test/Parallel/failed-build.py
new file mode 100644 (file)
index 0000000..64e90c9
--- /dev/null
@@ -0,0 +1,111 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# 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.
+#
+
+"""
+Verify that a failed build action with -j works as expected.
+"""
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import TestSCons
+
+_python_ = TestSCons._python_
+
+try:
+    import threading
+except ImportError:
+    # if threads are not supported, then
+    # there is nothing to test
+    TestCmd.no_result()
+    sys.exit()
+
+
+test = TestSCons.TestSCons()
+
+# We want to verify that -j 2 starts precisely two jobs, the first of
+# which fails and the second of which succeeds, and then stops processing
+# due to the first build failure.  To try to control the timing, the two
+# created build scripts use a pair of marker directories.
+#
+# The failure script waits until it sees the 'mycopy.started' directory
+# that indicates the successful script has, in fact, gotten started.
+# If we don't wait, then SCons could detect our script failure early
+# (typically if a high system load happens to delay SCons' ability to
+# start the next script) and then not start the successful script at all.
+#
+# The successful script waits until it sees the 'myfail.exiting' directory
+# that indicates the failure script has finished (with everything except
+# the final sys.exit(), that is).  If we don't wait for that, then SCons
+# could detect our successful exit first (typically if a high system
+# load happens to delay the failure script) and start another job before
+# it sees the failure from the first script.
+
+test.write('myfail.py', r"""\
+import os.path
+import sys
+import time
+while not os.path.exists('mycopy.started'):
+    time.sleep(1)
+os.mkdir('myfail.exiting')
+sys.exit(1)
+""")
+
+test.write('mycopy.py', r"""\
+import os
+import sys
+import time
+os.mkdir('mycopy.started')
+open(sys.argv[1], 'wb').write(open(sys.argv[2], 'rb').read())
+while not os.path.exists('myfail.exiting'):
+    time.sleep(1)
+sys.exit(0)
+""")
+
+test.write('SConstruct', """
+MyCopy = Builder(action = r'%(_python_)s mycopy.py $TARGET $SOURCE')
+Fail = Builder(action = r'%(_python_)s myfail.py $TARGETS $SOURCE')
+env = Environment(BUILDERS = { 'MyCopy' : MyCopy, 'Fail' : Fail })
+env.Fail(target = 'f3', source = 'f3.in')
+env.MyCopy(target = 'f4', source = 'f4.in')
+env.MyCopy(target = 'f5', source = 'f5.in')
+env.MyCopy(target = 'f6', source = 'f6.in')
+""" % locals())
+
+test.write('f3.in', "f3.in\n")
+test.write('f4.in', "f4.in\n")
+test.write('f5.in', "f5.in\n")
+test.write('f6.in', "f6.in\n")
+
+test.run(arguments = '-j 2 .',
+         status = 2,
+         stderr = "scons: *** [f3] Error 1\n")
+
+test.must_not_exist(test.workpath('f3'))
+test.must_match(test.workpath('f4'), 'f4.in\n')
+test.must_not_exist(test.workpath('f5')) 
+test.must_not_exist(test.workpath('f6'))
+
+
+
+test.pass_test()
index cd4c24a8a245bde81a5719c086f474ce9cadf9b1..d124431160f96823e61f78ddf8aa7a096825fc84 100644 (file)
@@ -204,7 +204,8 @@ test.write(['repository', 'src2', 'xxx', 'main.c'], r"""
 #ifdef  BAR
 #define MAIN_OS         "BAR"
 #endif
-main()
+int
+main(int argc, char *argv[])
 {
         printf(INCLUDE_STRING, INCLUDE_OS);
         printf(XXX_STRING, XXX_OS);
diff --git a/test/Requires/basic.py b/test/Requires/basic.py
new file mode 100644 (file)
index 0000000..59cdd37
--- /dev/null
@@ -0,0 +1,95 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# 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.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify basic operation of the env.Requires() method for specifying
+order-only prerequisites.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """
+def append_prereq_func(target, source, env):
+    fp = open(str(target[0]), 'wb')
+    for s in map(str, source):
+        fp.write(open(s, 'rb').read())
+    fp.write(open('prereq.out', 'rb').read())
+    fp.close()
+    return None
+append_prereq = Action(append_prereq_func)
+env = Environment()
+env.Requires('file.out', 'prereq.out')
+env.Command('file.out', 'file.in', append_prereq)
+env.Command('prereq.out', 'prereq.in', Copy('$TARGET', '$SOURCES'))
+""")
+
+test.write('file.in', "file.in 1\n")
+test.write('prereq.in', "prereq.in 1\n")
+
+# First:  build file.out.  prereq.out should be built first, and if
+# not, we'll get an error when the build action tries to use it to
+# build file.out.
+
+test.run(arguments = 'file.out')
+
+test.must_match('prereq.out', "prereq.in 1\n")
+test.must_match('file.out', "file.in 1\nprereq.in 1\n")
+
+# Update the prereq.out file.  file.out should still be up to date because
+# prereq.out is not actually a dependency, so we don't detect the
+# underlying change.
+
+test.write('prereq.out', "prereq.out 2\n")
+
+test.up_to_date(arguments = 'file.out')
+
+# Now update the prereq.in file.  Trying to rebuild file.out should
+# cause prereq.out to be updated because of the change, but file.out
+# should *not* be rebuilt because, again, prereq.out isn't actually
+# a dependency that causes rebuilds.
+
+test.write('prereq.in', "prereq.in 3\n")
+
+test.run(arguments = 'file.out')
+
+test.must_match('prereq.out', "prereq.in 3\n")
+test.must_match('file.out', "file.in 1\nprereq.in 1\n")
+
+# Now update file.in, which will cause file.out to be rebuilt, picking
+# up the change(s) to prereq.out of which we were previously oblivious.
+
+test.write('file.in', 'file.in 4\n')
+
+test.run(arguments = 'file.out')
+
+test.must_match('prereq.out', "prereq.in 3\n")
+test.must_match('file.out', "file.in 4\nprereq.in 3\n")
+
+
+
+test.pass_test()
diff --git a/test/Scanner/no-Dir-node.py b/test/Scanner/no-Dir-node.py
new file mode 100644 (file)
index 0000000..b5706e3
--- /dev/null
@@ -0,0 +1,136 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# 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.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that use of a Scanner that searches a *PATH list doesn't create
+nodes for directories that don't exist, so they don't get picked up
+by DirScanner.
+
+Under the covers, this tests the behavior of the SCons.Node.FS.find_file()
+utility function that is used by the Scanner.Classic class to search
+directories in variables such as $CPPPATH.
+"""
+
+import os.path
+
+import TestSCons
+
+_python_ = TestSCons._python_
+
+test = TestSCons.TestSCons()
+
+subdir_SConscript = os.path.join('subdir', 'SConscript')
+subdir_foo        = os.path.join('subdir', 'foo')
+subdir_foo_k      = os.path.join('subdir', 'foo.k')
+
+test.subdir('subdir', 'inc1', 'inc2')
+
+inc2_include_h = test.workpath('inc2', 'include.h')
+
+test.write('build.py', r"""
+import os.path
+import string
+import sys
+path = string.split(sys.argv[1])
+input = open(sys.argv[2], 'rb')
+output = open(sys.argv[3], 'wb')
+
+def find_file(f):
+    if os.path.isabs(f):
+        return open(f, 'rb')
+    for dir in path:
+        p = dir + os.sep + f
+        if os.path.exists(p):
+            return open(p, 'rb')
+    return None
+
+def process(infp, outfp):
+    for line in infp.readlines():
+        if line[:8] == 'include ':
+            file = line[8:-1]
+            process(find_file(file), outfp)
+        else:
+            outfp.write(line)
+
+process(input, output)
+
+sys.exit(0)
+""")
+
+test.write('SConstruct', """\
+def foo(target, source, env):
+    children = source[0].children()
+    children.sort(lambda a,b: cmp(a.name, b.name))
+    fp = open(str(target[0]), 'wb')
+    for c in children:
+        fp.write('%s\\n' % c)
+    fp.close()
+Command('list.out', 'subdir', foo, source_scanner = DirScanner)
+SConscript('subdir/SConscript')
+""")
+
+test.write(['subdir', 'SConscript'], """\
+import SCons.Scanner
+kscan = SCons.Scanner.Classic(name = 'kfile',
+                              suffixes = ['.k'],
+                              path_variable = 'KPATH',
+                              regex = r'^include\s+(\S+)$')
+
+env = Environment(KPATH=['.', '..'])
+env.Append(SCANNERS = kscan)
+
+env.Command('foo', 'foo.k', r'%(_python_)s build.py "$KPATH" $SOURCES $TARGET')
+""" % locals())
+
+test.write(['subdir', 'foo.k'], """\
+subdir/foo.k
+include inc1/include.h
+include %(inc2_include_h)s
+""" % locals())
+
+test.write(['inc1', 'include.h'], """\
+inc1/include.h
+""")
+
+test.write(['inc2', 'include.h'], """\
+inc2/include.h
+""")
+
+test.run(arguments = '.')
+
+test.must_match('subdir/foo', """\
+subdir/foo.k
+inc1/include.h
+inc2/include.h
+""")
+
+test.must_match('list.out', """\
+%(subdir_SConscript)s
+%(subdir_foo)s
+%(subdir_foo_k)s
+""" % locals())
+
+test.pass_test()
index b41db25d088af4ee06f657e46bdd1a3129e27d18..6ecc3ff77402bca2b8843fe2bdbe303e8c0da785 100644 (file)
@@ -36,6 +36,7 @@ test = TestSCons.TestSCons()
 
 test.write('SConstruct', """
 Execute(Touch('f1'))
+Execute(Touch(File('f1-File')))
 def cat(env, source, target):
     target = str(target[0])
     source = map(str, source)
@@ -54,13 +55,18 @@ env.Command('f6.out', 'f6.in', [Cat,
 """)
 
 test.write('f1', "f1\n")
+test.write('f1-File', "f1-File\n")
 test.write('f2.in', "f2.in\n")
 test.write('f5.in', "f5.in\n")
 test.write('f6.in', "f6.in\n")
 
-oldtime = os.path.getmtime(test.workpath('f1'))
+old_f1_time = os.path.getmtime(test.workpath('f1'))
+old_f1_File_time = os.path.getmtime(test.workpath('f1-File'))
 
-expect = test.wrap_stdout(read_str = 'Touch("f1")\n',
+expect = test.wrap_stdout(read_str = """\
+Touch("f1")
+Touch("f1-File")
+""",
                           build_str = """\
 cat(["f2.out"], ["f2.in"])
 Touch("f3")
@@ -74,8 +80,10 @@ test.run(options = '-n', arguments = '.', stdout = expect)
 
 test.sleep(2)
 
-newtime = os.path.getmtime(test.workpath('f1'))
-test.fail_test(oldtime != newtime)
+new_f1_time = os.path.getmtime(test.workpath('f1'))
+test.fail_test(old_f1_time != new_f1_time)
+new_f1_File_time = os.path.getmtime(test.workpath('f1-File'))
+test.fail_test(old_f1_File_time != new_f1_File_time)
 
 test.must_not_exist(test.workpath('f2.out'))
 test.must_not_exist(test.workpath('f3'))
@@ -87,8 +95,10 @@ test.must_not_exist(test.workpath('f6.out-Touch'))
 
 test.run()
 
-newtime = os.path.getmtime(test.workpath('f1'))
-test.fail_test(oldtime == newtime)
+new_f1_time = os.path.getmtime(test.workpath('f1'))
+test.fail_test(old_f1_time == new_f1_time)
+new_f1_File_time = os.path.getmtime(test.workpath('f1-File'))
+test.fail_test(old_f1_File_time == new_f1_File_time)
 
 test.must_match('f2.out', "f2.in\n")
 test.must_exist(test.workpath('f3'))
diff --git a/test/implicit-cache/DualTargets.py b/test/implicit-cache/DualTargets.py
new file mode 100644 (file)
index 0000000..cf41640
--- /dev/null
@@ -0,0 +1,137 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# 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.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Test that --implicit-cache works correctly in conjonction with a
+builder that produces multiple targets.
+"""
+
+import string
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+import os.path
+
+def emitter(target, source, env):
+    tgt0 = target[0].abspath
+    base,ext = os.path.splitext(tgt0)
+    target.append(base + '.b')
+    return(target, source)
+
+
+def source_scan(node, env, path):
+    path = node.abspath
+    base,ext = os.path.splitext(path)
+    return [base + '.lib']
+
+
+env = Environment()
+env['BUILDERS']['DualTarget'] = Builder(
+    action = Action(
+        [ 
+            Copy( '$TARGET', '$SOURCE' ), 
+            Copy( '${TARGET.base}.b', '$SOURCE' ), 
+            ],
+        ),
+    suffix = '.a',
+    src_suffix = '.cpp',
+    single_source = True,
+    emitter=emitter,
+    source_scanner=Scanner(source_scan),
+    )
+
+env.Command( 'x.cpp', '', Touch('$TARGET') )
+env.Command( 'x.lib', '', Touch('$TARGET') )
+
+env.DualTarget('x.cpp')
+""" % locals())
+
+test.must_not_exist('x.cpp')
+test.must_not_exist('x.lib')
+test.must_not_exist('x.a')
+test.must_not_exist('x.b')
+
+# Build everything first.
+test.run(arguments = '.')
+test.must_exist('x.cpp')
+test.must_exist('x.lib')
+test.must_exist('x.a')
+test.must_exist('x.b')
+
+test.fail_test(string.find(test.stdout(), 'Copy') == -1)
+
+# Double check that targets are not rebuilt.
+test.run(arguments = '.')
+test.must_exist('x.cpp')
+test.must_exist('x.lib')
+test.must_exist('x.a')
+test.must_exist('x.b')
+
+test.fail_test(string.find(test.stdout(), 'Copy') != -1)
+
+# Double check that targets are not rebuilt even with --implicit-cache
+test.run(arguments = '--implicit-cache x.a')
+test.must_exist('x.cpp')
+test.must_exist('x.lib')
+test.must_exist('x.a')
+test.must_exist('x.b')
+
+test.fail_test(string.find(test.stdout(), 'Copy') != -1)
+
+# Double check that targets are not rebuilt even with --implicit-cache
+# a second time.
+test.run(arguments = '--implicit-cache x.a')
+test.must_exist('x.cpp')
+test.must_exist('x.lib')
+test.must_exist('x.a')
+test.must_exist('x.b')
+
+test.fail_test(string.find(test.stdout(), 'Copy') != -1)
+
+# Double check that targets are not rebuilt if we reran without
+# --implicit-cache
+test.run(arguments = '.')
+test.must_exist('x.cpp')
+test.must_exist('x.lib')
+test.must_exist('x.a')
+test.must_exist('x.b')
+
+test.fail_test(string.find(test.stdout(), 'Copy') != -1)
+
+# Double check again
+test.run(arguments = '.')
+test.must_exist('x.cpp')
+test.must_exist('x.lib')
+test.must_exist('x.a')
+test.must_exist('x.b')
+
+test.fail_test(string.find(test.stdout(), 'Copy') != -1)
+
+# Then only of the targets using --implicit-cache
+test.pass_test()
diff --git a/test/no-global-dependencies.py b/test/no-global-dependencies.py
new file mode 100644 (file)
index 0000000..3cdea1b
--- /dev/null
@@ -0,0 +1,170 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# 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.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Test that files are correctly located in the build directory even when
+Scons does not have a global view of all targets.
+
+Sometimes, it might be interesting to not tell scons about every
+targets. For example, one might not read in all the SConscipts of a
+hierarchical build for a particular invocation of scons. This would be
+done to speed-up a partial rebuild when the developer knows that only
+a subset of the targets need to be rebuilt.
+"""
+
+import string
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.subdir('dir1')
+test.subdir('dir2')
+
+test.write('SConstruct', """\
+opts = Options()
+opts.AddOptions(
+    BoolOption('view_all_dependencies', 'View all dependencies', True),
+    BoolOption('duplicate', 'Duplicate sources to build dir', True)
+)
+
+env = Environment(options=opts)
+Export('env')
+
+SConscript(dirs='.', build_dir='build', duplicate=env['duplicate'])
+""" % locals())
+
+
+test.write('SConscript', """\
+Import('env')
+
+if env['view_all_dependencies']:
+    SConscript(dirs='dir1')
+
+SConscript(dirs='dir2')
+""" % locals())
+
+test.write('dir1/SConscript', """\
+Import('env')
+
+env.Command('x.cpp', '', Touch('$TARGET'))
+
+env.Object(env.File('x.cpp'))
+""" % locals())
+
+test.write('dir2/SConscript', """\
+Import('env')
+
+env.Object(env.File('#/build/dir1/x.cpp'))
+""" % locals())
+
+test.must_not_exist('build/dir1/x.cpp')
+
+
+############################################################
+#
+# Test without duplication
+#
+
+# Build everything first.
+test.run(arguments = 'duplicate=False view_all_dependencies=True .')
+test.must_exist('build/dir1/x.cpp')
+test.fail_test(string.find(test.stdout(), "`.' is up to date.") != -1)
+
+# Double check that targets are not rebuilt.
+test.run(arguments = 'duplicate=False view_all_dependencies=True .')
+test.must_exist('build/dir1/x.cpp')
+test.fail_test(string.find(test.stdout(), "`.' is up to date.") == -1)
+
+# Clean-up only the object file
+test.run(arguments = 'duplicate=False view_all_dependencies=False -c .')
+test.must_exist('build/dir1/x.cpp')
+
+# Rebuild the only object file without seeing all the dependencies.
+test.run(arguments = 'duplicate=False view_all_dependencies=False .')
+test.must_exist('build/dir1/x.cpp')
+test.fail_test(string.find(test.stdout(), "`.' is up to date.") != -1)
+
+# Double check that targets are not rebuilt without and with all the
+# dependencies.
+test.run(arguments = 'duplicate=False view_all_dependencies=False .')
+test.must_exist('build/dir1/x.cpp')
+test.fail_test(string.find(test.stdout(), "`.' is up to date.") == -1)
+
+test.run(arguments = 'duplicate=False view_all_dependencies=True .')
+test.must_exist('build/dir1/x.cpp')
+test.fail_test(string.find(test.stdout(), "`.' is up to date.") == -1)
+
+# Clean-up everything.
+test.run(arguments = 'duplicate=False view_all_dependencies=True -c .')
+test.must_not_exist('build/dir1/x.cpp')
+
+
+############################################################
+#
+# Test with duplication
+#
+# FIXME: This can not work for now because there is no way that SCons
+# can differentiate between a source that no longer exists and a file
+# that has a builder that scons does not know about because scons has
+# not parsed all the SConscript of a given project.
+#
+
+# # Build everything first.
+# test.run(arguments = 'duplicate=True view_all_dependencies=True .')
+# test.must_exist('build/dir1/x.cpp')
+# test.fail_test(string.find(test.stdout(), "`.' is up to date.") != -1)
+
+# # Double check that targets are not rebuilt.
+# test.run(arguments = 'duplicate=True view_all_dependencies=True .')
+# test.must_exist('build/dir1/x.cpp')
+# test.fail_test(string.find(test.stdout(), "`.' is up to date.") == -1)
+
+# # Clean-up only the object file
+# test.run(arguments = 'duplicate=True view_all_dependencies=False -c .')
+# test.must_exist('build/dir1/x.cpp')
+
+# # Rebuild the only object file without seeing all the dependencies.
+# test.run(arguments = 'duplicate=True view_all_dependencies=False .')
+# test.must_exist('build/dir1/x.cpp')
+# test.fail_test(string.find(test.stdout(), "`.' is up to date.") != -1)
+
+# # Double check that targets are not rebuilt without and with all the
+# # dependencies.
+# test.run(arguments = 'duplicate=True view_all_dependencies=False .')
+# test.must_exist('build/dir1/x.cpp')
+# test.fail_test(string.find(test.stdout(), "`.' is up to date.") == -1)
+
+# test.run(arguments = 'duplicate=True view_all_dependencies=True .')
+# test.must_exist('build/dir1/x.cpp')
+# test.fail_test(string.find(test.stdout(), "`.' is up to date.") == -1)
+
+# # Clean-up everything.
+# test.run(arguments = 'duplicate=True view_all_dependencies=True -c .')
+# test.must_not_exist('build/dir1/x.cpp')
+
+
+test.pass_test()
index bc36f08f321cc523d2d7a140db0795ec437e1140..ffb290cd0de165f7ef743d2658f68e012b9beac2 100644 (file)
@@ -218,42 +218,5 @@ start2, finish1 = RunTest('-j 1 f1 f2', "fourth")
 test.fail_test(start2 < finish1)
 
 
-# Test that a failed build with -j works properly.
-
-test.write('mycopy.py', r"""\
-import sys
-import time
-time.sleep(1)
-open(sys.argv[1], 'wb').write(open(sys.argv[2], 'rb').read())
-""")
-
-test.write('myfail.py', r"""\
-import sys
-sys.exit(1)
-""")
-
-test.write('SConstruct', """
-MyCopy = Builder(action = r'%(_python_)s mycopy.py $TARGET $SOURCE')
-Fail = Builder(action = r'%(_python_)s myfail.py $TARGETS $SOURCE')
-env = Environment(BUILDERS = { 'MyCopy' : MyCopy, 'Fail' : Fail })
-env.Fail(target = 'f3', source = 'f3.in')
-env.MyCopy(target = 'f4', source = 'f4.in')
-env.MyCopy(target = 'f5', source = 'f5.in')
-env.MyCopy(target = 'f6', source = 'f6.in')
-""" % locals())
-
-test.write('f3.in', "f3.in\n")
-test.write('f4.in', "f4.in\n")
-test.write('f5.in', "f5.in\n")
-test.write('f6.in', "f6.in\n")
-
-test.run(arguments = '-j 2 .',
-         status = 2,
-         stderr = "scons: *** [f3] Error 1\n")
-
-test.fail_test(os.path.exists(test.workpath('f3')))
-test.fail_test(test.read(test.workpath('f4')) != 'f4.in\n')
-test.fail_test(os.path.exists(test.workpath('f5'))) 
-test.fail_test(os.path.exists(test.workpath('f6'))) 
 
 test.pass_test()
index 06296b91d4ed15c821ac8732df9942a6634ce61f..3ef396ea17f6c2f04b2a6bd84e1d2ce4606eb9e9 100644 (file)
@@ -56,6 +56,7 @@ int main(int argc, char *argv[])
 
 test.write('bar.c', """
 #include "bar.h"
+int local = 1;
 """)
 
 test.write('foo.h', """
index 52d64a9d5649e1832b14c7575192eac2167dced5..172cbb089d4b98a20b81dd327e846cc63ca1261b 100644 (file)
@@ -56,6 +56,7 @@ int main(int argc, char *argv[])
 
 test.write('bar.c', """
 #include "bar.h"
+int local = 1;
 """)
 
 test.write('foo.h', """
index d25b7fa2741bc4f45fbe9a7cf95f3ce6a7ec73d1..bf65dbb989fbfa56a8f60dad1509bfc7a79cb455 100644 (file)
@@ -60,6 +60,7 @@ int main(int argc, char *argv[])
 
 test.write('bar.c', """
 #include "bar.h"
+int local = 1;
 """)
 
 test.write('foo.h', """
index 09cdffb7ca063bdf6656d9d95bf62d2caf285b5e..f581bc484657aeb3fa3c847ce44b75642159a190 100644 (file)
@@ -64,6 +64,7 @@ int main(int argc, char *argv[])
 
 test.write('Bar.c', """
 #include "Bar.h"
+int local = 1;
 """)
 
 test.write('Foo.h', """
index 7940d471d355421b220b7fb92375275ac7010c41..163d286a3a54ff93f52a77d63a61384c25c139b1 100644 (file)
@@ -64,6 +64,7 @@ int main(int argc, char *argv[])
 
 test.write('Bar.c', """
 #include "Bar.h"
+int local = 1;
 """)
 
 test.write('Foo.h', """
index 3ccada8e287f814c8e19bf62f60e29b20054d97f..43735f8cf98a55a6f2117944716096358d3794da 100644 (file)
@@ -56,6 +56,7 @@ int main(int argc, char *argv[])
 
 test.write('bar.c', """
 #include "bar.h"
+int local = 1;
 """)
 
 test.write('foo.h', """
index 3151c057d6d63d1a95ba787981da0e87e5638a5a..bbf273f9deb682641e11a42d160bbf0aa40a5987 100644 (file)
@@ -79,4 +79,21 @@ test.run(arguments='', stderr = None)
 test.must_exist( 'src-1.0.zip' )
 test.must_exist( 'src-1.0.tar.gz' )
 
+test.write('SConstruct', """
+Program( 'src/main.c' )
+env=Environment(tools=['default', 'packaging'])
+env.Package( PACKAGETYPE  = ['src_zip', 'src_targz'],
+             NAME = "src", VERSION = "1.0",
+             PACKAGEROOT  = 'test',
+             source       = [ 'src/main.c', 'SConstruct' ],
+             target       = 'src.zip' )
+""")
+
+test.run(arguments='', stderr = None)
+
+test.must_exist( 'src.zip' )
+test.must_exist( 'src-1.0.tar.gz' )
+
+
+
 test.pass_test()
index 12a6c6c2cce44085f4b1e473615fd94d40f93f7f..b9fcc0fb7fb31a95a5070befd232cc7d628d4cd0 100644 (file)
@@ -31,7 +31,6 @@ Test the ability to create a rpm package from a explicit target name.
 import os
 import TestSCons
 
-machine = TestSCons.machine
 _python_ = TestSCons._python_
 
 test = TestSCons.TestSCons()
@@ -81,15 +80,10 @@ env.Package( NAME           = 'foo',
         )
 """ % locals())
 
-test.run(arguments='', stderr = None)
+expect = """
+scons: *** Setting target is not supported for rpm.
+""" + test.python_file_line(test.workpath('SConstruct'), 24)
 
-src_rpm = 'foo-1.2.3-0.src.rpm'
-machine_rpm = 'foo-1.2.3-0.%s.rpm' % machine
-
-test.must_exist( machine_rpm )
-test.must_exist( src_rpm )
-test.must_not_exist( 'bin/main' )
-test.fail_test( not os.popen('rpm -qpl %s' % machine_rpm).read()=='/bin/main\n')
-test.fail_test( not os.popen('rpm -qpl %s' % src_rpm).read()=='foo-1.2.3.spec\nfoo-1.2.3.tar.gz\n')
+test.run(arguments='', status=2, stderr=expect)
 
 test.pass_test()
diff --git a/test/sconsign/ghost-entries.py b/test/sconsign/ghost-entries.py
new file mode 100644 (file)
index 0000000..45426ad
--- /dev/null
@@ -0,0 +1,111 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# 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.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that "ghost" entries in the .sconsign file don't have Nodes
+created for them on subsequent runs (which would cause errors
+when scanning directories).
+"""
+
+import os.path
+
+d_current_txt = os.path.join('d', 'current.txt')
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+# This test case is from Morten Elo Petersen.  It's harder because
+# blib-case1only actually exists in the build dir after the -c, so the
+# Dir scanner finds it and adds it to the dir's entries.
+# Unfortunately FS.py's File.exists() method checks it later and finds
+# it looks like a stale build copy of a missing source, so it deletes
+# it.  And then it's later discovered to be missing since it's still
+# in the dir's entries list.  The fix for this is to test for missing
+# source files in the dir scanner (Scanner/Dir.py), and delete them
+# (just like File.exists() does if they're found in the build dir
+# rather than making entries for them.
+
+test.write('SConstruct', """\
+def cat(target, source, env):
+    fp = open(str(target[0]), 'wb')
+    for s in source:
+        fp.write(open(str(s), 'rb').read())
+    fp.close()
+env=Environment()
+Export('env')
+env['BUILDERS']['Cat']=Builder(action=cat, multi=1)
+SConscript('src/SConscript',build_dir='build')
+""")
+
+test.subdir('src')
+test.write(['src', 'SConscript'], """\
+Import('env')
+if ARGUMENTS['case']=='1':
+    A=env.Cat('#build/lib/blib-case1only','src.txt')
+    B=env.Cat('#build/lib/blibB','#build/lib/blib-case1only')
+if ARGUMENTS['case']=='2':
+    A=env.Cat('case2only','src.txt')
+    B=env.Cat('#build/lib/blibB.txt','case2only')
+env.Alias('go','#build/lib')
+""")
+
+test.write(['src', 'src.txt'], "anything")
+
+test.run(arguments="-Q go case=1")
+test.must_exist('build/lib/blib-case1only')
+test.run(arguments="-Q go case=2")
+test.run(arguments="-Q go case=2 -c")
+test.run(arguments="-Q go case=2")
+
+
+#############################################################################
+# This test case is from Jason Orendorff.
+# Checking for existence before making nodes for things found in the
+# .sconsign fixes this one.
+
+test.write('SConstruct', """\
+Command("d/current.txt", [], [Touch("$TARGET")])
+if ARGUMENTS.has_key('pass') and ARGUMENTS['pass'] == '1':
+  Command("d/obsolete.txt", [], [Touch("$TARGET")])
+Command("installer.exe", ['d'], [Touch("$TARGET")])
+""")
+
+test.run(arguments='-Q pass=1')
+
+# Now delete the created files
+test.unlink(['d', 'obsolete.txt'])
+test.unlink(['d', 'current.txt'])
+test.rmdir(['d'])
+
+# Run again, as pass 2
+expect = """Touch("%(d_current_txt)s")
+Touch("installer.exe")
+""" % locals()
+
+test.run(arguments='-Q pass=2', stdout=expect)
+
+test.pass_test()
index 8e17e958c58d556bac1a1f79b8f37082988562da..19c8a1ca04ff2566473c747d400c3c36fe706e51 100644 (file)
@@ -30,6 +30,7 @@ info in them (which have different BuildInfo entries).
 """
 
 import os.path
+import re
 
 import TestSCons
 import TestSConsign
@@ -40,6 +41,8 @@ test = TestSConsign.TestSConsign(match = TestSConsign.match_re)
 
 CC = test.detect('CC', norm=1)
 CC_dir, CC_file = os.path.split(CC)
+CC_dir = re.escape(os.path.normcase(CC_dir))
+CC_file = re.escape(CC_file)
 
 # Note:  We don't use os.path.join() representations of the file names
 # in the expected output because paths in the .sconsign files are
index 99845e3ba660308f76e20e8d8cc476f7c6159c6a..54ecaaa202f2ba08eb998ab9e9e173961fc6cc46 100644 (file)
@@ -29,19 +29,61 @@ Verify that the sconsign script works with files generated when
 using the signatures in an SConsignFile().
 """
 
-import os.path
-
+import TestSCons
 import TestSConsign
 
-test = TestSConsign.TestSConsign(match = TestSConsign.match_re)
+_python_ = TestSCons._python_
 
-CC = test.detect('CC', norm=1)
-CC_dir, CC_file = os.path.split(CC)
-LINK = test.detect('LINK', norm=1)
-if LINK is None: LINK = CC
+test = TestSConsign.TestSConsign(match = TestSConsign.match_re)
 
 test.subdir('sub1', 'sub2')
 
+test.write('fake_cc.py', r"""
+import os.path
+import re
+import string
+import sys
+
+path = string.split(sys.argv[1])
+output = open(sys.argv[2], 'wb')
+input = open(sys.argv[3], 'rb')
+
+output.write('fake_cc.py:  %s\n' % sys.argv)
+
+def find_file(f):
+    for dir in path:
+        p = dir + os.sep + f
+        if os.path.exists(p):
+            return open(p, 'rb')
+    return None
+
+def process(infp, outfp):
+    for line in infp.readlines():
+        m = re.match('#include <(.*)>', line)
+        if m:
+            file = m.group(1)
+            process(find_file(file), outfp)
+        else:
+            outfp.write(line)
+
+process(input, output)
+
+sys.exit(0)
+""")
+
+test.write('fake_link.py', r"""
+import sys
+
+output = open(sys.argv[1], 'wb')
+input = open(sys.argv[2], 'rb')
+
+output.write('fake_link.py:  %s\n' % sys.argv)
+
+output.write(input.read())
+
+sys.exit(0)
+""")
+
 # Note:  We don't use os.path.join() representations of the file names
 # in the expected output because paths in the .sconsign files are
 # canonicalized to use / as the separator.
@@ -55,27 +97,20 @@ sub2_inc2_h     = 'sub2/inc2.h'
 
 test.write(['SConstruct'], """\
 SConsignFile()
-env1 = Environment(PROGSUFFIX = '.exe', OBJSUFFIX = '.obj')
+env1 = Environment(PROGSUFFIX = '.exe',
+                   OBJSUFFIX = '.obj',
+                   CCCOM = r'%(_python_)s fake_cc.py sub2 $TARGET $SOURCE',
+                   LINKCOM = r'%(_python_)s fake_link.py $TARGET $SOURCE')
 env1.Program('sub1/hello.c')
 env2 = env1.Clone(CPPPATH = ['sub2'])
 env2.Program('sub2/hello.c')
-""")
+""" % locals())
 
-test.write(['sub1', 'hello.c'], r"""\
-#include <stdio.h>
-#include <stdlib.h>
-int
-main(int argc, char *argv[])
-{
-        argv[argc++] = "--";
-        printf("sub1/hello.c\n");
-        exit (0);
-}
+test.write(['sub1', 'hello.c'], r"""
+sub1/hello.c
 """)
 
-test.write(['sub2', 'hello.c'], r"""\
-#include <stdio.h>
-#include <stdlib.h>
+test.write(['sub2', 'hello.c'], r"""
 #include <inc1.h>
 #include <inc2.h>
 int
@@ -102,29 +137,23 @@ sig_re = r'[0-9a-fA-F]{32}'
 test.run_sconsign(arguments = ".sconsign",
          stdout = r"""=== .:
 SConstruct: None \d+ \d+
-=== %(CC_dir)s:
-%(CC_file)s: %(sig_re)s \d+ \d+
 === sub1:
 hello.c: %(sig_re)s \d+ \d+
 hello.exe: %(sig_re)s \d+ \d+
         %(sub1_hello_obj)s: %(sig_re)s \d+ \d+
-        %(LINK)s: %(sig_re)s \d+ \d+
         %(sig_re)s \[.*\]
 hello.obj: %(sig_re)s \d+ \d+
         %(sub1_hello_c)s: %(sig_re)s \d+ \d+
-        %(CC)s: %(sig_re)s \d+ \d+
         %(sig_re)s \[.*\]
 === sub2:
 hello.c: %(sig_re)s \d+ \d+
 hello.exe: %(sig_re)s \d+ \d+
         %(sub2_hello_obj)s: %(sig_re)s \d+ \d+
-        %(LINK)s: %(sig_re)s \d+ \d+
         %(sig_re)s \[.*\]
 hello.obj: %(sig_re)s \d+ \d+
         %(sub2_hello_c)s: %(sig_re)s \d+ \d+
         %(sub2_inc1_h)s: %(sig_re)s \d+ \d+
         %(sub2_inc2_h)s: %(sig_re)s \d+ \d+
-        %(CC)s: %(sig_re)s \d+ \d+
         %(sig_re)s \[.*\]
 inc1.h: %(sig_re)s \d+ \d+
 inc2.h: %(sig_re)s \d+ \d+
@@ -133,29 +162,23 @@ inc2.h: %(sig_re)s \d+ \d+
 test.run_sconsign(arguments = "--raw .sconsign",
          stdout = r"""=== .:
 SConstruct: {'csig': None, 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
-=== %(CC_dir)s:
-%(CC_file)s: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
 === sub1:
 hello.c: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
 hello.exe: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
         %(sub1_hello_obj)s: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
-        %(LINK)s: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
         %(sig_re)s \[.*\]
 hello.obj: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
         %(sub1_hello_c)s: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
-        %(CC)s: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
         %(sig_re)s \[.*\]
 === sub2:
 hello.c: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
 hello.exe: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
         %(sub2_hello_obj)s: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
-        %(LINK)s: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
         %(sig_re)s \[.*\]
 hello.obj: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
         %(sub2_hello_c)s: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
         %(sub2_inc1_h)s: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
         %(sub2_inc2_h)s: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
-        %(CC)s: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
         %(sig_re)s \[.*\]
 inc1.h: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
 inc2.h: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
@@ -166,11 +189,6 @@ SConstruct:
     csig: None
     timestamp: \d+
     size: \d+
-=== %(CC_dir)s:
-%(CC_file)s:
-    csig: %(sig_re)s
-    timestamp: \d+
-    size: \d+
 === sub1:
 hello.c:
     csig: %(sig_re)s
@@ -185,10 +203,6 @@ hello.exe:
             csig: %(sig_re)s
             timestamp: \d+
             size: \d+
-        %(LINK)s:
-            csig: %(sig_re)s
-            timestamp: \d+
-            size: \d+
     action: %(sig_re)s \[.*\]
 hello.obj:
     csig: %(sig_re)s
@@ -199,10 +213,6 @@ hello.obj:
             csig: %(sig_re)s
             timestamp: \d+
             size: \d+
-        %(CC)s:
-            csig: %(sig_re)s
-            timestamp: \d+
-            size: \d+
     action: %(sig_re)s \[.*\]
 === sub2:
 hello.c:
@@ -218,10 +228,6 @@ hello.exe:
             csig: %(sig_re)s
             timestamp: \d+
             size: \d+
-        %(LINK)s:
-            csig: %(sig_re)s
-            timestamp: \d+
-            size: \d+
     action: %(sig_re)s \[.*\]
 hello.obj:
     csig: %(sig_re)s
@@ -240,10 +246,6 @@ hello.obj:
             csig: %(sig_re)s
             timestamp: \d+
             size: \d+
-        %(CC)s:
-            csig: %(sig_re)s
-            timestamp: \d+
-            size: \d+
     action: %(sig_re)s \[.*\]
 inc1.h:
     csig: %(sig_re)s
@@ -261,9 +263,6 @@ test.run_sconsign(arguments = "-c -v .sconsign",
          stdout = r"""=== .:
 SConstruct:
     csig: None
-=== %(CC_dir)s:
-%(CC_file)s:
-    csig: %(sig_re)s
 === sub1:
 hello.c:
     csig: %(sig_re)s
@@ -288,9 +287,6 @@ test.run_sconsign(arguments = "-s -v .sconsign",
          stdout = r"""=== .:
 SConstruct:
     size: \d+
-=== %(CC_dir)s:
-%(CC_file)s:
-    size: \d+
 === sub1:
 hello.c:
     size: \d+
@@ -315,9 +311,6 @@ test.run_sconsign(arguments = "-t -v .sconsign",
          stdout = r"""=== .:
 SConstruct:
     timestamp: \d+
-=== %(CC_dir)s:
-%(CC_file)s:
-    timestamp: \d+
 === sub1:
 hello.c:
     timestamp: \d+
@@ -340,64 +333,50 @@ inc2.h:
 
 test.run_sconsign(arguments = "-e hello.obj .sconsign",
          stdout = r"""=== .:
-=== %(CC_dir)s:
 === sub1:
 hello.obj: %(sig_re)s \d+ \d+
         %(sub1_hello_c)s: %(sig_re)s \d+ \d+
-        %(CC)s: %(sig_re)s \d+ \d+
         %(sig_re)s \[.*\]
 === sub2:
 hello.obj: %(sig_re)s \d+ \d+
         %(sub2_hello_c)s: %(sig_re)s \d+ \d+
         %(sub2_inc1_h)s: %(sig_re)s \d+ \d+
         %(sub2_inc2_h)s: %(sig_re)s \d+ \d+
-        %(CC)s: %(sig_re)s \d+ \d+
         %(sig_re)s \[.*\]
 """ % locals(),
-        stderr = r"""sconsign: no entry `hello.obj' in `\.'
-sconsign: no entry `hello.obj' in `%(CC_dir)s'
-""" % locals())
+         stderr = r"""sconsign: no entry `hello.obj' in `\.'
+""")
 
 test.run_sconsign(arguments = "-e hello.obj -e hello.exe -e hello.obj .sconsign",
          stdout = r"""=== .:
-=== %(CC_dir)s:
 === sub1:
 hello.obj: %(sig_re)s \d+ \d+
         %(sub1_hello_c)s: %(sig_re)s \d+ \d+
-        %(CC)s: %(sig_re)s \d+ \d+
         %(sig_re)s \[.*\]
 hello.exe: %(sig_re)s \d+ \d+
         %(sub1_hello_obj)s: %(sig_re)s \d+ \d+
-        %(LINK)s: %(sig_re)s \d+ \d+
         %(sig_re)s \[.*\]
 hello.obj: %(sig_re)s \d+ \d+
         %(sub1_hello_c)s: %(sig_re)s \d+ \d+
-        %(CC)s: %(sig_re)s \d+ \d+
         %(sig_re)s \[.*\]
 === sub2:
 hello.obj: %(sig_re)s \d+ \d+
         %(sub2_hello_c)s: %(sig_re)s \d+ \d+
         %(sub2_inc1_h)s: %(sig_re)s \d+ \d+
         %(sub2_inc2_h)s: %(sig_re)s \d+ \d+
-        %(CC)s: %(sig_re)s \d+ \d+
         %(sig_re)s \[.*\]
 hello.exe: %(sig_re)s \d+ \d+
         %(sub2_hello_obj)s: %(sig_re)s \d+ \d+
-        %(LINK)s: %(sig_re)s \d+ \d+
         %(sig_re)s \[.*\]
 hello.obj: %(sig_re)s \d+ \d+
         %(sub2_hello_c)s: %(sig_re)s \d+ \d+
         %(sub2_inc1_h)s: %(sig_re)s \d+ \d+
         %(sub2_inc2_h)s: %(sig_re)s \d+ \d+
-        %(CC)s: %(sig_re)s \d+ \d+
         %(sig_re)s \[.*\]
 """ % locals(),
         stderr = r"""sconsign: no entry `hello.obj' in `\.'
 sconsign: no entry `hello.exe' in `\.'
 sconsign: no entry `hello.obj' in `\.'
-sconsign: no entry `hello.obj' in `%(CC_dir)s'
-sconsign: no entry `hello.exe' in `%(CC_dir)s'
-sconsign: no entry `hello.obj' in `%(CC_dir)s'
 """ % locals())
 
 #test.run_sconsign(arguments = "-i -v .sconsign",
index fc85133e5b5662b9baf1052bbc328e0c18b26653..27e48679f5a7cd730c5457961c52ebd085ec2e2f 100644 (file)
@@ -31,13 +31,12 @@ SourceSignatures() and TargetSignatures() values (timestamp and content,
 respectively).
 """
 
+import TestSCons
 import TestSConsign
 
-test = TestSConsign.TestSConsign(match = TestSConsign.match_re)
+_python_ = TestSCons._python_
 
-CC = test.detect('CC', norm=1)
-LINK = test.detect('LINK', norm=1)
-if LINK is None: LINK = CC
+test = TestSConsign.TestSConsign(match = TestSConsign.match_re)
 
 # Note:  We don't use os.path.join() representations of the file names
 # in the expected output because paths in the .sconsign files are
@@ -46,47 +45,75 @@ if LINK is None: LINK = CC
 sub1_hello_c    = 'sub1/hello.c'
 sub1_hello_obj  = 'sub1/hello.obj'
 
-def re_sep(*args):
-    import os.path
-    import re
-    return re.escape(apply(os.path.join, args))
-
 test.subdir('sub1', 'sub2')
 
+test.write('fake_cc.py', r"""
+import os.path
+import re
+import string
+import sys
+
+path = string.split(sys.argv[1])
+output = open(sys.argv[2], 'wb')
+input = open(sys.argv[3], 'rb')
+
+output.write('fake_cc.py:  %s\n' % sys.argv)
+
+def find_file(f):
+    for dir in path:
+        p = dir + os.sep + f
+        if os.path.exists(p):
+            return open(p, 'rb')
+    return None
+
+def process(infp, outfp):
+    for line in infp.readlines():
+        m = re.match('#include <(.*)>', line)
+        if m:
+            file = m.group(1)
+            process(find_file(file), outfp)
+        else:
+            outfp.write(line)
+
+process(input, output)
+
+sys.exit(0)
+""")
+
+test.write('fake_link.py', r"""
+import sys
+
+output = open(sys.argv[1], 'wb')
+input = open(sys.argv[2], 'rb')
+
+output.write('fake_link.py:  %s\n' % sys.argv)
+
+output.write(input.read())
+
+sys.exit(0)
+""")
+
 test.write('SConstruct', """
 SConsignFile(None)
 SourceSignatures('timestamp')
 TargetSignatures('content')
-env1 = Environment(PROGSUFFIX = '.exe', OBJSUFFIX = '.obj')
+env1 = Environment(PROGSUFFIX = '.exe',
+                   OBJSUFFIX = '.obj',
+                   CCCOM = r'%(_python_)s fake_cc.py sub2 $TARGET $SOURCE',
+                   LINKCOM = r'%(_python_)s fake_link.py $TARGET $SOURCE')
 env1.Program('sub1/hello.c')
 env2 = env1.Clone(CPPPATH = ['sub2'])
 env2.Program('sub2/hello.c')
-""")
+""" % locals())
 
 test.write(['sub1', 'hello.c'], r"""\
-#include <stdio.h>
-#include <stdlib.h>
-int
-main(int argc, char *argv[])
-{
-        argv[argc++] = "--";
-        printf("sub1/hello.c\n");
-        exit (0);
-}
+sub1/hello.c
 """)
 
 test.write(['sub2', 'hello.c'], r"""\
-#include <stdio.h>
-#include <stdlib.h>
 #include <inc1.h>
 #include <inc2.h>
-int
-main(int argc, char *argv[])
-{
-        argv[argc++] = "--";
-        printf("sub2/goodbye.c\n");
-        exit (0);
-}
+sub2/hello.c
 """)
 
 test.write(['sub2', 'inc1.h'], r"""\
@@ -107,22 +134,18 @@ date_re = r'\S+ \S+ [ \d]\d \d\d:\d\d:\d\d \d\d\d\d'
 test.run_sconsign(arguments = "-e hello.exe -e hello.obj sub1/.sconsign",
          stdout = r"""hello.exe: %(sig_re)s \d+ \d+
         %(sub1_hello_obj)s: %(sig_re)s \d+ \d+
-        %(LINK)s: None \d+ \d+
         %(sig_re)s \[.*\]
 hello.obj: %(sig_re)s \d+ \d+
         %(sub1_hello_c)s: None \d+ \d+
-        %(CC)s: None \d+ \d+
         %(sig_re)s \[.*\]
 """ % locals())
 
 test.run_sconsign(arguments = "-e hello.exe -e hello.obj -r sub1/.sconsign",
          stdout = r"""hello.exe: %(sig_re)s '%(date_re)s' \d+
         %(sub1_hello_obj)s: %(sig_re)s '%(date_re)s' \d+
-        %(LINK)s: None '%(date_re)s' \d+
         %(sig_re)s \[.*\]
 hello.obj: %(sig_re)s '%(date_re)s' \d+
         %(sub1_hello_c)s: None '%(date_re)s' \d+
-        %(CC)s: None '%(date_re)s' \d+
         %(sig_re)s \[.*\]
 """ % locals())
 
index 1fcfbfda853319b8aae3642929b593c9ffa93e32..6aede19df578bbde062b31e9ac8fa1fc48240b83 100644 (file)
@@ -29,16 +29,61 @@ Verify that the sconsign script works when using an individual
 .sconsign file in each directory (SConsignFile(None)).
 """
 
+import TestSCons
 import TestSConsign
 
-test = TestSConsign.TestSConsign(match = TestSConsign.match_re)
+_python_ = TestSCons._python_
 
-CC = test.detect('CC', norm=1)
-LINK = test.detect('LINK', norm=1)
-if LINK is None: LINK = CC
+test = TestSConsign.TestSConsign(match = TestSConsign.match_re)
 
 test.subdir('sub1', 'sub2')
 
+test.write('fake_cc.py', r"""
+import os.path
+import re
+import string
+import sys
+
+path = string.split(sys.argv[1])
+output = open(sys.argv[2], 'wb')
+input = open(sys.argv[3], 'rb')
+
+output.write('fake_cc.py:  %s\n' % sys.argv)
+
+def find_file(f):
+    for dir in path:
+        p = dir + os.sep + f
+        if os.path.exists(p):
+            return open(p, 'rb')
+    return None
+
+def process(infp, outfp):
+    for line in infp.readlines():
+        m = re.match('#include <(.*)>', line)
+        if m:
+            file = m.group(1)
+            process(find_file(file), outfp)
+        else:
+            outfp.write(line)
+
+process(input, output)
+
+sys.exit(0)
+""")
+
+test.write('fake_link.py', r"""
+import sys
+
+output = open(sys.argv[1], 'wb')
+input = open(sys.argv[2], 'rb')
+
+output.write('fake_link.py:  %s\n' % sys.argv)
+
+output.write(input.read())
+
+sys.exit(0)
+""")
+
 # Note:  We don't use os.path.join() representations of the file names
 # in the expected output because paths in the .sconsign files are
 # canonicalized to use / as the separator.
@@ -52,36 +97,23 @@ sub2_inc2_h     = 'sub2/inc2.h'
 
 test.write(['SConstruct'], """
 SConsignFile(None)
-env1 = Environment(PROGSUFFIX = '.exe', OBJSUFFIX = '.obj')
+env1 = Environment(PROGSUFFIX = '.exe',
+                   OBJSUFFIX = '.obj',
+                   CCCOM = r'%(_python_)s fake_cc.py sub2 $TARGET $SOURCE',
+                   LINKCOM = r'%(_python_)s fake_link.py $TARGET $SOURCE')
 env1.Program('sub1/hello.c')
 env2 = env1.Clone(CPPPATH = ['sub2'])
 env2.Program('sub2/hello.c')
-""")
+""" % locals())
 
 test.write(['sub1', 'hello.c'], r"""\
-#include <stdio.h>
-#include <stdlib.h>
-int
-main(int argc, char *argv[])
-{
-        argv[argc++] = "--";
-        printf("sub1/hello.c\n");
-        exit (0);
-}
+sub1/hello.c
 """)
 
 test.write(['sub2', 'hello.c'], r"""\
-#include <stdio.h>
-#include <stdlib.h>
 #include <inc1.h>
 #include <inc2.h>
-int
-main(int argc, char *argv[])
-{
-        argv[argc++] = "--";
-        printf("sub2/goodbye.c\n");
-        exit (0);
-}
+sub2/hello.c
 """)
 
 test.write(['sub2', 'inc1.h'], r"""\
@@ -99,11 +131,9 @@ sig_re = r'[0-9a-fA-F]{32}'
 expect = r"""hello.c: %(sig_re)s \d+ \d+
 hello.exe: %(sig_re)s \d+ \d+
         %(sub1_hello_obj)s: %(sig_re)s \d+ \d+
-        %(LINK)s: %(sig_re)s \d+ \d+
         %(sig_re)s \[.*\]
 hello.obj: %(sig_re)s \d+ \d+
         %(sub1_hello_c)s: %(sig_re)s \d+ \d+
-        %(CC)s: %(sig_re)s \d+ \d+
         %(sig_re)s \[.*\]
 """ % locals()
 
@@ -115,11 +145,9 @@ test.run_sconsign(arguments = "--raw sub1/.sconsign",
          stdout = r"""hello.c: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
 hello.exe: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
         %(sub1_hello_obj)s: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
-        %(LINK)s: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
         %(sig_re)s \[.*\]
 hello.obj: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
         %(sub1_hello_c)s: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
-        %(CC)s: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
         %(sig_re)s \[.*\]
 """ % locals())
 
@@ -137,10 +165,6 @@ hello.exe:
             csig: %(sig_re)s
             timestamp: \d+
             size: \d+
-        %(LINK)s:
-            csig: %(sig_re)s
-            timestamp: \d+
-            size: \d+
     action: %(sig_re)s \[.*\]
 hello.obj:
     csig: %(sig_re)s
@@ -151,10 +175,6 @@ hello.obj:
             csig: %(sig_re)s
             timestamp: \d+
             size: \d+
-        %(CC)s:
-            csig: %(sig_re)s
-            timestamp: \d+
-            size: \d+
     action: %(sig_re)s \[.*\]
 """ % locals())
 
@@ -188,22 +208,18 @@ hello.obj:
 test.run_sconsign(arguments = "-e hello.obj sub1/.sconsign",
          stdout = r"""hello.obj: %(sig_re)s \d+ \d+
         %(sub1_hello_c)s: %(sig_re)s \d+ \d+
-        %(CC)s: %(sig_re)s \d+ \d+
         %(sig_re)s \[.*\]
 """ % locals())
 
 test.run_sconsign(arguments = "-e hello.obj -e hello.exe -e hello.obj sub1/.sconsign",
          stdout = r"""hello.obj: %(sig_re)s \d+ \d+
         %(sub1_hello_c)s: %(sig_re)s \d+ \d+
-        %(CC)s: %(sig_re)s \d+ \d+
         %(sig_re)s \[.*\]
 hello.exe: %(sig_re)s \d+ \d+
         %(sub1_hello_obj)s: %(sig_re)s \d+ \d+
-        %(LINK)s: %(sig_re)s \d+ \d+
         %(sig_re)s \[.*\]
 hello.obj: %(sig_re)s \d+ \d+
         %(sub1_hello_c)s: %(sig_re)s \d+ \d+
-        %(CC)s: %(sig_re)s \d+ \d+
         %(sig_re)s \[.*\]
 """ % locals())
 
@@ -211,13 +227,11 @@ test.run_sconsign(arguments = "sub2/.sconsign",
          stdout = r"""hello.c: %(sig_re)s \d+ \d+
 hello.exe: %(sig_re)s \d+ \d+
         %(sub2_hello_obj)s: %(sig_re)s \d+ \d+
-        %(LINK)s: %(sig_re)s \d+ \d+
         %(sig_re)s \[.*\]
 hello.obj: %(sig_re)s \d+ \d+
         %(sub2_hello_c)s: %(sig_re)s \d+ \d+
         %(sub2_inc1_h)s: %(sig_re)s \d+ \d+
         %(sub2_inc2_h)s: %(sig_re)s \d+ \d+
-        %(CC)s: %(sig_re)s \d+ \d+
         %(sig_re)s \[.*\]
 inc1.h: %(sig_re)s \d+ \d+
 inc2.h: %(sig_re)s \d+ \d+
@@ -240,11 +254,9 @@ test.run_sconsign(arguments = "-e hello.obj sub2/.sconsign sub1/.sconsign",
         %(sub2_hello_c)s: %(sig_re)s \d+ \d+
         %(sub2_inc1_h)s: %(sig_re)s \d+ \d+
         %(sub2_inc2_h)s: %(sig_re)s \d+ \d+
-        %(CC)s: %(sig_re)s \d+ \d+
         %(sig_re)s \[.*\]
 hello.obj: %(sig_re)s \d+ \d+
         %(sub1_hello_c)s: %(sig_re)s \d+ \d+
-        %(CC)s: %(sig_re)s \d+ \d+
         %(sig_re)s \[.*\]
 """ % locals())
 
index a81695753b192ea21818cc694330d1fb2c50e5ef..72c776662203d466e1e91eea7b3f25d25c33dc19 100644 (file)
@@ -34,6 +34,8 @@ import os.path
 
 import TestSCons
 
+_python_ = TestSCons._python_
+
 test = TestSCons.TestSCons()
 
 #if os.path.exists('sconsign.py'):
@@ -46,58 +48,79 @@ test = TestSCons.TestSCons()
 
 test.subdir('src', ['src', 'sub'])
 
+test.write('fake_cc.py', """\
+import sys
+ofp = open(sys.argv[1], 'wb')
+ofp.write('fake_cc.py:  %s\\n' % sys.argv)
+for s in sys.argv[2:]:
+    ofp.write(open(s, 'rb').read())
+""")
+
+test.write('fake_link.py', """\
+import sys
+ofp = open(sys.argv[1], 'wb')
+ofp.write('fake_link.py:  %s\\n' % sys.argv)
+for s in sys.argv[2:]:
+    ofp.write(open(s, 'rb').read())
+""")
+
 test.write('SConstruct', """\
 SConsignFile(None)
 TargetSignatures('content')
-env = Environment()
+env = Environment(PROGSUFFIX = '.exe',
+                  OBJSUFFIX = '.obj',
+                  CCCOM = r'%(_python_)s fake_cc.py $TARGET $SOURCES',
+                  LINKCOM = r'%(_python_)s fake_link.py $TARGET $SOURCES')
 env.SConscript('src/SConstruct', exports=['env'])
 env.Object('foo.c')
-""")
+""" % locals())
 
 test.write(['src', 'SConstruct'], """\
 SConsignFile(None)
 TargetSignatures('content')
-env = Environment()
-p = env.Program('prog', ['main.c', '../foo%s', 'sub/bar.c'])
+env = Environment(PROGSUFFIX = '.exe',
+                  OBJSUFFIX = '.obj',
+                  CCCOM = r'%(_python_)s fake_cc.py $TARGET $SOURCES',
+                  LINKCOM = r'%(_python_)s fake_link.py $TARGET $SOURCES')
+p = env.Program('prog', ['main.c', '../foo$OBJSUFFIX', 'sub/bar.c'])
 env.Default(p)
-""" % TestSCons._obj)
+""" % locals())
 
 test.write('foo.c', """\
-#include <stdio.h>
-#include <stdlib.h>
-void
-foo(void) {
-    printf("foo.c\\n");
-}
+foo.c
 """)
 
 test.write(['src', 'main.c'], """\
-#include <stdio.h>
-#include <stdlib.h>
-extern void foo(void);
-extern void bar(void);
-int
-main(int argc, char *argv[]) {
-    foo();
-    bar();
-    printf("src/main.c\\n");
-    exit (0);
-}
+src/main.c
 """)
 
 test.write(['src', 'sub', 'bar.c'], """\
-#include <stdio.h>
-#include <stdlib.h>
-void
-bar(void) {
-    printf("bar.c\\n");
-}
+src/sub/bar.c
 """)
 
 test.run()
 
-test.run(program=test.workpath('src', 'prog'),
-         stdout="foo.c\nbar.c\nsrc/main.c\n")
+src_prog_exe    = os.path.join('src', 'prog.exe')
+src_main_c      = os.path.join('src', 'main.c')
+src_main_obj    = os.path.join('src', 'main.obj')
+src_sub_bar_c   = os.path.join('src', 'sub', 'bar.c')
+src_sub_bar_obj = os.path.join('src', 'sub', 'bar.obj')
+
+expect = """\
+fake_link.py:  ['fake_link.py', '%(src_prog_exe)s', '%(src_main_obj)s', 'foo.obj', '%(src_sub_bar_obj)s']
+fake_cc.py:  ['fake_cc.py', '%(src_main_obj)s', '%(src_main_c)s']
+src/main.c
+fake_cc.py:  ['fake_cc.py', 'foo.obj', 'foo.c']
+foo.c
+fake_cc.py:  ['fake_cc.py', '%(src_sub_bar_obj)s', '%(src_sub_bar_c)s']
+src/sub/bar.c
+""" % locals()
+
+if os.sep == '\\':
+    import string
+    expect = string.replace(expect, '\\', '\\\\')
+
+test.must_match(['src', 'prog.exe'], expect)
 
 test.up_to_date(chdir='src', arguments = test.workpath())