Merged revisions 2302-2362,2364-2452 via svnmerge from
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Wed, 19 Sep 2007 12:52:29 +0000 (12:52 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Wed, 19 Sep 2007 12:52:29 +0000 (12:52 +0000)
http://scons.tigris.org/svn/scons/branches/core

................
  r2311 | stevenknight | 2007-08-17 07:51:31 -0500 (Fri, 17 Aug 2007) | 2 lines

  Fix the CHECKPOINT= help text.
................
  r2313 | stevenknight | 2007-08-17 13:12:13 -0500 (Fri, 17 Aug 2007) | 2 lines

  Make sure the --debug=time works when -h is specified, too.
................
  r2320 | stevenknight | 2007-08-18 08:54:49 -0500 (Sat, 18 Aug 2007) | 3 lines

  Don't execute any Configure() actions while reading SConscript files
  when -c or -h or -H are specified.
................
  r2321 | stevenknight | 2007-08-20 08:48:57 -0500 (Mon, 20 Aug 2007) | 2 lines

  Cleaner test failure if before-and-after PostScript files don't match.
................
  r2322 | stevenknight | 2007-08-20 11:02:57 -0500 (Mon, 20 Aug 2007) | 2 lines

  Remove function definitions that make some Java versions fail.
................
  r2354 | stevenknight | 2007-08-20 14:26:13 -0500 (Mon, 20 Aug 2007) | 3 lines

  Relax test conditions for two scripts that have differing behavior
  under different versions of Java + SWIG (+ gcc...?).
................
  r2355 | stevenknight | 2007-08-20 14:29:36 -0500 (Mon, 20 Aug 2007) | 2 lines

  Fix the SConstruct file so it doesn't die if Subversion isn't installed.
................
  r2356 | stevenknight | 2007-08-20 22:26:35 -0500 (Mon, 20 Aug 2007) | 3 lines

  Update to latest Test{Cmd,Common}.py, with better avoidance of
  race conditions on temporary file and directory names.
................
  r2373 | stevenknight | 2007-08-27 10:47:21 -0500 (Mon, 27 Aug 2007) | 2 lines

  Windows portability fixes in tests.
................
  r2377 | stevenknight | 2007-08-27 12:33:47 -0500 (Mon, 27 Aug 2007) | 2 lines

  Add a null command list to suppress the baseline build when testing.
................
  r2380 | stevenknight | 2007-08-27 16:33:42 -0500 (Mon, 27 Aug 2007) | 616 lines

  Merged revisions 1503-1543,1545-1546,1548-1558,1560-1562,1564-1886,1888-1909,1911-1941,1943,1945-1955,1957-1960,1962-1963,1965-1999,2001-2019,2021-2037,2039-2061,2063-2067,2069-2146,2148-2150,2152-2156,2158-2199,2201-2208,2210-2212,2214-2238,2240,2242-2288,2290-2291,2293-2305,2307-2364,2366-2373,2375-2377,2379 via svnmerge from
  http://scons.tigris.org/svn/scons/branches/sigrefactor

  ........
    r1550 | stevenknight | 2006-07-27 21:40:08 -0500 (Thu, 27 Jul 2006) | 3 lines

    First cut at storing csig separately in the NodeInfo,
    but still using bsigs for up-to-date checks.
  ........
    r1551 | stevenknight | 2006-07-28 07:07:27 -0500 (Fri, 28 Jul 2006) | 2 lines

    Refactor the use of dictify() to avoid __builtin__ issues with zip on 1.5.
  ........
    r1552 | stevenknight | 2006-07-28 10:05:18 -0500 (Fri, 28 Jul 2006) | 3 lines

    Have msvsTests.py print why it's not executing tests
    when on a non-win32 system.
  ........
    r1553 | stevenknight | 2006-07-28 11:28:37 -0500 (Fri, 28 Jul 2006) | 3 lines

    Refactor when NodeInfo objects get instantiated (when requested
    explicitly, not as a side effect of creating a BuildInfo object).
  ........
    r1554 | stevenknight | 2006-07-28 11:42:53 -0500 (Fri, 28 Jul 2006) | 3 lines

    Refactor test/{Source,Target}Signatures.py to make changing their
    semantics easier.
  ........
    r1555 | stevenknight | 2006-07-28 13:18:35 -0500 (Fri, 28 Jul 2006) | 6 lines

    Eliminate use of build signatures, changing interfaces and semantics
    of SourceSignature() and TargetSignatures(), accordingly.  Make use
    of content signatures the default behavior.  Get rid of most uses
    the Sig/*.py calculator modules and passing around a "calc" argument
    everywhere.
  ........
    r1556 | stevenknight | 2006-07-28 15:14:03 -0500 (Fri, 28 Jul 2006) | 3 lines

    Fix use of CacheDir() with Alias and Value Nodes.
    Refactor sub-tests in test/CacheDir.py into separate test scripts.
  ........
    r1557 | stevenknight | 2006-07-28 21:40:31 -0500 (Fri, 28 Jul 2006) | 2 lines

    Finish the necessary coding to remove the no-longer-needed Sig/*.py modules.
  ........
    r1560 | stevenknight | 2006-07-29 05:52:27 -0500 (Sat, 29 Jul 2006) | 3 lines

    Move current_*() methods for the different flavors of file comparison into
    (e.g.) the Node.FS.File class itself, not its FileNodeInfo class.
  ........
    r1562 | stevenknight | 2006-07-29 08:46:23 -0500 (Sat, 29 Jul 2006) | 2 lines

    Fix the default returned Boolean value of changed_{content,timestamp}().
  ........
    r1564 | stevenknight | 2006-07-29 10:51:24 -0500 (Sat, 29 Jul 2006) | 3 lines

    Fix the --debug=explain option.  Move test/explain.py to
    test/option/debug-explain.py.
  ........
    r1565 | stevenknight | 2006-07-29 16:09:31 -0500 (Sat, 29 Jul 2006) | 2 lines

    Refactor Taskmaster to visit source file Nodes in its walk.
  ........
    r1566 | stevenknight | 2006-07-29 17:50:38 -0500 (Sat, 29 Jul 2006) | 4 lines

    Merge Node.FS._cur2() into Node.FS.is_up_to_date().
    Use a Node.FS.*.make_ready() to handle disambiguation.
    Split the guts of has_src_builder() into a separate find_src_builder() method.
  ........
    r1567 | stevenknight | 2006-07-29 17:55:36 -0500 (Sat, 29 Jul 2006) | 2 lines

    Make the sconsign script print the stored action information.
  ........
    r1568 | stevenknight | 2006-07-29 17:59:03 -0500 (Sat, 29 Jul 2006) | 2 lines

    Refactor for test readability.
  ........
    r1569 | stevenknight | 2006-07-29 20:32:26 -0500 (Sat, 29 Jul 2006) | 2 lines

    Get rid of the now-unnecessary "module" argument to SConsign methods.
  ........
    r1570 | stevenknight | 2006-07-29 22:05:47 -0500 (Sat, 29 Jul 2006) | 3 lines

    Move content signature calcuation to an explicit make_ready()
    function.  Collect NodeInfo field updating in the base class.
  ........
    r1571 | stevenknight | 2006-07-30 06:26:17 -0500 (Sun, 30 Jul 2006) | 2 lines

    Eliminte the no-longer-necessary is_pseudo_derived() method.
  ........
    r1572 | stevenknight | 2006-07-30 07:53:40 -0500 (Sun, 30 Jul 2006) | 2 lines

    Add tgt_sig_type arguments to various changed() methods' calling arguments.
  ........
    r1573 | stevenknight | 2006-07-30 08:17:43 -0500 (Sun, 30 Jul 2006) | 2 lines

    More informative output from test/SideEffect.py when it fails.
  ........
    r1574 | stevenknight | 2006-07-30 09:53:11 -0500 (Sun, 30 Jul 2006) | 3 lines

    Restore the ability to fall back gracefully to timestamps if the running
    version of Python has no md5 module available.
  ........
    r1575 | stevenknight | 2006-08-02 20:21:04 -0500 (Wed, 02 Aug 2006) | 3 lines

    Fix a failing Qt test by calling disambiguate() before make_ready()
    for all of the targets in a list, not just the first.
  ........
    r1646 | stevenknight | 2006-10-17 17:21:58 -0500 (Tue, 17 Oct 2006) | 6 lines

    Move all the scons.org stuff from the scons source tree itself to a
    directory next to the trunk, and delete the copies from the branches.
    There's a lot of stuff there (what with all of the documentation of
    the different versions) and it's ridiculous to make everyone sync it
    just to work on the code.
  ........
    r1888 | stevenknight | 2007-04-17 14:57:47 -0500 (Tue, 17 Apr 2007) | 2 lines

    More efficient/cleaner code for dictifying --debug=explain info.
  ........
    r1889 | stevenknight | 2007-04-17 15:55:00 -0500 (Tue, 17 Apr 2007) | 3 lines

    More efficient taskmaster:  filter the list once for anything that hasn't
    been built, then extract a sub-list of anything that hasn't been visited.
  ........
    r1894 | stevenknight | 2007-04-25 13:07:29 -0500 (Wed, 25 Apr 2007) | 4 lines

    Push conversion of individual NodeInfo objects into the objects themselves
    (instead of having the FileBuildInfo class assume that every dependency
    is a file system Entry of some kind).
  ........
    r1895 | stevenknight | 2007-05-04 23:49:13 -0500 (Fri, 04 May 2007) | 3 lines

    Store content signatures for Aliases so we can really use them
    as dependencies.
  ........
    r1896 | stevenknight | 2007-05-07 16:37:06 -0500 (Mon, 07 May 2007) | 3 lines

    Move the TargetSignatures.py test into a subdirectory, which we can
    populate with other tests (of the new settings, etc.).
  ........
    r1897 | stevenknight | 2007-05-07 17:36:04 -0500 (Mon, 07 May 2007) | 3 lines

    Fix use of TargetSignatures('content') to override settings of
    SourceSignatures('timestamp').
  ........
    r1900 | stevenknight | 2007-05-14 18:48:39 -0500 (Mon, 14 May 2007) | 5 lines

    More specific sconsign signature tests: check for an actual 32-digit-long
    hex string when an MD5 checksum is expected, so we can correctly detect
    test failures if "None" shows up instead.  (This will be used for getting
    avoiding calculating the content signature in 'timestamp' mode.)
  ........
    r1949 | stevenknight | 2007-05-30 22:45:25 -0500 (Wed, 30 May 2007) | 7 lines

    Refactor the get_{csig,timestamp,size}() methods so they use the Memoizer
    pattern, instead of the the NodeInfo object, to cache the results.

    This won't be the final form (we have to reach and stuff a csig value
    in the cache slot to make --implicit-cache work) but it's a step that
    should make it easier to work on other in-progress modifications.
  ........
    r1957 | stevenknight | 2007-06-01 10:18:15 -0500 (Fri, 01 Jun 2007) | 14 lines

    Shift updating a Node's signature/timestamp/size information until after
    the Node has been built (or visited, if it's a leaf Node).  This therefore
    changes the semantics of the .visited() method, so that it's called for
    *every* Node visited, not just leaf Nodes.

    This adds a Taskmaster method for updating a Node's status without
    calling the .built() and .visited() methods, which is used by the
    CleanTask subclass to avoid writing .sconsign info when cleaning (-c).

    Interesting ripple effect in the SConf.py module:  we have to manually
    override the .store_info() method on the Nodes we create, so that they
    don't write out their normal .sconsign information.  The SConf subsystem
    writes out its own by hand.
  ........
    r1962 | stevenknight | 2007-06-01 13:12:24 -0500 (Fri, 01 Jun 2007) | 5 lines

    Fix the ability to catch {IO,OS}Errors during build preparation and
    display the actual Node that triggered the error.  (Which Node actually
    triggers this case, and therefore the displayed error message, changed
    after we delayed content evaluation until *after* a Node is built.)
  ........
    r1975 | stevenknight | 2007-06-04 15:21:09 -0500 (Mon, 04 Jun 2007) | 3 lines

    Small refactoring:  use {new,get}_ninfo() instead of instantiating
    self.NodeInfo() directly.
  ........
    r1976 | stevenknight | 2007-06-04 16:14:12 -0500 (Mon, 04 Jun 2007) | 5 lines

    Similar to the previous change, call new_binfo() instead of instantiating
    the BuildInfo() type directly.  Change new_binfo() so it doesn't
    automatically attach a NodeInfo object (because sometimes we want the
    existing NodeInfo, and sometimes we want a completely new one).
  ........
    r1977 | stevenknight | 2007-06-04 16:35:44 -0500 (Mon, 04 Jun 2007) | 3 lines

    The body of DirFile.write() was all indented underneath a "if self.dirty:"
    block.  Invert the test and outdent the body.
  ........
    r1978 | stevenknight | 2007-06-04 16:52:14 -0500 (Mon, 04 Jun 2007) | 2 lines

    Memoize the stored information we get from the .sconsign file.
  ........
    r1979 | stevenknight | 2007-06-05 14:17:32 -0500 (Tue, 05 Jun 2007) | 6 lines

    Delay merging a Node's build information into the .sconsign entry until
    the entry is being written out.  This will allow us to include information
    (such as a content signature) that gets fetched for the first time *after*
    a Node is built.  (The code here isn't the prettiest at the moment, but
    this checkpoints an implementation that passes all the tests.)
  ........
    r1980 | stevenknight | 2007-06-06 10:06:56 -0500 (Wed, 06 Jun 2007) | 4 lines

    Refactor to have the store_info() method pull the binfo from
    the Node (instead of having the Node passing it in).
    Add a do_not_store_info() method to override the behavior.
  ........
    r1981 | stevenknight | 2007-06-06 11:18:29 -0500 (Wed, 06 Jun 2007) | 3 lines

    Refactor the unit test invocation so we can run multiple test
    methods from each class.
  ........
    r1982 | stevenknight | 2007-06-06 11:48:00 -0500 (Wed, 06 Jun 2007) | 7 lines

    Move responsibility for merging buildinfo entries from Node.FS to the
    SConsign module, through a new Base.store_info() method.  Return the
    existing Base.set_entry() method to actually setting the entry, and use
    the new Base.store_info() method for things that need delayed storage
    and merging.  Adds some commented-out prototype code for delaying
    the buildinfo entry merge until later.
  ........
    r1983 | stevenknight | 2007-06-06 22:57:14 -0500 (Wed, 06 Jun 2007) | 4 lines

    Better testing for parallel-build exceptions:  re-order the file names
    so that the input files are evaluated before the output files, increasing
    the likelihood of all three being built by simultaneous threads.
  ........
    r1984 | stevenknight | 2007-06-06 22:57:55 -0500 (Wed, 06 Jun 2007) | 2 lines

    Have Trace() flush after write so interleaving stays correct.
  ........
    r1985 | stevenknight | 2007-06-06 23:01:23 -0500 (Wed, 06 Jun 2007) | 11 lines

    Pull the NodeInfo objects out of the BuildInfo object and just deal
    with them as co-equal attributes of a Node.

    .sconsign files now have a place-holder SConsignEntry class that holds a
    Node's separate binfo and ninfo objects.  This will also be more flexible
    if we ever add more information in the future.

    Since this changes the format (but not content) of the .sconsign file
    entries, we will need to double back and figure out how to make the
    transition from pre-BSR .sconsign files before this goes live.
  ........
    r1986 | stevenknight | 2007-06-07 00:08:21 -0500 (Thu, 07 Jun 2007) | 2 lines

    Store the computed csig in the NodeInfo structure.
  ........
    r1987 | stevenknight | 2007-06-07 18:59:31 -0500 (Thu, 07 Jun 2007) | 12 lines

    Track the content signature directly in the NodeInfo object, not in the
    _memo dictionary.

    Wipe out the existing NodeInfo after the Node has
    been built so the content signature gets recalculated correctly.

    Change the NodeInfoBase.update() method so that the default behavior is
    to always update the specified fields, not just ones that exist.  This
    was necessary to get Alias and Value Nodes to work with this.

    Remove left-over comments.
  ........
    r1988 | stevenknight | 2007-06-08 17:09:24 -0500 (Fri, 08 Jun 2007) | 5 lines

    Invert the logic (is_up_to_date => changed) when gathering Configure
    Node states, and eliminate (most) Boolean logic that short-circuits
    some of the tests.  (This area isn't critical, so making sure the code
    is readable is more important than pure performance.)
  ........
    r1990 | stevenknight | 2007-06-09 17:14:32 -0500 (Sat, 09 Jun 2007) | 7 lines

    Refactor the Node.changed() method so it always evaluates every
    dependency, instead of returning early as soon as it finds a difference
    (like a different number of old and new dependencies).  This will be
    necessary to delay evaluation of Node information (content signatures)
    until it's actually needed (instead of evaluating it up front in case
    it's needed later).
  ........
    r2001 | stevenknight | 2007-06-11 11:20:29 -0500 (Mon, 11 Jun 2007) | 3 lines

    Use SCons.compat in the refactored SConsign.py so use of True/False/etc.
    works on Python 1.5 and 2.1.
  ........
    r2002 | stevenknight | 2007-06-11 11:57:56 -0500 (Mon, 11 Jun 2007) | 3 lines

    Have the "sconsign" script handle Configure context .sconsign information,
    which comes from different structures that have no NodeInfo objects.
  ........
    r2003 | stevenknight | 2007-06-11 15:08:52 -0500 (Mon, 11 Jun 2007) | 8 lines

    Avoid generating content signatures unless they're actually used:

    Don't automatically create a content signature after a File Node is
    built/visited, and delay the .sconsign merge of the information until
    just before the information will be written.  This means we'll only write
    out information that's actually fetched and used as part of deciding
    whether or not to rebuild any targets.
  ........
    r2004 | stevenknight | 2007-06-13 00:05:52 -0500 (Wed, 13 Jun 2007) | 4 lines

    Fetch the {Source,Target}Signatures() values inside the
    Node.FS.changed_since_last_build() method, not in Node.Node.changed().
    This is slightly less efficient, but cleaner architecturally.
  ........
    r2005 | stevenknight | 2007-06-13 10:18:17 -0500 (Wed, 13 Jun 2007) | 6 lines

    Use zip() to collect the children and previous signatures info more
    efficiently when deciding if a node must be rebuilt.  (The comment in
    the previous version of the source code about not using zip() because
    of Python 1.5 name space problems was due to the way the SConfTests.py
    module tried to reset the world's state.)
  ........
    r2006 | stevenknight | 2007-06-13 10:59:16 -0500 (Wed, 13 Jun 2007) | 4 lines

    Add a NotImplementedError base class implementation of
    changed_since_last_build(), with doc string commentary about why the
    method is called through the dependency Node, not the target Node.
  ........
    r2019 | stevenknight | 2007-06-18 12:26:06 -0500 (Mon, 18 Jun 2007) | 4 lines

    Remove the unnecessary cut-and-paste "import SCons.Action" in the
    changed_since_last_build() method, which was a left-over cut and paste.
    SCons.Action was already imported up top.
  ........
    r2021 | stevenknight | 2007-06-18 18:31:09 -0500 (Mon, 18 Jun 2007) | 2 lines

    Make sure all "is up to date" messages get printed when -j is used.
  ........
    r2022 | stevenknight | 2007-06-19 16:26:22 -0500 (Tue, 19 Jun 2007) | 3 lines

    Refactor the __checkClass() and must_be_a_Dir() methods into a more
    general and more efficient must_be_same() method.
  ........
    r2024 | stevenknight | 2007-06-19 19:16:09 -0500 (Tue, 19 Jun 2007) | 3 lines

    More clean up:  change various self.fs.Entry() calls to calls through
    the bound directory.Entry() method.
  ........
    r2026 | stevenknight | 2007-06-19 22:10:04 -0500 (Tue, 19 Jun 2007) | 10 lines

    Refactor lookup of Node.FS nodes as follows:
    * Completely get rid of _doLookup(), which was old, complicated code
      that implemented a top-down, entry-by-entry search for path names.
    * Use a new FS._lookup() method, which normalizes names to absolute
      paths, with a new RootDir._lookup_abs() method, which handles the
      actual lookup.  We plan to use the latter method for fast(er) internal
      lookups of already-normalized paths.
    * Create a new separate FS.get_root() method, just to avoid clutter
      in the RootDir._lookup_abs() method.
  ........
    r2027 | stevenknight | 2007-06-20 13:29:19 -0500 (Wed, 20 Jun 2007) | 3 lines

    Remove the debug-explain.py test from sigrefactor, branches/core
    split this up into multiple sub-tests.
  ........
    r2028 | stevenknight | 2007-06-20 13:38:28 -0500 (Wed, 20 Jun 2007) | 2 lines

    Handle a line-ending mismatch on Windows.
  ........
    r2029 | stevenknight | 2007-06-20 13:39:05 -0500 (Wed, 20 Jun 2007) | 2 lines

    Support the ability to -d when path names have Windows \ separators.
  ........
    r2030 | stevenknight | 2007-06-20 13:55:11 -0500 (Wed, 20 Jun 2007) | 2 lines

    Windows portability:  TestSConsign._obj instead of a hard-coded '.o' suffix.
  ........
    r2031 | stevenknight | 2007-06-20 15:30:06 -0500 (Wed, 20 Jun 2007) | 2 lines

    Python 1.5 namespace portability.
  ........
    r2032 | stevenknight | 2007-06-20 16:38:28 -0500 (Wed, 20 Jun 2007) | 2 lines

    Fix tests affected by the -d fix for runtest.py.
  ........
    r2039 | stevenknight | 2007-06-21 12:17:06 -0500 (Thu, 21 Jun 2007) | 2 lines

    Ignore *.pyo files, too, now that one of the tests causes us to generate them.
  ........
    r2041 | stevenknight | 2007-06-21 12:36:30 -0500 (Thu, 21 Jun 2007) | 7 lines

    In the .sconsign file, store the paths to all of the dependencies relative
    to the top-level SConstruct directory, not the target's directory.
    This allows us to be much more efficient when writing the .sconsign file
    (since we can just store the already-computed path) and reading the
    .sconsign file (since we can use the quick, normalized-lookup method to
    translate the string into the correct Node).
  ........
    r2042 | stevenknight | 2007-06-22 13:25:26 -0500 (Fri, 22 Jun 2007) | 2 lines

    Remove left-over debug code (instrumenting os.path.normpath()).
  ........
    r2043 | stevenknight | 2007-06-22 13:28:35 -0500 (Fri, 22 Jun 2007) | 8 lines

    Actually use the new Dir._lookup_abs() method for fast .sconsign lookups
    relative to the top-level SConstruct directory and make it work correctly.
    (I had commented out the call to binfo.prepare_dependencies() and
    forgot to un-comment it.)  This simplifies things by getting rid of the
    target node we attached to a BuildInfo (purely so we could do relative
    path lookups) and the corresponding conversions into and out of the
    .sconsign file.
  ........
    r2044 | stevenknight | 2007-06-22 19:31:00 -0500 (Fri, 22 Jun 2007) | 5 lines

    Delay fetching the {Source,Target}Signature() values from an environment
    until they're actually needed.  (If the performance here is "close
    enough", then we may not have to look at more complicated loop-invariant
    schemes.)
  ........
    r2045 | stevenknight | 2007-06-22 22:06:19 -0500 (Fri, 22 Jun 2007) | 4 lines

    Replace the Environment.our_deepcopy() function with a
    Util.semi_deepcopy() function, rewritten to use the more efficient
    dispatch-table approach that the normal Python copy.py module uses.
  ........
    r2050 | stevenknight | 2007-06-24 23:49:06 -0500 (Sun, 24 Jun 2007) | 5 lines

    Make the latest signature refactoring portable to Windows, and between
    platforms, by always storing the dependency paths with POSIX separators.
    Handle multiple drive letters by tracking a separate "labspath" attribute
    for the lookup path relative to the root directory's drive letter.
  ........
    r2065 | stevenknight | 2007-06-27 11:02:48 -0500 (Wed, 27 Jun 2007) | 2 lines

    Branch files that were left out of the last merge.
  ........
    r2066 | stevenknight | 2007-06-27 11:03:05 -0500 (Wed, 27 Jun 2007) | 2 lines

    Branch files that were left out of the last merge.
  ........
    r2067 | stevenknight | 2007-06-27 11:03:24 -0500 (Wed, 27 Jun 2007) | 2 lines

    Branch files that were left out of the last merge.
  ........
    r2069 | stevenknight | 2007-06-27 19:08:50 -0500 (Wed, 27 Jun 2007) | 2 lines

    Remove an unnecessary target.has_builder() call in changed_since_last_build().
  ........
    r2070 | stevenknight | 2007-06-27 19:25:47 -0500 (Wed, 27 Jun 2007) | 3 lines

    Refactor changed_since_last_build() to avoid code duplication and simplify
    (?) the structure--probably negligible performance impact.
  ........
    r2071 | stevenknight | 2007-06-29 16:21:37 -0500 (Fri, 29 Jun 2007) | 3 lines

    Add the Decider() function, globally, and settable per construction
    environment or per Node.  This hasn't yet been optimized.
  ........
    r2072 | stevenknight | 2007-06-29 18:01:53 -0500 (Fri, 29 Jun 2007) | 2 lines

    Memoize Executor.get_build_env().
  ........
    r2073 | stevenknight | 2007-06-30 01:04:43 -0500 (Sat, 30 Jun 2007) | 5 lines

    Constructing a dictionary to map signature types ('build', 'MD5', etc.)
    to a dependency's methods ends up being less efficient than just using
    a series of if-elif statements.  Refactor the default_decider_function()
    accordingly.
  ........
    r2074 | stevenknight | 2007-07-01 08:59:35 -0500 (Sun, 01 Jul 2007) | 4 lines

    Now that the objects returned by Node.FS.get_stored_implicit() already
    convert the .sconsign strings to Nodes, don't have the --implicit-cache
    loop run them through the source_factory method.
  ........
    r2075 | stevenknight | 2007-07-02 09:46:21 -0500 (Mon, 02 Jul 2007) | 4 lines

    Eliminate an if-test for the intialized default construction environment
    for internal calls of SCons.Defaults.DefaultEnvironment(), and some
    external calls as well.
  ........
    r2076 | stevenknight | 2007-07-02 10:17:58 -0500 (Mon, 02 Jul 2007) | 3 lines

    More efficient use of the changed_since_last_build() method from
    the default construction environment.
  ........
    r2077 | stevenknight | 2007-07-02 12:26:34 -0500 (Mon, 02 Jul 2007) | 6 lines

    Move the decision about whether a given dependent Node has a Builder
    or not back to when the Builder is actually set, by splitting the
    decision path in two;  one for sources and one for targets.  (Setting a
    construction environment's decider sets both of them, which may or may
    not be what we want in the long term.))
  ........
    r2079 | stevenknight | 2007-07-02 13:14:40 -0500 (Mon, 02 Jul 2007) | 4 lines

    Now explicity set the source decider function for a construction
    environment when the SourceSignatures() method is called, avoiding
    the delayed-evaluation tests.
  ........
    r2081 | stevenknight | 2007-07-02 13:55:58 -0500 (Mon, 02 Jul 2007) | 6 lines

    And now explicity set the target decider function for a construction
    environment when the TargetSignatures() method is called, avoiding even
    more delayed-evaluation tests.

    This also renames the cslb_*() functions to decide_*().
  ........
    r2084 | stevenknight | 2007-07-03 07:13:38 -0500 (Tue, 03 Jul 2007) | 3 lines

    Memoize the Node.get_build_env() value, too, since we're now callng
    that repeatedly.
  ........
    r2085 | stevenknight | 2007-07-03 09:50:25 -0500 (Tue, 03 Jul 2007) | 5 lines

    Get rid of the str_to_nodes() function within the
    FileBuildInfo.prepare_dependencies() method that checks for Alias lookups,
    in favor of a loop that uses new str_to_nodes() methods in the individual
    *NodeInfo classes to convert the strings to Nodes more directly.
  ........
    r2086 | stevenknight | 2007-07-03 10:07:04 -0500 (Tue, 03 Jul 2007) | 3 lines

    Format the FileBuildInfo entries by zipping the children and their
    NodeInfo signatures, not by fetching each one by index.
  ........
    r2089 | stevenknight | 2007-07-03 20:20:15 -0500 (Tue, 03 Jul 2007) | 4 lines

    Remove the _add_child() checks that verify that the argument is a list
    and contains all Nodes.  It's internal, so any necessary verification
    can happen in the methods that call it.
  ........
    r2101 | stevenknight | 2007-07-08 16:13:56 -0500 (Sun, 08 Jul 2007) | 3 lines

    Add string aliases for the canned Decider functions we'll support out of the
    box:  timestamp-match (a.k.a. make), timestamp-newer, MD5 (a.k.a.  content).
  ........
    r2115 | stevenknight | 2007-07-10 21:00:14 -0500 (Tue, 10 Jul 2007) | 2 lines

    Refactor test/option-q.py into two separate subtests.
  ........
    r2144 | stevenknight | 2007-07-16 01:04:25 -0500 (Mon, 16 Jul 2007) | 3 lines

    Fix -q exiting with a non-zero exit status when a file with no builder
    is specified on the command line.
  ........
    r2314 | stevenknight | 2007-08-17 13:46:27 -0500 (Fri, 17 Aug 2007) | 3 lines

    Capture a test case to make sure get_csig() can be called from
    within a function Action.  (Damyan Pepper)
  ........
    r2315 | stevenknight | 2007-08-17 15:05:54 -0500 (Fri, 17 Aug 2007) | 3 lines

    Uncomment the test for content signatures with directories as sources,
    which the signature refactoring makes work.
  ........
    r2316 | stevenknight | 2007-08-17 16:33:25 -0500 (Fri, 17 Aug 2007) | 2 lines

    Make sure that CacheDir() works even when timestamp signatures are used.
  ........
    r2317 | stevenknight | 2007-08-17 16:51:02 -0500 (Fri, 17 Aug 2007) | 2 lines

    Change line endings from DOS to UNIX.
  ........
    r2318 | stevenknight | 2007-08-18 07:13:09 -0500 (Sat, 18 Aug 2007) | 4 lines

    Add a new MD5-timestamp decider function that assumes that if the
    timestamp hasn't changed, then the content hasn't changed either, and
    it should then just re-use the content signature from the last run.
  ........
    r2319 | stevenknight | 2007-08-18 07:41:59 -0500 (Sat, 18 Aug 2007) | 3 lines

    Add a test script to verify that action changes cause rebuilds even
    when file decisions are configured for timestamps.
  ........
    r2360 | stevenknight | 2007-08-23 08:07:16 -0500 (Thu, 23 Aug 2007) | 2 lines

    Fix the test by resetting the content3.in file to the correct timestamp.
  ........
    r2361 | stevenknight | 2007-08-23 12:02:04 -0500 (Thu, 23 Aug 2007) | 3 lines

    Use the new hashlib module for our MD5 signature calculations, with the
    introduction of a compatibility module for pre-2.5 Python versions.
  ........
    r2362 | stevenknight | 2007-08-23 14:02:56 -0500 (Thu, 23 Aug 2007) | 2 lines

    Make targets implicitly depend on the commands used to build them.
  ........
    r2364 | stevenknight | 2007-08-24 00:07:46 -0500 (Fri, 24 Aug 2007) | 2 lines

    Add code to convert what we can from the old .sconsign file entries.
  ........
    r2366 | stevenknight | 2007-08-24 19:40:09 -0500 (Fri, 24 Aug 2007) | 2 lines

    Add a $IMPLICIT_COMMAND_DEPENDENCIES variable.
  ........
    r2367 | stevenknight | 2007-08-24 19:43:58 -0500 (Fri, 24 Aug 2007) | 2 lines

    Add mention of $IMPLICIT_COMMAND_DEPENDENCIES to the release notes.
  ........
    r2375 | stevenknight | 2007-08-27 11:43:04 -0500 (Mon, 27 Aug 2007) | 2 lines

    Windows portability fixes in tests.
  ........
    r2376 | stevenknight | 2007-08-27 12:19:52 -0500 (Mon, 27 Aug 2007) | 2 lines

    Use string.join() instead of a more modern string object method.
  ........
    r2379 | stevenknight | 2007-08-27 12:40:02 -0500 (Mon, 27 Aug 2007) | 3 lines

    Add version ID's to the classes stored in a .sconsign file, for easier
    future conversion as we change schemas.
  ........
................
  r2390 | stevenknight | 2007-08-27 17:32:00 -0500 (Mon, 27 Aug 2007) | 4 lines

  Remove the old TargetSignatures.py test, which was
  overlooked in the merge.  (Its functionality got moved to
  test/TargetSignatures/build-content.py.)
................
  r2391 | stevenknight | 2007-08-28 10:47:15 -0500 (Tue, 28 Aug 2007) | 2 lines

  Don't die if an old .sconsign entry has no .ninfo attribute.
................
  r2392 | stevenknight | 2007-08-28 15:52:56 -0500 (Tue, 28 Aug 2007) | 2 lines

  Fix an O(N^2) search in Tool.install.add_targets_to_INSTALLED_FILES().
................
  r2393 | stevenknight | 2007-08-28 21:28:30 -0500 (Tue, 28 Aug 2007) | 2 lines

  Add support for an ensure_suffix Builder keyword argument.
................
  r2394 | stevenknight | 2007-08-28 21:37:04 -0500 (Tue, 28 Aug 2007) | 3 lines

  Separate the failure tests when trying to connect to an X server,
  so we can tell why it's failing...
................
  r2395 | stevenknight | 2007-08-29 11:46:34 -0500 (Wed, 29 Aug 2007) | 3 lines

  Look for a raw exit status of 1, or the shifted exit status, so we're
  not dependent on how the test infrastructure hands it to us.
................
  r2396 | stevenknight | 2007-08-29 18:12:39 -0500 (Wed, 29 Aug 2007) | 2 lines

  Disable universal_newlines when calling subprocess.
................
  r2397 | stevenknight | 2007-08-29 18:13:29 -0500 (Wed, 29 Aug 2007) | 3 lines

  Add a Progress() hook to support display of per-Node progress while
  walking the DAG.
................
  r2398 | stevenknight | 2007-08-29 19:43:12 -0500 (Wed, 29 Aug 2007) | 7 lines

  Add a test case (courtesy Greg Noel) to characterize how we handle
  mid-build changes to implicit dependency files (we currently don't detect
  the change and rebuild all dependents).

  Move two other test scripts related to implicit dependency behavior
  into a common subdirectory.
................
  r2399 | stevenknight | 2007-08-30 10:32:50 -0500 (Thu, 30 Aug 2007) | 4 lines

  Add a $JAVABOOTCLASSPATH variable.
  Commonize $JAVA*PATH expansion with a utility class.
  (Greg Ward)
................
  r2400 | stevenknight | 2007-08-30 10:59:56 -0500 (Thu, 30 Aug 2007) | 3 lines

  Make sure extra auxiliary files generated by LaTeX packages are deleted
  by scons -c.  (Matthias Troffaes)
................
  r2401 | stevenknight | 2007-08-30 18:43:24 -0500 (Thu, 30 Aug 2007) | 3 lines

  Issue 1589:  when Cloning a construction environment, apply set variables
  both before and after calling tools (like during intialization).
................
  r2402 | stevenknight | 2007-08-30 18:52:02 -0500 (Thu, 30 Aug 2007) | 3 lines

  Issue 1555:  add Windows64 support for the Intel C compiler.
  (Gary Oberbrunner)
................
  r2403 | stevenknight | 2007-08-30 19:57:24 -0500 (Thu, 30 Aug 2007) | 4 lines

  Issue 1493:  have ParseConfig(), MergeFlags() and ParseFlags() handle
  output from *-config commands with quoted arguments (path names that
  contain spaces).
................
  r2410 | stevenknight | 2007-09-04 00:28:46 -0500 (Tue, 04 Sep 2007) | 4 lines

  Add a shlex.split() compatability wrapper for pre-Python 2.3 versions.
  Unfortunately, it doesn't behave exactly like later versions (it doesn't
  detect mid-token quotes), but we're not going to worry about that.
................
  r2411 | stevenknight | 2007-09-04 12:56:36 -0500 (Tue, 04 Sep 2007) | 3 lines

  Add a Sig.py module that generates a warning if user code tries to
  "import SCons.Sig" directly.
................
  r2412 | stevenknight | 2007-09-04 20:53:09 -0500 (Tue, 04 Sep 2007) | 3 lines

  Make the Return() function return immediately.  Add a stop= keyword
  argument in case anyone really needs the old behavior.
................
  r2413 | stevenknight | 2007-09-05 10:38:40 -0500 (Wed, 05 Sep 2007) | 4 lines

  Refactor initialization of different pieces (the underlying
  InstallBuilder, the Install() and InstallAs() wrappers) into separate
  blocks.
................
  r2414 | stevenknight | 2007-09-05 11:39:25 -0500 (Wed, 05 Sep 2007) | 4 lines

  Move the definitions of the wrapper functions to the global namespace
  (nesting them and delaying intialization of global variables doesn't
  save anything and just complicates reading the code).
................
  r2415 | stevenknight | 2007-09-05 13:31:27 -0500 (Wed, 05 Sep 2007) | 3 lines

  When Cloning a construction environment, re-bind any methods added by
  the AddMethod() method to the new construction environment.
................
  r2416 | stevenknight | 2007-09-06 10:04:18 -0500 (Thu, 06 Sep 2007) | 7 lines

  Add a MethodWrapper class that handles generic association of a
  callable and an object that wants to use the callable as a "method."
  Provide hooks for centralized cloning of the different "methods" on to
  a new, copied underlying environment (used by the env.Clone() method).
  Make BuilderWrapper a subclass of MethodWrapper.  Modify env.Clone()
  for the new, combined way of handling these "methods."
................
  r2417 | stevenknight | 2007-09-06 14:11:35 -0500 (Thu, 06 Sep 2007) | 2 lines

  Refactor test failure conditions to use test.must_match().
................
  r2418 | stevenknight | 2007-09-06 14:28:52 -0500 (Thu, 06 Sep 2007) | 9 lines

  Restore the ability to use the Install() and InstallAs() builders without
  having to specify the 'install' tool directly.
  Add a ToolInitializer class that handles delayed intialization of tool
  modules when added to a construction environment as a "method."
  Add an env.RemoveModule() method that takes care of removing an added
  MethodWrapper instance from the environment, deleting it from the
  list that gets copied on cloning (and changing the name of that list
  back to "added_methods").
................
  r2419 | stevenknight | 2007-09-07 06:27:33 -0500 (Fri, 07 Sep 2007) | 3 lines

  Issue 1725:  avoid race conditions when pushing a file to CacheDir().
  (Carsten Koch)
................
  r2420 | pscholl | 2007-09-07 09:13:28 -0500 (Fri, 07 Sep 2007) | 7 lines

   * delete the push_emitter() function. Direct manipulation of the source and
     target lists seem the way to go for packaging. Fix all packagers using this
     function.

   * fix the internationalization testcase
................
  r2421 | pscholl | 2007-09-07 09:14:12 -0500 (Fri, 07 Sep 2007) | 3 lines

   * new testcase for building multiple rpm packages from one scons call.
................
  r2422 | stevenknight | 2007-09-07 14:12:22 -0500 (Fri, 07 Sep 2007) | 4 lines

  Capture a test script with a particular reported test case for multiple
  Package() calls spread across different SConscript subdirectories.
  (Use case courtesy Andrew Smith.)
................
  r2423 | stevenknight | 2007-09-07 17:40:25 -0500 (Fri, 07 Sep 2007) | 4 lines

  Fix use of exitstatfunc on Action objects by getting rid of the ability
  to override exitstatfunc when calling a function (which we were only using
  internally to basically suppress whatever was set on the Action object).
................
  r2424 | stevenknight | 2007-09-10 20:06:59 -0500 (Mon, 10 Sep 2007) | 3 lines

  Make sure the library dependencies show up in --tree=derived output.
  (The Microsoft toolchain used to fail this on SCons 0.97 and earlier.)
................
  r2425 | stevenknight | 2007-09-10 22:25:27 -0500 (Mon, 10 Sep 2007) | 5 lines

  When adding a new entry to a directory, reset the .implicit attribute to
  None so that the directory will get "re-scanned" for implicit dependencies
  (the entries within the directory) if it gets re-visited later in the
  DAG walk.
................
  r2426 | stevenknight | 2007-09-12 12:38:05 -0500 (Wed, 12 Sep 2007) | 2 lines

  String method fix for Python 1.5.2.
................
  r2427 | stevenknight | 2007-09-12 12:38:29 -0500 (Wed, 12 Sep 2007) | 2 lines

  Remove left-over commented-out Trace() call.
................
  r2428 | stevenknight | 2007-09-13 01:36:34 -0500 (Thu, 13 Sep 2007) | 2 lines

  Handle Java inner class names with $ in them.  (Tzvetan Mikov)
................
  r2429 | stevenknight | 2007-09-13 14:29:09 -0500 (Thu, 13 Sep 2007) | 2 lines

  Add a check that should (possibly) avoid import errors on Solaris.
................
  r2430 | stevenknight | 2007-09-13 14:30:36 -0500 (Thu, 13 Sep 2007) | 5 lines

  Windows portability fixes for Progress() tests, restoring the original
  universal_newlines setting on the test infrastructure, changing file
  names to make the order in which Nodes are visited case-insensitive, and
  fixing line endings on output we expect to read from files.
................
  r2431 | stevenknight | 2007-09-14 07:58:44 -0500 (Fri, 14 Sep 2007) | 2 lines

  Make sure all function examples are introduced by "Example:" or "Examples:".
................
  r2432 | stevenknight | 2007-09-14 11:35:03 -0500 (Fri, 14 Sep 2007) | 3 lines

  Clean up Options files initialization so we don't have to check on usage
  for whether it's been set.
................
  r2433 | stevenknight | 2007-09-14 23:12:32 -0500 (Fri, 14 Sep 2007) | 4 lines

  Fix use of {Source,Target}Signatures() with construction variable
  overrides by making the default decider_*() things unbound functions,
  instead of bound methods.
................
  r2434 | stevenknight | 2007-09-14 23:14:25 -0500 (Fri, 14 Sep 2007) | 2 lines

  Don't use "True" in the test infrastructure, use 1 instead.
................
  r2435 | stevenknight | 2007-09-15 00:52:56 -0500 (Sat, 15 Sep 2007) | 2 lines

  Avoid use of False in two tests.
................
  r2436 | pscholl | 2007-09-17 08:14:35 -0500 (Mon, 17 Sep 2007) | 21 lines

   * additional testcase for packaging:
      * building multiple packages from one scons call
      * supplying explicit target names for msi and rpm
      * test the Find*Files() functions

   * modify the package-type option test to not only catch
     the PACKAGETYPE, but also the --package-type alias
     command line argument

   * move FindInstalledFiles to Environment.py and remove
     Find*Files() functions from the packaging module.

   * capitalize the VENDOR tag for msi

   * remove the superfluous packager.py files

   * Add documentation for FindInstalledFiles() and
     FindSourceFiles()
................
  r2437 | stevenknight | 2007-09-17 11:06:28 -0500 (Mon, 17 Sep 2007) | 2 lines

  Provide compatibility for the variables defined in SCons.Sig.
................
  r2438 | stevenknight | 2007-09-17 23:13:27 -0500 (Mon, 17 Sep 2007) | 5 lines

  Handle duplicate fies in a target list:  only decrement the reference
  count once for the target, not once for each time it shows up in the
  list, so dependencies don't "disappear" from the DAG walk because the
  reference count gets decremented past zero.
................
  r2439 | stevenknight | 2007-09-17 23:15:45 -0500 (Mon, 17 Sep 2007) | 2 lines

  Fix syntax errors from failure to quote the package type.
................
  r2440 | stevenknight | 2007-09-17 23:21:20 -0500 (Mon, 17 Sep 2007) | 2 lines

  Use AddOption() to support the --package-type option.
................
  r2441 | stevenknight | 2007-09-17 23:31:40 -0500 (Mon, 17 Sep 2007) | 2 lines

  Skip the test if tar isn't available.
................
  r2442 | stevenknight | 2007-09-18 06:35:04 -0500 (Tue, 18 Sep 2007) | 2 lines

  Remove Tool/packaging/packager.py from the manifest.
................
  r2443 | garyo | 2007-09-18 08:13:15 -0500 (Tue, 18 Sep 2007) | 1 line

  SGI IRIX: use proper C++ compiler for SHCXX, don't hardcode CC.
................
  r2444 | garyo | 2007-09-18 08:17:03 -0500 (Tue, 18 Sep 2007) | 1 line

  Avoid running MinGW tests on IRIX; they're irrelevant there and they fail anyway (they seem to pass on Linux, so left them in there.)
................
  r2445 | garyo | 2007-09-18 08:20:52 -0500 (Tue, 18 Sep 2007) | 1 line

  Fixed wrong return type in test code; caused warnings on IRIX which made test fail.
................
  r2446 | stevenknight | 2007-09-18 11:21:53 -0500 (Tue, 18 Sep 2007) | 2 lines

  Record Gary's change to the sgic++ tool.
................
  r2451 | stevenknight | 2007-09-19 00:01:46 -0500 (Wed, 19 Sep 2007) | 2 lines

  Windows portability fix (when checking for path names in output).
................
  r2452 | stevenknight | 2007-09-19 00:08:23 -0500 (Wed, 19 Sep 2007) | 2 lines

  Handle the ImportError if there's no threading module on Windows.
................

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

182 files changed:
.svnt/conf
QMTest/TestCmd.py
QMTest/TestCommon.py
QMTest/TestSCons.py
SConstruct
bench/dependency-func.py [new file with mode: 0644]
doc/man/scons.1
doc/man/sconsign.1
runtest.py
src/CHANGES.txt
src/RELEASE.txt
src/engine/MANIFEST.in
src/engine/SCons/Action.py
src/engine/SCons/Action.xml
src/engine/SCons/Builder.py
src/engine/SCons/CacheDir.py
src/engine/SCons/CacheDirTests.py
src/engine/SCons/Debug.py
src/engine/SCons/Defaults.py
src/engine/SCons/Environment.py
src/engine/SCons/EnvironmentTests.py
src/engine/SCons/Executor.py
src/engine/SCons/ExecutorTests.py
src/engine/SCons/JobTests.py
src/engine/SCons/Node/Alias.py
src/engine/SCons/Node/FS.py
src/engine/SCons/Node/FSTests.py
src/engine/SCons/Node/NodeTests.py
src/engine/SCons/Node/Python.py
src/engine/SCons/Node/PythonTests.py
src/engine/SCons/Node/__init__.py
src/engine/SCons/Options/__init__.py
src/engine/SCons/SConf.py
src/engine/SCons/SConfTests.py
src/engine/SCons/SConsign.py
src/engine/SCons/SConsignTests.py
src/engine/SCons/Scanner/ScannerTests.py
src/engine/SCons/Scanner/__init__.py
src/engine/SCons/Script/Main.py
src/engine/SCons/Script/SConscript.py
src/engine/SCons/Script/__init__.py
src/engine/SCons/Sig.py [new file with mode: 0644]
src/engine/SCons/Sig/.aeignore [deleted file]
src/engine/SCons/Sig/.cvsignore [deleted file]
src/engine/SCons/Sig/MD5.py [deleted file]
src/engine/SCons/Sig/MD5Tests.py [deleted file]
src/engine/SCons/Sig/TimeStampTests.py [deleted file]
src/engine/SCons/Taskmaster.py
src/engine/SCons/TaskmasterTests.py
src/engine/SCons/Tool/JavaCommon.py
src/engine/SCons/Tool/JavaCommonTests.py
src/engine/SCons/Tool/__init__.py
src/engine/SCons/Tool/install.py
src/engine/SCons/Tool/intelc.py
src/engine/SCons/Tool/javac.py
src/engine/SCons/Tool/javac.xml
src/engine/SCons/Tool/midl.py
src/engine/SCons/Tool/msvs.py
src/engine/SCons/Tool/msvsTests.py
src/engine/SCons/Tool/packaging/__init__.py
src/engine/SCons/Tool/packaging/ipk.py
src/engine/SCons/Tool/packaging/msi.py
src/engine/SCons/Tool/packaging/packager.py [deleted file]
src/engine/SCons/Tool/packaging/rpm.py
src/engine/SCons/Tool/packaging/src_tarbz2.py
src/engine/SCons/Tool/packaging/src_targz.py
src/engine/SCons/Tool/packaging/src_zip.py
src/engine/SCons/Tool/packaging/tarbz2.py
src/engine/SCons/Tool/packaging/targz.py
src/engine/SCons/Tool/packaging/zip.py
src/engine/SCons/Tool/rpm.py
src/engine/SCons/Tool/sgic++.py
src/engine/SCons/Tool/tex.py
src/engine/SCons/Tool/wix.py
src/engine/SCons/Util.py
src/engine/SCons/UtilTests.py
src/engine/SCons/compat/__init__.py
src/engine/SCons/compat/_scons_hashlib.py [new file with mode: 0644]
src/engine/SCons/compat/_scons_subprocess.py
src/script/sconsign.py
src/setup.py
test/Actions/addpost-link.py
test/Actions/exitstatfunc.py [moved from src/engine/SCons/Sig/TimeStamp.py with 50% similarity]
test/Actions/timestamp.py [new file with mode: 0644]
test/AddMethod.py
test/Alias/Depends.py [new file with mode: 0644]
test/BuildDir/errors.py
test/Builder/ensure_suffix.py [new file with mode: 0644]
test/CacheDir/multiple-targets.py [new file with mode: 0644]
test/CacheDir/scanner-target.py [new file with mode: 0644]
test/CacheDir/timestamp.py [moved from src/engine/SCons/Sig/SigTests.py with 69% similarity]
test/Configure/clean.py [new file with mode: 0644]
test/Configure/help.py [new file with mode: 0644]
test/Copy.py
test/Decider/Environment.py [new file with mode: 0644]
test/Decider/MD5-timestamp.py [new file with mode: 0644]
test/Decider/Node.py [new file with mode: 0644]
test/Decider/default.py [new file with mode: 0644]
test/Decider/mixed.py [new file with mode: 0644]
test/Decider/timestamp.py [new file with mode: 0644]
test/Decider/unknown.py [new file with mode: 0644]
test/Delete.py
test/Depends.py
test/Dir/source.py
test/Errors/preparation.py
test/Install/Install.py
test/Install/tool.py [moved from src/engine/SCons/Sig/__init__.py with 67% similarity]
test/Java/JAVABOOTCLASSPATH.py [new file with mode: 0644]
test/Java/multi-step.py
test/Java/swig-dependencies.py
test/LIBS.py
test/MinGW/RCCOM.py
test/MinGW/RCCOMSTR.py
test/NodeOps.py
test/Parallel/duplicate-target.py [new file with mode: 0644]
test/Progress/TARGET.py [new file with mode: 0644]
test/Progress/dots.py [new file with mode: 0644]
test/Progress/file.py [new file with mode: 0644]
test/Progress/function.py [new file with mode: 0644]
test/Progress/interval.py [new file with mode: 0644]
test/Progress/object.py [new file with mode: 0644]
test/Progress/spinner.py [new file with mode: 0644]
test/QT/installed.py
test/QT/moc-from-header.py
test/SConscript/Return.py [new file with mode: 0644]
test/SWIG/build-dir.py
test/Scanner/generated.py
test/Sig.py [new file with mode: 0644]
test/SourceCode.py
test/SourceSignatures.py [deleted file]
test/SourceSignatures/basic.py [new file with mode: 0644]
test/SourceSignatures/env.py [new file with mode: 0644]
test/SourceSignatures/implicit-cache.py [new file with mode: 0644]
test/SourceSignatures/no-csigs.py [new file with mode: 0644]
test/SourceSignatures/overrides.py [new file with mode: 0644]
test/SourceSignatures/switch-rebuild.py [new file with mode: 0644]
test/TEX/auxiliaries.py
test/TEX/clean.py [new file with mode: 0644]
test/TargetSignatures/build-content.py [moved from test/TargetSignatures.py with 56% similarity]
test/TargetSignatures/content.py [new file with mode: 0644]
test/TargetSignatures/overrides.py [new file with mode: 0644]
test/Value.py
test/bad-drive.py
test/chained-build.py
test/exceptions.py
test/explain/get_csig.py [new file with mode: 0644]
test/implicit-cache/basic.py
test/implicit/IMPLICIT_COMMAND_DEPENDENCIES.py [new file with mode: 0644]
test/implicit/asynchronous-modification.py [new file with mode: 0644]
test/implicit/changed-node.py [moved from test/changed-node.py with 100% similarity]
test/option-u.py
test/option/debug-memoizer.py
test/option/debug-stree.py
test/option/debug-time.py
test/option/debug-tree.py
test/option/taskmastertrace.py
test/option/tree-all.py
test/option/tree-lib.py [new file with mode: 0644]
test/packaging/convenience-functions.py [new file with mode: 0644]
test/packaging/msi/explicit-target.py [new file with mode: 0644]
test/packaging/multiple-packages-at-once.py [new file with mode: 0644]
test/packaging/multiple-subdirs.py [new file with mode: 0644]
test/packaging/option--package-type.py
test/packaging/place-files-in-subdirectory.py
test/packaging/rpm/cleanup.py
test/packaging/rpm/explicit-target.py [new file with mode: 0644]
test/packaging/rpm/internationalization.py
test/packaging/rpm/multipackage.py [new file with mode: 0644]
test/packaging/strip-install-dir.py
test/question/Configure.py [moved from test/option-q.py with 53% similarity]
test/question/basic.py [new file with mode: 0644]
test/question/no-builder.py [new file with mode: 0644]
test/runtest/fallback.py
test/runtest/noqmtest.py
test/runtest/python.py
test/sconsign/script/Configure.py [new file with mode: 0644]
test/sconsign/script/SConsignFile.py
test/sconsign/script/Signatures.py
test/sconsign/script/dblite.py
test/sconsign/script/no-SConsignFile.py
test/timestamp-fallback.py
test/up-to-date.py

index dece3240aa1f1ece2ebe2f54ca1f21f0f1b11edf..d4ffcb7e08b84cf7a5ba5055cd62c26a4a44bd69 100644 (file)
@@ -7,6 +7,8 @@ build = [
     '"%(python)s" bootstrap.py %%s' % locals()
 ]
 
+build_test_baseline = []
+
 cmd = '"%(python)s" runtest.py -q --noqmtest %%s' % locals()
 
 test_inputs = [
index 7a668e8e2f5c5e9a8ebc61163ab0049077c4384f..08642cb848166382d0031649222740b91be84def 100644 (file)
@@ -103,6 +103,9 @@ things.  Here is an overview of them:
     test.match_re_dotall("actual 1\nactual 2\n", regex_string)
     test.match_re_dotall(["actual 1\n", "actual 2\n"], list_of_regexes)
 
+    test.tempdir()
+    test.tempdir('temporary-directory')
+
     test.sleep()
     test.sleep(seconds)
 
@@ -176,8 +179,8 @@ version.
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 
 __author__ = "Steven Knight <knight at baldmt dot com>"
-__revision__ = "TestCmd.py 0.23.D001 2006/11/30 13:57:29 knight"
-__version__ = "0.23"
+__revision__ = "TestCmd.py 0.26.D001 2007/08/20 21:58:58 knight"
+__version__ = "0.26"
 
 import os
 import os.path
@@ -225,6 +228,10 @@ else:
         return type(e) is types.StringType or isinstance(e, UserString)
 
 tempfile.template = 'testcmd.'
+if os.name in ('posix', 'nt'):
+    tempfile.template = 'testcmd.' + str(os.getpid()) + '.'
+else:
+    tempfile.template = 'testcmd.'
 
 re_space = re.compile('\s')
 
@@ -459,7 +466,8 @@ class TestCmd:
                        subdir = None,
                        verbose = None,
                        match = None,
-                       combine = 0):
+                       combine = 0,
+                       universal_newlines = 1):
         self._cwd = os.getcwd()
         self.description_set(description)
         self.program_set(program)
@@ -471,6 +479,7 @@ class TestCmd:
                 verbose = 0
         self.verbose_set(verbose)
         self.combine = combine
+        self.universal_newlines = universal_newlines
         if not match is None:
             self.match_func = match
         else:
@@ -685,7 +694,8 @@ class TestCmd:
                   interpreter = None,
                   arguments = None,
                   chdir = None,
-                  stdin = None):
+                  stdin = None,
+                  universal_newlines = None):
         """Runs a test of the program or script for the test
         environment.  Standard output and error output are saved for
         future retrieval via the stdout() and stderr() methods.
@@ -721,48 +731,67 @@ class TestCmd:
         cmd_string = string.join(map(self.escape, cmd), ' ')
         if self.verbose:
             sys.stderr.write(cmd_string + "\n")
+        if universal_newlines is None:
+            universal_newlines = self.universal_newlines
+
         try:
-            p = popen2.Popen3(cmd, 1)
-        except AttributeError:
-            if sys.platform == 'win32' and cmd_string[0] == '"':
-                cmd_string = '"' + cmd_string + '"'
-            (tochild, fromchild, childerr) = os.popen3(' ' + cmd_string)
-            if stdin:
-                if is_List(stdin):
-                    for line in stdin:
-                        tochild.write(line)
-                else:
-                    tochild.write(stdin)
-            tochild.close()
-            out = fromchild.read()
-            err = childerr.read()
-            if self.combine:
-                self._stdout.append(out + err)
+            import subprocess
+        except ImportError:
+            try:
+                Popen3 = popen2.Popen3
+            except AttributeError:
+                class Popen3:
+                    def __init__(self, command):
+                        (stdin, stdout, stderr) = os.popen3(' ' + command)
+                        self.stdin = stdin
+                        self.stdout = stdout
+                        self.stderr = stderr
+                    def close_output(self):
+                        self.stdout.close()
+                        self.resultcode = self.stderr.close()
+                    def wait(self):
+                        return self.resultcode
+                if sys.platform == 'win32' and cmd_string[0] == '"':
+                    cmd_string = '"' + cmd_string + '"'
+                p = Popen3(cmd_string)
             else:
-                self._stdout.append(out)
-                self._stderr.append(err)
-            fromchild.close()
-            self.status = childerr.close()
-            if not self.status:
-                self.status = 0
-        except:
-            raise
+                p = Popen3(cmd, 1)
+                p.stdin = p.tochild
+                p.stdout = p.fromchild
+                p.stderr = p.childerr
         else:
-            if stdin:
-                if is_List(stdin):
-                    for line in stdin:
-                        p.tochild.write(line)
-                else:
-                    p.tochild.write(stdin)
-            p.tochild.close()
-            out = p.fromchild.read()
-            err = p.childerr.read()
-            if self.combine:
-                self._stdout.append(out + err)
+            p = subprocess.Popen(cmd,
+                                 stdin=subprocess.PIPE,
+                                 stdout=subprocess.PIPE,
+                                 stderr=subprocess.PIPE,
+                                 universal_newlines=universal_newlines)
+
+        if stdin:
+            if is_List(stdin):
+                for line in stdin:
+                    p.stdin.write(line)
             else:
-                self._stdout.append(out)
-                self._stderr.append(err)
-            self.status = p.wait()
+                p.stdin.write(stdin)
+        p.stdin.close()
+
+        out = p.stdout.read()
+        err = p.stderr.read()
+        try:
+            p.close_output()
+        except AttributeError:
+            p.stdout.close()
+            p.stderr.close()
+
+        self.status = p.wait()
+        if not self.status:
+            self.status = 0
+
+        if self.combine:
+            self._stdout.append(out + err)
+        else:
+            self._stdout.append(out)
+            self._stderr.append(err)
+
         if chdir:
             os.chdir(oldcwd)
         if self.verbose >= 2:
@@ -852,6 +881,45 @@ class TestCmd:
         link = self.canonicalize(link)
         os.symlink(target, link)
 
+    def tempdir(self, path=None):
+        """Creates a temporary directory.
+        A unique directory name is generated if no path name is specified.
+        The directory is created, and will be removed when the TestCmd
+        object is destroyed.
+        """
+        if path is None:
+            try:
+                path = tempfile.mktemp(prefix=tempfile.template)
+            except TypeError:
+                path = tempfile.mktemp()
+        os.mkdir(path)
+
+        # Symlinks in the path will report things
+        # differently from os.getcwd(), so chdir there
+        # and back to fetch the canonical path.
+        cwd = os.getcwd()
+        try:
+            os.chdir(path)
+            path = os.getcwd()
+        finally:
+            os.chdir(cwd)
+
+        # Uppercase the drive letter since the case of drive
+        # letters is pretty much random on win32:
+        drive,rest = os.path.splitdrive(path)
+        if drive:
+            path = string.upper(drive) + rest
+
+        #
+        self._dirlist.append(path)
+        global _Cleanup
+        try:
+            _Cleanup.index(self)
+        except ValueError:
+            _Cleanup.append(self)
+
+        return path
+
     def touch(self, path, mtime=None):
         """Updates the modification time on the specified file or
         directory path name.  The default is to update to the
@@ -894,32 +962,9 @@ class TestCmd:
         """
         if (path != None):
             if path == '':
-                path = tempfile.mktemp()
-            if path != None:
-                os.mkdir(path)
-            # We'd like to set self.workdir like this:
-            #     self.workdir = path
-            # But symlinks in the path will report things
-            # differently from os.getcwd(), so chdir there
-            # and back to fetch the canonical path.
-            cwd = os.getcwd()
-            os.chdir(path)
-            self.workdir = os.getcwd()
-            os.chdir(cwd)
-            # Uppercase the drive letter since the case of drive
-            # letters is pretty much random on win32:
-            drive,rest = os.path.splitdrive(self.workdir)
-            if drive:
-                self.workdir = string.upper(drive) + rest
-            #
-            self._dirlist.append(self.workdir)
-            global _Cleanup
-            try:
-                _Cleanup.index(self)
-            except ValueError:
-                _Cleanup.append(self)
-        else:
-            self.workdir = None
+                path = None
+            path = self.tempdir(path)
+        self.workdir = path
 
     def workpath(self, *args):
         """Returns the absolute path name to a subdirectory or file
index b8fb5cddb08f1e3e63e0c8d9fae6d9b2e79e4302..f4c7c541dd364e990a05e6f3b08343c78c545d83 100644 (file)
@@ -80,8 +80,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.23.D001 2006/11/30 13:57:29 knight"
-__version__ = "0.23"
+__revision__ = "TestCommon.py 0.26.D001 2007/08/20 21:58:58 knight"
+__version__ = "0.26"
 
 import os
 import os.path
index 9c270fa2da73be21162ab55faa832478a58fd0ea..b1fdbc1cbc7a0ce3e309b6713846369a44483d28 100644 (file)
@@ -207,7 +207,7 @@ class TestSCons(TestCommon):
         except (SCons.Errors.UserError, SCons.Errors.InternalError):
             return None
 
-    def detect(self, var, prog=None, ENV=None):
+    def detect(self, var, prog=None, ENV=None, norm=None):
         """
         Detect a program named 'prog' by first checking the construction
         variable named 'var' and finally searching the path used by
@@ -224,7 +224,10 @@ class TestSCons(TestCommon):
             prog = v
         if v != prog:
             return None
-        return env.WhereIs(prog)
+        result = env.WhereIs(prog)
+        if norm and os.sep != '/':
+            result = string.replace(result, os.sep, '/')
+        return result
 
     def detect_tool(self, tool, prog=None, ENV=None):
         """
index e00e10919904cb4911bd5aecaa16a4cf096a27ef..b35e3b0f146b2d70fbcca5c5ca94a844c43defe8 100644 (file)
@@ -119,11 +119,12 @@ if checkpoint:
         checkpoint = 'r' + revision
     version = version + checkpoint
 
+svn_status = None
+svn_status_lines = []
+
 if svn:
     svn_status = os.popen("%s status --verbose 2> /dev/null" % svn, "r").read()
     svn_status_lines = svn_status[:-1].split('\n')
-else:
-    svn_status_lines = []
 
 build_id = ARGUMENTS.get('BUILD_ID')
 if build_id is None:
@@ -158,14 +159,14 @@ command_line_variables = [
                         "The default is whatever hostname is returned " +
                         "by socket.gethostname()."),
 
-    ("CHECKPOINT=",     "The specific checkpoint release being packaged " +
-                        "This will be appended to the VERSION string.  " +
+    ("CHECKPOINT=",     "The specific checkpoint release being packaged, " +
+                        "which will be appended to the VERSION string.  " +
                         "A value of CHECKPOINT=d will generate a string " +
-                        "of 'd' plus today's date in the format YYYMMDD." +
+                        "of 'd' plus today's date in the format YYYMMDD.  " +
                         "A value of CHECKPOINT=r will generate a " +
-                        "string of 'r' plus the Subversion revision number.  " +
-                        "Any other CHECKPOINT= string will be used as is." +
-                        "There is no default value."),
+                        "string of 'r' plus the Subversion revision " +
+                        "number.  Any other CHECKPOINT= string will be " +
+                        "used as is.  There is no default value."),
 
     ("DATE=",           "The date string representing when the packaging " +
                         "build occurred.  The default is the day and time " +
@@ -238,7 +239,7 @@ import textwrap
 
 indent_fmt = '  %-26s  '
 
-Help("""
+Help("""\
 The following aliases build packages of various types, and unpack the
 contents into build/test-$PACKAGE subdirectories, which can be used by the
 runtest.py -p option to run tests against what's been actually packaged:
diff --git a/bench/dependency-func.py b/bench/dependency-func.py
new file mode 100644 (file)
index 0000000..0af411d
--- /dev/null
@@ -0,0 +1,98 @@
+# __COPYRIGHT__
+#
+# Benchmarks for testing the selection of dependency changed functions
+# in src/engine/Environment.py.
+
+
+def use_a_dict(env, dep, arg):
+    func = {
+        '1111' : dep.func1,
+        '2222' : dep.func2,
+        '3333' : dep.func3,
+        '4444' : dep.func4,
+    }
+    t = env.get_type()
+    return func[t](arg)
+
+
+def use_if_tests(env, dep, arg):
+    t = env.get_type()
+    if t == '1111':
+        func = dep.func1
+    elif t == '2222':
+        func = dep.func2
+    elif t == '3333':
+        func = dep.func3
+    elif t == '4444':
+        func = dep.func4
+    else:
+        raise Exception, "bad key %s" % t
+    return func(arg)
+
+
+class Environment():
+    def __init__(self, t):
+        self.t = t
+    def get_type(self):
+        return self.t
+
+class Node():
+    def func1(self, arg):
+        pass
+    def func2(self, arg):
+        pass
+    def func3(self, arg):
+        pass
+    def func4(self, arg):
+        pass
+
+node = Node()
+
+def Func01(t):
+    """use_a_dict"""
+    env = Environment(t)
+    for i in IterationList:
+        use_a_dict(env, node, None)
+
+def Func02(t):
+    """use_if_tests"""
+    env = Environment(t)
+    for i in IterationList:
+        use_if_tests(env, node, None)
+
+
+
+# Data to pass to the functions on each run.  Each entry is a
+# three-element tuple:
+#
+#   (
+#       "Label to print describing this data run",
+#       ('positional', 'arguments'),
+#       {'keyword' : 'arguments'},
+#   ),
+
+class A:
+    pass
+
+Data = [
+    (
+        "1",
+        ('1111',),
+        {},
+    ),
+    (
+        "2",
+        ('2222',),
+        {},
+    ),
+    (
+        "3",
+        ('3333',),
+        {},
+    ),
+    (
+        "4",
+        ('4444',),
+        {},
+    ),
+]
index a20a0f63a54c8f4ec9e439a6a92dab48287a3e59..6f87b203c3ac9bb2400670e2abf33b5e980a1a2f 100644 (file)
@@ -2013,6 +2013,8 @@ specified
 .I function
 itself is used for the method name.
 
+Examples:
+
 .ES
 # Note that the first argument to the function to
 # be attached as a method must be the object through
@@ -2192,6 +2194,8 @@ can be called multiple times for the same
 alias to add additional targets to the alias,
 or additional actions to the list for this alias.
 
+Examples:
+
 .ES
 Alias('install')
 Alias('install', '/usr/bin')
@@ -2225,6 +2229,7 @@ If
 is called multiple times,
 each call completely overwrites the previous list
 of allowed exceptions.
+
 Example:
 
 .ES
@@ -2276,6 +2281,8 @@ are both coerced to lists,
 and the lists are added together.
 (See also the Prepend method, below.)
 
+Example:
+
 .ES
 env.Append(CCFLAGS = ' -g', FOO = ['foo.yyy'])
 .EE
@@ -2298,6 +2305,7 @@ and
 This can also handle the
 case where the given old path variable is a list instead of a
 string, in which case a list will be returned instead of a string.
+
 Example:
 
 .ES
@@ -2325,6 +2333,8 @@ construction variable will
 .I not
 be added again to the list.
 
+Example:
+
 .ES
 env.AppendUnique(CCFLAGS = '-g', FOO = ['foo.yyy'])
 .EE
@@ -2341,6 +2351,8 @@ is intended to be passed to the
 .B SourceCode
 function.
 
+Example:
+
 .ES
 env.SourceCode('.', env.BitKeeper())
 .EE
@@ -2666,6 +2678,7 @@ or by a
 .B \-
 (hyphen)
 to ignore the exit status of the external command.
+
 Examples:
 
 .ES
@@ -2700,7 +2713,9 @@ by using the
 .BR Dir ()
 or
 .BR env.Dir ()
-functions:
+functions.
+
+Examples:
 
 .ES
 env.Command('ddd.list', Dir('ddd'), 'ls -l $SOURCE > $TARGET')
@@ -2733,6 +2748,8 @@ they are added to the returned copy,
 overwriting any existing values
 for the keywords.
 
+Example:
+
 .ES
 env2 = env.Clone()
 env3 = env.Clone(CCFLAGS = '-g')
@@ -2750,7 +2767,7 @@ env4 = env.Clone(tools = ['msvc', MyTool])
 .TP
 .RI env.Copy([ key = val ", ...])"
 A synonym for
-env.Clone().
+.BR env.Clone() .
 (This will probably be officially deprecated some day.)
 
 '\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
@@ -2777,7 +2794,9 @@ from the repository path names,
 so that you only have to
 replicate part of the repository
 directory hierarchy in your
-local build directory:
+local build directory.
+
+Examples:
 
 .ES
 # Will fetch foo/bar/src.c
@@ -2793,6 +2812,163 @@ env.SourceCode('.', env.CVS('/usr/local/CVSROOT', 'foo'))
 env.SourceCode('.', env.CVS('/usr/local/CVSROOT', 'foo/bar'))
 .EE
 
+'\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+.TP 
+.RI Decider( function )
+.TP
+.RI env.Decider( function )
+Specifies that all up-to-date decisions for
+targets built through this construction environment
+will be handled by the specified
+.IR function .
+The
+.I function
+can be one of the following strings
+that specify the type of decision function
+to be performed:
+
+.RS 10
+.B timestamp-newer
+Specifies that a target shall be considered out of date and rebuilt
+if the dependency's timestamp is newer than the target file's timestamp.
+This is the behavior of the classic Make utility,
+and
+.B make
+can be used a synonym for
+.BR timestamp-newer .
+
+.HP 6
+.B timestamp-match
+Specifies that a target shall be considered out of date and rebuilt
+if the dependency's timestamp is different than the
+timestamp recorded the last time the target was built.
+This provides behavior very similar to the classic Make utility
+(in particular, files are not opened up so that their
+contents can be checksummed)
+except that the target will also be rebuilt if a
+dependency file has been restored to a version with an
+.I earlier
+timestamp, such as can happen when restoring files from backup archives.
+
+.HP 6
+.B MD5
+Specifies that a target shall be considered out of date and rebuilt
+if the dependency's content has changed sine the last time
+the target was built,
+as determined be performing an MD5 checksum
+on the dependency's contents
+and comparing it to the checksum recorded the
+last time the target was built.
+.B content
+can be used as a synonym for
+.BR MD5 .
+
+.HP 6
+.B MD5-timestamp
+Specifies that a target shall be considered out of date and rebuilt
+if the dependency's content has changed sine the last time
+the target was built,
+except that dependencies with a timestamp that matches
+the last time the target was rebuilt will be
+assumed to be up-to-date and
+.I not
+rebuilt.
+This provides behavior very similar
+to the
+.B MD5
+behavior of always checksumming file contents,
+with an optimization of not checking
+the contents of files whose timestamps haven't changed.
+The drawback is that SCons will
+.I not
+detect if a file's content has changed
+but its timestamp is the same,
+as might happen in an automated script
+that runs a build,
+updates a file,
+and runs the build again,
+all within a single second.
+.RE
+
+Examples:
+
+.ES
+# Use exact timestamp matches by default.
+Decider('timestamp-match')
+
+# Use MD5 content signatures for any targets built
+# with the attached construction environment.
+env.Decider('content')
+.EE
+
+In addition to the above already-available functions,
+the
+.I function
+argument may be an actual Python function
+that takes the following three arguments:
+
+.IP dependency
+The Node (file) which
+should cause the
+.I target
+to be rebuilt
+if it has "changed" since the last tme
+.I target was built.
+
+.IP target
+The Node (file) being built.
+In the normal case,
+this is what should get rebuilt
+if the
+.I dependency
+has "changed."
+
+.IP prev_ni
+Stored information about the state of the
+.I dependency
+the last time the
+.I target
+was built.
+This can be consulted to match various
+file characteristics
+such as the timestamp,
+size, or content signature.
+
+The
+.I function
+should return a
+.B True
+(non-zero)
+value if the
+.I dependency
+has "changed" since the last time
+the
+.I target
+was built
+(indicating that the target
+.I should
+be rebuilt),
+and
+.B False
+(zero)
+otherwise
+(indicating that the target should
+.I not
+be rebuilt).
+Note that the decision can be made
+using whatever criteria are appopriate.
+Ignoring some or all of the function arguments
+is perfectly normal.
+
+Example:
+
+.ES
+def my_decider(dependency, target, prev_ni):
+    return not os.path.exists(str(target))
+
+env.Decider(my_decider)
+.EE
+
 '\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
 .TP 
 .RI Default( targets )
@@ -2815,6 +2991,7 @@ method, or as a list.
 will also accept the Node returned by any
 of a construction environment's
 builder methods.
+
 Examples:
 
 .ES
@@ -2863,6 +3040,8 @@ for cases where the dependency
 is not caught by a Scanner
 for the file.
 
+Example:
+
 .ES
 env.Depends('foo', 'other-input-file-for-foo')
 .EE
@@ -2877,6 +3056,8 @@ If there are any variable names specified,
 only the specified construction
 variables are returned in the dictionary.
 
+Example:
+
 .ES
 dict = env.Dictionary()
 cc_dict = env.Dictionary('CC', 'CCFLAGS', 'CCCOM')
@@ -2952,6 +3133,8 @@ This function will
 print out an error message and exit SCons with a non-zero exit code if the
 actual Python version is not late enough.
 
+Example:
+
 .ES
 EnsurePythonVersion(2,2)
 .EE
@@ -2972,6 +3155,8 @@ This function will
 print out an error message and exit SCons with a non-zero exit code if the
 actual SCons version is not late enough.
 
+Examples:
+
 .ES
 EnsureSConsVersion(0,14)
 
@@ -3042,6 +3227,7 @@ Multiple variable names can be passed to
 as separate arguments or as a list. A dictionary can be used to map
 variables to a different name when exported. Both local variables and
 global variables can be exported.
+
 Examples:
 
 .ES
@@ -3106,10 +3292,76 @@ may be a list of file names or a single file name. In addition to searching
 for files that exist in the filesytem, this function also searches for
 derived files that have not yet been built.
 
+Example:
+
 .ES
 foo = env.FindFile('foo', ['dir1', 'dir2'])
 .EE
 
+'\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+.TP
+.RI FindInstalledFiles( )
+.TP
+.RI env.FindInstalledFiles( )
+Returns the list of targets setup by the
+.B Install()
+or
+.B InstallAs()
+builders.
+
+This function serves as a convenient method to select the contents of
+a Binary Package.
+
+Example:
+
+.ES
+Install( '/bin', [ 'executable_a', 'executable_b' ] )
+
+# will return the file node list
+# [ '/bin/executable_a', '/bin/executable_b' ]
+FindInstalledFiles()
+
+Install( '/lib', [ 'some_library' ] )
+
+# will return the file node list
+# [ '/bin/executable_a', '/bin/executable_b', '/lib/some_library' ]
+FindInstalledFiles()
+.EE
+
+'\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+.TP
+.RI FindSourceFiles( node = '"."' )
+.TP
+.RI env.FindSourceFiles( node = '"."' )
+
+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
+.B node.
+These are all children which have no further children.
+
+This function is a convenient method to select the contents of a Source
+Package.
+
+Example:
+
+.ES
+Program( 'src/main_a.c' )
+Program( 'src/main_b.c' )
+Program( 'main_c.c' )
+
+# returns ['main_c.c', 'src/main_a.c', 'SConstruct', 'src/main_b.c']
+FindSourceFiles()
+
+# returns ['src/main_b.c', 'src/main_a.c' ]
+FindSourceFiles( 'src' )
+.EE
+
+As you can see build support files (SConstruct in the above example)
+will also be returned by this function.
+
 '\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
 .TP
 .RI FindPathDirs( variable )
@@ -3178,7 +3430,9 @@ the lists returned by calls to Builders;
 other Builders will automatically
 flatten lists specified as input,
 but direct Python manipulation of
-these lists does not:
+these lists does not.
+
+Examples:
 
 .ES
 foo = Object('foo.c')
@@ -3288,6 +3542,8 @@ The specified dependency file(s)
 will be ignored when deciding if
 the target file(s) need to be rebuilt.
 
+Examples:
+
 .ES
 env.Ignore('foo', 'foo.c')
 env.Ignore('bar', ['bar1.h', 'bar2.h'])
@@ -3314,6 +3570,7 @@ Multiple variable names can be passed to
 .BR Import ()
 as separate arguments or as a list. The variable "*" can be used
 to import all variables.
+
 Examples:
 
 .ES
@@ -3644,7 +3901,9 @@ from the Perforce source code management system.
 The returned Builder
 is intended to be passed to the
 .B SourceCode
-function:
+function.
+
+Example:
 
 .ES
 env.SourceCode('.', env.Perforce())
@@ -3673,7 +3932,9 @@ USERNAME.
 Returns a callable object
 that can be used to initialize
 a construction environment using the
-platform keyword of the Environment() method:
+platform keyword of the Environment() method.
+
+Example:
 
 .ES
 env = Environment(platform = Platform('win32'))
@@ -3705,6 +3966,124 @@ external CVS repository specifications like
 .BR :pserver:anonymous@cvs.sourceforge.net:/cvsroot/scons )
 will work on Windows systems.
 
+'\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+.TP
+.RI Progress( callable ", [" interval ])
+.TP
+.RI Progress( string ", [" interval ", " file ", " overwrite ])
+.TP
+.RI Progress( list_of_strings ", [" interval ", " file ", " overwrite ])
+Allows SCons to show progress made during the build
+by displaying a string or calling a function while
+evaluating Nodes (e.g. files).
+
+If the first specified argument is a Python callable
+(a function or an object that has a
+.BR __call__ ()
+method),
+the function will be called
+once every
+.I interval
+times a Node is evaluated.
+The callable will be passed the evaluated Node
+as its only argument.
+(For future compatibility,
+it's a good idea to also add
+.B *args
+and
+.B **kw
+as arguments to your function or method.
+This will prevent the code from breaking
+if SCons ever changes the interface
+to call the function with additional arguments in the future.)
+
+An example of a simple custom progress function
+that prints a string containing the Node name
+every 10 Nodes:
+
+.ES
+def my_progress_function(node, *args, **kw):
+    print 'Evaluating node %s!' % node
+Progress(my_progress_function, interval=10)
+.EE
+.IP
+A more complicated example of a custom progress display object
+that prints a string containing a count
+every 100 evaluated Nodes.
+Note the use of
+.B \\\\r
+(a carriage return)
+at the end so that the string
+will overwrite itself on a display:
+
+.ES
+import sys
+class ProgressCounter:
+    count = 0
+    def __call__(self, node, *args, **kw):
+        self.count += 100
+        sys.stderr.write('Evaluated %s nodes\\r' % self.count)
+Progress(ProgressCounter(), interval=100)
+.EE
+.IP
+If the first argument
+.BR Progress ()
+is a string,
+the string will be displayed
+every
+.I interval
+evaluated Nodes.
+The default is to print the string on standard output;
+an alternate output stream
+may be specified with the
+.B file=
+argument.
+The following will print a series of dots
+on the error output,
+one dot for every 100 evaluated Nodes:
+
+.ES
+import sys
+Progress('.', interval=100, file=sys.stderr)
+.EE
+.IP
+If the string contains the verbatim substring
+.B $TARGET,
+it will be replaced with the Node.
+Note that, for performance reasons, this is
+.I not
+a regular SCons variable substition,
+so you can not use other variables
+or use curly braces.
+The following example will print the name of
+every evaluated Node,
+using a
+.B \\\\r
+(carriage return) to cause each line to overwritten by the next line,
+and the
+.B overwrite=
+keyword argument to make sure the previously-printed
+file name is overwritten with blank spaces:
+
+.ES
+import sys
+Progress('$TARGET\\r', overwrite=True)
+.EE
+.IP
+If the first argument to
+.BR Progress ()
+is a list of strings,
+then each string in the list will be displayed
+in rotating fashion every
+.I interval
+evaluated Nodes.
+This can be used to implement a "spinner"
+on the user's screen as follows:
+
+.ES
+Progress(['-\\r', '\\\\\\r', '|\\r', '/\\r'], interval=5)
+.EE
+
 '\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
 .TP
 .RI Precious( target ", ...)"
@@ -3735,6 +4114,8 @@ are both coerced to lists,
 and the lists are added together.
 (See also the Append method, above.)
 
+Example:
+
 .ES
 env.Prepend(CCFLAGS = '-g ', FOO = ['foo.yyy'])
 .EE
@@ -3757,6 +4138,7 @@ and
 This can also handle the
 case where the given old path variable is a list instead of a
 string, in which case a list will be returned instead of a string.
+
 Example:
 
 .ES
@@ -3764,8 +4146,11 @@ print 'before:',env['ENV']['INCLUDE']
 include_path = '/foo/bar:/foo'
 env.PrependENVPath('INCLUDE', include_path)
 print 'after:',env['ENV']['INCLUDE']
+.EE
 
-yields:
+The above exmaple will print:
+
+.ES
 before: /biz:/foo
 after: /foo/bar:/foo:/biz
 .EE
@@ -3784,6 +4169,8 @@ construction variable will
 .I not
 be added again to the list.
 
+Example:
+
 .ES
 env.PrependUnique(CCFLAGS = '-g', FOO = ['foo.yyy'])
 .EE
@@ -3800,6 +4187,8 @@ is intended to be passed to the
 .B SourceCode
 function:
 
+Examples:
+
 .ES
 env.SourceCode('.', env.RCS())
 .EE
@@ -3824,6 +4213,8 @@ for a specific subdirectory.
 Replaces construction variables in the Environment
 with the specified keyword arguments.
 
+Example:
+
 .ES
 env.Replace(CCFLAGS = '-g', FOO = 'foo.xxx')
 .EE
@@ -3876,20 +4267,46 @@ method.
 
 '\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
 .TP
-.RI Return( vars )
-This tells
-.B scons
-what variable(s) to use as the return value(s) of the current SConscript
-file. These variables will be returned to the "calling" SConscript file
-as the return value(s) of 
-.BR SConscript ().
-Multiple variable names should be passed to 
+.RI Return([ vars "... , " stop= ])
+By default,
+this stops processing the current SConscript
+file and returns to the calling SConscript file
+the values of the variables named in the
+.I vars
+string arguments.
+Multiple strings contaning variable names may be passed to
+.BR Return ().
+Any strings that contain white space
+
+The optional
+.B stop=
+keyword argument may be set to a false value
+to continue processing the rest of the SConscript
+file after the
 .BR Return ()
-as a list. Example:
+call.
+This was the default behavior prior to SCons 0.98.
+However, the values returned
+are still the values of the variables in the named
+.I vars
+at the point
+.BR Return ()
+is called.
+
+Examples:
 
 .ES
+# Returns without returning a value.
+Return()
+
+# Returns the value of the 'foo' Python variable.
 Return("foo")
-Return(["foo", "bar"])
+
+# Returns the values of the Python variables 'foo' and 'bar'.
+Return("foo", "bar")
+
+# Returns the values of Python variables 'val1' and 'val2'.
+Return('val1 val2')
 .EE
 
 '\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
@@ -3913,7 +4330,9 @@ from SCCS.
 The returned Builder
 is intended to be passed to the
 .B SourceCode
-function:
+function.
+
+Example:
 
 .ES
 env.SourceCode('.', env.SCCS())
@@ -4090,11 +4509,12 @@ while reading all SConscript files.
 (This may be necessary when building from repositories,
 when all the directories in which SConscript files may be found
 don't necessarily exist locally.)
-
 You may enable and disable
 this ability by calling
 SConscriptChdir()
-multiple times:
+multiple times.
+
+Example:
 
 .ES
 env = Environment()
@@ -4212,7 +4632,9 @@ which corresponds to -j and --jobs.
 which corresponds to --random.
 See the documentation for the
 corresponding command line object for information about each specific
-option. Example:
+option.
+
+Example:
 
 .ES
 SetOption('max_drift', 1)
@@ -4393,6 +4815,8 @@ you can use the Python
 idiom to pass in an unnamed function
 that simply returns its unconverted argument.
 
+Example:
+
 .ES
 print env.subst("The C compiler is: $CC")
 
@@ -4428,7 +4852,9 @@ source_nodes = env.subst('$EXPAND_TO_NODELIST',
 '\"so that you only have to
 '\"replicate part of the repository
 '\"directory hierarchy in your
-'\"local build directory:
+'\"local build directory.
+'\"
+'\"Example:
 '\"
 '\".ES
 '\"# Will fetch foo/bar/src.c
@@ -4449,10 +4875,17 @@ source_nodes = env.subst('$EXPAND_TO_NODELIST',
 .RI SourceSignatures( type )
 .TP
 .RI env.SourceSignatures( type )
-This function tells SCons what type of signature to use for source files:
+This function tells
+.B scons
+how to decide if a source file
+(a file that is not built from any other files)
+has changed since the last time it
+was used to build a particular target file.
+Legal values are
 .B "MD5"
 or
 .BR "timestamp" .
+
 If the environment method is used,
 the specified type of source signature
 is only used when deciding whether targets
@@ -4462,17 +4895,48 @@ the specified type of source signature becomes the default
 used for all decisions
 about whether targets are up-to-date.
 
-"MD5" means the signature of a source file
-is the MD5 checksum of its contents.
-"timestamp" means the signature of a source file
-is its timestamp (modification time).
+.B "MD5"
+means
+.B scons
+decides that a source file has changed
+if the MD5 checksum of its contents has changed since
+the last time it was used to rebuild a particular target file.
+
+.B "timestamp"
+means
+.B scons
+decides that a source file has changed
+if its timestamp (modification time) is newer than
+the last time it was used to rebuild a particular target file.
+
 There is no different between the two behaviors
 for Python
 .BR Value ()
 node objects.
-"MD5" signatures take longer to compute,
-but are more accurate than "timestamp" signatures.
-The default is "MD5".
+
+.B "MD5"
+signatures take longer to compute,
+but are more accurate than
+.B "timestamp"
+signatures.
+The default value is
+.BR "MD5" .
+
+Note that the default
+.BR TargetSignatures ()
+setting (see below)
+is to use this
+.BR SourceSignatures ()
+setting for any target files that are used
+to build other target files.
+Consequently, changing the value of
+.BR SourceSignatures ()
+will, by default,
+affect the up-to-date decision for all files in the build
+(or all files built with a specific construction environment
+when
+.BR env.SourceSignatures ()
+is used).
 
 '\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
 .TP
@@ -4490,6 +4954,8 @@ If arg is any other type of object,
 it will be returned as a list
 containing just the object.
 
+Example:
+
 .ES
 files = Split("f1.c f2.c f3.c")
 files = env.Split("f4.c f5.c f6.c")
@@ -4509,6 +4975,8 @@ information about how the
 Builder should package those files or directories.
 All tags are optional.
 
+Examples:
+
 .ES
 # makes sure the built library will be installed with 0644 file
 # access mode
@@ -4525,13 +4993,25 @@ Tag( 'file2.txt', DOC )
 .RI TargetSignatures( type )
 .TP
 .RI env.TargetSignatures( type )
-This function tells SCons what type of signatures to use
-for target files:
-.B "build"
+This function tells
+.B scons
+how to decide if a target file
+(a file that
+.I is
+built from any other files)
+has changed since the last time it
+was used to build some other target file.
+Legal values are
+.BR "build" ;
+.BR "content"
+(or its synonym
+.BR "MD5" );
+.BR "timestamp" ;
 or
-.BR "content" .
+.BR "source" .
+
 If the environment method is used,
-the specified type of signature is only used
+the specified type of target signature is only used
 for targets built with that environment.
 If the global function is used,
 the specified type of signature becomes the default
@@ -4539,16 +5019,94 @@ used for all target files that
 don't have an explicit target signature type
 specified for their environments.
 
-"build" means the signature of a target file
-is made by concatenating all of the
-signatures of all its source files.
-"content" means the signature of a target
-file is an MD5 checksum of its contents.
-"build" signatures are usually faster to compute,
-but "content" signatures can prevent unnecessary rebuilds
+.B "content"
+(or its synonym
+.BR "MD5" )
+means
+.B scons
+decides that a target file has changed
+if the MD5 checksum of its contents has changed since
+the last time it was used to rebuild some other target file.
+This means
+.B scons
+will open up
+MD5 sum the contents
+of target files after they're built,
+and may decide that it does not need to rebuild
+"downstream" target files if a file was
+rebuilt with exactly the same contents as the last time.
+
+.B "timestamp"
+means
+.B scons
+decides that a target file has changed
+if its timestamp (modification time) is newer than
+the last time it was used to rebuild some other target file.
+
+.B "source"
+means
+.B scons
+decides that a target file has changed
+as specified by the corresponding
+.BR SourceSignatures ()
+setting
+.BR "" ( "MD5"
+or
+.BR "timestamp" ).
+This means that
+.B scons
+will treat all input files to a target the same way,
+regardless of whether they are source files
+or have been built from other files.
+
+.B "build"
+means
+.B scons
+decides that a target file has changed
+if it has been rebuilt in this invocation
+or if its content or timestamp have changed
+as specified by the corresponding
+.BR SourceSignatures ()
+setting.
+This "propagates" the status of a rebuilt file
+so that other "downstream" target files
+will always be rebuilt,
+even if the contents or the timestamp
+have not changed.
+
+.B "build"
+signatures are fastest because
+.B "content"
+(or
+.BR "MD5" )
+signatures take longer to compute,
+but are more accurate than
+.B "timestamp"
+signatures,
+and can prevent unnecessary "downstream" rebuilds
 when a target file is rebuilt to the exact same contents
 as the previous build.
-The default is "build".
+The
+.B "source"
+setting provides the most consistent behavior
+when other target files may be rebuilt from
+both source and target input files.
+The default value is
+.BR "source" .
+
+Because the default setting is
+.BR "source" ,
+using
+.BR SourceSignatures ()
+is generally preferable to
+.BR TargetSignatures () ,
+so that the up-to-date decision
+will be consistent for all files
+(or all files built with a specific construction environment).
+Use of
+.BR TargetSignatures ()
+provides specific control for how built target files
+affect their "downstream" dependencies.
 
 '\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
 .TP
@@ -4570,6 +5128,8 @@ Additional keyword arguments are passed to the tool's
 .B generate()
 method.
 
+Examples:
+
 .ES
 env = Environment(tools = [ Tool('msvc') ])
 
@@ -4605,7 +5165,10 @@ calling
 .BR str( value )
 changes between SCons runs, any targets depending on
 .BR Value( value )
-will be rebuilt.  When using timestamp source signatures, Value Nodes'
+will be rebuilt.
+(This is true even when using timestamps to decide if
+files are up-to-date.)
+When using timestamp source signatures, Value Nodes'
 timestamps are equal to the system time when the Node is created.
 
 The returned Value Node object has a
@@ -4622,22 +5185,38 @@ There is a corresponding
 .BR read ()
 method that will return the built value of the Node.
 
+Examples:
+
 .ES
+env = Environment()
+
 def create(target, source, env):
+    # A function that will write a 'prefix=$SOURCE'
+    # string into the file name specified as the
+    # $TARGET.
     f = open(str(target[0]), 'wb')
     f.write('prefix=' + source[0].get_contents())
     
+# Fetch the prefix= argument, if any, from the command
+# line, and use /usr/local as the default.
 prefix = ARGUMENTS.get('prefix', '/usr/local')
-env = Environment()
+
+# Attach a .Config() builder for the above function action
+# to the construction environment.
 env['BUILDERS']['Config'] = Builder(action = create)
 env.Config(target = 'package-config', source = Value(prefix))
 
 def build_value(target, source, env):
+    # A function that "builds" a Python Value by updating
+    # the the Python value with the contents of the file
+    # specified as the source of the Builder call ($SOURCE).
     target[0].write(source[0].get_contents())
 
 output = env.Value('before')
 input = env.Value('after')
 
+# Attach a .UpdateValue() builder for the above function
+# action to the construction environment.
 env['BUILDERS']['UpdateValue'] = Builder(action = build_value)
 env.UpdateValue(target = Value(output), source = Value(input))
 .EE
@@ -4711,6 +5290,8 @@ and
 .B [1]
 of the tuple, respectively.
 
+Example:
+
 .ES
 print "first keyword, value =", ARGLIST[0][0], ARGLIST[0][1]
 print "second keyword, value =", ARGLIST[1][0], ARGLIST[1][1]
@@ -4735,6 +5316,8 @@ the one in the
 .B ARGUMENTS
 dictionary.
 
+Example:
+
 .ES
 if ARGUMENTS.get('debug', 0):
     env = Environment(CCFLAGS = '-g')
@@ -4771,6 +5354,8 @@ See the
 list, below,
 for additional information.
 
+Example:
+
 .ES
 if 'foo' in BUILD_TARGETS:
     print "Don't forget to test the `foo' program!"
@@ -4800,7 +5385,9 @@ the list is empty.
 This can be used, for example,
 to take specific actions only
 when a certain target or targets
-is explicitly being built:
+is explicitly being built.
+
+Example:
 
 .ES
 if 'foo' in COMMAND_LINE_TARGETS:
@@ -4822,6 +5409,8 @@ so you need to run them through the Python
 .B str
 function to get at the path name for each Node.
 
+Example:
+
 .ES
 print str(DEFAULT_TARGETS[0])
 if 'foo' in map(str, DEFAULT_TARGETS):
@@ -6007,6 +6596,33 @@ b = Builder("build_it < $SOURCE > $TARGET",
                        "$SRC_SFX_A": gen_suffix })
 .EE
 
+.IP ensure_suffix
+When set to any true value, causes 
+.B scons
+to add the target suffix specified by the
+.I suffix
+keyword to any target strings
+that have a different suffix.
+(The default behavior is to leave untouched
+any target file name that looks like it already has any suffix.)
+
+.ES
+b1 = Builder("build_it < $SOURCE > $TARGET"
+             suffix = ".out")
+b2 = Builder("build_it < $SOURCE > $TARGET"
+             suffix = ".out",
+             ensure_suffix)
+env = Environment()
+env['BUILDERS']['B1'] = b1
+env['BUILDERS']['B2'] = b2
+
+# Builds "foo.txt" because ensure_suffix is not set.
+env.B1('foo.txt', 'foo.in')
+
+# Builds "bar.txt.out" because ensure_suffix is set.
+env.B2('bar.txt', 'bar.in')
+.EE
+
 .IP src_suffix
 The expected source file name suffix.  This may be a string or a list
 of strings.
index 3d01cf4e332ee2598483cfb966622c89769daa77..7c80327a94581bc7630f21d28958af1e087da128 100644 (file)
@@ -56,14 +56,19 @@ dumps the entire contents of the
 specified file(s).
 Each entry is printed in the following format:
 
-    file: timestamp bsig csig
-            implicit_dependency_1
-            implicit_dependency_2
+    file: signature timestamp length
+            implicit_dependency_1: signature timestamp length
+            implicit_dependency_2: signature timestamp length
+            action_signature [action string]
 
-If the entry has no timestamp, bsig, or csig, a dash
 .B None
-is printed.
+is printed
+in place of any missing timestamp, bsig, or csig
+values for
+any entry
+or any of its dependencies.
 If the entry has no implicit dependencies,
+or no build action,
 the lines are simply omitted.
 
 By default,
@@ -100,8 +105,8 @@ Various options control what information is printed
 and the format:
 
 .TP
--b, --bsig
-Prints the build signature (bsig) information
+-a, --act, --action
+Prints the build action information
 for all entries or the specified entries.
 
 .TP
@@ -165,7 +170,9 @@ for all entries or the the specified entries.
 --raw
 Prints a pretty-printed representation
 of the raw Python dictionary that holds
-build information about an entry.
+build information about individual entry
+(both the entry itself or its implicit dependencies).
+An entry's build action is still printed in its usual format.
 
 .TP
 -r, --readable
index f99f5a1b4ad5c2831dd59974fea7e12f59df9b42..a3cf016b64de64770c5297a0b0cbc50e9ccc043a 100644 (file)
@@ -302,6 +302,7 @@ _ws = re.compile('\s')
 def escape(s):
     if _ws.search(s):
         s = '"' + s + '"'
+    s = string.replace(s, '\\', '\\\\')
     return s
 
 # Set up lowest-common-denominator spawning of a process on both Windows
index 3699e95f278e68b79e2c98a8744deb224bf1be5e..8886204a6c622cd3d30455f5a1f0aee7375608ff 100644 (file)
@@ -21,6 +21,82 @@ RELEASE 0.XX - XXX
   - Allow env.CacheDir() to be set per construction environment.  The
     global CacheDir() function now sets an overridable global default.
 
+  - Add an env.Decider() method and a Node.Decider() method that allow
+    flexible specification of an arbitrary function to decide if a given
+    dependency has changed since the last time a target was built.
+
+  - Don't execute Configure actions (while reading SConscript files)
+    when cleaning (-c) or getting help (-h or -H).
+
+  - Add to each target an implicit dependency on the external command(s)
+    used to build the target, as found by searching env['ENV']['PATH']
+    for the first argument on each executed command line.
+
+  - Add support for a $IMPLICIT_COMMAND_DEPENDENCIES construction
+    variabe that can be used to disable the automatic implicit
+    dependency on executed commands.
+
+  - Add an "ensure_suffix" keyword to Builder() definitions that, when
+    true, will add the configured suffix to the targets even if it looks
+    like they already have a different suffix.
+
+  - Add a Progress() function that allows for calling a function or string
+    (or list of strings) to display progress while walking the DAG.
+
+  - Allow ParseConfig(), MergeFlags() and ParseFlags() to handle output
+    from a *config command with quoted path names that contain spaces.
+
+  - Make the Return() function stop processing the SConscript file and
+    return immediately.  Add a "stop=" keyword argument that can be set
+    to False to preserve the old behavior.
+
+  - Fix use of exitstatfunc on an Action.
+
+  - Introduce all man page function examples with "Example:" or "Examples:".
+
+  - When a file gets added to a directory, make sure the directory gets
+    re-scanned for the new implicit dependency.
+
+  - Fix handling a file that's specified multiple times in a target
+    list so that it doesn't cause dependent Nodes to "disappear" from
+    the dependency graph walk.
+
+  From Carsten Koch:
+
+  - Avoid race conditions with same-named files and directory creation
+    when pushing copies of files to CacheDir().
+
+  From Tzvetan Mikov:
+
+  - Handle $ in Java class names.
+
+  From Gary Oberbrunner:
+
+  - Add support for the Intel C compiler on Windows64.
+
+  - On SGI IRIX, have $SHCXX use $CXX by default (like other platforms).
+
+  From Sohail Somani:
+
+  - When Cloning a construction environment, set any variables before
+    applying tools (so the tool module can access the configured settings)
+    and re-set them after (so they end up matching what the user set).
+
+  From Matthias Troffaes:
+
+  - Make sure extra auxiliary files generated by some LaTeX packages
+    and not ending in .aux also get deleted by scons -c.
+
+  From Greg Ward:
+
+  - Add a $JAVABOOTCLASSPATH variable for directories to be passed to the
+    javac -bootclasspath option.
+
+  From Christoph Wiedemann:
+
+  - Add implicit dependencies on the commands used to build a target.
+
+
 
 
 RELEASE 0.97.0d20070809 - Fri, 10 Aug 2007 10:51:27 -0500
index 195697e1a66ad96eab0138ee1d0cedfaf84f9d37..23c563847ec17d99af2bbc861a26e705707d99e4 100644 (file)
@@ -20,11 +20,93 @@ more effectively, please sign up for the scons-users mailing list at:
 
 
 
-RELEASE 0.97.0d20070809 - Fri, 10 Aug 2007 10:51:27 -0500
+RELEASE 0.97.0d200709XX - XXX
 
   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.0d20070809:
+
+    --  "content" SIGNATURES ARE NOW THE DEFAULT BEHAVIOR
+
+        The default behavior of SCons is now to use the MD5 checksum of
+        all file contents to decide if any files have changed and should
+        cause rebuilds of their source files.  This means that SCons may
+        decide not to rebuild "downstream" targets if a a given input
+        file is rebuilt to the exact same contents as the last time.
+        The old behavior may preserved by explicity specifying:
+
+            TargetSignatures("build")
+
+        In any of your SConscript files.
+
+    --  TARGETS NOW IMPLICITLY DEPEND ON THE COMMAND THAT BUILDS THEM
+
+        For all targets built by calling external commands (such as a
+        compiler or other utility), SCons now adds an implicit dependency
+        on the command(s) used to build the target.
+
+        This will cause rebuilds of all targets built by external commands
+        when running SCons in a tree built by previous version of SCons,
+        in order to update the recorded signatures.
+
+        The old behavior of not having targets depend on the external
+        commands that build them can be preserved by setting a new
+        $IMPLICIT_COMMAND_DEPENDENCIES construction variable to a
+        non-True value:
+
+            env = Environment(IMPLICIT_COMMAND_DEPENDENCIES = 0)
+        
+        or by adding Ignore() calls for any targets where the behavior
+        is desired:
+
+            Ignore('/usr/bin/gcc', 'foo.o')
+
+        Both of these settings are compatible with older versions
+        of SCons.
+
+    --  CHANGING SourceSignature() MAY CAUSE "UNECESSARY" REBUILDS
+
+        If you change the SourceSignature() value from 'timestamp' to
+        'MD5', SCons will now rebuild targets that were already up-to-date
+        with respect to their source files.
+
+        This will happen because SCons did not record the content
+        signatures of the input source files when the target was last
+        built--it only recorded the timestamps--and it must record them
+        to make sure the signature information is correct.  However,
+        the content of source files may have changed since the last
+        timestamp build was performed, and SCons would not have any way to
+        verify that.  (It would have had to open up the file and record
+        a content signature, which is one of the things you're trying to
+        avoid by specifying use of timestamps....)  So in order to make
+        sure the built targets reflect the contents of the source files,
+        the targets must be rebuilt.
+
+        Change the SourceSignature() value from 'MD5' to 'timestamp'
+        should correctly not rebuild target files, because the timestamp
+        of the files is always recorded.
+
+        In previous versions of SCons, changing the SourceSignature()
+        value would lead to unpredictable behavior, usually including
+        rebuilding targets.
+
+    --  THE Return() FUNCTION NOW ACTUALLY RETURNS IMMEDIATELY
+
+        The Return() function now immediately stops processing the
+        SConscript file in which it appears and returns the values of the
+        variables named in its arguments.  It used to continue processing
+        the rest of the SConscript file, and then return the values of the
+        specified variables at the point the Return() function was called.
+
+        The old behavior may be requested by adding a "stop=False"
+        keyword argument to the Return() call:
+
+                Return('value', stop=False)
+
+        The "stop=" keyword argument is *not* compatible with SCons
+        versions 0.97.0d20070809 or earlier.
+
   Please note the following important changes since release 0.97:
 
     --  env.CacheDir() NOW ONLY AFFECTS CONSTRUCTION ENVIRONMENT TARGETS
@@ -112,7 +194,7 @@ RELEASE 0.97.0d20070809 - Fri, 10 Aug 2007 10:51:27 -0500
         This should not cause any problems in the normal use of "#ifdef
         HAVE_{FEATURE}" statements interpreted by a C preprocessor, but
         might cause a compatibility issue if a script or other utility
-        was looking for an exact match of the previous text.
+        looks for an exact match of the previous text.
 
   Please note the following important changes since release 0.96.93:
 
index 9eccbdbc75f62532a9eb163545f53c916991c27e..52323d20c11c0b5a14f44ccbe11fa7851f96477e 100644 (file)
@@ -2,6 +2,7 @@ SCons/__init__.py
 SCons/Action.py
 SCons/Builder.py
 SCons/compat/__init__.py
+SCons/compat/_scons_hashlib.py
 SCons/compat/_scons_optparse.py
 SCons/compat/_scons_sets.py
 SCons/compat/_scons_sets15.py
@@ -56,9 +57,7 @@ SCons/Script/__init__.py
 SCons/Script/Main.py
 SCons/Script/SConscript.py
 SCons/Script/SConsOptions.py
-SCons/Sig/__init__.py
-SCons/Sig/MD5.py
-SCons/Sig/TimeStamp.py
+SCons/Sig.py
 SCons/Subst.py
 SCons/Taskmaster.py
 SCons/Tool/__init__.py
@@ -125,7 +124,6 @@ SCons/Tool/nasm.py
 SCons/Tool/packaging/__init__.py
 SCons/Tool/packaging/ipk.py
 SCons/Tool/packaging/msi.py
-SCons/Tool/packaging/packager.py
 SCons/Tool/packaging/rpm.py
 SCons/Tool/packaging/src_tarbz2.py
 SCons/Tool/packaging/src_targz.py
index 04e68a3ce8cd1ec86b82e089d668b9cca7192800..bdedc99b8bd6ab834a4b36aa7db6b18455e2acb2 100644 (file)
@@ -31,8 +31,8 @@ other modules:
 
     get_contents()
         Fetches the "contents" of an Action for signature calculation.
-        This is what the Sig/*.py subsystem uses to decide if a target
-        needs to be rebuilt because its action changed.
+        This is what gets MD5 checksumm'ed to decide if a target needs
+        to be rebuilt because its action changed.
 
     genstring()
         Returns a string representation of the Action *without*
@@ -495,6 +495,22 @@ class CommandAction(_ActionAction):
             cmd = str(cmd)
         return env.subst_target_source(cmd, SUBST_SIG, target, source)
 
+    def get_implicit_deps(self, target, source, env):
+        icd = env.get('IMPLICIT_COMMAND_DEPENDENCIES', True)
+        if SCons.Util.is_String(icd) and icd[:1] == '$':
+            icd = env.subst(icd)
+        if not icd or icd in ('0', 'None'):
+            return []
+        from SCons.Subst import SUBST_SIG
+        cmd_list = env.subst_list(self.cmd_list, SUBST_SIG, target, source)
+        res = []
+        for cmd_line in cmd_list:
+            if cmd_line:
+                d = env.WhereIs(str(cmd_line[0]))
+                if d:
+                    res.append(env.fs.File(d))
+        return res
+
 class CommandGeneratorAction(ActionBase):
     """Class for command-generator actions."""
     def __init__(self, generator, *args, **kw):
@@ -542,6 +558,9 @@ class CommandGeneratorAction(ActionBase):
         """
         return self._generate(target, source, env, 1).get_contents(target, source, env)
 
+    def get_implicit_deps(self, target, source, env):
+        return self._generate(target, source, env, 1).get_implicit_deps(target, source, env)
+
 
 
 # A LazyAction is a kind of hybrid generator and command action for
@@ -717,6 +736,9 @@ class FunctionAction(_ActionAction):
         return contents + env.subst(string.join(map(lambda v: '${'+v+'}',
                                                      self.varlist)))
 
+    def get_implicit_deps(self, target, source, env):
+        return []
+
 class ListAction(ActionBase):
     """Class for lists of other actions."""
     def __init__(self, list):
@@ -760,6 +782,12 @@ class ListAction(ActionBase):
                 return stat
         return 0
 
+    def get_implicit_deps(self, target, source, env):
+        result = []
+        for act in self.list:
+            result.extend(act.get_implicit_deps(target, source, env))
+        return result
+
 class ActionCaller:
     """A class for delaying calling an Action function with specific
     (positional and keyword) arguments until the Action is actually
index da2f3e3d0802a126e58290a35c204873bcc65fbc..a82b038cd25e09974155f7bcf2529f1b2823ba3d 100644 (file)
@@ -4,6 +4,41 @@ __COPYRIGHT__
 This file is processed by the bin/SConsDoc.py module.
 See its __doc__ string for a discussion of the format.
 -->
+<cvar name="IMPLICIT_COMMAND_DEPENDENCIES">
+<summary>
+Controls whether or not SCons will
+add implicit dependencies for the commands
+executed to build targets.
+
+By default, SCons will add
+to each target
+an implicit dependency on the command
+represented by the first argument on any
+command line it executes.
+The specific file for the dependency is
+found by searching the
+<varname>PATH</varname>
+variable in the
+<varname>ENV</varname>
+environment used to execute the command.
+
+If the construction variable
+&cv-IMPLICIT_COMMAND_DEPENDENCIES;
+is set to a false value
+(<literal>None</literal>,
+<literal>False</literal>,
+<literal>0</literal>,
+etc.),
+then the implicit dependency will
+not be added to the targets
+built with that construction environment.
+
+<example>
+env = Environment(IMPLICIT_COMMAND_DEPENDENCIES = 0)
+</example>
+</summary>
+</cvar>
+
 <cvar name="PRINT_CMD_LINE_FUNC">
 <summary>
 A Python function used to print the command lines as they are executed
index ff31c5e6db7a7fbf21c279f63b4ec9f2404e23a6..6164a55edf58dcb058c648844c7e891f7f3a861e 100644 (file)
@@ -39,9 +39,6 @@ used by other modules:
         variable.  This also takes care of warning about possible mistakes
         in keyword arguments.
 
-    targets()
-        Returns the list of targets for a specific builder instance.
-
     add_emitter()
         Adds an emitter for a specific file suffix, used by some Tool
         modules to specify that (for example) a yacc invocation on a .y
@@ -367,6 +364,7 @@ class BuilderBase:
                         chdir = _null,
                         is_explicit = 1,
                         src_builder = [],
+                        ensure_suffix = False,
                         **overrides):
         if __debug__: logInstanceCreation(self, 'Builder.BuilderBase')
         self._memo = {}
@@ -394,6 +392,7 @@ class BuilderBase:
 
         self.set_suffix(suffix)
         self.set_src_suffix(src_suffix)
+        self.ensure_suffix = ensure_suffix
 
         self.target_factory = target_factory
         self.source_factory = source_factory
@@ -467,7 +466,7 @@ class BuilderBase:
             executor.add_sources(slist)
             return executor
 
-    def _adjustixes(self, files, pre, suf):
+    def _adjustixes(self, files, pre, suf, ensure_suffix=False):
         if not files:
             return []
         result = []
@@ -476,7 +475,7 @@ class BuilderBase:
 
         for f in files:
             if SCons.Util.is_String(f):
-                f = SCons.Util.adjustixes(f, pre, suf)
+                f = SCons.Util.adjustixes(f, pre, suf, ensure_suffix)
             result.append(f)
         return result
 
@@ -505,7 +504,7 @@ class BuilderBase:
                 splitext = lambda S,self=self,env=env: self.splitext(S,env)
                 tlist = [ t_from_s(pre, suf, splitext) ]
         else:
-            target = self._adjustixes(target, pre, suf)
+            target = self._adjustixes(target, pre, suf, self.ensure_suffix)
             tlist = env.arg2nodes(target, target_factory)
 
         if self.emitter:
@@ -652,13 +651,6 @@ class BuilderBase:
             return ''
         return ret[0]
 
-    def targets(self, node):
-        """Return the list of targets for this builder instance.
-
-        For most normal builders, this is just the supplied node.
-        """
-        return [ node ]
-
     def add_emitter(self, suffix, emitter):
         """Add a suffix-emitter mapping to this Builder.
 
@@ -669,16 +661,6 @@ class BuilderBase:
         """
         self.emitter[suffix] = emitter
 
-    def push_emitter(self, emitter):
-        """Add a emitter to the beginning of the emitter list of this Builder.
-
-        This creates an empty list if the emitter is None.
-        """
-        if not self.emitter:
-            self.emitter = ListEmitter( [emitter] )
-        else:
-            self.emitter.insert(0, emitter)
-
     def add_src_builder(self, builder):
         """
         Add a new Builder to the list of src_builders.
index 6a1cc04f1bf02e2f819f5451f28bcc8fbe1cb96f..e3730a4caddeeaffdfeb68c819a4c1dbdf15f8e6 100644 (file)
@@ -89,10 +89,19 @@ def CachePushFunc(target, source, env):
 
     cd.CacheDebug('CachePush(%s):  pushing to %s\n', t, cachefile)
 
+    tempfile = cachefile+'.tmp'+str(os.getpid())
+    errfmt = "Unable to copy %s to cache. Cache file is %s"
+
     if not fs.isdir(cachedir):
-        fs.makedirs(cachedir)
+        try:
+            fs.makedirs(cachedir)
+        except EnvironmentError:
+            # We may have received an exception because another process
+            # has beaten us creating the directory.
+            if not fs.isdir(cachedir):
+                msg = errfmt % (str(target), cachefile)
+                raise SCons.Errors.EnvironmentError, msg
 
-    tempfile = cachefile+'.tmp'
     try:
         if fs.islink(t.path):
             fs.symlink(fs.readlink(t.path), tempfile)
@@ -101,15 +110,14 @@ def CachePushFunc(target, source, env):
         fs.rename(tempfile, cachefile)
         st = fs.stat(t.path)
         fs.chmod(cachefile, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
-    except (IOError, OSError):
+    except EnvironmentError:
         # It's possible someone else tried writing the file at the
         # same time we did, or else that there was some problem like
         # the CacheDir being on a separate file system that's full.
         # In any case, inability to push a file to cache doesn't affect
         # the correctness of the build, so just print a warning.
-        SCons.Warnings.warn(SCons.Warnings.CacheWriteErrorWarning,
-                            "Unable to copy %s to cache. Cache file is %s"
-                                % (str(target), cachefile))
+        msg = errfmt % (str(target), cachefile)
+        SCons.Warnings.warn(SCons.Warnings.CacheWriteErrorWarning, msg)
 
 CachePush = SCons.Action.Action(CachePushFunc, None)
 
@@ -117,9 +125,9 @@ class CacheDir:
 
     def __init__(self, path):
         try:
-            import SCons.Sig.MD5
+            import hashlib
         except ImportError:
-            msg = "No MD5 module available, CacheDir() not supported"
+            msg = "No hashlib or MD5 module available, CacheDir() not supported"
             SCons.Warnings.warn(SCons.Warnings.NoMD5ModuleWarning, msg)
         else:
             self.path = path
@@ -203,5 +211,7 @@ class CacheDir:
 class Null(SCons.Util.Null):
     def repr(self):
         return 'CacheDir.Null()'
+    def cachepath(self, node):
+        return None, None
     def retrieve(self, node):
         return False
index 564b76228533d55304fe5f9d1fa994b654237a44..e31f67bcf9def3e9ec5c85384ebbfad0ad137b70 100644 (file)
@@ -35,17 +35,23 @@ import SCons.CacheDir
 built_it = None
 
 class Action:
-    def __call__(self, targets, sources, env, errfunc, **kw):
+    def __call__(self, targets, sources, env, **kw):
         global built_it
         if kw.get('execute', 1):
             built_it = 1
         return 0
+    def genstring(self, target, source, env):
+        return str(self)
+    def get_contents(self, target, source, env):
+        return ''
 
 class Builder:
     def __init__(self, environment, action):
         self.env = environment
         self.action = action
         self.overrides = {}
+        self.source_scanner = None
+        self.target_scanner = None
 
 class Environment:
     def __init__(self, cachedir):
@@ -71,8 +77,9 @@ class BaseTestCase(unittest.TestCase):
         node = self.fs.File(name)
         node.builder_set(Builder(Environment(self._CacheDir), action))
         if bsig:
-            node.binfo = node.BuildInfo(node)
-            node.binfo.ninfo.bsig = bsig
+            node.cachesig = bsig
+            #node.binfo = node.BuildInfo(node)
+            #node.binfo.ninfo.bsig = bsig
         return node
 
 class CacheDirTestCase(BaseTestCase):
@@ -86,8 +93,8 @@ class CacheDirTestCase(BaseTestCase):
         # of the file in cache.
         def my_collect(list):
             return list[0]
-        save_collect = SCons.Sig.MD5.collect
-        SCons.Sig.MD5.collect = my_collect
+        save_collect = SCons.Util.MD5collect
+        SCons.Util.MD5collect = my_collect
 
         try:
             f5 = self.File("cd.f5", 'a_fake_bsig')
@@ -96,7 +103,7 @@ class CacheDirTestCase(BaseTestCase):
             filename = os.path.join(dirname, 'a_fake_bsig')
             assert result == (dirname, filename), result
         finally:
-            SCons.Sig.MD5.collect = save_collect
+            SCons.Util.MD5collect = save_collect
 
 class FileTestCase(BaseTestCase):
     """
@@ -210,18 +217,6 @@ class FileTestCase(BaseTestCase):
         finally:
             SCons.CacheDir.CachePush = save_CachePush
 
-    def test_no_bsig(self):
-        """Test that no bsig raises an InternalError"""
-
-        f6 = self.File("cd.f6")
-        f6.binfo = f6.BuildInfo(f6)
-        exc_caught = 0
-        try:
-            cp = self._CacheDir.cachepath(f6)
-        except SCons.Errors.InternalError:
-            exc_caught = 1
-        assert exc_caught
-
     def test_warning(self):
         """Test raising a warning if we can't copy a file to cache."""
 
index 47d2134975a83957fd01fd6de32565a51f12834e..c54eae2c287b0d013bfcaeae09071f841eada28a 100644 (file)
@@ -197,3 +197,4 @@ def Trace(msg, file=None, mode='w'):
             # Assume we were passed an open file pointer.
             fp = file
     fp.write(msg)
+    fp.flush()
index 29868ecc1587cd8b8e0b60907bc0c4ebf8e23bec..9308051b15fc2b591e7fdedd618ba4b5781625e1 100644 (file)
@@ -49,7 +49,6 @@ import SCons.Builder
 import SCons.CacheDir
 import SCons.Environment
 import SCons.PathList
-import SCons.Sig
 import SCons.Subst
 import SCons.Tool
 
@@ -61,12 +60,40 @@ _default_env = None
 
 # Lazily instantiate the default environment so the overhead of creating
 # it doesn't apply when it's not needed.
+def _fetch_DefaultEnvironment(*args, **kw):
+    """
+    Returns the already-created default construction environment.
+    """
+    global _default_env
+    return _default_env
+
 def DefaultEnvironment(*args, **kw):
+    """
+    Initial public entry point for creating the default construction
+    Environment.
+
+    After creating the environment, we overwrite our name
+    (DefaultEnvironment) with the _fetch_DefaultEnvironment() function,
+    which more efficiently returns the initialized default construction
+    environment without checking for its existence.
+
+    (This function still exists with its _default_check because someone
+    else (*cough* Script/__init__.py *cough*) may keep a reference
+    to this function.  So we can't use the fully functional idiom of
+    having the name originally be a something that *only* creates the
+    construction environment and then overwrites the name.)
+    """
     global _default_env
     if not _default_env:
+        import SCons.Util
         _default_env = apply(SCons.Environment.Environment, args, kw)
-        _default_env._build_signature = 1
-        _default_env._calc_module = SCons.Sig.default_module
+        _default_env.TargetSignatures('source')
+        if SCons.Util.md5:
+            _default_env.SourceSignatures('MD5')
+        else:
+            _default_env.SourceSignatures('timestamp')
+        global DefaultEnvironment
+        DefaultEnvironment = _fetch_DefaultEnvironment
         _default_env._CacheDir = SCons.CacheDir.Null()
     return _default_env
 
@@ -106,9 +133,9 @@ LaTeXScan = SCons.Tool.LaTeXScanner
 ObjSourceScan = SCons.Tool.SourceFileScanner
 ProgScan = SCons.Tool.ProgramScanner
 
-# This isn't really a tool scanner, so it doesn't quite belong with
-# the rest of those in Tool/__init__.py, but I'm not sure where else it
-# should go.  Leave it here for now.
+# These aren't really tool scanners, so they don't quite belong with
+# the rest of those in Tool/__init__.py, but I'm not sure where else
+# they should go.  Leave them here for now.
 import SCons.Scanner.Dir
 DirScanner = SCons.Scanner.Dir.DirScanner()
 DirEntryScanner = SCons.Scanner.Dir.DirEntryScanner()
index 012c36c39ca9f91f5c6149b43b085de603fd3a13..2f4c34e669fd516681cc614a7fc30ce927b433ed 100644 (file)
@@ -38,6 +38,7 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 import copy
 import os
 import os.path
+import shlex
 import string
 from UserDict import UserDict
 
@@ -53,7 +54,6 @@ import SCons.Node.FS
 import SCons.Node.Python
 import SCons.Platform
 import SCons.SConsign
-import SCons.Sig
 import SCons.Subst
 import SCons.Tool
 import SCons.Util
@@ -125,8 +125,9 @@ def _set_BUILDERS(env, key, value):
         for k in bd.keys():
             del bd[k]
     except KeyError:
-        env._dict[key] = BuilderDict(kwbd, env)
-    env._dict[key].update(value)
+        bd = BuilderDict(kwbd, env)
+        env._dict[key] = bd
+    bd.update(value)
 
 def _del_SCANNERS(env, key):
     del env._dict[key]
@@ -136,13 +137,72 @@ def _set_SCANNERS(env, key, value):
     env._dict[key] = value
     env.scanner_map_delete()
 
-class BuilderWrapper:
-    """Wrapper class that associates an environment with a Builder at
-    instantiation."""
-    def __init__(self, env, builder):
-        self.env = env
-        self.builder = builder
 
+
+# The following is partly based on code in a comment added by Peter
+# Shannon at the following page (there called the "transplant" class):
+#
+# ASPN : Python Cookbook : Dynamically added methods to a class
+# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/81732
+#
+# We had independently been using the idiom as BuilderWrapper, but
+# factoring out the common parts into this base class, and making
+# BuilderWrapper a subclass that overrides __call__() to enforce specific
+# Builder calling conventions, simplified some of our higher-layer code.
+
+class MethodWrapper:
+    """
+    A generic Wrapper class that associates a method (which can
+    actually be any callable) with an object.  As part of creating this
+    MethodWrapper object an attribute with the specified (by default,
+    the name of the supplied method) is added to the underlying object.
+    When that new "method" is called, our __call__() method adds the
+    object as the first argument, simulating the Python behavior of
+    supplying "self" on method calls.
+
+    We hang on to the name by which the method was added to the underlying
+    base class so that we can provide a method to "clone" ourselves onto
+    a new underlying object being copied (without which we wouldn't need
+    to save that info).
+    """
+    def __init__(self, object, method, name=None):
+        if name is None:
+            name = method.__name__
+        self.object = object
+        self.method = method
+        self.name = name
+        setattr(self.object, name, self)
+
+    def __call__(self, *args, **kwargs):
+        nargs = (self.object,) + args
+        return apply(self.method, nargs, kwargs)
+
+    def clone(self, new_object):
+        """
+        Returns an object that re-binds the underlying "method" to
+        the specified new object.
+        """
+        return self.__class__(new_object, self.method, self.name)
+
+class BuilderWrapper(MethodWrapper):
+    """
+    A MethodWrapper subclass that that associates an environment with
+    a Builder.
+
+    This mainly exists to wrap the __call__() function so that all calls
+    to Builders can have their argument lists massaged in the same way
+    (treat a lone argument as the source, treat two arguments as target
+    then source, make sure both target and source are lists) without
+    having to have cut-and-paste code to do it.
+
+    As a bit of obsessive backwards compatibility, we also intercept
+    attempts to get or set the "env" or "builder" attributes, which were
+    the names we used before we put the common functionality into the
+    MethodWrapper base class.  We'll keep this around for a while in case
+    people shipped Tool modules that reached into the wrapper (like the
+    Tool/qt.py module does, or did).  There shouldn't be a lot attribute
+    fetching or setting on these, so a little extra work shouldn't hurt.
+    """
     def __call__(self, target=None, source=_null, *args, **kw):
         if source is _null:
             source = target
@@ -151,7 +211,26 @@ class BuilderWrapper:
             target = [target]
         if not source is None and not SCons.Util.is_List(source):
             source = [source]
-        return apply(self.builder, (self.env, target, source) + args, kw)
+        return apply(MethodWrapper.__call__, (self, target, source) + args, kw)
+
+    def __repr__(self):
+        return '<BuilderWrapper %s>' % repr(self.name)
+
+    def __getattr__(self, name):
+        if name == 'env':
+            return self.object
+        elif name == 'builder':
+            return self.method
+        else:
+            return self.__dict__[name]
+
+    def __setattr__(self, name, value):
+        if name == 'env':
+            self.object = value
+        elif name == 'builder':
+            self.method = value
+        else:
+            self.__dict__[name] = value
 
     # This allows a Builder to be executed directly
     # through the Environment to which it's attached.
@@ -160,9 +239,9 @@ class BuilderWrapper:
     # But we do have a unit test for this, and can't
     # yet rule out that it would be useful in the
     # future, so leave it for now.
-    def execute(self, **kw):
-        kw['env'] = self.env
-        apply(self.builder.execute, (), kw)
+    #def execute(self, **kw):
+    #    kw['env'] = self.env
+    #    apply(self.builder.execute, (), kw)
 
 class BuilderDict(UserDict):
     """This is a dictionary-like class used by an Environment to hold
@@ -181,26 +260,7 @@ class BuilderDict(UserDict):
 
     def __setitem__(self, item, val):
         UserDict.__setitem__(self, item, val)
-        try:
-            self.setenvattr(item, val)
-        except AttributeError:
-            # Have to catch this because sometimes __setitem__ gets
-            # called out of __init__, when we don't have an env
-            # attribute yet, nor do we want one!
-            pass
-
-    def setenvattr(self, item, val):
-        """Set the corresponding environment attribute for this Builder.
-
-        If the value is already a BuilderWrapper, we pull the builder
-        out of it and make another one, so that making a copy of an
-        existing BuilderDict is guaranteed separate wrappers for each
-        Builder + Environment pair."""
-        try:
-            builder = val.builder
-        except AttributeError:
-            builder = val
-        setattr(self.env, item, BuilderWrapper(self.env, builder))
+        BuilderWrapper(self.env, val, item)
 
     def __delitem__(self, item):
         UserDict.__delitem__(self, item)
@@ -248,6 +308,7 @@ class SubstitutionEnvironment:
         self.lookup_list = SCons.Node.arg2nodes_lookups
         self._dict = kw.copy()
         self._init_special()
+        self.added_methods = []
         #self._memo = {}
 
     def _init_special(self):
@@ -450,7 +511,16 @@ class SubstitutionEnvironment:
         environment with the specified name.  If the name is omitted,
         the default name is the name of the function itself.
         """
-        SCons.Util.AddMethod(self, function, name)
+        method = MethodWrapper(self, function, name)
+        self.added_methods.append(method)
+
+    def RemoveMethod(self, function):
+        """
+        Removes the specified function's MethodWrapper from the
+        added_methods list, so we don't re-bind it when making a clone.
+        """
+        is_not_func = lambda dm, f=function: not dm.method is f
+        self.added_methods = filter(is_not_func, self.added_methods)
 
     def Override(self, overrides):
         """
@@ -543,7 +613,7 @@ class SubstitutionEnvironment:
             #  -R dir          (deprecated linker rpath)
             # IBM compilers may also accept -qframeworkdir=foo
     
-            params = string.split(arg)
+            params = shlex.split(arg)
             append_next_arg_to = None   # for multi-word args
             for arg in params:
                 if append_next_arg_to:
@@ -695,6 +765,14 @@ def build_source(ss, result):
         elif isinstance(s.disambiguate(), SCons.Node.FS.File):
             result.append(s)
 
+def default_decide_source(dependency, target, prev_ni):
+    f = SCons.Defaults.DefaultEnvironment().decide_source
+    return f(dependency, target, prev_ni)
+
+def default_decide_target(dependency, target, prev_ni):
+    f = SCons.Defaults.DefaultEnvironment().decide_target
+    return f(dependency, target, prev_ni)
+
 class Base(SubstitutionEnvironment):
     """Base class for "real" construction Environments.  These are the
     primary objects used to communicate dependency and construction
@@ -747,6 +825,16 @@ class Base(SubstitutionEnvironment):
         self.lookup_list = SCons.Node.arg2nodes_lookups
         self._dict = semi_deepcopy(SCons.Defaults.ConstructionEnvironment)
         self._init_special()
+        self.added_methods = []
+
+        # We don't use AddMethod, or define these as methods in this
+        # class, because we *don't* want these functions to be bound
+        # methods.  They need to operate independently so that the
+        # settings will work properly regardless of whether a given
+        # target ends up being built with a Base environment or an
+        # OverrideEnvironment or what have you.
+        self.decide_target = default_decide_target
+        self.decide_source = default_decide_source
 
         self._dict['BUILDERS'] = BuilderDict(self._dict['BUILDERS'], self)
 
@@ -777,6 +865,8 @@ class Base(SubstitutionEnvironment):
                 # reserved variable name like TARGETS.
                 pass
 
+        SCons.Tool.Initializers(self)
+
         if tools is None:
             tools = self._dict.get('TOOLS', None)
             if tools is None:
@@ -802,17 +892,6 @@ class Base(SubstitutionEnvironment):
         except KeyError:
             return None
 
-    def get_calculator(self):
-        try:
-            module = self._calc_module
-            c = apply(SCons.Sig.Calculator, (module,), CalculatorArgs)
-        except AttributeError:
-            # Note that we're calling get_calculator() here, so the
-            # DefaultEnvironment() must have a _calc_module attribute
-            # to avoid infinite recursion.
-            c = SCons.Defaults.DefaultEnvironment().get_calculator()
-        return c
-
     def get_CacheDir(self):
         try:
             return self._CacheDir
@@ -901,13 +980,21 @@ class Base(SubstitutionEnvironment):
         """
         self._dict.update(dict)
 
-    def use_build_signature(self):
+    def get_src_sig_type(self):
+        try:
+            return self.src_sig_type
+        except AttributeError:
+            t = SCons.Defaults.DefaultEnvironment().src_sig_type
+            self.src_sig_type = t
+            return t
+
+    def get_tgt_sig_type(self):
         try:
-            return self._build_signature
+            return self.tgt_sig_type
         except AttributeError:
-            b = SCons.Defaults.DefaultEnvironment()._build_signature
-            self._build_signature = b
-            return b
+            t = SCons.Defaults.DefaultEnvironment().tgt_sig_type
+            self.tgt_sig_type = t
+            return t
 
     #######################################################################
     # Public methods for manipulating an Environment.  These begin with
@@ -1040,28 +1127,84 @@ class Base(SubstitutionEnvironment):
         """
         clone = copy.copy(self)
         clone._dict = semi_deepcopy(self._dict)
+
         try:
             cbd = clone._dict['BUILDERS']
-            clone._dict['BUILDERS'] = BuilderDict(cbd, clone)
         except KeyError:
             pass
+        else:
+            clone._dict['BUILDERS'] = BuilderDict(cbd, clone)
 
-        clone._memo = {}
+        clone.added_methods = []
+        for mw in self.added_methods:
+            mw.clone(clone)
 
-        apply_tools(clone, tools, toolpath)
+        clone._memo = {}
 
-        # Apply passed-in variables after the new tools.
+        # Apply passed-in variables before the tools
+        # so the tools can use the new variables
         kw = copy_non_reserved_keywords(kw)
         new = {}
         for key, value in kw.items():
             new[key] = SCons.Subst.scons_subst_once(value, self, key)
         apply(clone.Replace, (), new)
+
+        apply_tools(clone, tools, toolpath)
+
+        # apply them again in case the tools overwrote them
+        apply(clone.Replace, (), new)        
+
         if __debug__: logInstanceCreation(self, 'Environment.EnvironmentClone')
         return clone
 
     def Copy(self, *args, **kw):
         return apply(self.Clone, args, kw)
 
+    def _changed_build(self, dependency, target, prev_ni):
+        if dependency.changed_state(target, prev_ni):
+            return 1
+        return self.decide_source(dependency, target, prev_ni)
+
+    def _changed_content(self, dependency, target, prev_ni):
+        return dependency.changed_content(target, prev_ni)
+
+    def _changed_source(self, dependency, target, prev_ni):
+        target_env = dependency.get_build_env()
+        type = target_env.get_tgt_sig_type()
+        if type == 'source':
+            return target_env.decide_source(dependency, target, prev_ni)
+        else:
+            return target_env.decide_target(dependency, target, prev_ni)
+
+    def _changed_timestamp_then_content(self, dependency, target, prev_ni):
+        return dependency.changed_timestamp_then_content(target, prev_ni)
+
+    def _changed_timestamp_newer(self, dependency, target, prev_ni):
+        return dependency.changed_timestamp_newer(target, prev_ni)
+
+    def _changed_timestamp_match(self, dependency, target, prev_ni):
+        return dependency.changed_timestamp_match(target, prev_ni)
+
+    def Decider(self, function):
+        if function in ('MD5', 'content'):
+            if not SCons.Util.md5:
+                raise UserError, "MD5 signatures are not available in this version of Python."
+            function = self._changed_content
+        elif function == 'MD5-timestamp':
+            function = self._changed_timestamp_then_content
+        elif function in ('timestamp-newer', 'make'):
+            function = self._changed_timestamp_newer
+        elif function == 'timestamp-match':
+            function = self._changed_timestamp_match
+        elif not callable(function):
+            raise UserError, "Unknown Decider value %s" % repr(function)
+
+        # We don't use AddMethod because we don't want to turn the
+        # function, which only expects three arguments, into a bound
+        # method, which would add self as an initial, fourth argument.
+        self.decide_target = function
+        self.decide_source = function
+
     def Detect(self, progs):
         """Return the first available program in progs.
         """
@@ -1294,11 +1437,13 @@ class Base(SubstitutionEnvironment):
         with new construction variables and/or values.
         """
         try:
-            kwbd = semi_deepcopy(kw['BUILDERS'])
-            del kw['BUILDERS']
-            self.__setitem__('BUILDERS', kwbd)
+            kwbd = kw['BUILDERS']
         except KeyError:
             pass
+        else:
+            kwbd = semi_deepcopy(kwbd)
+            del kw['BUILDERS']
+            self.__setitem__('BUILDERS', kwbd)
         kw = copy_non_reserved_keywords(kw)
         self._update(semi_deepcopy(kw))
         self.scanner_map_delete(kw)
@@ -1659,21 +1804,15 @@ class Base(SubstitutionEnvironment):
 
     def SourceSignatures(self, type):
         type = self.subst(type)
+        self.src_sig_type = type
         if type == 'MD5':
-            try:
-                import SCons.Sig.MD5
-            except ImportError:
-                msg = "No MD5 module available, using time stamps"
-                SCons.Warnings.warn(SCons.Warnings.NoMD5ModuleWarning, msg)
-                import SCons.Sig.TimeStamp
-                self._calc_module = SCons.Sig.TimeStamp
-            else:
-                self._calc_module = SCons.Sig.MD5
+            if not SCons.Util.md5:
+                raise UserError, "MD5 signatures are not available in this version of Python."
+            self.decide_source = self._changed_content
         elif type == 'timestamp':
-            import SCons.Sig.TimeStamp
-            self._calc_module = SCons.Sig.TimeStamp
+            self.decide_source = self._changed_timestamp_newer
         else:
-            raise UserError, "Unknown source signature type '%s'"%type
+            raise UserError, "Unknown source signature type '%s'" % type
 
     def Split(self, arg):
         """This function converts a string or list into a list of strings
@@ -1695,12 +1834,19 @@ class Base(SubstitutionEnvironment):
 
     def TargetSignatures(self, type):
         type = self.subst(type)
-        if type == 'build':
-            self._build_signature = 1
-        elif type == 'content':
-            self._build_signature = 0
+        self.tgt_sig_type = type
+        if type in ('MD5', 'content'):
+            if not SCons.Util.md5:
+                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
+        elif type == 'build':
+            self.decide_target = self._changed_build
+        elif type == 'source':
+            self.decide_target = self._changed_source
         else:
-            raise SCons.Errors.UserError, "Unknown target signature type '%s'"%type
+            raise UserError, "Unknown target signature type '%s'"%type
 
     def Value(self, value, built_value=None):
         """
@@ -1740,6 +1886,14 @@ class Base(SubstitutionEnvironment):
         # remove duplicates
         return list(set(sources))
 
+    def FindInstalledFiles(self):
+        """ returns the list of all targets of the Install and InstallAs Builder.
+        """
+        from SCons.Tool import install
+        if install._UNIQUE_INSTALLED_FILES is None:
+            install._UNIQUE_INSTALLED_FILES = SCons.Util.uniquer_hashables(install._INSTALLED_FILES)
+        return install._UNIQUE_INSTALLED_FILES
+
 class OverrideEnvironment(Base):
     """A proxy that overrides variables in a wrapped construction
     environment by returning values from an overrides dictionary in
index 82f220ab91f05586dbba940cf6ad36eaff872c2c..630f5948ef4b6e9bb4f5cdacccdb5b9bf5ff8a5d 100644 (file)
@@ -729,7 +729,9 @@ sys.exit(1)
         assert d == empty, d
 
         s = "-I/usr/include/fum -I bar -X\n" + \
+            '-I"C:\\Program Files\\ASCEND\\include" ' + \
             "-L/usr/fax -L foo -lxxx -l yyy " + \
+            '-L"C:\\Program Files\\ASCEND" -lascend ' + \
             "-Wa,-as -Wl,-link " + \
             "-Wl,-rpath=rpath1 " + \
             "-Wl,-R,rpath2 " + \
@@ -743,10 +745,44 @@ sys.exit(1)
             "-pthread " + \
             "-mno-cygwin -mwindows " + \
             "-arch i386 -isysroot /tmp +DD64 " + \
-            "-DFOO -DBAR=value -D BAZ"
+            "-DFOO -DBAR=value -D BAZ "
 
         d = env.ParseFlags(s)
 
+        if sys.version[:3] in ('1.5', '1.6', '2.0', '2.1', '2.2'):
+            # Pre-2.3 Python has no shlex.split() function.
+            # The compatibility layer does its best can by wrapping
+            # the old shlex.shlex class, but that class doesn't really
+            # understand quoting within the body of a token.  We're just
+            # going to live with this; it's the behavior they'd
+            # have anyway if they use the shlex module...
+            #
+            # (Note that we must test the actual Python version numbers
+            # above, not just test for whether trying to use shlex.split()
+            # throws an AttributeError, because the compatibility layer
+            # adds our wrapper function to the module as shlex.split().)
+
+            expect_CPPPATH = ['/usr/include/fum',
+                              'bar',
+                              '"C:\\Program']
+            expect_LIBPATH = ['/usr/fax',
+                              'foo',
+                              '"C:\\Program']
+            expect_LIBS = ['Files\\ASCEND\\include"',
+                           'xxx',
+                           'yyy',
+                           'Files\\ASCEND"',
+                           'ascend']
+        else:
+            expect_CPPPATH = ['/usr/include/fum',
+                              'bar',
+                              'C:\\Program Files\\ASCEND\\include']
+            expect_LIBPATH = ['/usr/fax',
+                              'foo',
+                              'C:\\Program Files\\ASCEND']
+            expect_LIBS = ['xxx', 'yyy', 'ascend']
+
+
         assert d['ASFLAGS'] == ['-as'], d['ASFLAGS']
         assert d['CFLAGS']  == ['-std=c99']
         assert d['CCFLAGS'] == ['-X', '-Wa,-as',
@@ -755,11 +791,12 @@ sys.exit(1)
                                   '+DD64'], d['CCFLAGS']
         assert d['CPPDEFINES'] == ['FOO', ['BAR', 'value'], 'BAZ'], d['CPPDEFINES']
         assert d['CPPFLAGS'] == ['-Wp,-cpp'], d['CPPFLAGS']
-        assert d['CPPPATH'] == ['/usr/include/fum', 'bar'], d['CPPPATH']
+        assert d['CPPPATH'] == expect_CPPPATH, d['CPPPATH']
         assert d['FRAMEWORKPATH'] == ['fwd1', 'fwd2', 'fwd3'], d['FRAMEWORKPATH']
         assert d['FRAMEWORKS'] == ['Carbon'], d['FRAMEWORKS']
-        assert d['LIBPATH'] == ['/usr/fax', 'foo'], d['LIBPATH']
-        assert d['LIBS'] == ['xxx', 'yyy'], d['LIBS']
+        assert d['LIBPATH'] == expect_LIBPATH, d['LIBPATH']
+        LIBS = map(str, d['LIBS'])
+        assert LIBS == expect_LIBS, (d['LIBS'], LIBS)
         assert d['LINKFLAGS'] == ['-Wl,-link', '-pthread',
                                   '-mno-cygwin', '-mwindows',
                                   ('-arch', 'i386'),
@@ -866,7 +903,11 @@ class BaseTestCase(unittest.TestCase,TestEnvironmentFixture):
 
 
 
-    def test_Builder_execs(self):
+    # This unit test is currently disabled because we don't think the
+    # underlying method it tests (Environment.BuilderWrapper.execute())
+    # is necessary, but we're leaving the code here for now in case
+    # that's mistaken.
+    def _DO_NOT_test_Builder_execs(self):
         """Test Builder execution through different environments
 
         One environment is initialized with a single
@@ -890,8 +931,10 @@ class BaseTestCase(unittest.TestCase,TestEnvironmentFixture):
         assert built_it['out3']
 
         env4 = env3.Clone()
-        assert env4.builder1.env is env4, "builder1.env (%s) == env3 (%s)?" % (env4.builder1.env, env3)
-        assert env4.builder2.env is env4, "builder2.env (%s) == env3 (%s)?" % (env4.builder1.env, env3)
+        assert env4.builder1.env is env4, "builder1.env (%s) == env3 (%s)?" % (
+env4.builder1.env, env3)
+        assert env4.builder2.env is env4, "builder2.env (%s) == env3 (%s)?" % (
+env4.builder1.env, env3)
 
         # Now test BUILDERS as a dictionary.
         built_it = {}
@@ -911,6 +954,8 @@ class BaseTestCase(unittest.TestCase,TestEnvironmentFixture):
         assert built_it['out1']
         assert built_it['out2']
 
+
+
     def test_Scanners(self):
         """Test setting SCANNERS in various ways
 
@@ -1568,15 +1613,15 @@ def exists(env):
         #
         env1 = self.TestEnvironment(BUILDERS = {'b1' : 1})
         assert hasattr(env1, 'b1'), "env1.b1 was not set"
-        assert env1.b1.env == env1, "b1.env doesn't point to env1"
+        assert env1.b1.object == env1, "b1.object doesn't point to env1"
         env2 = env1.Clone(BUILDERS = {'b2' : 2})
         assert env2 is env2
         assert env2 == env2
         assert hasattr(env1, 'b1'), "b1 was mistakenly cleared from env1"
-        assert env1.b1.env == env1, "b1.env was changed"
+        assert env1.b1.object == env1, "b1.object was changed"
         assert not hasattr(env2, 'b1'), "b1 was not cleared from env2"
         assert hasattr(env2, 'b2'), "env2.b2 was not set"
-        assert env2.b2.env == env2, "b2.env doesn't point to env2"
+        assert env2.b2.object == env2, "b2.object doesn't point to env2"
 
         # Ensure that specifying new tools in a copied environment
         # works.
@@ -1640,6 +1685,25 @@ def generate(env):
         env = env.Clone(tools=['yyy'])
         assert env['YYY'] == 'two', env['YYY']
 
+
+        # Test that
+        real_value = [4]
+
+        def my_tool(env, rv=real_value):
+            assert env['KEY_THAT_I_WANT'] == rv[0]
+            env['KEY_THAT_I_WANT'] = rv[0] + 1
+
+        env = self.TestEnvironment()
+
+        real_value[0] = 5
+        env = env.Clone(KEY_THAT_I_WANT=5, tools=[my_tool])
+        assert env['KEY_THAT_I_WANT'] == real_value[0], env['KEY_THAT_I_WANT']
+
+        real_value[0] = 6
+        env = env.Clone(KEY_THAT_I_WANT=6, tools=[my_tool])
+        assert env['KEY_THAT_I_WANT'] == real_value[0], env['KEY_THAT_I_WANT']
+
+
     def test_Copy(self):
         """Test copying using the old env.Copy() method"""
         env1 = self.TestEnvironment(XXX = 'x', YYY = 'y')
@@ -2999,6 +3063,8 @@ def generate(env):
 
     def test_SourceSignatures(type):
         """Test the SourceSignatures() method"""
+        import SCons.Errors
+
         env = type.TestEnvironment(M = 'MD5', T = 'timestamp')
 
         exc_caught = None
@@ -3007,19 +3073,31 @@ def generate(env):
         except SCons.Errors.UserError:
             exc_caught = 1
         assert exc_caught, "did not catch expected UserError"
-        assert not hasattr(env, '_calc_module')
 
         env.SourceSignatures('MD5')
-        m = env._calc_module
+        assert env.src_sig_type == 'MD5', env.src_sig_type
 
         env.SourceSignatures('$M')
-        assert env._calc_module is m
+        assert env.src_sig_type == 'MD5', env.src_sig_type
 
         env.SourceSignatures('timestamp')
-        t = env._calc_module
+        assert env.src_sig_type == 'timestamp', env.src_sig_type
 
         env.SourceSignatures('$T')
-        assert env._calc_module is t
+        assert env.src_sig_type == 'timestamp', env.src_sig_type
+
+        try:
+            import SCons.Util
+            save_md5 = SCons.Util.md5
+            SCons.Util.md5 = None
+            try:
+                env.SourceSignatures('MD5')
+            except SCons.Errors.UserError:
+                pass
+            else:
+                self.fail('Did not catch expected UserError')
+        finally:
+            SCons.Util.md5 = save_md5
 
     def test_Split(self):
         """Test the Split() method"""
@@ -3039,6 +3117,8 @@ def generate(env):
 
     def test_TargetSignatures(type):
         """Test the TargetSignatures() method"""
+        import SCons.Errors
+
         env = type.TestEnvironment(B = 'build', C = 'content')
 
         exc_caught = None
@@ -3050,16 +3130,41 @@ def generate(env):
         assert not hasattr(env, '_build_signature')
 
         env.TargetSignatures('build')
-        assert env._build_signature == 1, env._build_signature
-
-        env.TargetSignatures('content')
-        assert env._build_signature == 0, env._build_signature
+        assert env.tgt_sig_type == 'build', env.tgt_sig_type
 
         env.TargetSignatures('$B')
-        assert env._build_signature == 1, env._build_signature
+        assert env.tgt_sig_type == 'build', env.tgt_sig_type
+
+        env.TargetSignatures('content')
+        assert env.tgt_sig_type == 'content', env.tgt_sig_type
 
         env.TargetSignatures('$C')
-        assert env._build_signature == 0, env._build_signature
+        assert env.tgt_sig_type == 'content', env.tgt_sig_type
+
+        env.TargetSignatures('MD5')
+        assert env.tgt_sig_type == 'MD5', env.tgt_sig_type
+
+        env.TargetSignatures('timestamp')
+        assert env.tgt_sig_type == 'timestamp', env.tgt_sig_type
+
+        try:
+            import SCons.Util
+            save_md5 = SCons.Util.md5
+            SCons.Util.md5 = None
+            try:
+                env.TargetSignatures('MD5')
+            except SCons.Errors.UserError:
+                pass
+            else:
+                self.fail('Did not catch expected UserError')
+            try:
+                env.TargetSignatures('content')
+            except SCons.Errors.UserError:
+                pass
+            else:
+                self.fail('Did not catch expected UserError')
+        finally:
+            SCons.Util.md5 = save_md5
 
     def test_Value(self):
         """Test creating a Value() object
index 7d8df68b66b5e95d73ccca2092eb938ded9ad150..88a46cca9bde853a7ec83aa1730b72b6cc09942d 100644 (file)
@@ -74,10 +74,17 @@ class Executor:
     def get_action_list(self):
         return self.pre_actions + self.action_list + self.post_actions
 
+    memoizer_counters.append(SCons.Memoize.CountValue('get_build_env'))
+
     def get_build_env(self):
         """Fetch or create the appropriate build Environment
         for this Executor.
         """
+        try:
+            return self._memo['get_build_env']
+        except KeyError:
+            pass
+
         # Create the build environment instance with appropriate
         # overrides.  These get evaluated against the current
         # environment's construction variables so that users can
@@ -91,11 +98,12 @@ class Executor:
         env = self.env or SCons.Defaults.DefaultEnvironment()
         build_env = env.Override(overrides)
 
+        self._memo['get_build_env'] = build_env
+
         return build_env
 
     def get_build_scanner_path(self, scanner):
-        """Fetch the scanner path for this executor's targets
-        and sources.
+        """Fetch the scanner path for this executor's targets and sources.
         """
         env = self.get_build_env()
         try:
@@ -109,24 +117,26 @@ class Executor:
         result.update(kw)
         return result
 
-    def do_nothing(self, target, exitstatfunc, kw):
-        pass
+    def do_nothing(self, target, kw):
+        return 0
 
-    def do_execute(self, target, exitstatfunc, kw):
+    def do_execute(self, target, kw):
         """Actually execute the action list."""
         env = self.get_build_env()
         kw = self.get_kw(kw)
+        status = 0
         for act in self.get_action_list():
-            apply(act,
-                  (self.targets, self.sources, env, exitstatfunc),
-                  kw)
+            status = apply(act, (self.targets, self.sources, env), kw)
+            if status:
+                break
+        return status
 
     # use extra indirection because with new-style objects (Python 2.2
     # and above) we can't override special methods, and nullify() needs
     # to be able to do this.
 
-    def __call__(self, target, exitstatfunc, **kw):
-        self.do_execute(target, exitstatfunc, kw)
+    def __call__(self, target, **kw):
+        return self.do_execute(target, kw)
 
     def cleanup(self):
         self._memo = {}
@@ -218,10 +228,13 @@ class Executor:
         scanner_list = map(select_specific_scanner, scanner_list)
         scanner_list = filter(remove_null_scanners, scanner_list)
         scanner_path_list = map(add_scanner_path, scanner_list)
+
         deps = []
         for node, scanner, path in scanner_path_list:
             deps.extend(node.get_implicit_deps(env, scanner, path))
 
+        deps.extend(self.get_implicit_deps())
+
         for tgt in self.targets:
             tgt.add_to_implicit(deps)
 
@@ -280,6 +293,15 @@ class Executor:
 
         return result
 
+    def get_implicit_deps(self):
+        """Return the executor's implicit dependencies, i.e. the nodes of
+        the commands to be executed."""
+        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))
+        return result
+
 
 _Executor = Executor
 
@@ -297,7 +319,15 @@ class Null(_Executor):
         apply(_Executor.__init__, (self,), kw)
     def get_build_env(self):
         import SCons.Util
-        return SCons.Util.Null()
+        class NullEnvironment(SCons.Util.Null):
+            #def get_scanner(self, key):
+            #    return None
+            #def changed_since_last_build(self, dependency, target, prev_ni):
+            #    return dependency.changed_since_last_buld(target, prev_ni)
+            def get_CacheDir(self):
+                import SCons.CacheDir
+                return SCons.CacheDir.Null()
+        return NullEnvironment()
     def get_build_scanner_path(self):
         return None
     def cleanup(self):
index 8aedb7605e1a75094bec702beb39b8f5d69a6be3..59deca571346ac7ec84845f2dca30f4be47adb72 100644 (file)
@@ -46,13 +46,15 @@ class MyEnvironment:
 class MyAction:
     def __init__(self, actions=['action1', 'action2']):
         self.actions = actions
-    def __call__(self, target, source, env, errfunc, **kw):
+    def __call__(self, target, source, env, **kw):
         for action in self.actions:
-            apply(action, (target, source, env, errfunc), kw)
+            apply(action, (target, source, env), kw)
     def genstring(self, target, source, env):
         return string.join(['GENSTRING'] + map(str, self.actions) + target + source)
     def get_contents(self, target, source, env):
         return string.join(self.actions + target + source)
+    def get_implicit_deps(self, target, source, env):
+        return []
 
 class MyBuilder:
     def __init__(self, env, overrides):
@@ -69,7 +71,7 @@ class MyNode:
         self.missing_val = None
     def __str__(self):
         return self.name
-    def build(self, errfunc=None):
+    def build(self):
         executor = SCons.Executor.Executor(MyAction(self.pre_actions +
                                                     [self.builder.action] +
                                                     self.post_actions),
@@ -77,7 +79,7 @@ class MyNode:
                                            [],
                                            [self],
                                            ['s1', 's2'])
-        apply(executor, (self, errfunc), {})
+        apply(executor, (self), {})
     def get_env_scanner(self, env, kw):
         return MyScanner('dep-')
     def get_implicit_deps(self, env, scanner, path):
@@ -207,13 +209,13 @@ class ExecutorTestCase(unittest.TestCase):
     def test__call__(self):
         """Test calling an Executor"""
         result = []
-        def pre(target, source, env, errfunc, result=result, **kw):
+        def pre(target, source, env, result=result, **kw):
             result.append('pre')
-        def action1(target, source, env, errfunc, result=result, **kw):
+        def action1(target, source, env, result=result, **kw):
             result.append('action1')
-        def action2(target, source, env, errfunc, result=result, **kw):
+        def action2(target, source, env, result=result, **kw):
             result.append('action2')
-        def post(target, source, env, errfunc, result=result, **kw):
+        def post(target, source, env, result=result, **kw):
             result.append('post')
 
         env = MyEnvironment()
@@ -223,32 +225,18 @@ class ExecutorTestCase(unittest.TestCase):
         x = SCons.Executor.Executor(a, env, [], t, ['s1', 's2'])
         x.add_pre_action(pre)
         x.add_post_action(post)
-        x(t, lambda x: x)
+        x(t)
         assert result == ['pre', 'action1', 'action2', 'post'], result
         del result[:]
 
-        def pre_err(target, source, env, errfunc, result=result, **kw):
+        def pre_err(target, source, env, result=result, **kw):
             result.append('pre_err')
-            if errfunc:
-                errfunc(1)
             return 1
 
         x = SCons.Executor.Executor(a, env, [], t, ['s1', 's2'])
         x.add_pre_action(pre_err)
         x.add_post_action(post)
-        x(t, lambda x: x)
-        assert result == ['pre_err', 'action1', 'action2', 'post'], result
-        del result[:]
-
-        def errfunc(stat):
-            raise "errfunc %s" % stat
-
-        try:
-            x(t, errfunc)
-        except:
-            assert sys.exc_type == "errfunc 1", sys.exc_type
-        else:
-            assert 0, "did not catch expected exception"
+        x(t)
         assert result == ['pre_err'], result
         del result[:]
 
@@ -319,14 +307,14 @@ class ExecutorTestCase(unittest.TestCase):
         env = MyEnvironment(S='string')
 
         result = []
-        def action1(target, source, env, errfunc, result=result, **kw):
+        def action1(target, source, env, result=result, **kw):
             result.append('action1')
 
         env = MyEnvironment()
         a = MyAction([action1])
         x = SCons.Executor.Executor(a, env, [], ['t1', 't2'], ['s1', 's2'])
 
-        x(MyNode('', [], []), None)
+        x(MyNode('', [], []))
         assert result == ['action1'], result
         s = str(x)
         assert s[:10] == 'GENSTRING ', s
@@ -335,7 +323,7 @@ class ExecutorTestCase(unittest.TestCase):
         x.nullify()
 
         assert result == [], result
-        x(MyNode('', [], []), None)
+        x(MyNode('', [], []))
         assert result == [], result
         s = str(x)
         assert s == '', s
@@ -367,7 +355,7 @@ class ExecutorTestCase(unittest.TestCase):
         t1 = MyNode('t1')
         t2 = MyNode('t2')
         sources = [MyNode('s1'), MyNode('s2')]
-        x = SCons.Executor.Executor('b', env, [{}], [t1, t2], sources)
+        x = SCons.Executor.Executor(MyAction(), env, [{}], [t1, t2], sources)
 
         deps = x.scan_targets(None)
         assert t1.implicit == ['dep-t1', 'dep-t2'], t1.implicit
@@ -386,7 +374,7 @@ class ExecutorTestCase(unittest.TestCase):
         t1 = MyNode('t1')
         t2 = MyNode('t2')
         sources = [MyNode('s1'), MyNode('s2')]
-        x = SCons.Executor.Executor('b', env, [{}], [t1, t2], sources)
+        x = SCons.Executor.Executor(MyAction(), env, [{}], [t1, t2], sources)
 
         deps = x.scan_sources(None)
         assert t1.implicit == ['dep-s1', 'dep-s2'], t1.implicit
index 5a47da72e25983fafda6bfdd37dadfff6ed01f6c..5f056e86453ecace6879a07f709ad7f45f4eef25 100644 (file)
@@ -359,14 +359,21 @@ import SCons.Taskmaster
 import SCons.Node
 import time
 
+class DummyNodeInfo:
+    def update(self, obj):
+        pass
 
 class testnode (SCons.Node.Node):
     def __init__(self):
         SCons.Node.Node.__init__(self)
         self.expect_to_be = SCons.Node.executed
+        self.ninfo = DummyNodeInfo()
 
 class goodnode (testnode):
-    pass
+    def __init__(self):
+        SCons.Node.Node.__init__(self)
+        self.expect_to_be = SCons.Node.up_to_date
+        self.ninfo = DummyNodeInfo()
 
 class slowgoodnode (goodnode):
     def prepare(self):
@@ -469,8 +476,9 @@ class _SConsTaskTest(unittest.TestCase):
         # mislabelling of results).
 
         for N in testnodes:
-            self.failUnless(N.get_state() in [SCons.Node.no_state, N.expect_to_be],
-                            "node ran but got unexpected result")
+            state = N.get_state()
+            self.failUnless(state in [SCons.Node.no_state, N.expect_to_be],
+                            "Node %s got unexpected result: %s" % (N, state))
 
         self.failUnless(filter(lambda N: N.get_state(), testnodes),
                         "no nodes ran at all.")
index 6d4440b9205ba62c895bc09319985ba43a5d5f8c..15de6644e176b7b9c214b196bdfa8b8e53a5d459 100644 (file)
@@ -57,10 +57,13 @@ class AliasNameSpace(UserDict.UserDict):
             return None
 
 class AliasNodeInfo(SCons.Node.NodeInfoBase):
-    pass
+    current_version_id = 1
+    field_list = ['csig']
+    def str_to_node(self, s):
+        return default_ans.Alias(s)
 
 class AliasBuildInfo(SCons.Node.BuildInfoBase):
-    pass
+    current_version_id = 1
 
 class Alias(SCons.Node.Node):
 
@@ -74,8 +77,11 @@ class Alias(SCons.Node.Node):
     def __str__(self):
         return self.name
 
+    def make_ready(self):
+        self.get_csig()
+
     really_build = SCons.Node.Node.build
-    current = SCons.Node.Node.children_are_up_to_date
+    is_up_to_date = SCons.Node.Node.children_are_up_to_date
 
     def is_under(self, dir):
         # Make Alias nodes get built regardless of
@@ -97,6 +103,13 @@ class Alias(SCons.Node.Node):
     #
     #
 
+    def changed_since_last_build(self, target, prev_ni):
+        cur_csig = self.get_csig()
+        try:
+            return cur_csig != prev_ni.csig
+        except AttributeError:
+            return 1
+
     def build(self):
         """A "builder" for aliases."""
         pass
@@ -107,6 +120,25 @@ class Alias(SCons.Node.Node):
         self.reset_executor()
         self.build = self.really_build
 
+    def get_csig(self):
+        """
+        Generate a node's content signature, the digested signature
+        of its content.
+
+        node - the node
+        cache - alternate node to use for the signature cache
+        returns - the content signature
+        """
+        try:
+            return self.ninfo.csig
+        except AttributeError:
+            pass
+           
+        contents = self.get_contents()
+        csig = SCons.Util.MD5signature(contents)
+        self.get_ninfo().csig = csig
+        return csig
+
 default_ans = AliasNameSpace()
 
 SCons.Node.arg2nodes_lookups.append(default_ans.lookup)
index 74dc655e969ab1754e5999aafd8e79fa830a7eb2..964af62dddaffd5eb81c0176745bb9e8a39c760b 100644 (file)
@@ -49,10 +49,13 @@ from SCons.Debug import logInstanceCreation
 import SCons.Errors
 import SCons.Memoize
 import SCons.Node
+import SCons.Node.Alias
 import SCons.Subst
 import SCons.Util
 import SCons.Warnings
 
+from SCons.Debug import Trace
+
 # The max_drift value:  by default, use a cached signature value for
 # any file that's been untouched for more than two days.
 default_max_drift = 2*24*60*60
@@ -487,6 +490,7 @@ class Base(SCons.Node.Node):
         assert directory, "A directory must be provided"
 
         self.abspath = directory.entry_abspath(name)
+        self.labspath = directory.entry_labspath(name)
         if directory.path == '.':
             self.path = name
         else:
@@ -841,11 +845,17 @@ 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()
+
+    def changed_since_last_build(self, target, prev_ni):
+        return self.disambiguate().changed_since_last_build(target, prev_ni)
 
 # This is for later so we can differentiate between Entry the class and Entry
 # the method of the FS class.
@@ -960,11 +970,14 @@ class FS(LocalFS):
             self.pathTop = path
         self.defaultDrive = _my_normcase(os.path.splitdrive(self.pathTop)[0])
 
-        self.Top = self._doLookup(Dir, os.path.normpath(self.pathTop))
+        self.Top = self.Dir(self.pathTop)
         self.Top.path = '.'
         self.Top.tpath = '.'
         self._cwd = self.Top
 
+        DirNodeInfo.top = self.Top
+        FileNodeInfo.top = self.Top
+    
     def set_SConstruct_dir(self, dir):
         self.SConstruct_dir = dir
 
@@ -977,161 +990,6 @@ class FS(LocalFS):
     def getcwd(self):
         return self._cwd
 
-    def _doLookup_key(self, fsclass, name, directory = None, create = 1):
-        return (fsclass, name, directory)
-
-    memoizer_counters.append(SCons.Memoize.CountDict('_doLookup', _doLookup_key))
-
-    def _doLookup(self, fsclass, name, directory = None, create = 1):
-        """This method differs from the File and Dir factory methods in
-        one important way: the meaning of the directory parameter.
-        In this method, if directory is None or not supplied, the supplied
-        name is expected to be an absolute path.  If you try to look up a
-        relative path with directory=None, then an AssertionError will be
-        raised.
-        """
-        memo_key = (fsclass, name, directory)
-        try:
-            memo_dict = self._memo['_doLookup']
-        except KeyError:
-            memo_dict = {}
-            self._memo['_doLookup'] = memo_dict
-        else:
-            try:
-                return memo_dict[memo_key]
-            except KeyError:
-                pass
-
-        if not name:
-            # This is a stupid hack to compensate for the fact that the
-            # POSIX and Windows versions of os.path.normpath() behave
-            # differently in older versions of Python.  In particular,
-            # in POSIX:
-            #   os.path.normpath('./') == '.'
-            # in Windows:
-            #   os.path.normpath('./') == ''
-            #   os.path.normpath('.\\') == ''
-            #
-            # This is a definite bug in the Python library, but we have
-            # to live with it.
-            name = '.'
-        path_orig = string.split(name, os.sep)
-        path_norm = string.split(_my_normcase(name), os.sep)
-
-        first_orig = path_orig.pop(0)   # strip first element
-        unused = path_norm.pop(0)   # strip first element
-
-        drive, path_first = os.path.splitdrive(first_orig)
-        if path_first:
-            path_orig = [ path_first, ] + path_orig
-            path_norm = [ _my_normcase(path_first), ] + path_norm
-        else:
-            # Absolute path
-            drive = _my_normcase(drive)
-            try:
-                directory = self.Root[drive]
-            except KeyError:
-                if not create:
-                    raise SCons.Errors.UserError
-                directory = RootDir(drive, self)
-                self.Root[drive] = directory
-                if not drive:
-                    self.Root[self.defaultDrive] = directory
-                elif drive == self.defaultDrive:
-                    self.Root[''] = directory
-
-        if not path_orig:
-            memo_dict[memo_key] = directory
-            return directory
-
-        last_orig = path_orig.pop()     # strip last element
-        last_norm = path_norm.pop()     # strip last element
-
-        # Lookup the directory
-        for orig, norm in map(None, path_orig, path_norm):
-            try:
-                entries = directory.entries
-            except AttributeError:
-                # We tried to look up the entry in either an Entry or
-                # a File.  Give whatever it is a chance to do what's
-                # appropriate: morph into a Dir or raise an exception.
-                directory.must_be_same(Dir)
-                entries = directory.entries
-            try:
-                directory = entries[norm]
-            except KeyError:
-                if not create:
-                    raise SCons.Errors.UserError
-
-                d = Dir(orig, directory, self)
-
-                # Check the file system (or not, as configured) to make
-                # sure there isn't already a file there.
-                d.diskcheck_match()
-
-                directory.entries[norm] = d
-                directory.add_wkid(d)
-                directory = d
-
-        directory.must_be_same(Dir)
-
-        try:
-            e = directory.entries[last_norm]
-        except KeyError:
-            if not create:
-                raise SCons.Errors.UserError
-
-            result = fsclass(last_orig, directory, self)
-
-            # Check the file system (or not, as configured) to make
-            # sure there isn't already a directory at the path on
-            # disk where we just created a File node, and vice versa.
-            result.diskcheck_match()
-
-            directory.entries[last_norm] = result
-            directory.add_wkid(result)
-        else:
-            e.must_be_same(fsclass)
-            result = e
-
-        memo_dict[memo_key] = result
-
-        return result 
-
-    def _transformPath(self, name, directory):
-        """Take care of setting up the correct top-level directory,
-        usually in preparation for a call to doLookup().
-
-        If the path name is prepended with a '#', then it is unconditionally
-        interpreted as relative to the top-level directory of this FS.
-
-        If directory is None, and name is a relative path,
-        then the same applies.
-        """
-        try:
-            # Decide if this is a top-relative look up.  The normal case
-            # (by far) is handed a non-zero-length string to look up,
-            # so just (try to) check for the initial '#'.
-            top_relative = (name[0] == '#')
-        except (AttributeError, IndexError):
-            # The exceptions we may encounter in unusual cases:
-            #   AttributeError: a proxy without a __getitem__() method.
-            #   IndexError: a null string.
-            top_relative = False
-            name = str(name)
-        if top_relative:
-            directory = self.Top
-            name = name[1:]
-            if name and (name[0] == os.sep or name[0] == '/'):
-                # Correct such that '#/foo' is equivalent
-                # to '#foo'.
-                name = name[1:]
-            name = os.path.normpath(os.path.join('.', name))
-            return (name, directory)
-        elif not directory:
-            directory = self._cwd
-        return (os.path.normpath(name), directory)
-
     def chdir(self, dir, change_os_dir=0):
         """Change the current working directory for lookups.
         If change_os_dir is true, we will also change the "real" cwd
@@ -1147,25 +1005,86 @@ class FS(LocalFS):
             self._cwd = curr
             raise
 
-    def Entry(self, name, directory = None, create = 1, klass=None):
+    def get_root(self, drive):
+        """
+        Returns the root directory for the specified drive, creating
+        it if necessary.
+        """
+        drive = _my_normcase(drive)
+        try:
+            return self.Root[drive]
+        except KeyError:
+            root = RootDir(drive, self)
+            self.Root[drive] = root
+            if not drive:
+                self.Root[self.defaultDrive] = root
+            elif drive == self.defaultDrive:
+                self.Root[''] = root
+            return root
+
+    def _lookup(self, p, directory, fsclass, create=1):
+        """
+        The generic entry point for Node lookup with user-supplied data.
+
+        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.
+
+        If the path name begins with '#', it is unconditionally
+        interpreted relative to the top-level directory of this FS.
+
+        If the path name is relative, then the path is looked up relative
+        to the specified directory, or the current directory (self._cwd,
+        typically the SConscript directory) if the specified directory
+        is None.
+        """
+        if isinstance(p, Base):
+            # It's already a Node.FS object.  Make sure it's the right
+            # class and return.
+            p.must_be_same(fsclass)
+            return p
+        # str(p) in case it's something like a proxy object
+        p = str(p)
+        drive, p = os.path.splitdrive(p)
+        if drive and not p:
+            # A drive letter without a path...
+            p = os.sep
+            root = self.get_root(drive)
+        elif os.path.isabs(p):
+            # An absolute path...
+            p = os.path.normpath(p)
+            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)
+            root = directory.root
+        if os.sep != '/':
+            p = string.replace(p, os.sep, '/')
+        return root._lookup_abs(p, fsclass, create)
+
+    def Entry(self, name, directory = None, create = 1):
         """Lookup or create a generic Entry 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, or to the top level directory of the FS (supplied at
         construction time) if no directory is supplied.
         """
-
-        if not klass:
-            klass = Entry
-
-        if isinstance(name, Base):
-            name.must_be_same(klass)
-            return name
-        else:
-            if directory and not isinstance(directory, Dir):
-                directory = self.Dir(directory)
-            name, directory = self._transformPath(name, directory)
-            return self._doLookup(klass, name, directory, create)
+        return self._lookup(name, directory, Entry, create)
 
     def File(self, name, directory = None, create = 1):
         """Lookup or create a File node with the specified name.  If
@@ -1177,7 +1096,7 @@ class FS(LocalFS):
         This method will raise TypeError if a directory is found at the
         specified path.
         """
-        return self.Entry(name, directory, create, File)
+        return self._lookup(name, directory, File, create)
 
     def Dir(self, name, directory = None, create = 1):
         """Lookup or create a Dir node with the specified name.  If
@@ -1189,7 +1108,7 @@ class FS(LocalFS):
         This method will raise TypeError if a normal file is found at the
         specified path.
         """
-        return self.Entry(name, directory, create, Dir)
+        return self._lookup(name, directory, Dir, create)
 
     def BuildDir(self, build_dir, src_dir, duplicate=1):
         """Link the supplied build directory to the source directory
@@ -1242,10 +1161,22 @@ class FS(LocalFS):
         return targets, message
 
 class DirNodeInfo(SCons.Node.NodeInfoBase):
-    pass
+    # This should get reset by the FS initialization.
+    current_version_id = 1
+
+    top = None
+
+    def str_to_node(self, s):
+        top = self.top
+        if os.path.isabs(s):
+            n = top.fs._lookup(s, top, Entry)
+        else:
+            s = top.labspath + '/' + s
+            n = top.root._lookup_abs(s, Entry)
+        return n
 
 class DirBuildInfo(SCons.Node.BuildInfoBase):
-    pass
+    current_version_id = 1
 
 class Dir(Base):
     """A class for directories in a file system.
@@ -1280,6 +1211,7 @@ class Dir(Base):
         self.searched = 0
         self._sconsign = None
         self.build_dirs = []
+        self.root = self.dir.root
 
         # Don't just reset the executor, replace its action list,
         # because it might have some pre-or post-actions that need to
@@ -1314,18 +1246,44 @@ class Dir(Base):
             node.duplicate = node.get_dir().duplicate
 
     def Entry(self, name):
-        """Create an entry node named 'name' relative to this directory."""
+        """
+        Looks up or creates an entry node named 'name' relative to
+        this directory.
+        """
         return self.fs.Entry(name, self)
 
     def Dir(self, name):
-        """Create a directory node named 'name' relative to this directory."""
+        """
+        Looks up or creates a directory node named 'name' relative to
+        this directory.
+        """
         dir = self.fs.Dir(name, self)
         return dir
 
     def File(self, name):
-        """Create a file node named 'name' relative to this directory."""
+        """
+        Looks up or creates a file node named 'name' relative to
+        this directory.
+        """
         return self.fs.File(name, self)
 
+    def _lookup_rel(self, name, klass, create=1):
+        """
+        Looks up a *normalized* relative path name, relative to this
+        directory.
+
+        This method is intended for use by internal lookups with
+        already-normalized path data.  For general-purpose lookups,
+        use the Entry(), Dir() and File() methods above.
+
+        This method does *no* input checking and will die or give
+        incorrect results if it's passed a non-normalized path name (e.g.,
+        a path containing '..'), an absolute path name, a top-relative
+        ('#foo') path name, or any kind of object.
+        """
+        name = self.labspath + '/' + name
+        return self.root._lookup_abs(name, klass, create)
+
     def link(self, srcdir, duplicate):
         """Set this directory as the build directory for the
         supplied source directory."""
@@ -1371,57 +1329,66 @@ class Dir(Base):
     def up(self):
         return self.entries['..']
 
-    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
+# 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 get_env_scanner(self, env, kw={}):
         import SCons.Defaults
@@ -1452,12 +1419,23 @@ class Dir(Base):
         self.clear()
         return scanner(self, env, path)
 
+    #
+    # Taskmaster interface subsystem
+    #
+
+    def prepare(self):
+        pass
+
     def build(self, **kw):
         """A null "builder" for directories."""
         global MkdirBuilder
         if not self.builder is MkdirBuilder:
             apply(SCons.Node.Node.build, [self,], kw)
 
+    #
+    #
+    #
+
     def _create(self):
         """Create this directory, silently and without worrying about
         whether the builder is the default or not."""
@@ -1506,13 +1484,12 @@ class Dir(Base):
         contents = map(lambda n: n.get_contents(), self.children())
         return  string.join(contents, '')
 
-    def prepare(self):
-        pass
-
     def do_duplicate(self, src):
         pass
 
-    def current(self, calc=None):
+    changed_since_last_build = SCons.Node.Node.state_has_changed
+
+    def is_up_to_date(self):
         """If any child is not up-to-date, then this directory isn't,
         either."""
         if not self.builder is MkdirBuilder and not self.exists():
@@ -1560,6 +1537,9 @@ class Dir(Base):
     def entry_abspath(self, name):
         return self.abspath + os.sep + name
 
+    def entry_labspath(self, name):
+        return self.labspath + '/' + name
+
     def entry_path(self, name):
         return self.path + os.sep + name
 
@@ -1640,7 +1620,7 @@ class Dir(Base):
 
         def func(node):
             if (isinstance(node, File) or isinstance(node, Entry)) and \
-               (node.is_derived() or node.is_pseudo_derived() or node.exists()):
+               (node.is_derived() or node.exists()):
                     return node
             return None
 
@@ -1727,30 +1707,89 @@ class RootDir(Dir):
         # attribute) so we have to set up some values so Base.__init__()
         # won't gag won't it calls some of our methods.
         self.abspath = ''
+        self.labspath = ''
         self.path = ''
         self.tpath = ''
         self.path_elements = []
         self.duplicate = 0
+        self.root = self
         Base.__init__(self, name, self, fs)
 
         # Now set our paths to what we really want them to be: the
-        # initial drive letter (the name) plus the directory separator.
+        # initial drive letter (the name) plus the directory separator,
+        # except for the "lookup abspath," which does not have the
+        # drive letter.
         self.abspath = name + os.sep
+        self.labspath = '/'
         self.path = name + os.sep
         self.tpath = name + os.sep
         self._morph()
 
+        self._lookupDict = {}
+
+        # The // and os.sep + os.sep entries are necessary because
+        # os.path.normpath() seems to preserve double slashes at the
+        # beginning of a path (presumably for UNC path names), but
+        # collapses triple slashes to a single slash.
+        self._lookupDict['/'] = self
+        self._lookupDict['//'] = self
+        self._lookupDict[os.sep] = self
+        self._lookupDict[os.sep + os.sep] = self
+
     def must_be_same(self, klass):
         if klass is Dir:
             return
         Base.must_be_same(self, klass)
 
+    def _lookup_abs(self, p, klass, create=1):
+        """
+        Fast (?) lookup of a *normalized* absolute path.
+
+        This method is intended for use by internal lookups with
+        already-normalized path data.  For general-purpose lookups,
+        use the FS.Entry(), FS.Dir() or FS.File() methods.
+
+        The caller is responsible for making sure we're passed a
+        normalized absolute path; we merely let Python's dictionary look
+        up and return the One True Node.FS object for the path.
+
+        If no Node for the specified "p" doesn't already exist, and
+        "create" is specified, the Node may be created after recursive
+        invocation to find or create the parent directory or directories.
+        """
+        k = _my_normcase(p)
+        try:
+            result = self._lookupDict[k]
+        except KeyError:
+            if not create:
+                raise SCons.Errors.UserError
+            # There is no Node for this path name, and we're allowed
+            # to create it.
+            dir_name, file_name = os.path.split(p)
+            dir_node = self._lookup_abs(dir_name, Dir)
+            result = klass(file_name, dir_node, self.fs)
+            self._lookupDict[k] = result
+            dir_node.entries[_my_normcase(file_name)] = result
+            dir_node.implicit = None
+
+            # Double-check on disk (as configured) that the Node we
+            # created matches whatever is out there in the real world.
+            result.diskcheck_match()
+        else:
+            # There is already a Node for this path name.  Allow it to
+            # complain if we were looking for an inappropriate type.
+            result.must_be_same(klass)
+        return result
+
     def __str__(self):
         return self.abspath
 
     def entry_abspath(self, name):
         return self.abspath + name
 
+    def entry_labspath(self, name):
+        return self.labspath + name
+
     def entry_path(self, name):
         return self.path + name
 
@@ -1773,90 +1812,96 @@ class RootDir(Dir):
         return _null
 
 class FileNodeInfo(SCons.Node.NodeInfoBase):
-    def __init__(self, node):
-        SCons.Node.NodeInfoBase.__init__(self, node)
-        self.update(node)
-    def __cmp__(self, other):
-        try: return cmp(self.bsig, other.bsig)
-        except AttributeError: return 1
-    def update(self, node):
-        self.timestamp = node.get_timestamp()
-        self.size = node.getsize()
+    current_version_id = 1
+
+    field_list = ['csig', 'timestamp', 'size']
+
+    # This should get reset by the FS initialization.
+    top = None
+
+    def str_to_node(self, s):
+        top = self.top
+        if os.path.isabs(s):
+            n = top.fs._lookup(s, top, Entry)
+        else:
+            s = top.labspath + '/' + s
+            n = top.root._lookup_abs(s, Entry)
+        return n
 
 class FileBuildInfo(SCons.Node.BuildInfoBase):
-    def __init__(self, node):
-        SCons.Node.BuildInfoBase.__init__(self, node)
-        self.node = node
+    current_version_id = 1
+
     def convert_to_sconsign(self):
-        """Convert this FileBuildInfo object for writing to a .sconsign file
+        """
+        Converts this FileBuildInfo object for writing to a .sconsign file
 
-        We hung onto the node that we refer to so that we can translate
-        the lists of bsources, bdepends and bimplicit Nodes into strings
-        relative to the node, but we don't want to write out that Node
-        itself to the .sconsign file, so we delete the attribute in
-        preparation.
+        This replaces each Node in our various dependency lists with its
+        usual string representation: relative to the top-level SConstruct
+        directory, or an absolute path if it's outside.
         """
-        rel_path = self.node.rel_path
-        delattr(self, 'node')
+        if os.sep == '/':
+            node_to_str = str
+        else:
+            def node_to_str(n):
+                try:
+                    s = n.path
+                except AttributeError:
+                    s = str(n)
+                else:
+                    s = string.replace(s, os.sep, '/')
+                return s
         for attr in ['bsources', 'bdepends', 'bimplicit']:
             try:
                 val = getattr(self, attr)
             except AttributeError:
                 pass
             else:
-                setattr(self, attr, map(rel_path, val))
+                setattr(self, attr, map(node_to_str, val))
     def convert_from_sconsign(self, dir, name):
-        """Convert a newly-read FileBuildInfo object for in-SCons use
-
-        An on-disk BuildInfo comes without a reference to the node for
-        which it's intended, so we have to convert the arguments and add
-        back a self.node attribute.  We don't worry here about converting
-        the bsources, bdepends and bimplicit lists from strings to Nodes
-        because they're not used in the normal case of just deciding
-        whether or not to rebuild things.
         """
-        self.node = dir.Entry(name)
+        Converts a newly-read FileBuildInfo object for in-SCons use
+
+        For normal up-to-date checking, we don't have any conversion to
+        perform--but we're leaving this method here to make that clear.
+        """
+        pass
     def prepare_dependencies(self):
-        """Prepare a FileBuildInfo object for explaining what changed
+        """
+        Prepares a FileBuildInfo object for explaining what changed
 
-        The bsources, bdepends and bimplicit lists have all been stored
-        on disk as paths relative to the Node for which they're stored
-        as dependency info.  Convert the strings to actual Nodes (for
-        use by the --debug=explain code and --implicit-cache).
+        The bsources, bdepends and bimplicit lists have all been
+        stored on disk as paths relative to the top-level SConstruct
+        directory.  Convert the strings to actual Nodes (for use by the
+        --debug=explain code and --implicit-cache).
         """
-        def str_to_node(s, entry=self.node.dir.Entry):
-            # This is a little bogus; we're going to mimic the lookup
-            # order of env.arg2nodes() by hard-coding an Alias lookup
-            # before we assume it's an Entry.  This should be able to
-            # go away once the Big Signature Refactoring pickles the
-            # actual NodeInfo object, which will let us know precisely
-            # what type of Node to turn it into.
-            import SCons.Node.Alias
-            n = SCons.Node.Alias.default_ans.lookup(s)
-            if not n:
-                n = entry(s)
-            return n
-        for attr in ['bsources', 'bdepends', 'bimplicit']:
+        attrs = [
+            ('bsources', 'bsourcesigs'),
+            ('bdepends', 'bdependsigs'),
+            ('bimplicit', 'bimplicitsigs'),
+        ]
+        for (nattr, sattr) in attrs:
             try:
-                val = getattr(self, attr)
+                strings = getattr(self, nattr)
+                nodeinfos = getattr(self, sattr)
             except AttributeError:
                 pass
             else:
-                setattr(self, attr, map(str_to_node, val))
-    def format(self):
-        result = [ self.ninfo.format() ]
+                nodes = []
+                for s, ni in zip(strings, nodeinfos):
+                    if not isinstance(s, SCons.Node.Node):
+                        s = ni.str_to_node(s)
+                    nodes.append(s)
+                setattr(self, nattr, nodes)
+    def format(self, names=0):
+        result = []
         bkids = self.bsources + self.bdepends + self.bimplicit
         bkidsigs = self.bsourcesigs + self.bdependsigs + self.bimplicitsigs
-        for i in xrange(len(bkids)):
-            result.append(str(bkids[i]) + ': ' + bkidsigs[i].format())
+        for bkid, bkidsig in zip(bkids, bkidsigs):
+            result.append(str(bkid) + ': ' +
+                          string.join(bkidsig.format(names=names), ' '))
+        result.append('%s [%s]' % (self.bactsig, self.bact))
         return string.join(result, '\n')
 
-class NodeInfo(FileNodeInfo):
-    pass
-
-class BuildInfo(FileBuildInfo):
-    pass
-
 class File(Base):
     """A class for files in a file system.
     """
@@ -1908,6 +1953,19 @@ class File(Base):
         if not hasattr(self, '_local'):
             self._local = 0
 
+        # If there was already a Builder set on this entry, then
+        # we need to make sure we call the target-decider function,
+        # not the source-decider.  Reaching in and doing this by hand
+        # is a little bogus.  We'd prefer to handle this by adding
+        # an Entry.builder_set() method that disambiguates like the
+        # other methods, but that starts running into problems with the
+        # fragile way we initialize Dir Nodes with their Mkdir builders,
+        # yet still allow them to be overridden by the user.  Since it's
+        # not clear right now how to fix that, stick with what works
+        # until it becomes clear...
+        if self.has_builder():
+            self.changed_since_last_build = self.decide_target
+
     def scanner_key(self):
         return self.get_suffix()
 
@@ -1923,47 +1981,198 @@ class File(Base):
             raise
         return r
 
+    memoizer_counters.append(SCons.Memoize.CountValue('get_size'))
+
+    def get_size(self):
+        try:
+            return self._memo['get_size']
+        except KeyError:
+            pass
+
+        if self.rexists():
+            size = self.rfile().getsize()
+        else:
+            size = 0
+
+        self._memo['get_size'] = size
+
+        return size
+
+    memoizer_counters.append(SCons.Memoize.CountValue('get_timestamp'))
+
     def get_timestamp(self):
+        try:
+            return self._memo['get_timestamp']
+        except KeyError:
+            pass
+
         if self.rexists():
-            return self.rfile().getmtime()
+            timestamp = self.rfile().getmtime()
         else:
-            return 0
+            timestamp = 0
+
+        self._memo['get_timestamp'] = timestamp
+
+        return timestamp
 
-    def store_info(self, obj):
+    def store_info(self):
         # Merge our build information into the already-stored entry.
         # This accomodates "chained builds" where a file that's a target
         # in one build (SConstruct file) is a source in a different build.
         # See test/chained-build.py for the use case.
-        entry = self.get_stored_info()
-        entry.merge(obj)
-        self.dir.sconsign().set_entry(self.name, entry)
+        self.dir.sconsign().store_info(self.name, self)
+
+    convert_copy_attrs = [
+        'bsources',
+        'bimplicit',
+        'bdepends',
+        'bact',
+        'bactsig',
+        'ninfo',
+    ]
+
+
+    convert_sig_attrs = [
+        'bsourcesigs',
+        'bimplicitsigs',
+        'bdependsigs',
+    ]
+
+    def convert_old_entry(self, old_entry):
+        # Convert a .sconsign entry from before the Big Signature
+        # Refactoring, doing what we can to convert its information
+        # to the new .sconsign entry format.
+        #
+        # The old format looked essentially like this:
+        #
+        #   BuildInfo
+        #       .ninfo (NodeInfo)
+        #           .bsig
+        #           .csig
+        #           .timestamp
+        #           .size
+        #       .bsources
+        #       .bsourcesigs ("signature" list)
+        #       .bdepends
+        #       .bdependsigs ("signature" list)
+        #       .bimplicit
+        #       .bimplicitsigs ("signature" list)
+        #       .bact
+        #       .bactsig
+        #
+        # The new format looks like this:
+        #
+        #   .ninfo (NodeInfo)
+        #       .bsig
+        #       .csig
+        #       .timestamp
+        #       .size
+        #   .binfo (BuildInfo)
+        #       .bsources
+        #       .bsourcesigs (NodeInfo list)
+        #           .bsig
+        #           .csig
+        #           .timestamp
+        #           .size
+        #       .bdepends
+        #       .bdependsigs (NodeInfo list)
+        #           .bsig
+        #           .csig
+        #           .timestamp
+        #           .size
+        #       .bimplicit
+        #       .bimplicitsigs (NodeInfo list)
+        #           .bsig
+        #           .csig
+        #           .timestamp
+        #           .size
+        #       .bact
+        #       .bactsig
+        #
+        # The basic idea of the new structure is that a NodeInfo always
+        # holds all available information about the state of a given Node
+        # at a certain point in time.  The various .b*sigs lists can just
+        # be a list of pointers to the .ninfo attributes of the different
+        # dependent nodes, without any copying of information until it's
+        # time to pickle it for writing out to a .sconsign file.
+        #
+        # The complicating issue is that the *old* format only stored one
+        # "signature" per dependency, based on however the *last* build
+        # was configured.  We don't know from just looking at it whether
+        # it was a build signature, a content signature, or a timestamp
+        # "signature".  Since we no longer use build signatures, the
+        # best we can do is look at the length and if it's thirty two,
+        # assume that it was (or might have been) a content signature.
+        # If it was actually a build signature, then it will cause a
+        # rebuild anyway when it doesn't match the new content signature,
+        # but that's probably the best we can do.
+        import SCons.SConsign
+        new_entry = SCons.SConsign.SConsignEntry()
+        new_entry.binfo = self.new_binfo()
+        binfo = new_entry.binfo
+        for attr in self.convert_copy_attrs:
+            try:
+                value = getattr(old_entry, attr)
+            except AttributeError:
+                pass
+            else:
+                setattr(binfo, attr, value)
+                delattr(old_entry, attr)
+        for attr in self.convert_sig_attrs:
+            try:
+                sig_list = getattr(old_entry, attr)
+            except AttributeError:
+                pass
+            else:
+                value = []
+                for sig in sig_list:
+                    ninfo = self.new_ninfo()
+                    if len(sig) == 32:
+                        ninfo.csig = sig
+                    else:
+                        ninfo.timestamp = sig
+                    value.append(ninfo)
+                setattr(binfo, attr, value)
+                delattr(old_entry, attr)
+        return new_entry
+
+    memoizer_counters.append(SCons.Memoize.CountValue('get_stored_info'))
 
     def get_stored_info(self):
         try:
-            stored = self.dir.sconsign().get_entry(self.name)
+            return self._memo['get_stored_info']
+        except KeyError:
+            pass
+
+        try:
+            sconsign_entry = self.dir.sconsign().get_entry(self.name)
         except (KeyError, OSError):
-            return self.new_binfo()
+            import SCons.SConsign
+            sconsign_entry = SCons.SConsign.SConsignEntry()
+            sconsign_entry.binfo = self.new_binfo()
+            sconsign_entry.ninfo = self.new_ninfo()
         else:
-            if not hasattr(stored, 'ninfo'):
-                # Transition:  The .sconsign file entry has no NodeInfo
-                # object, which means it's a slightly older BuildInfo.
-                # Copy over the relevant attributes.
-                ninfo = stored.ninfo = self.new_ninfo()
-                for attr in ninfo.__dict__.keys():
-                    try:
-                        setattr(ninfo, attr, getattr(stored, attr))
-                    except AttributeError:
-                        pass
-            return stored
+            if isinstance(sconsign_entry, FileBuildInfo):
+                # This is a .sconsign file from before the Big Signature
+                # Refactoring; convert it as best we can.
+                sconsign_entry = self.convert_old_entry(sconsign_entry)
+            try:
+                delattr(sconsign_entry.ninfo, 'bsig')
+            except AttributeError:
+                pass
+
+        self._memo['get_stored_info'] = sconsign_entry
+
+        return sconsign_entry
 
     def get_stored_implicit(self):
-        binfo = self.get_stored_info()
+        binfo = self.get_stored_info().binfo
         binfo.prepare_dependencies()
         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)
@@ -2037,6 +2246,63 @@ class File(Base):
         if self.exists():
             self.get_build_env().get_CacheDir().push_if_forced(self)
 
+        ninfo = self.get_ninfo()
+        old = self.get_stored_info()
+
+        csig = None
+        mtime = self.get_timestamp()
+        size = self.get_size()
+
+        max_drift = self.fs.max_drift
+        if max_drift > 0:
+            if (time.time() - mtime) > max_drift:
+                try:
+                    n = old.ninfo
+                    if n.timestamp and n.csig and n.timestamp == mtime:
+                        csig = n.csig
+                except AttributeError:
+                    pass
+        elif max_drift == 0:
+            try:
+                csig = old.ninfo.csig
+            except AttributeError:
+                pass
+
+        if csig:
+            ninfo.csig = csig
+
+        ninfo.timestamp = mtime
+        ninfo.size = size
+
+        if not self.has_builder():
+            # This is a source file, but it might have been a target file
+            # in another build that included more of the DAG.  Copy
+            # any build information that's stored in the .sconsign file
+            # into our binfo object so it doesn't get lost.
+            self.get_binfo().__dict__.update(old.binfo.__dict__)
+
+        self.store_info()
+
+    def find_src_builder(self):
+        if self.rexists():
+            return None
+        scb = self.dir.src_builder()
+        if scb is _null:
+            if diskcheck_sccs(self.dir, self.name):
+                scb = get_DefaultSCCSBuilder()
+            elif diskcheck_rcs(self.dir, self.name):
+                scb = get_DefaultRCSBuilder()
+            else:
+                scb = None
+        if scb is not None:
+            try:
+                b = self.builder
+            except AttributeError:
+                b = None
+            if b is None:
+                self.builder_set(scb)
+        return scb
+
     def has_src_builder(self):
         """Return whether this Node has a source builder or not.
 
@@ -2051,20 +2317,7 @@ class File(Base):
         try:
             scb = self.sbuilder
         except AttributeError:
-            if self.rexists():
-                scb = None
-            else:
-                scb = self.dir.src_builder()
-                if scb is _null:
-                    if diskcheck_sccs(self.dir, self.name):
-                        scb = get_DefaultSCCSBuilder()
-                    elif diskcheck_rcs(self.dir, self.name):
-                        scb = get_DefaultRCSBuilder()
-                    else:
-                        scb = None
-                if scb is not None:
-                    self.builder_set(scb)
-            self.sbuilder = scb
+            scb = self.sbuilder = self.find_src_builder()
         return not scb is None
 
     def alter_targets(self):
@@ -2074,13 +2327,18 @@ class File(Base):
             return [], None
         return self.fs.build_dir_target_climb(self, self.dir, [self.name])
 
-    def is_pseudo_derived(self):
-        return self.has_src_builder()
-
     def _rmv_existing(self):
         self.clear_memoized_values()
         Unlink(self, [], None)
 
+    #
+    # Taskmaster interface subsystem
+    #
+
+    def make_ready(self):
+        self.has_src_builder()
+        self.get_binfo()
+
     def prepare(self):
         """Prepare for this file to be created."""
         SCons.Node.Node.prepare(self)
@@ -2096,6 +2354,10 @@ class File(Base):
                     desc = "No drive `%s' for target `%s'." % (drive, self)
                     raise SCons.Errors.StopError, desc
 
+    #
+    #
+    #
+
     def remove(self):
         """Remove this file."""
         if self.exists() or self.islink():
@@ -2156,7 +2418,7 @@ class File(Base):
     # SIGNATURE SUBSYSTEM
     #
 
-    def get_csig(self, calc=None):
+    def get_csig(self):
         """
         Generate a node's content signature, the digested signature
         of its content.
@@ -2165,74 +2427,101 @@ class File(Base):
         cache - alternate node to use for the signature cache
         returns - the content signature
         """
+        ninfo = self.get_ninfo()
         try:
-            return self.binfo.ninfo.csig
+            return ninfo.csig
         except AttributeError:
             pass
 
-        if calc is None:
-            calc = self.calculator()
+        try:
+            contents = self.get_contents()
+        except IOError:
+            # This can happen if there's actually a directory on-disk,
+            # 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
+        else:
+            csig = SCons.Util.MD5signature(contents)
 
-        max_drift = self.fs.max_drift
-        mtime = self.get_timestamp()
-        use_stored = max_drift >= 0 and (time.time() - mtime) > max_drift
+        ninfo.csig = csig
 
-        csig = None
-        if use_stored:
-            old = self.get_stored_info().ninfo
+        return csig
+
+    #
+    # DECISION SUBSYSTEM
+    #
+
+    def builder_set(self, builder):
+        SCons.Node.Node.builder_set(self, builder)
+        self.changed_since_last_build = self.decide_target
+
+    def changed_content(self, target, prev_ni):
+        cur_csig = self.get_csig()
+        try:
+            return cur_csig != prev_ni.csig
+        except AttributeError:
+            return 1
+
+    def changed_state(self, target, prev_ni):
+        return (self.state != SCons.Node.up_to_date)
+
+    def changed_timestamp_then_content(self, target, prev_ni):
+        if not self.changed_timestamp_match(target, prev_ni):
             try:
-                if old.timestamp and old.csig and old.timestamp == mtime:
-                    csig = old.csig
+                self.get_ninfo().csig = prev_ni.csig
             except AttributeError:
                 pass
-        if csig is None:
-            csig = calc.module.signature(self)
+            return False
+        return self.changed_content(target, prev_ni)
 
-        binfo = self.get_binfo()
-        ninfo = binfo.ninfo
-        ninfo.csig = csig
-        ninfo.update(self)
+    def changed_timestamp_newer(self, target, prev_ni):
+        try:
+            return self.get_timestamp() > target.get_timestamp()
+        except AttributeError:
+            return 1
+
+    def changed_timestamp_match(self, target, prev_ni):
+        try:
+            return self.get_timestamp() != prev_ni.timestamp
+        except AttributeError:
+            return 1
 
-        if use_stored:
-            self.store_info(binfo)
+    def decide_source(self, target, prev_ni):
+        return target.get_build_env().decide_source(self, target, prev_ni)
 
-        return csig
+    def decide_target(self, target, prev_ni):
+        return target.get_build_env().decide_target(self, target, prev_ni)
 
-    #
-    #
-    #
+    # Initialize this Node's decider function to decide_source() because
+    # every file is a source file until it has a Builder attached...
+    changed_since_last_build = decide_source
 
-    def is_up_to_date(self, node=None, bi=None):
-        """Returns if the node is up-to-date with respect to stored
-        BuildInfo.  The default is to compare it against our own
-        previously stored BuildInfo, but the stored BuildInfo from another
-        Node (typically one in a Repository) can be used instead."""
-        if bi is None:
-            if node is None:
-                node = self
-            bi = node.get_stored_info()
-        new = self.get_binfo()
-        return new == bi
-
-    def current(self, calc=None):
-        self.binfo = self.gen_binfo(calc)
-        return self._cur2()
-    def _cur2(self):
+    def is_up_to_date(self):
+        T = 0
+        if T: Trace('is_up_to_date(%s):' % self)
         if not self.exists():
+            if T: Trace(' not self.exists():')
             # The file doesn't exist locally...
             r = self.rfile()
             if r != self:
                 # ...but there is one in a Repository...
-                if self.is_up_to_date(r):
+                if not self.changed(r):
+                    if T: Trace(' changed(%s):' % r)
                     # ...and it's even up-to-date...
                     if self._local:
                         # ...and they'd like a local copy.
                         LocalCopy(self, r, None)
-                        self.store_info(self.get_binfo())
+                        self.store_info()
+                    if T: Trace(' 1\n')
                     return 1
+            self.changed()
+            if T: Trace(' None\n')
             return None
         else:
-            return self.is_up_to_date()
+            r = self.changed()
+            if T: Trace(' self.exists():  %s\n' % r)
+            return not r
 
     memoizer_counters.append(SCons.Memoize.CountValue('rfile'))
 
@@ -2258,19 +2547,49 @@ class File(Base):
     def rstr(self):
         return str(self.rfile())
 
+    def get_cachedir_csig(self):
+        """
+        Fetch a Node's content signature for purposes of computing
+        another Node's cachesig.
+
+        This is a wrapper around the normal get_csig() method that handles
+        the somewhat obscure case of using CacheDir with the -n option.
+        Any files that don't exist would normally be "built" by fetching
+        them from the cache, but the normal get_csig() method will try
+        to open up the local file, which doesn't exist because the -n
+        option meant we didn't actually pull the file from cachedir.
+        But since the file *does* actually exist in the cachedir, we
+        can use its contents for the csig.
+        """
+        try:
+            return self.cachedir_csig
+        except AttributeError:
+            pass
+
+        cachedir, cachefile = self.get_build_env().get_CacheDir().cachepath(self)
+        if not self.exists() and cachefile and os.path.exists(cachefile):
+            contents = open(cachefile, 'rb').read()
+            self.cachedir_csig = SCons.Util.MD5signature(contents)
+        else:
+            self.cachedir_csig = self.get_csig()
+        return self.cachedir_csig
+
     def get_cachedir_bsig(self):
-        import SCons.Sig.MD5
-        ninfo = self.get_binfo().ninfo
-        if not hasattr(ninfo, 'bsig'):
-            import SCons.Errors
-            raise SCons.Errors.InternalError, "cachepath(%s) found no bsig" % self.path
-        elif ninfo.bsig is None:
-            import SCons.Errors
-            raise SCons.Errors.InternalError, "cachepath(%s) found a bsig of None" % self.path
+        try:
+            return self.cachesig
+        except AttributeError:
+            pass
+
         # Add the path to the cache signature, because multiple
         # targets built by the same action will all have the same
         # build signature, and we have to differentiate them somehow.
-        return SCons.Sig.MD5.collect([ninfo.bsig, self.path])
+        children =  self.children()
+        sigs = map(lambda n: n.get_cachedir_csig(), children)
+        executor = self.get_executor()
+        sigs.append(SCons.Util.MD5signature(executor.get_contents()))
+        sigs.append(self.path)
+        self.cachesig = SCons.Util.MD5collect(sigs)
+        return self.cachesig
 
 default_fs = None
 
index ed8d6ec7eaa2b4e9279ebcc037ab302ea9f810ad..225226deb66beff4eadfa2aa44a539a16868b1f7 100644 (file)
@@ -35,6 +35,7 @@ import stat
 
 import SCons.Errors
 import SCons.Node.FS
+import SCons.Util
 import SCons.Warnings
 
 built_it = None
@@ -80,15 +81,21 @@ class Environment:
         pass
 
 class Action:
-    def __call__(self, targets, sources, env, errfunc, **kw):
+    def __call__(self, targets, sources, env, **kw):
         global built_it
         if kw.get('execute', 1):
             built_it = 1
         return 0
     def show(self, string):
         pass
+    def get_contents(self, target, source, env):
+        return ""
+    def genstring(self, target, source, env):
+        return ""
     def strfunction(self, targets, sources, env):
         return ""
+    def get_implicit_deps(self, target, source, env):
+        return []
 
 class Builder:
     def __init__(self, factory, action=Action()):
@@ -296,7 +303,7 @@ class BuildDirTestCase(unittest.TestCase):
         class MkdirAction(Action):
             def __init__(self, dir_made):
                 self.dir_made = dir_made
-            def __call__(self, target, source, env, errfunc):
+            def __call__(self, target, source, env):
                 self.dir_made.extend(target)
 
         save_Link = SCons.Node.FS.Link
@@ -697,49 +704,38 @@ class FileNodeInfoTestCase(_tempdirTestCase):
         """Test FileNodeInfo initialization"""
         fff = self.fs.File('fff')
         ni = SCons.Node.FS.FileNodeInfo(fff)
-        assert hasattr(ni, 'timestamp')
-        assert hasattr(ni, 'size')
-
-    def test___cmp__(self):
-        """Test comparing File.NodeInfo objects"""
-        f1 = self.fs.File('f1')
-        f2 = self.fs.File('f2')
+        assert isinstance(ni, SCons.Node.FS.FileNodeInfo)
 
-        ni1 = SCons.Node.FS.FileNodeInfo(f1)
-        ni2 = SCons.Node.FS.FileNodeInfo(f2)
+    def test_update(self):
+        """Test updating a File.NodeInfo with on-disk information"""
+        test = self.test
+        fff = self.fs.File('fff')
 
-        msg = "cmp(%s, %s) returned %s, not %s"
+        ni = SCons.Node.FS.FileNodeInfo(fff)
 
-        c = cmp(ni1, ni2)
-        assert c == 1, msg % (ni1, ni2, c, 1)
+        test.write('fff', "fff\n")
 
-        ni1.bsig = 777
-        c = cmp(ni1, ni2)
-        assert c == 1, msg % (ni1.bsig, ni2, c, 1)
+        st = os.stat('fff')
 
-        ni2.bsig = 666
-        c = cmp(ni1, ni2)
-        assert c == 1, msg % (ni1.bsig, ni2.bsig, c, 1)
+        ni.update(fff)
 
-        ni2.bsig = 777
-        c = cmp(ni1, ni2)
-        assert c == 0, msg % (ni1.bsig, ni2.bsig, c, 0)
+        assert hasattr(ni, 'timestamp')
+        assert hasattr(ni, 'size')
 
-        ni2.bsig = 888
-        c = cmp(ni1, ni2)
-        assert c == -1, msg % (ni1.bsig, ni2.bsig, c, -1)
+        ni.timestamp = 0
+        ni.size = 0
 
-    def test_update(self):
-        """Test updating a File.NodeInfo with on-disk information"""
-        test = self.test
-        fff = self.fs.File('fff')
+        ni.update(fff)
 
-        ni = SCons.Node.FS.FileNodeInfo(fff)
+        mtime = st[stat.ST_MTIME]
+        assert ni.timestamp == mtime, (ni.timestamp, mtime)
+        size = st[stat.ST_SIZE]
+        assert ni.size == size, (ni.size, size)
 
         import time
         time.sleep(2)
 
-        test.write('fff', "fff\n")
+        test.write('fff', "fff longer size, different time stamp\n")
 
         st = os.stat('fff')
 
@@ -748,22 +744,22 @@ class FileNodeInfoTestCase(_tempdirTestCase):
         size = st[stat.ST_SIZE]
         assert ni.size != size, (ni.size, size)
 
-        fff.clear()
-        ni.update(fff)
+        #fff.clear()
+        #ni.update(fff)
 
-        st = os.stat('fff')
+        #st = os.stat('fff')
 
-        mtime = st[stat.ST_MTIME]
-        assert ni.timestamp == mtime, (ni.timestamp, mtime)
-        size = st[stat.ST_SIZE]
-        assert ni.size == size, (ni.size, size)
+        #mtime = st[stat.ST_MTIME]
+        #assert ni.timestamp == mtime, (ni.timestamp, mtime)
+        #size = st[stat.ST_SIZE]
+        #assert ni.size == size, (ni.size, size)
 
 class FileBuildInfoTestCase(_tempdirTestCase):
     def test___init__(self):
         """Test File.BuildInfo initialization"""
         fff = self.fs.File('fff')
-        bi = SCons.Node.FS.BuildInfo(fff)
-        assert bi.node is fff, bi.node
+        bi = SCons.Node.FS.FileBuildInfo(fff)
+        assert bi, bi
 
     def test_convert_to_sconsign(self):
         """Test converting to .sconsign file format"""
@@ -786,14 +782,14 @@ class FileBuildInfoTestCase(_tempdirTestCase):
     def test_format(self):
         """Test the format() method"""
         f1 = self.fs.File('f1')
-        bi1 = SCons.Node.FS.BuildInfo(f1)
+        bi1 = SCons.Node.FS.FileBuildInfo(f1)
 
         s1sig = SCons.Node.FS.FileNodeInfo(self.fs.File('n1'))
-        s1sig.a = 1
+        s1sig.csig = 1
         d1sig = SCons.Node.FS.FileNodeInfo(self.fs.File('n2'))
-        d1sig.a = 2
+        d1sig.timestamp = 2
         i1sig = SCons.Node.FS.FileNodeInfo(self.fs.File('n3'))
-        i1sig.a = 3
+        i1sig.size = 3
 
         bi1.bsources = [self.fs.File('s1')]
         bi1.bdepends = [self.fs.File('d1')]
@@ -801,17 +797,19 @@ class FileBuildInfoTestCase(_tempdirTestCase):
         bi1.bsourcesigs = [s1sig]
         bi1.bdependsigs = [d1sig]
         bi1.bimplicitsigs = [i1sig]
+        bi1.bact = 'action'
+        bi1.bactsig = 'actionsig'
 
         expect_lines = [
-            'None 0',
-            's1: 1 None 0',
-            'd1: 2 None 0',
-            'i1: 3 None 0',
+            's1: 1 None None',
+            'd1: None 2 None',
+            'i1: None None 3',
+            'actionsig [action]',
         ]
 
         expect = string.join(expect_lines, '\n')
         format = bi1.format()
-        assert format == expect, (repr(format), repr(expect))
+        assert format == expect, (repr(expect), repr(format))
 
 class FSTestCase(_tempdirTestCase):
     def test_runTest(self):
@@ -890,7 +888,7 @@ class FSTestCase(_tempdirTestCase):
         except TypeError:
             pass
         else:
-            assert 0
+            raise Exception, "did not catch expected TypeError"
 
         assert x1.Entry(x4) == x4
         try:
@@ -898,7 +896,7 @@ class FSTestCase(_tempdirTestCase):
         except TypeError:
             pass
         else:
-            assert 0
+            raise Exception, "did not catch expected TypeError"
 
         x6 = x1.File(x6)
         assert isinstance(x6, SCons.Node.FS.File)
@@ -989,16 +987,26 @@ class FSTestCase(_tempdirTestCase):
             except:
                 raise
 
-            # Test that just specifying the drive works to identify
-            # its root directory.
-            p = os.path.abspath(test.workpath('root_file'))
+        # Test that just specifying the drive works to identify
+        # its root directory.
+        p = os.path.abspath(test.workpath('root_file'))
+        drive, path = os.path.splitdrive(p)
+        if drive:
+            # The assert below probably isn't correct for the general
+            # case, but it works for Windows, which covers a lot
+            # of ground...
+            dir = fs.Dir(drive)
+            assert str(dir) == drive + os.sep, str(dir)
+
+            # Make sure that lookups with and without the drive are
+            # equivalent.
+            p = os.path.abspath(test.workpath('some/file'))
             drive, path = os.path.splitdrive(p)
-            if drive:
-                # The assert below probably isn't correct for the general
-                # case, but it works for Windows, which covers a lot
-                # of ground...
-                dir = fs.Dir(drive)
-                assert str(dir) == drive + os.sep, str(dir)
+
+            e1 = fs.Entry(p)
+            e2 = fs.Entry(path)
+            assert e1 is e2, (e1, e2)
+            assert str(e1) is str(e2), (str(e1), str(e2))
 
         # Test for a bug in 0.04 that did not like looking up
         # dirs with a trailing slash on Windows.
@@ -1179,7 +1187,7 @@ class FSTestCase(_tempdirTestCase):
             except SCons.Errors.UserError:
                 pass
             else:
-                raise TestFailed, "did not catch expected UserError"
+                raise Exception, "did not catch expected UserError"
 
         nonexistent(fs.Entry, 'nonexistent')
         nonexistent(fs.Entry, 'nonexistent/foo')
@@ -1213,15 +1221,15 @@ class FSTestCase(_tempdirTestCase):
         f = fs.File('f_local')
         assert f._local == 0
 
-        #XXX test current() for directories
+        #XXX test_is_up_to_date() for directories
 
-        #XXX test sconsign() for directories
+        #XXX test_sconsign() for directories
 
-        #XXX test set_signature() for directories
+        #XXX test_set_signature() for directories
 
-        #XXX test build() for directories
+        #XXX test_build() for directories
 
-        #XXX test root()
+        #XXX test_root()
 
         # test Entry.get_contents()
         e = fs.Entry('does_not_exist')
@@ -1318,9 +1326,7 @@ class FSTestCase(_tempdirTestCase):
             exc_caught = 1
         assert exc_caught, "Should have caught a TypeError"
 
-        # XXX test calc_signature()
-
-        # XXX test current()
+        # XXX test_is_up_to_date()
 
         d = fs.Dir('dir')
         r = d.remove()
@@ -1407,8 +1413,7 @@ class FSTestCase(_tempdirTestCase):
 
         subdir = fs.Dir('subdir')
         fs.chdir(subdir, change_os_dir=1)
-        path, dir = fs._transformPath('#build/file', subdir)
-        self.fs._doLookup(SCons.Node.FS.File, path, dir)
+        self.fs._lookup('#build/file', subdir, SCons.Node.FS.File)
 
     def test_above_root(self):
         """Testing looking up a path above the root directory"""
@@ -1421,7 +1426,13 @@ class FSTestCase(_tempdirTestCase):
         above_path = apply(os.path.join, ['..']*len(dirs) + ['above'])
         above = d2.Dir(above_path)
 
-    def test_rel_path(self):
+    # 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):
         """Test the rel_path() method"""
         test = self.test
         fs = self.fs
@@ -1570,6 +1581,27 @@ class DirTestCase(_tempdirTestCase):
                         os.path.join('ddd', 'f2'),
                         os.path.join('ddd', 'f3')], kids
 
+    def test_implicit_re_scans(self):
+        """Test that adding entries causes a directory to be re-scanned
+        """
+
+        fs = self.fs
+
+        dir = fs.Dir('ddd')
+
+        fs.File(os.path.join('ddd', 'f1'))
+        dir.scan()
+        kids = map(lambda x: x.path, dir.children())
+        kids.sort()
+        assert kids == [os.path.join('ddd', 'f1')], kids
+
+        fs.File(os.path.join('ddd', 'f2'))
+        dir.scan()
+        kids = map(lambda x: x.path, dir.children())
+        kids.sort()
+        assert kids == [os.path.join('ddd', 'f1'),
+                        os.path.join('ddd', 'f2')], kids
+
     def test_entry_exists_on_disk(self):
         """Test the Dir.entry_exists_on_disk() method
         """
@@ -1696,15 +1728,11 @@ class DirTestCase(_tempdirTestCase):
 
         derived_f = src0.File('derived-f')
         derived_f.is_derived = return_true
-        pseudo_f = src0.File('pseudo-f')
-        pseudo_f.is_pseudo_derived = return_true
         exists_f = src0.File('exists-f')
         exists_f.exists = return_true
 
         derived_e = src0.Entry('derived-e')
         derived_e.is_derived = return_true
-        pseudo_e = src0.Entry('pseudo-e')
-        pseudo_e.is_pseudo_derived = return_true
         exists_e = src0.Entry('exists-e')
         exists_e.exists = return_true
 
@@ -1719,8 +1747,6 @@ class DirTestCase(_tempdirTestCase):
 
         n = src0.srcdir_find_file('derived-f')
         check(n, ['src0/derived-f', 'src0'])
-        n = src0.srcdir_find_file('pseudo-f')
-        check(n, ['src0/pseudo-f', 'src0'])
         n = src0.srcdir_find_file('exists-f')
         check(n, ['src0/exists-f', 'src0'])
         n = src0.srcdir_find_file('on-disk-f1')
@@ -1728,8 +1754,6 @@ class DirTestCase(_tempdirTestCase):
 
         n = src0.srcdir_find_file('derived-e')
         check(n, ['src0/derived-e', 'src0'])
-        n = src0.srcdir_find_file('pseudo-e')
-        check(n, ['src0/pseudo-e', 'src0'])
         n = src0.srcdir_find_file('exists-e')
         check(n, ['src0/exists-e', 'src0'])
         n = src0.srcdir_find_file('on-disk-e1')
@@ -1741,8 +1765,6 @@ class DirTestCase(_tempdirTestCase):
 
         n = bld0.srcdir_find_file('derived-f')
         check(n, ['src0/derived-f', 'bld0'])
-        n = bld0.srcdir_find_file('pseudo-f')
-        check(n, ['src0/pseudo-f', 'bld0'])
         n = bld0.srcdir_find_file('exists-f')
         check(n, ['src0/exists-f', 'bld0'])
         n = bld0.srcdir_find_file('on-disk-f2')
@@ -1750,8 +1772,6 @@ class DirTestCase(_tempdirTestCase):
 
         n = bld0.srcdir_find_file('derived-e')
         check(n, ['src0/derived-e', 'bld0'])
-        n = bld0.srcdir_find_file('pseudo-e')
-        check(n, ['src0/pseudo-e', 'bld0'])
         n = bld0.srcdir_find_file('exists-e')
         check(n, ['src0/exists-e', 'bld0'])
         n = bld0.srcdir_find_file('on-disk-e2')
@@ -1769,15 +1789,11 @@ class DirTestCase(_tempdirTestCase):
 
         derived_f = src1.File('derived-f')
         derived_f.is_derived = return_true
-        pseudo_f = src1.File('pseudo-f')
-        pseudo_f.is_pseudo_derived = return_true
         exists_f = src1.File('exists-f')
         exists_f.exists = return_true
 
         derived_e = src1.Entry('derived-e')
         derived_e.is_derived = return_true
-        pseudo_e = src1.Entry('pseudo-e')
-        pseudo_e.is_pseudo_derived = return_true
         exists_e = src1.Entry('exists-e')
         exists_e.exists = return_true
 
@@ -1787,8 +1803,6 @@ class DirTestCase(_tempdirTestCase):
 
         n = src1.srcdir_find_file('derived-f')
         check(n, ['src1/derived-f', 'src1'])
-        n = src1.srcdir_find_file('pseudo-f')
-        check(n, ['src1/pseudo-f', 'src1'])
         n = src1.srcdir_find_file('exists-f')
         check(n, ['src1/exists-f', 'src1'])
         n = src1.srcdir_find_file('on-disk-f1')
@@ -1796,8 +1810,6 @@ class DirTestCase(_tempdirTestCase):
 
         n = src1.srcdir_find_file('derived-e')
         check(n, ['src1/derived-e', 'src1'])
-        n = src1.srcdir_find_file('pseudo-e')
-        check(n, ['src1/pseudo-e', 'src1'])
         n = src1.srcdir_find_file('exists-e')
         check(n, ['src1/exists-e', 'src1'])
         n = src1.srcdir_find_file('on-disk-e1')
@@ -1809,8 +1821,6 @@ class DirTestCase(_tempdirTestCase):
 
         n = bld1.srcdir_find_file('derived-f')
         check(n, ['bld1/derived-f', 'src1'])
-        n = bld1.srcdir_find_file('pseudo-f')
-        check(n, ['bld1/pseudo-f', 'src1'])
         n = bld1.srcdir_find_file('exists-f')
         check(n, ['bld1/exists-f', 'src1'])
         n = bld1.srcdir_find_file('on-disk-f2')
@@ -1818,8 +1828,6 @@ class DirTestCase(_tempdirTestCase):
 
         n = bld1.srcdir_find_file('derived-e')
         check(n, ['bld1/derived-e', 'src1'])
-        n = bld1.srcdir_find_file('pseudo-e')
-        check(n, ['bld1/pseudo-e', 'src1'])
         n = bld1.srcdir_find_file('exists-e')
         check(n, ['bld1/exists-e', 'src1'])
         n = bld1.srcdir_find_file('on-disk-e2')
@@ -1917,19 +1925,6 @@ class EntryTestCase(_tempdirTestCase):
         test.subdir('e5d')
         test.write('e5f', "e5f\n")
 
-        e5f = fs.Entry('e5f')
-        sig = e5f.calc_signature(MyCalc(666))
-        assert e5f.__class__ is SCons.Node.FS.File, e5f.__class__
-        # This node has no builder, so it just calculates the
-        # signature once: the source content signature.
-        assert sig == 888, sig
-
-        e5n = fs.Entry('e5n')
-        sig = e5n.calc_signature(MyCalc(777))
-        assert e5n.__class__ is SCons.Node.FS.File, e5n.__class__
-        # Doesn't exist, no sources, and no builder: no sig
-        assert sig is None, sig
-
     def test_Entry_Entry_lookup(self):
         """Test looking up an Entry within another Entry"""
         self.fs.Entry('#topdir')
@@ -2299,9 +2294,7 @@ class RepositoryTestCase(_tempdirTestCase):
         finally:
             test.unlink(["rep3", "contents"])
 
-    #def test calc_signature(self):
-
-    #def test current(self):
+    #def test_is_up_to_date(self):
 
 
 
@@ -2402,8 +2395,7 @@ class stored_infoTestCase(unittest.TestCase):
         d = fs.Dir('sub')
         f = fs.File('file1', d)
         bi = f.get_stored_info()
-        assert bi.ninfo.timestamp == 0, bi.ninfo.timestamp
-        assert bi.ninfo.size == None, bi.ninfo.size
+        assert hasattr(bi, 'ninfo')
 
         class MySConsign:
             class Null:
@@ -2509,7 +2501,7 @@ class prepareTestCase(unittest.TestCase):
         class MkdirAction(Action):
             def __init__(self, dir_made):
                 self.dir_made = dir_made
-            def __call__(self, target, source, env, errfunc):
+            def __call__(self, target, source, env):
                 self.dir_made.extend(target)
 
         dir_made = []
@@ -2529,6 +2521,8 @@ class prepareTestCase(unittest.TestCase):
         dir = fs.Dir("dir")
         dir.prepare()
 
+
+
 class SConstruct_dirTestCase(unittest.TestCase):
     def runTest(self):
         """Test setting the SConstruct directory"""
@@ -2537,6 +2531,19 @@ class SConstruct_dirTestCase(unittest.TestCase):
         fs.set_SConstruct_dir(fs.Dir('xxx'))
         assert fs.SConstruct_dir.path == 'xxx'
 
+
+
+class CacheDirTestCase(unittest.TestCase):
+
+    def test_get_cachedir_csig(self):
+        fs = SCons.Node.FS.FS()
+
+        f9 = fs.File('f9')
+        r = f9.get_cachedir_csig()
+        assert r == 'd41d8cd98f00b204e9800998ecf8427e', r
+
+
+
 class clearTestCase(unittest.TestCase):
     def runTest(self):
         """Test clearing FS nodes of cached data."""
@@ -2584,6 +2591,8 @@ class clearTestCase(unittest.TestCase):
         assert not f.rexists()
         assert str(f) == test.workpath('f'), str(f)
 
+
+
 class disambiguateTestCase(unittest.TestCase):
     def runTest(self):
         """Test calling the disambiguate() method."""
@@ -2659,6 +2668,8 @@ class postprocessTestCase(unittest.TestCase):
         f = fs.File('f')
         f.postprocess()
 
+
+
 class SpecialAttrTestCase(unittest.TestCase):
     def runTest(self):
         """Test special attributes of file nodes."""
@@ -2744,7 +2755,7 @@ class SpecialAttrTestCase(unittest.TestCase):
         assert s == os.path.normpath('baz/bar/baz.blat'), s
         assert f.srcpath.is_literal(), f.srcpath
         g = f.srcpath.get()
-        assert isinstance(g, SCons.Node.FS.Entry), g.__class__
+        assert isinstance(g, SCons.Node.FS.File), g.__class__
 
         s = str(f.srcdir)
         assert s == os.path.normpath('baz/bar'), s
@@ -2814,6 +2825,8 @@ class SpecialAttrTestCase(unittest.TestCase):
             caught = 1
         assert caught, "did not catch expected AttributeError"
 
+
+
 class SaveStringsTestCase(unittest.TestCase):
     def runTest(self):
         """Test caching string values of nodes."""
@@ -2873,6 +2886,8 @@ class SaveStringsTestCase(unittest.TestCase):
         expect = map(os.path.normpath, ['src/f', 'd1/f', 'd0/b', 'd1/b'])
         assert s == expect, 'node str() not cached: %s'%s
 
+
+
 if __name__ == "__main__":
     suite = unittest.TestSuite()
     suite.addTest(BuildDirTestCase())
@@ -2889,6 +2904,7 @@ if __name__ == "__main__":
     suite.addTest(SaveStringsTestCase())
     tclasses = [
         BaseTestCase,
+        CacheDirTestCase,
         DirTestCase,
         DirBuildInfoTestCase,
         DirNodeInfoTestCase,
index 09ab5c780da5a281840499d4cbd53c2b3b830f21..fe42035d163340957df5ed248e380fcbada43342 100644 (file)
@@ -67,7 +67,7 @@ class MyAction(MyActionBase):
     def __init__(self):
         self.order = 0
 
-    def __call__(self, target, source, env, errfunc):
+    def __call__(self, target, source, env):
         global built_it, built_target, built_source, built_args, built_order
         built_it = 1
         built_target = target
@@ -77,6 +77,9 @@ class MyAction(MyActionBase):
         self.order = built_order
         return 0
 
+    def get_implicit_deps(self, target, source, env):
+        return []
+
 class MyExecutor:
     def __init__(self, env=None, targets=[], sources=[]):
         self.env = env
@@ -104,9 +107,9 @@ class MyExecutor:
 class MyListAction(MyActionBase):
     def __init__(self, list):
         self.list = list
-    def __call__(self, target, source, env, errfunc):
+    def __call__(self, target, source, env):
         for A in self.list:
-            A(target, source, env, errfunc)
+            A(target, source, env)
 
 class Environment:
     def __init__(self, **kw):
@@ -122,8 +125,6 @@ class Environment:
         return apply(Environment, (), d)
     def _update(self, dict):
         self._dict.update(dict)
-    def get_calculator(self):
-        return SCons.Sig.default_calc
     def get_factory(self, factory):
         return factory or MyNode
     def get_scanner(self, scanner_key):
@@ -217,27 +218,6 @@ class Calculator:
 
 class NodeInfoBaseTestCase(unittest.TestCase):
 
-    def test_prepare_dependencies(self):
-        """Test that we have a prepare_dependencies() method"""
-        ni = SCons.Node.NodeInfoBase(SCons.Node.Node())
-        ni.prepare_dependencies()
-
-    def test___cmp__(self):
-        """Test comparing NodeInfoBase objects"""
-        ni1 = SCons.Node.NodeInfoBase(SCons.Node.Node())
-        ni2 = SCons.Node.NodeInfoBase(SCons.Node.Node())
-
-        assert ni1 == ni2, "%s != %s" % (ni1.__dict__, ni2.__dict__)
-
-        ni1.foo = 777
-        assert ni1 != ni2, "%s == %s" % (ni1.__dict__, ni2.__dict__)
-
-        ni2.foo = 888
-        assert ni1 != ni2, "%s == %s" % (ni1.__dict__, ni2.__dict__)
-
-        ni1.foo = 888
-        assert ni1 == ni2, "%s != %s" % (ni1.__dict__, ni2.__dict__)
-
     def test_merge(self):
         """Test merging NodeInfoBase attributes"""
         ni1 = SCons.Node.NodeInfoBase(SCons.Node.Node())
@@ -250,7 +230,8 @@ class NodeInfoBaseTestCase(unittest.TestCase):
         ni2.a3 = 333
 
         ni1.merge(ni2)
-        assert ni1.__dict__ == {'a1':1, 'a2':222, 'a3':333}, ni1.__dict__
+        expect = {'a1':1, 'a2':222, 'a3':333, '_version_id':1}
+        assert ni1.__dict__ == expect, ni1.__dict__
 
     def test_update(self):
         """Test the update() method"""
@@ -265,12 +246,12 @@ class NodeInfoBaseTestCase(unittest.TestCase):
         ni1.zzz = 'z'
 
         f = ni1.format()
-        assert f == 'x y z', f
+        assert f == ['1', 'x', 'y', 'z'], f
 
         ni1.field_list = ['xxx', 'zzz', 'aaa']
 
         f = ni1.format()
-        assert f == 'x z None', f
+        assert f == ['x', 'z', 'None'], f
 
 
 
@@ -278,38 +259,16 @@ class BuildInfoBaseTestCase(unittest.TestCase):
 
     def test___init__(self):
         """Test BuildInfoBase initialization"""
-        bi = SCons.Node.BuildInfoBase(SCons.Node.Node())
-        assert hasattr(bi, 'ninfo')
-
-        class MyNode(SCons.Node.Node):
-            def NodeInfo(self, node):
-                return 'ninfo initialization'
-        bi = SCons.Node.BuildInfoBase(MyNode())
-        assert bi.ninfo == 'ninfo initialization', bi.ninfo
-
-    def test___cmp__(self):
-        """Test comparing BuildInfoBase objects"""
-        bi1 = SCons.Node.BuildInfoBase(SCons.Node.Node())
-        bi2 = SCons.Node.BuildInfoBase(SCons.Node.Node())
-
-        assert bi1 == bi2, "%s != %s" % (bi1.__dict__, bi2.__dict__)
-
-        bi1.ninfo.foo = 777
-        assert bi1 != bi2, "%s == %s" % (bi1.__dict__, bi2.__dict__)
-
-        bi2.ninfo.foo = 888
-        assert bi1 != bi2, "%s == %s" % (bi1.__dict__, bi2.__dict__)
-
-        bi1.ninfo.foo = 888
-        assert bi1 == bi2, "%s != %s" % (bi1.__dict__, bi2.__dict__)
-
-        bi1.foo = 999
-        assert bi1 == bi2, "%s != %s" % (bi1.__dict__, bi2.__dict__)
+        n = SCons.Node.Node()
+        bi = SCons.Node.BuildInfoBase(n)
+        assert bi
 
     def test_merge(self):
         """Test merging BuildInfoBase attributes"""
-        bi1 = SCons.Node.BuildInfoBase(SCons.Node.Node())
-        bi2 = SCons.Node.BuildInfoBase(SCons.Node.Node())
+        n1 = SCons.Node.Node()
+        bi1 = SCons.Node.BuildInfoBase(n1)
+        n2 = SCons.Node.Node()
+        bi2 = SCons.Node.BuildInfoBase(n2)
 
         bi1.a1 = 1
         bi1.a2 = 2
@@ -317,19 +276,10 @@ class BuildInfoBaseTestCase(unittest.TestCase):
         bi2.a2 = 222
         bi2.a3 = 333
 
-        bi1.ninfo.a4 = 4
-        bi1.ninfo.a5 = 5
-        bi2.ninfo.a5 = 555
-        bi2.ninfo.a6 = 666
-
         bi1.merge(bi2)
         assert bi1.a1 == 1, bi1.a1
         assert bi1.a2 == 222, bi1.a2
         assert bi1.a3 == 333, bi1.a3
-        assert bi1.ninfo.a4 == 4, bi1.ninfo.a4
-        assert bi1.ninfo.a5 == 555, bi1.ninfo.a5
-        assert bi1.ninfo.a6 == 666, bi1.ninfo.a6
-
 
 
 class NodeTestCase(unittest.TestCase):
@@ -469,13 +419,18 @@ class NodeTestCase(unittest.TestCase):
 
     def test_built(self):
         """Test the built() method"""
+        class SubNodeInfo(SCons.Node.NodeInfoBase):
+            def update(self, node):
+                self.updated = 1
         class SubNode(SCons.Node.Node):
             def clear(self):
                 self.cleared = 1
 
         n = SubNode()
+        n.ninfo = SubNodeInfo(n)
         n.built()
         assert n.cleared, n.cleared
+        assert n.ninfo.updated, n.ninfo.cleared
 
     def test_retrieve_from_cache(self):
         """Test the base retrieve_from_cache() method"""
@@ -560,11 +515,11 @@ class NodeTestCase(unittest.TestCase):
         assert t == [], t
         assert m == None, m
 
-    def test_current(self):
-        """Test the default current() method
+    def test_is_up_to_date(self):
+        """Test the default is_up_to_date() method
         """
         node = SCons.Node.Node()
-        assert node.current() is None
+        assert node.is_up_to_date() is None
 
     def test_children_are_up_to_date(self):
         """Test the children_are_up_to_date() method used by subclasses
@@ -572,16 +527,14 @@ class NodeTestCase(unittest.TestCase):
         n1 = SCons.Node.Node()
         n2 = SCons.Node.Node()
 
-        calc = Calculator(111)
-
-        n1.add_source(n2)
-        assert n1.children_are_up_to_date(calc), "expected up to date"
+        n1.add_source([n2])
+        assert n1.children_are_up_to_date(), "expected up to date"
         n2.set_state(SCons.Node.executed)
-        assert not n1.children_are_up_to_date(calc), "expected not up to date"
+        assert not n1.children_are_up_to_date(), "expected not up to date"
         n2.set_state(SCons.Node.up_to_date)
-        assert n1.children_are_up_to_date(calc), "expected up to date"
+        assert n1.children_are_up_to_date(), "expected up to date"
         n1.always_build = 1
-        assert not n1.children_are_up_to_date(calc), "expected not up to date"
+        assert not n1.children_are_up_to_date(), "expected not up to date"
 
     def test_env_set(self):
         """Test setting a Node's Environment
@@ -599,23 +552,21 @@ class NodeTestCase(unittest.TestCase):
         a = node.builder.get_actions()
         assert isinstance(a[0], MyAction), a[0]
 
-    def test_get_bsig(self):
-        """Test generic build signature calculation
+    def test_get_csig(self):
+        """Test generic content signature calculation
         """
         node = SCons.Node.Node()
-        result = node.get_bsig(Calculator(222))
-        assert result == 222, result
-        result = node.get_bsig(Calculator(333))
-        assert result == 222, result
+        node.get_contents = lambda: 444
+        result = node.get_csig()
+        assert result == '550a141f12de6341fba65b0ad0433500', result
 
-    def test_get_csig(self):
-        """Test generic content signature calculation
+    def test_get_cachedir_csig(self):
+        """Test content signature calculation for CacheDir
         """
         node = SCons.Node.Node()
-        result = node.get_csig(Calculator(444))
-        assert result == 444, result
-        result = node.get_csig(Calculator(555))
-        assert result == 444, result
+        node.get_contents = lambda: 555
+        result = node.get_cachedir_csig()
+        assert result == '15de21c670ae7c3f6f3f1f37029303c9', result
 
     def test_get_binfo(self):
         """Test fetching/creating a build information structure
@@ -625,20 +576,15 @@ class NodeTestCase(unittest.TestCase):
         binfo = node.get_binfo()
         assert isinstance(binfo, SCons.Node.BuildInfoBase), binfo
 
-        node.binfo = 777
-        binfo = node.get_binfo()
-        assert binfo == 777, binfo
-
-    def test_gen_binfo(self):
-        """Test generating a build information structure
-        """
         node = SCons.Node.Node()
         d = SCons.Node.Node()
+        d.get_ninfo().csig = 777
         i = SCons.Node.Node()
+        i.get_ninfo().csig = 888
         node.depends = [d]
         node.implicit = [i]
-        node.gen_binfo(Calculator(666))
-        binfo = node.binfo
+
+        binfo = node.get_binfo()
         assert isinstance(binfo, SCons.Node.BuildInfoBase), binfo
         assert hasattr(binfo, 'bsources')
         assert hasattr(binfo, 'bsourcesigs')
@@ -646,7 +592,6 @@ class NodeTestCase(unittest.TestCase):
         assert hasattr(binfo, 'bdependsigs')
         assert binfo.bimplicit == [i]
         assert hasattr(binfo, 'bimplicitsigs')
-        assert binfo.ninfo.bsig == 1998, binfo.ninfo.bsig
 
     def test_explain(self):
         """Test explaining why a Node must be rebuilt
@@ -662,15 +607,21 @@ class NodeTestCase(unittest.TestCase):
 
         class testNode2(SCons.Node.Node):
             def __str__(self): return 'null_binfo'
+        class FS:
+            pass
         node = testNode2()
+        node.fs = FS()
+        node.fs.Top = SCons.Node.Node()
         result = node.explain()
         assert result == None, result
 
         def get_null_info():
-            class Null_BInfo:
-                def prepare_dependencies(self):
-                    pass
-            return Null_BInfo()
+            class Null_SConsignEntry:
+                class Null_BuildInfo:
+                    def prepare_dependencies(self):
+                        pass
+                binfo = Null_BuildInfo()
+            return Null_SConsignEntry()
 
         node.get_stored_info = get_null_info
         #see above: node.__str__ = lambda: 'null_binfo'
@@ -690,10 +641,8 @@ class NodeTestCase(unittest.TestCase):
     def test_store_info(self):
         """Test calling the method to store build information
         """
-        class Entry:
-            pass
         node = SCons.Node.Node()
-        node.store_info(Entry())
+        node.store_info()
 
     def test_get_stored_info(self):
         """Test calling the method to fetch stored build information
@@ -814,7 +763,7 @@ class NodeTestCase(unittest.TestCase):
         five = SCons.Node.Node()
         six = SCons.Node.Node()
 
-        node.add_dependency(zero)
+        node.add_dependency([zero])
         assert node.depends == [zero]
         node.add_dependency([one])
         assert node.depends == [zero, one]
@@ -846,7 +795,7 @@ class NodeTestCase(unittest.TestCase):
         five = SCons.Node.Node()
         six = SCons.Node.Node()
 
-        node.add_source(zero)
+        node.add_source([zero])
         assert node.sources == [zero]
         node.add_source([one])
         assert node.sources == [zero, one]
@@ -877,7 +826,7 @@ class NodeTestCase(unittest.TestCase):
         five = SCons.Node.Node()
         six = SCons.Node.Node()
 
-        node.add_ignore(zero)
+        node.add_ignore([zero])
         assert node.ignore == [zero]
         node.add_ignore([one])
         assert node.ignore == [zero, one]
@@ -1037,21 +986,11 @@ class NodeTestCase(unittest.TestCase):
         # if the stored dependencies need recalculation.
         class StoredNode(MyNode):
             def get_stored_implicit(self):
-                return ['implicit1', 'implicit2']
-
-        class NotCurrent:
-            def current(self, node, sig):
-                return None
-            def bsig(self, node):
-                return 0
-
-        import SCons.Sig
+                return [MyNode('implicit1'), MyNode('implicit2')]
 
-        save_default_calc = SCons.Sig.default_calc
         save_implicit_cache = SCons.Node.implicit_cache
         save_implicit_deps_changed = SCons.Node.implicit_deps_changed
         save_implicit_deps_unchanged = SCons.Node.implicit_deps_unchanged
-        SCons.Sig.default_calc = NotCurrent()
         SCons.Node.implicit_cache = 1
         SCons.Node.implicit_deps_changed = None
         SCons.Node.implicit_deps_unchanged = None
@@ -1066,7 +1005,6 @@ class NodeTestCase(unittest.TestCase):
             assert sn.children() == [], sn.children()
 
         finally:
-            SCons.Sig.default_calc = save_default_calc
             SCons.Node.implicit_cache = save_implicit_cache
             SCons.Node.implicit_deps_changed = save_implicit_deps_changed
             SCons.Node.implicit_deps_unchanged = save_implicit_deps_unchanged
@@ -1278,10 +1216,8 @@ class NodeTestCase(unittest.TestCase):
 
         n.clear()
 
-        assert not hasattr(n, 'binfo'), n.bsig
         assert n.includes is None, n.includes
         assert n.found_includes == {}, n.found_includes
-        assert n.implicit is None, n.implicit
         assert x.cleaned_up
 
     def test_get_subst_proxy(self):
index a639aee3a36545241b2d3bcd3683fbd7164eb032..7cdea14c60b6884dca52fef2d99430718363787b 100644 (file)
@@ -32,10 +32,15 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 import SCons.Node
 
 class ValueNodeInfo(SCons.Node.NodeInfoBase):
-    pass
+    current_version_id = 1
+
+    field_list = ['csig']
+
+    def str_to_node(self, s):
+        return Value(s)
 
 class ValueBuildInfo(SCons.Node.BuildInfoBase):
-    pass
+    current_version_id = 1
 
 class Value(SCons.Node.Node):
     """A class for Python variables, typically passed on the command line 
@@ -54,11 +59,14 @@ class Value(SCons.Node.Node):
     def __str__(self):
         return repr(self.value)
 
+    def make_ready(self):
+        self.get_csig()
+
     def build(self, **kw):
         if not hasattr(self, 'built_value'):
             apply (SCons.Node.Node.build, (self,), kw)
 
-    current = SCons.Node.Node.children_are_up_to_date
+    is_up_to_date = SCons.Node.Node.children_are_up_to_date
 
     def is_under(self, dir):
         # Make Value nodes get built regardless of 
@@ -88,17 +96,21 @@ class Value(SCons.Node.Node):
             contents = contents + kid.get_contents()
         return contents
 
+    def changed_since_last_build(self, target, prev_ni):
+        cur_csig = self.get_csig()
+        try:
+            return cur_csig != prev_ni.csig
+        except AttributeError:
+            return 1
+
     def get_csig(self, calc=None):
         """Because we're a Python value node and don't have a real
         timestamp, we get to ignore the calculator and just use the
         value contents."""
         try:
-            binfo = self.binfo
-        except AttributeError:
-            binfo = self.binfo = self.new_binfo()
-        try:
-            return binfo.ninfo.csig
+            return self.ninfo.csig
         except AttributeError:
-            binfo.ninfo.csig = self.get_contents()
-            self.store_info(binfo)
-            return binfo.ninfo.csig
+            pass
+        contents = self.get_contents()
+        self.get_ninfo().csig = contents
+        return contents
index 62bcf8b6f9e5d789f74cf039f817f4cb26b672e3..1827a305d08de0630d5a5206471828abe2469745 100644 (file)
@@ -52,7 +52,7 @@ class ValueTestCase(unittest.TestCase):
         """Test "building" a Value Node
         """
         class fake_executor:
-            def __call__(self, node, exitstatfunc):
+            def __call__(self, node):
                 node.write('faked')
 
         v1 = SCons.Node.Python.Value('b', 'built')
index f550b5b7cebbc29c8c8c957722aabc5e40bb02e8..7ddca37e979918aa03c9045e0cd7f147b92a904d 100644 (file)
@@ -53,9 +53,13 @@ import UserList
 from SCons.Debug import logInstanceCreation
 import SCons.Executor
 import SCons.Memoize
-import SCons.SConsign
 import SCons.Util
 
+from SCons.Debug import Trace
+
+def classname(obj):
+    return string.split(str(obj.__class__), '.')[-1]
+
 # Node states
 #
 # These are in "priority" order, so that the maximum value for any
@@ -103,38 +107,53 @@ class NodeInfoBase:
     Node subclasses should subclass NodeInfoBase to provide their own
     logic for dealing with their own Node-specific signature information.
     """
+    current_version_id = 1
     def __init__(self, node):
-        """A null initializer so that subclasses have a superclass
-        initialization method to call for future use.
-        """
-        pass
-    def __cmp__(self, other):
-        return cmp(self.__dict__, other.__dict__)
+        # Create an object attribute from the class attribute so it ends up
+        # in the pickled data in the .sconsign file.
+        self._version_id = self.current_version_id
     def update(self, node):
-        pass
-    def merge(self, other):
-        for key, val in other.__dict__.items():
-            self.__dict__[key] = val
-    def prepare_dependencies(self):
-        pass
-    def format(self):
         try:
             field_list = self.field_list
         except AttributeError:
-            field_list = self.__dict__.keys()
-            field_list.sort()
+            return
+        for f in field_list:
+            try:
+                delattr(self, f)
+            except AttributeError:
+                pass
+            try:
+                func = getattr(node, 'get_' + f)
+            except AttributeError:
+                pass
+            else:
+                setattr(self, f, func())
+    def convert(self, node, val):
+        pass
+    def merge(self, other):
+        self.__dict__.update(other.__dict__)
+    def format(self, field_list=None, names=0):
+        if field_list is None:
+            try:
+                field_list = self.field_list
+            except AttributeError:
+                field_list = self.__dict__.keys()
+                field_list.sort()
         fields = []
         for field in field_list:
             try:
                 f = getattr(self, field)
             except AttributeError:
                 f = None
-            fields.append(str(f))
-        return string.join(fields, " ")
+            f = str(f)
+            if names:
+                f = field + ': ' + f
+            fields.append(f)
+        return fields
 
 class BuildInfoBase:
     """
-    The generic base clasee for build information for a Node.
+    The generic base class for build information for a Node.
 
     This is what gets stored in a .sconsign file for each target file.
     It contains a NodeInfo instance for this node (signature information
@@ -142,22 +161,17 @@ class BuildInfoBase:
     generic build stuff we have to track:  sources, explicit dependencies,
     implicit dependencies, and action information.
     """
+    current_version_id = 1
     def __init__(self, node):
-        self.ninfo = node.NodeInfo(node)
+        # Create an object attribute from the class attribute so it ends up
+        # in the pickled data in the .sconsign file.
+        self._version_id = self.current_version_id
         self.bsourcesigs = []
         self.bdependsigs = []
         self.bimplicitsigs = []
         self.bactsig = None
-    def __cmp__(self, other):
-        return cmp(self.ninfo, other.ninfo)
     def merge(self, other):
-        for key, val in other.__dict__.items():
-            try:
-                merge = self.__dict__[key].merge
-            except (AttributeError, KeyError):
-                self.__dict__[key] = val
-            else:
-                merge(val)
+        self.__dict__.update(other.__dict__)
 
 class Node:
     """The base Node class, for entities that we know how to
@@ -225,10 +239,18 @@ class Node:
     def get_suffix(self):
         return ''
 
+    memoizer_counters.append(SCons.Memoize.CountValue('get_build_env'))
+
     def get_build_env(self):
         """Fetch the appropriate Environment to build this node.
         """
-        return self.get_executor().get_build_env()
+        try:
+            return self._memo['get_build_env']
+        except KeyError:
+            pass
+        result = self.get_executor().get_build_env()
+        self._memo['get_build_env'] = result
+        return result
 
     def get_build_scanner_path(self, scanner):
         """Fetch the appropriate scanner path for this node."""
@@ -286,19 +308,64 @@ class Node:
         """
         return 0
 
+    #
+    # Taskmaster interface subsystem
+    #
+
+    def make_ready(self):
+        """Get a Node ready for evaluation.
+
+        This is called before the Taskmaster decides if the Node is
+        up-to-date or not.  Overriding this method allows for a Node
+        subclass to be disambiguated if necessary, or for an implicit
+        source builder to be attached.
+        """
+        pass
+
+    def prepare(self):
+        """Prepare for this Node to be built.
+
+        This is called after the Taskmaster has decided that the Node
+        is out-of-date and must be rebuilt, but before actually calling
+        the method to build the Node.
+
+        This default implemenation checks that all children either exist
+        or are derived, and initializes the BuildInfo structure that
+        will hold the information about how this node is, uh, built.
+
+        Overriding this method allows for for a Node subclass to remove
+        the underlying file from the file system.  Note that subclass
+        methods should call this base class method to get the child
+        check and the BuildInfo structure.
+        """
+        l = self.depends
+        if not self.implicit is None:
+            l = l + self.implicit
+        missing_sources = self.get_executor().get_missing_sources() \
+                          + filter(lambda c: c.missing(), l)
+        if missing_sources:
+            desc = "Source `%s' not found, needed by target `%s'." % (missing_sources[0], self)
+            raise SCons.Errors.StopError, desc
+
+        self.binfo = self.get_binfo()
+
     def build(self, **kw):
         """Actually build the node.
 
+        This is called by the Taskmaster after it's decided that the
+        Node is out-of-date and must be rebuilt, and after the prepare()
+        method has gotten everything, uh, prepared.
+
         This method is called from multiple threads in a parallel build,
-        so only do thread safe stuff here. Do thread unsafe stuff in
-        built().
+        so only do thread safe stuff here. Do thread unsafe stuff
+        in built().
+
         """
-        def exitstatfunc(stat, node=self):
-            if stat:
-                msg = "Error %d" % stat
-                raise SCons.Errors.BuildError(node=node, errstr=msg)
         executor = self.get_executor()
-        apply(executor, (self, exitstatfunc), kw)
+        stat = apply(executor, (self,), kw)
+        if stat:
+            msg = "Error %d" % stat
+            raise SCons.Errors.BuildError(node=self, errstr=msg)
 
     def built(self):
         """Called just after this node is successfully built."""
@@ -309,28 +376,26 @@ class Node:
             parent.implicit = None
             parent.del_binfo()
 
+        self.clear()
+
+        self.ninfo.update(self)
+
+    def visited(self):
+        """Called just after this node has been visited (with or
+        without a build)."""
         try:
-            new = self.binfo
+            binfo = self.binfo
         except AttributeError:
-            # Node arrived here without build info; apparently it
-            # doesn't need it, so don't bother calculating or storing
-            # it.
-            new = None
-
-        # Reset this Node's cached state since it was just built and
-        # various state has changed.
-        self.clear()
+            # Apparently this node doesn't need build info, so
+            # don't bother calculating or storing it.
+            pass
+        else:
+            self.ninfo.update(self)
+            self.store_info()
 
-        if new:
-            # It had build info, so it should be stored in the signature
-            # cache.  However, if the build info included a content
-            # signature then it must be recalculated before being stored.
-            if hasattr(new.ninfo, 'csig'):
-                self.get_csig()
-            else:
-                new.ninfo.update(self)
-                self.binfo = new
-            self.store_info(self.binfo)
+    #
+    #
+    #
 
     def add_to_waiting_s_e(self, node):
         self.waiting_s_e[node] = 1
@@ -367,27 +432,33 @@ class Node:
         can be re-evaluated by interfaces that do continuous integration
         builds).
         """
+        # Note in case it's important in the future:  We also used to clear
+        # the build information (the lists of dependencies) here like this:
+        #
+        #    self.del_binfo()
+        #
+        # But we now rely on the fact that we're going to look at that
+        # once before the build, and then store the results in the
+        # .sconsign file after the build.
         self.clear_memoized_values()
+        self.ninfo = self.new_ninfo()
         self.executor_cleanup()
-        self.del_binfo()
         try:
             delattr(self, '_calculated_sig')
         except AttributeError:
             pass
         self.includes = None
         self.found_includes = {}
-        self.implicit = None
 
     def clear_memoized_values(self):
         self._memo = {}
 
-    def visited(self):
-        """Called just after this node has been visited
-        without requiring a build.."""
-        pass
-
     def builder_set(self, builder):
         self.builder = builder
+        try:
+            del self.executor
+        except AttributeError:
+            pass
 
     def has_builder(self):
         """Return whether this Node has a builder or not.
@@ -405,8 +476,7 @@ class Node:
         except AttributeError:
             # There was no explicit builder for this Node, so initialize
             # the self.builder attribute to None now.
-            self.builder = None
-            b = self.builder
+            b = self.builder = None
         return not b is None
 
     def set_explicit(self, is_explicit):
@@ -446,14 +516,6 @@ class Node:
         """
         return self.has_builder() or self.side_effect
 
-    def is_pseudo_derived(self):
-        """
-        Returns true iff this node is built, but should use a source path
-        when duplicate=0 and should contribute a content signature (i.e.
-        source signature) when used as a source for other derived files.
-        """
-        return 0
-
     def alter_targets(self):
         """Return a list of alternate targets for this Node.
         """
@@ -557,28 +619,15 @@ class Node:
         if implicit_cache and not implicit_deps_changed:
             implicit = self.get_stored_implicit()
             if implicit is not None:
-                factory = build_env.get_factory(self.builder.source_factory)
-                nodes = []
-                for i in implicit:
-                    try:
-                        n = factory(i)
-                    except TypeError:
-                        # The implicit dependency was cached as one type
-                        # of Node last time, but the configuration has
-                        # changed (probably) and it's a different type
-                        # this time.  Just ignore the mismatch and go
-                        # with what our current configuration says the
-                        # Node is.
-                        pass
-                    else:
-                        nodes.append(n)
-                self._add_child(self.implicit, self.implicit_dict, nodes)
-                calc = build_env.get_calculator()
-                if implicit_deps_unchanged or self.current(calc):
+                # We now add the implicit dependencies returned from the
+                # 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)
+                if implicit_deps_unchanged or self.is_up_to_date():
                     return
-                # one of this node's sources has changed, so
-                # we need to recalculate the implicit deps,
-                # and the bsig:
+                # one of this node's sources has changed,
+                # so we must recalculate the implicit deps:
                 self.implicit = []
                 self.implicit_dict = {}
                 self._children_reset()
@@ -620,65 +669,24 @@ class Node:
     NodeInfo = NodeInfoBase
     BuildInfo = BuildInfoBase
 
-    def calculator(self):
-        import SCons.Defaults
-        
-        env = self.env or SCons.Defaults.DefaultEnvironment()
-        return env.get_calculator()
-
-    memoizer_counters.append(SCons.Memoize.CountValue('calc_signature'))
-
-    def calc_signature(self, calc=None):
-        """
-        Select and calculate the appropriate build signature for a node.
-
-        self - the node
-        calc - the signature calculation module
-        returns - the signature
-        """
-        try:
-            return self._memo['calc_signature']
-        except KeyError:
-            pass
-        if self.is_derived():
-            import SCons.Defaults
-
-            env = self.env or SCons.Defaults.DefaultEnvironment()
-            if env.use_build_signature():
-                result = self.get_bsig(calc)
-            else:
-                result = self.get_csig(calc)
-        elif not self.rexists():
-            result = None
-        else:
-            result = self.get_csig(calc)
-        self._memo['calc_signature'] = result
-        return result
-
     def new_ninfo(self):
-        return self.NodeInfo(self)
-
-    def new_binfo(self):
-        return self.BuildInfo(self)
+        ninfo = self.NodeInfo(self)
+        return ninfo
 
-    def get_binfo(self):
+    def get_ninfo(self):
         try:
-            return self.binfo
+            return self.ninfo
         except AttributeError:
-            self.binfo = self.new_binfo()
-            return self.binfo
+            self.ninfo = self.new_ninfo()
+            return self.ninfo
 
-    def del_binfo(self):
-        """Delete the build info from this node."""
-        try:
-            delattr(self, 'binfo')
-        except AttributeError:
-            pass
+    def new_binfo(self):
+        binfo = self.BuildInfo(self)
+        return binfo
 
-    def gen_binfo(self, calc=None, scan=1):
+    def get_binfo(self):
         """
-        Generate a node's build signature, the digested signatures
-        of its dependency files and build information.
+        Fetch a node's build information.
 
         node - the node whose sources will be collected
         cache - alternate node to use for the signature cache
@@ -689,21 +697,17 @@ class Node:
         already built and updated by someone else, if that's
         what's wanted.
         """
+        try:
+            return self.binfo
+        except AttributeError:
+            pass
 
-        if calc is None:
-            calc = self.calculator()
-
-        binfo = self.get_binfo()
-
-        if scan:
-            self.scan()
+        binfo = self.new_binfo()
+        self.binfo = binfo
 
         executor = self.get_executor()
-        def calc_signature(node, calc=calc):
-            return node.calc_signature(calc)
 
         sources = executor.get_unignored_sources(self.ignore)
-        sourcesigs = executor.process_sources(calc_signature, self.ignore)
 
         depends = self.depends
         implicit = self.implicit or []
@@ -712,15 +716,16 @@ class Node:
             depends = filter(self.do_not_ignore, depends)
             implicit = filter(self.do_not_ignore, implicit)
 
-        dependsigs = map(calc_signature, depends)
-        implicitsigs = map(calc_signature, implicit)
+        def get_ninfo(node):
+            return node.get_ninfo()
 
-        sigs = sourcesigs + dependsigs + implicitsigs
+        sourcesigs = map(get_ninfo, sources)
+        dependsigs = map(get_ninfo, depends)
+        implicitsigs = map(get_ninfo, implicit)
 
         if self.has_builder():
             binfo.bact = str(executor)
-            binfo.bactsig = calc.module.signature(executor)
-            sigs.append(binfo.bactsig)
+            binfo.bactsig = SCons.Util.MD5signature(executor.get_contents())
 
         binfo.bsources = sources
         binfo.bdepends = depends
@@ -730,33 +735,34 @@ class Node:
         binfo.bdependsigs = dependsigs
         binfo.bimplicitsigs = implicitsigs
 
-        binfo.ninfo.bsig = calc.module.collect(filter(None, sigs))
-
         return binfo
 
-    def get_bsig(self, calc=None):
-        binfo = self.get_binfo()
+    def del_binfo(self):
+        """Delete the build info from this node."""
         try:
-            return binfo.ninfo.bsig
+            delattr(self, 'binfo')
         except AttributeError:
-            self.binfo = self.gen_binfo(calc)
-            return self.binfo.ninfo.bsig
+            pass
 
-    def get_csig(self, calc=None):
-        binfo = self.get_binfo()
+    def get_csig(self):
         try:
-            return binfo.ninfo.csig
+            return self.ninfo.csig
         except AttributeError:
-            if calc is None:
-                calc = self.calculator()
-            csig = binfo.ninfo.csig = calc.module.signature(self)
-            return csig
+            ninfo = self.get_ninfo()
+            ninfo.csig = SCons.Util.MD5signature(self.get_contents())
+            return self.ninfo.csig
+
+    def get_cachedir_csig(self):
+        return self.get_csig()
 
-    def store_info(self, obj):
+    def store_info(self):
         """Make the build signature permanent (that is, store it in the
         .sconsign file or equivalent)."""
         pass
 
+    def do_not_store_info(self):
+        pass
+
     def get_stored_info(self):
         return None
 
@@ -800,23 +806,8 @@ class Node:
 
     def missing(self):
         return not self.is_derived() and \
-               not self.is_pseudo_derived() and \
                not self.linked and \
                not self.rexists()
-    
-    def prepare(self):
-        """Prepare for this Node to be created.
-        The default implemenation checks that all children either exist
-        or are derived.
-        """
-        l = self.depends
-        if not self.implicit is None:
-            l = l + self.implicit
-        missing_sources = self.get_executor().get_missing_sources() \
-                          + filter(lambda c: c.missing(), l)
-        if missing_sources:
-            desc = "Source `%s' not found, needed by target `%s'." % (missing_sources[0], self)
-            raise SCons.Errors.StopError, desc
 
     def remove(self):
         """Remove this Node:  no-op by default."""
@@ -861,11 +852,11 @@ class Node:
     def _add_child(self, collection, dict, child):
         """Adds 'child' to 'collection', first checking 'dict' to see
         if it's already present."""
-        if type(child) is not type([]):
-            child = [child]
-        for c in child:
-            if not isinstance(c, Node):
-                raise TypeError, c
+        #if type(child) is not type([]):
+        #    child = [child]
+        #for c in child:
+        #    if not isinstance(c, Node):
+        #        raise TypeError, c
         added = None
         for c in child:
             if not dict.has_key(c):
@@ -883,7 +874,7 @@ class Node:
     def _children_reset(self):
         self.clear_memoized_values()
         # We need to let the Executor clear out any calculated
-        # bsig info that it's cached so we can re-calculate it.
+        # build info that it's cached so we can re-calculate it.
         self.executor_cleanup()
 
     def do_not_ignore(self, node):
@@ -944,19 +935,107 @@ class Node:
     def get_state(self):
         return self.state
 
-    def current(self, calc=None):
+    def state_has_changed(self, target, prev_ni):
+        return (self.state != SCons.Node.up_to_date)
+
+    def get_env(self):
+        env = self.env
+        if not env:
+            import SCons.Defaults
+            env = SCons.Defaults.DefaultEnvironment()
+        return env
+
+    def changed_since_last_build(self, target, prev_ni):
+        """
+
+        Must be overridden in a specific subclass to return True if this
+        Node (a dependency) has changed since the last time it was used
+        to build the specified target.  prev_ni is this Node's state (for
+        example, its file timestamp, length, maybe content signature)
+        as of the last time the target was built.
+
+        Note that this method is called through the dependency, not the
+        target, because a dependency Node must be able to use its own
+        logic to decide if it changed.  For example, File Nodes need to
+        obey if we're configured to use timestamps, but Python Value Nodes
+        never use timestamps and always use the content.  If this method
+        were called through the target, then each Node's implementation
+        of this method would have to have more complicated logic to
+        handle all the different Node types on which it might depend.
+        """
+        raise NotImplementedError
+
+    def Decider(self, function):
+        SCons.Util.AddMethod(self, function, 'changed_since_last_build')
+
+    def changed(self, node=None):
+        """
+        Returns if the node is up-to-date with respect to the BuildInfo
+        stored last time it was built.  The default behavior is to compare
+        it against our own previously stored BuildInfo, but the stored
+        BuildInfo from another Node (typically one in a Repository)
+        can be used instead.
+
+        Note that we now *always* check every dependency.  We used to
+        short-circuit the check by returning as soon as we detected
+        any difference, but we now rely on checking every dependency
+        to make sure that any necessary Node information (for example,
+        the content signature of an #included .h file) is updated.
+        """
+        t = 0
+        if t: Trace('changed(%s [%s], %s)' % (self, classname(self), node))
+        if node is None:
+            node = self
+
+        result = False
+
+        bi = node.get_stored_info().binfo
+        then = bi.bsourcesigs + bi.bdependsigs + bi.bimplicitsigs
+        children = self.children()
+
+        diff = len(children) - len(then)
+        if diff:
+            # The old and new dependency lists are different lengths.
+            # This always indicates that the Node must be rebuilt.
+            # We also extend the old dependency list with enough None
+            # entries to equal the new dependency list, for the benefit
+            # of the loop below that updates node information.
+            then.extend([None] * diff)
+            result = True
+
+        for child, prev_ni in zip(children, then):
+            if child.changed_since_last_build(self, prev_ni):
+                if t: Trace(': %s changed' % child)
+                result = True
+
+        contents = self.get_executor().get_contents()
+        if self.has_builder():
+            import SCons.Util
+            newsig = SCons.Util.MD5signature(contents)
+            if bi.bactsig != newsig:
+                if t: Trace(': bactsig %s != newsig %s' % (bi.bactsig, newsig))
+                result = True
+
+        if not result:
+            if t: Trace(': up to date')
+
+        if t: Trace('\n')
+
+        return result
+
+    def is_up_to_date(self):
         """Default check for whether the Node is current: unknown Node
         subtypes are always out of date, so they will always get built."""
         return None
 
-    def children_are_up_to_date(self, calc=None):
+    def children_are_up_to_date(self):
         """Alternate check for whether the Node is current:  If all of
         our children were up-to-date, then this Node was up-to-date, too.
 
         The SCons.Node.Alias and SCons.Node.Python.Value subclasses
         rebind their current() method to this method."""
         # Allow the children to calculate their signatures.
-        self.binfo = self.gen_binfo(calc)
+        self.binfo = self.get_binfo()
         if self.always_build:
             return None
         state = 0
@@ -1052,6 +1131,8 @@ class Node:
         old = self.get_stored_info()
         if old is None:
             return None
+
+        old = old.binfo
         old.prepare_dependencies()
 
         try:
@@ -1087,7 +1168,7 @@ class Node:
         for k in new_bkids:
             if not k in old_bkids:
                 lines.append("`%s' is a new dependency\n" % stringify(k))
-            elif osig[k] != nsig[k]:
+            elif k.changed_since_last_build(self, osig[k]):
                 lines.append("`%s' changed\n" % stringify(k))
 
         if len(lines) == 0 and old_bkids != new_bkids:
index ebd6052147282b1c683edf93b776d8ebd49ce033..e2ad80f74273be2a9a9d11241b049c0bd6bb6415 100644 (file)
@@ -50,7 +50,7 @@ class Options:
     Holds all the options, updates the environment with the variables,
     and renders the help text.
     """
-    def __init__(self, files=None, args={}, is_global=1):
+    def __init__(self, files=[], args={}, is_global=1):
         """
         files - [optional] List of option configuration files to load
             (backward compatibility) If a single string is passed it is
@@ -58,11 +58,12 @@ class Options:
         """
         self.options = []
         self.args = args
-        self.files = None
-        if SCons.Util.is_String(files):
-            self.files = [ files ]
-        elif files:
-            self.files = files
+        if not SCons.Util.is_List(files):
+            if files:
+                files = [ files ]
+            else:
+                files = []
+        self.files = files
 
         # create the singleton instance
         if is_global:
@@ -155,10 +156,9 @@ class Options:
                 values[option.key] = option.default
 
         # next set the value specified in the options file
-        if self.files:
-           for filename in self.files:
-              if os.path.exists(filename):
-                 execfile(filename, values)
+        for filename in self.files:
+            if os.path.exists(filename):
+                execfile(filename, values)
 
         # finally set the values specified on the command line
         if args is None:
index 21305b9396bc85e52f3588eea7340e3cac98a62e..47a552adb8ef40add80cd23a04351d29c23bb34d 100644 (file)
@@ -28,6 +28,8 @@ Autoconf-like configuration support.
 
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
+import SCons.compat
+
 import os
 import re
 import string
@@ -46,6 +48,8 @@ import SCons.Util
 import SCons.Warnings
 import SCons.Conftest
 
+from SCons.Debug import Trace
+
 # Turn off the Conftest error logging
 SCons.Conftest.LogInputFiles = 0
 SCons.Conftest.LogErrorMessages = 0
@@ -158,12 +162,10 @@ class SConfBuildInfo(SCons.Node.FS.FileBuildInfo):
     """
     result = None # -> 0/None -> no error, != 0 error
     string = None # the stdout / stderr output when building the target
-    
-    def __init__(self, node, result, string, sig):
-        SCons.Node.FS.FileBuildInfo.__init__(self, node)
+
+    def set_build_result(self, result, string):
         self.result = result
         self.string = string
-        self.ninfo.bsig = sig
 
 
 class Streamer:
@@ -211,7 +213,7 @@ class SConfBuildTask(SCons.Taskmaster.Task):
         """
         if not isinstance(bi, SConfBuildInfo):
             SCons.Warnings.warn(SConfWarning,
-              "The stored build information has an unexpected class.")
+              "The stored build information has an unexpected class: %s" % bi.__class__)
         else:
             self.display("The original builder output was:\n" +
                          string.replace("  |" + str(bi.string),
@@ -245,30 +247,39 @@ class SConfBuildTask(SCons.Taskmaster.Task):
         #       cached_error  is 1, if the node(s) are up_to_date, but the
         #                           build will fail
         #       cachable      is 0, if some nodes are not in our cache
-        is_up_to_date = 1
-        cached_error = 0
-        cachable = 1
+        T = 0
+        changed = False
+        cached_error = False
+        cachable = True
         for t in self.targets:
-            bi = t.get_stored_info()
+            if T: Trace('%s' % (t))
+            bi = t.get_stored_info().binfo
             if isinstance(bi, SConfBuildInfo):
+                if T: Trace(': SConfBuildInfo')
                 if cache_mode == CACHE:
                     t.set_state(SCons.Node.up_to_date)
+                    if T: Trace(': set_state(up_to-date)')
                 else:
-                    new_bsig = t.calc_signature(sconf_global.calc)
-                    if t.env.use_build_signature():
-                        old_bsig = bi.ninfo.bsig
-                    else:
-                        old_bsig = bi.ninfo.csig
-                    is_up_to_date = (is_up_to_date and new_bsig == old_bsig)
+                    if T: Trace(': get_state() %s' % t.get_state())
+                    if T: Trace(': changed() %s' % t.changed())
+                    if (t.get_state() != SCons.Node.up_to_date and t.changed()):
+                        changed = True
+                    if T: Trace(': changed %s' % changed)
                 cached_error = cached_error or bi.result
             else:
+                if T: Trace(': else')
                 # the node hasn't been built in a SConf context or doesn't
                 # exist
-                cachable = 0
-                is_up_to_date = 0
-        return (is_up_to_date, cached_error, cachable)
+                cachable = False
+                changed = ( t.get_state() != SCons.Node.up_to_date )
+                if T: Trace(': changed %s' % changed)
+        if T: Trace('\n')
+        return (not changed, cached_error, cachable)
 
     def execute(self):
+        if not self.targets[0].has_builder():
+            return
+
         sconf = sconf_global
 
         is_up_to_date, cached_error, cachable = self.collect_node_states()
@@ -281,11 +292,13 @@ class SConfBuildTask(SCons.Taskmaster.Task):
         if cached_error and is_up_to_date:
             self.display("Building \"%s\" failed in a previous run and all "
                          "its sources are up to date." % str(self.targets[0]))
-            self.display_cached_string(self.targets[0].get_stored_info())
+            binfo = self.targets[0].get_stored_info().binfo
+            self.display_cached_string(binfo)
             raise SCons.Errors.BuildError # will be 'caught' in self.failed
         elif is_up_to_date:            
             self.display("\"%s\" is up to date." % str(self.targets[0]))
-            self.display_cached_string(self.targets[0].get_stored_info())
+            binfo = self.targets[0].get_stored_info().binfo
+            self.display_cached_string(binfo)
         elif dryrun:
             raise ConfigureDryRunError(self.targets[0])
         else:
@@ -305,19 +318,41 @@ class SConfBuildTask(SCons.Taskmaster.Task):
             except SystemExit:
                 exc_value = sys.exc_info()[1]
                 raise SCons.Errors.ExplicitExit(self.targets[0],exc_value.code)
-            except:
+            except Exception, e:
                 for t in self.targets:
-                    sig = t.calc_signature(sconf.calc)
-                    string = s.getvalue()
-                    binfo = SConfBuildInfo(t,1,string,sig)
-                    t.dir.sconsign().set_entry(t.name, binfo)
-                raise
+                    binfo = t.get_binfo()
+                    binfo.__class__ = SConfBuildInfo
+                    binfo.set_build_result(1, s.getvalue())
+                    sconsign_entry = SCons.SConsign.SConsignEntry()
+                    sconsign_entry.binfo = binfo
+                    #sconsign_entry.ninfo = self.get_ninfo()
+                    # We'd like to do this as follows:
+                    #    t.store_info(binfo)
+                    # However, we need to store it as an SConfBuildInfo
+                    # object, and store_info() will turn it into a
+                    # regular FileNodeInfo if the target is itself a
+                    # regular File.
+                    sconsign = t.dir.sconsign()
+                    sconsign.set_entry(t.name, sconsign_entry)
+                    sconsign.merge()
+                raise e
             else:
                 for t in self.targets:
-                    sig = t.calc_signature(sconf.calc)
-                    string = s.getvalue()
-                    binfo = SConfBuildInfo(t,0,string,sig)
-                    t.dir.sconsign().set_entry(t.name, binfo)
+                    binfo = t.get_binfo()
+                    binfo.__class__ = SConfBuildInfo
+                    binfo.set_build_result(0, s.getvalue())
+                    sconsign_entry = SCons.SConsign.SConsignEntry()
+                    sconsign_entry.binfo = binfo
+                    #sconsign_entry.ninfo = self.get_ninfo()
+                    # We'd like to do this as follows:
+                    #    t.store_info(binfo)
+                    # However, we need to store it as an SConfBuildInfo
+                    # object, and store_info() will turn it into a
+                    # regular FileNodeInfo if the target is itself a
+                    # regular File.
+                    sconsign = t.dir.sconsign()
+                    sconsign.set_entry(t.name, sconsign_entry)
+                    sconsign.merge()
 
 class SConf:
     """This is simply a class to represent a configure context. After
@@ -369,7 +404,6 @@ class SConf:
         self.AddTests(default_tests)
         self.AddTests(custom_tests)
         self.confdir = SConfFS.Dir(env.subst(conf_dir))
-        self.calc = None
         if not config_h is None:
             config_h = SConfFS.File(config_h)
         self.config_h = config_h
@@ -377,7 +411,8 @@ class SConf:
 
     def Finish(self):
         """Call this method after finished with your tests:
-        env = sconf.Finish()"""
+                env = sconf.Finish()
+        """
         self._shutdown()
         return self.env
 
@@ -398,6 +433,13 @@ class SConf:
         old_os_dir = os.getcwd()
         SConfFS.chdir(SConfFS.Top, change_os_dir=1)
 
+        # Because we take responsibility here for writing out our
+        # own .sconsign info (see SConfBuildTask.execute(), above),
+        # we override the store_info() method with a null place-holder
+        # so we really control how it gets written.
+        for n in nodes:
+            n.store_info = n.do_not_store_info
+
         ret = 1
 
         try:
index d4ed34604557727f74fb2866c7268bd2f75159ec..2c35730596e5ae50842ea08a9bbe29aad661956c 100644 (file)
@@ -60,7 +60,7 @@ class SConfTestCase(unittest.TestCase):
         import SCons.SConsign
         SCons.SConsign.write() # simulate normal scons-finish
         for n in sys.modules.keys():
-            if string.split(n, '.')[0] == 'SCons':
+            if string.split(n, '.')[0] == 'SCons' and n[:12] != 'SCons.compat':
                 m = sys.modules[n]
                 if type(m) is ModuleType:
                     # if this is really a scons module, clear its namespace
@@ -96,9 +96,9 @@ class SConfTestCase(unittest.TestCase):
         
         def checks(self, sconf, TryFuncString):
             TryFunc = self.SConf.SConf.__dict__[TryFuncString]
-            res1 = TryFunc( sconf, "int main() { return 0; }", ".c" )
+            res1 = TryFunc( sconf, "int main() { return 0; }\n", ".c" )
             res2 = TryFunc( sconf,
-                            '#include "no_std_header.h"\nint main() {return 0; }',
+                            '#include "no_std_header.h"\nint main() {return 0; }\n',
                             '.c' )
             return (res1,res2)
 
@@ -136,8 +136,9 @@ class SConfTestCase(unittest.TestCase):
         sconf = self.SConf.SConf(self.scons_env,
                                  conf_dir=self.test.workpath('config.tests'),
                                  log_file=self.test.workpath('config.log'))
-        test_h = self.test.write( self.test.workpath('config.tests', 'no_std_header.h'),
-                                  "/* we are changing a dependency now */" );
+        no_std_header_h = self.test.workpath('config.tests', 'no_std_header.h')
+        test_h = self.test.write( no_std_header_h,
+                                  "/* we are changing a dependency now */\n" );
         try:
             res = checks( self, sconf, TryFunc )
             log = self.test.read( self.test.workpath('config.log') )
@@ -187,7 +188,7 @@ class SConfTestCase(unittest.TestCase):
                         pass
                     def clear(self):
                         pass
-                    def current(self, calc=None):
+                    def is_up_to_date(self):
                         return None
                     def prepare(self):
                         pass
@@ -199,7 +200,7 @@ class SConfTestCase(unittest.TestCase):
                         pass
                     def get_stored_info(self):
                         pass
-                    def calc_signature(self, calc):
+                    def do_not_store_info(self):
                         pass
                     def get_executor(self):
                         class Executor:
@@ -236,7 +237,7 @@ int main() {
 }
 """
             res1 = sconf.TryRun( prog, ".c" ) 
-            res2 = sconf.TryRun( "not a c program", ".c" )
+            res2 = sconf.TryRun( "not a c program\n", ".c" )
             return (res1, res2)
         
         self._resetSConfState()
@@ -275,7 +276,7 @@ int main() {
         """Test SConf.TryAction
         """
         def actionOK(target, source, env):
-            open(str(target[0]), "w").write( "RUN OK" )
+            open(str(target[0]), "w").write( "RUN OK\n" )
             return None
         def actionFAIL(target, source, env):
             return 1
@@ -285,7 +286,7 @@ int main() {
                                   log_file=self.test.workpath('config.log'))
         try:
             (ret, output) = sconf.TryAction(action=actionOK)
-            assert ret and output == "RUN OK", (ret, output)
+            assert ret and output == "RUN OK" + os.linesep, (ret, output)
             (ret, output) = sconf.TryAction(action=actionFAIL)
             assert not ret and output == "", (ret, output)
         finally:
index dcd6979ec75e0091ae33a0ad4271245aa8f4ebff..bd4270610aa7e7c8e259de860db6674914ab3ba4 100644 (file)
@@ -29,12 +29,13 @@ Writing and reading information to the .sconsign file or files.
 
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
+import SCons.compat
+
 import cPickle
 import os
 import os.path
 
 import SCons.dblite
-import SCons.Sig
 import SCons.Warnings
 
 def corrupt_dblite_warning(filename):
@@ -107,6 +108,24 @@ def write():
         else:
             syncmethod()
 
+class SConsignEntry:
+    """
+    Wrapper class for the generic entry in a .sconsign file.
+    The Node subclass populates it with attributes as it pleases.
+
+    XXX As coded below, we do expect a '.binfo' attribute to be added,
+    but we'll probably generalize this in the next refactorings.
+    """
+    current_version_id = 1
+    def __init__(self):
+        # Create an object attribute from the class attribute so it ends up
+        # in the pickled data in the .sconsign file.
+        _version_id = self.current_version_id
+    def convert_to_sconsign(self):
+        self.binfo.convert_to_sconsign()
+    def convert_from_sconsign(self, dir, name):
+        self.binfo.convert_from_sconsign(dir, name)
+
 class Base:
     """
     This is the controlling class for the signatures for the collection of
@@ -116,14 +135,10 @@ class Base:
     methods for fetching and storing the individual bits of information
     that make up signature entry.
     """
-    def __init__(self, module=None):
-        """
-        module - the signature module being used
-        """
-
-        self.module = module or SCons.Sig.default_calc.module
+    def __init__(self):
         self.entries = {}
-        self.dirty = 0
+        self.dirty = False
+        self.to_be_merged = {}
 
     def get_entry(self, filename):
         """
@@ -136,19 +151,43 @@ class Base:
         Set the entry.
         """
         self.entries[filename] = obj
-        self.dirty = 1
+        self.dirty = True
 
     def do_not_set_entry(self, filename, obj):
         pass
 
+    def store_info(self, filename, node):
+        entry = node.get_stored_info()
+        entry.binfo.merge(node.get_binfo())
+        self.to_be_merged[filename] = node
+        self.dirty = True
+
+    def do_not_store_info(self, filename, node):
+        pass
+
+    def merge(self):
+        for key, node in self.to_be_merged.items():
+            entry = node.get_stored_info()
+            try:
+                ninfo = entry.ninfo
+            except AttributeError:
+                # This happens with SConf Nodes, because the configuration
+                # subsystem takes direct control over how the build decision
+                # is made and its information stored.
+                pass
+            else:
+                ninfo.merge(node.get_ninfo())
+            self.entries[key] = entry
+        self.to_be_merged = {}
+
 class DB(Base):
     """
     A Base subclass that reads and writes signature information
     from a global .sconsign.db* file--the actual file suffix is
-    determined by the specified database module.
+    determined by the database module.
     """
-    def __init__(self, dir, module=None):
-        Base.__init__(self, module)
+    def __init__(self, dir):
+        Base.__init__(self)
 
         self.dir = dir
 
@@ -182,6 +221,7 @@ class DB(Base):
             # a file there.  Don't actually set any entry info, so we
             # won't try to write to that .sconsign.dblite file.
             self.set_entry = self.do_not_set_entry
+            self.store_info = self.do_not_store_info
 
         global sig_files
         sig_files.append(self)
@@ -190,6 +230,8 @@ class DB(Base):
         if not self.dirty:
             return
 
+        self.merge()
+
         db, mode = Get_DataBase(self.dir)
 
         # Write using the path relative to the top of the SConstruct
@@ -211,27 +253,31 @@ class DB(Base):
                 syncmethod()
 
 class Dir(Base):
-    def __init__(self, fp=None, module=None):
+    def __init__(self, fp=None, dir=None):
         """
         fp - file pointer to read entries from
-        module - the signature module being used
         """
-        Base.__init__(self, module)
+        Base.__init__(self)
+
+        if not fp:
+            return
 
-        if fp:
-            self.entries = cPickle.load(fp)
-            if type(self.entries) is not type({}):
-                self.entries = {}
-                raise TypeError
+        self.entries = cPickle.load(fp)
+        if type(self.entries) is not type({}):
+            self.entries = {}
+            raise TypeError
+
+        if dir:
+            for key, entry in self.entries.items():
+                entry.convert_from_sconsign(dir, key)
 
 class DirFile(Dir):
     """
     Encapsulates reading and writing a per-directory .sconsign file.
     """
-    def __init__(self, dir, module=None):
+    def __init__(self, dir):
         """
         dir - the directory for the file
-        module - the signature module being used
         """
 
         self.dir = dir
@@ -243,7 +289,7 @@ class DirFile(Dir):
             fp = None
 
         try:
-            Dir.__init__(self, fp, module)
+            Dir.__init__(self, fp, dir)
         except KeyboardInterrupt:
             raise
         except:
@@ -253,15 +299,6 @@ class DirFile(Dir):
         global sig_files
         sig_files.append(self)
 
-    def get_entry(self, filename):
-        """
-        Fetch the specified entry attribute, converting from .sconsign
-        format to in-memory format.
-        """
-        entry = Dir.get_entry(self, filename)
-        entry.convert_from_sconsign(self.dir, filename)
-        return entry
-
     def write(self, sync=1):
         """
         Write the .sconsign file to disk.
@@ -275,48 +312,52 @@ class DirFile(Dir):
         to the .sconsign file.  Either way, always try to remove
         the temporary file at the end.
         """
-        if self.dirty:
-            temp = os.path.join(self.dir.path, '.scons%d' % os.getpid())
+        if not self.dirty:
+            return
+
+        self.merge()
+
+        temp = os.path.join(self.dir.path, '.scons%d' % os.getpid())
+        try:
+            file = open(temp, 'wb')
+            fname = temp
+        except IOError:
             try:
-                file = open(temp, 'wb')
-                fname = temp
+                file = open(self.sconsign, 'wb')
+                fname = self.sconsign
             except IOError:
-                try:
-                    file = open(self.sconsign, 'wb')
-                    fname = self.sconsign
-                except IOError:
-                    return
-            for key, entry in self.entries.items():
-                entry.convert_to_sconsign()
-            cPickle.dump(self.entries, file, 1)
-            file.close()
-            if fname != self.sconsign:
-                try:
-                    mode = os.stat(self.sconsign)[0]
-                    os.chmod(self.sconsign, 0666)
-                    os.unlink(self.sconsign)
-                except (IOError, OSError):
-                    # Try to carry on in the face of either OSError
-                    # (things like permission issues) or IOError (disk
-                    # or network issues).  If there's a really dangerous
-                    # issue, it should get re-raised by the calls below.
-                    pass
-                try:
-                    os.rename(fname, self.sconsign)
-                except OSError:
-                    # An OSError failure to rename may indicate something
-                    # like the directory has no write permission, but
-                    # the .sconsign file itself might still be writable,
-                    # so try writing on top of it directly.  An IOError
-                    # here, or in any of the following calls, would get
-                    # raised, indicating something like a potentially
-                    # serious disk or network issue.
-                    open(self.sconsign, 'wb').write(open(fname, 'rb').read())
-                    os.chmod(self.sconsign, mode)
+                return
+        for key, entry in self.entries.items():
+            entry.convert_to_sconsign()
+        cPickle.dump(self.entries, file, 1)
+        file.close()
+        if fname != self.sconsign:
             try:
-                os.unlink(temp)
+                mode = os.stat(self.sconsign)[0]
+                os.chmod(self.sconsign, 0666)
+                os.unlink(self.sconsign)
             except (IOError, OSError):
+                # Try to carry on in the face of either OSError
+                # (things like permission issues) or IOError (disk
+                # or network issues).  If there's a really dangerous
+                # issue, it should get re-raised by the calls below.
                 pass
+            try:
+                os.rename(fname, self.sconsign)
+            except OSError:
+                # An OSError failure to rename may indicate something
+                # like the directory has no write permission, but
+                # the .sconsign file itself might still be writable,
+                # so try writing on top of it directly.  An IOError
+                # here, or in any of the following calls, would get
+                # raised, indicating something like a potentially
+                # serious disk or network issue.
+                open(self.sconsign, 'wb').write(open(fname, 'rb').read())
+                os.chmod(self.sconsign, mode)
+        try:
+            os.unlink(temp)
+        except (IOError, OSError):
+            pass
 
 ForDirectory = DB
 
index e292aedaaa477399a3874fea90230b429d2a512e..2a04c897e603cb6293a4d5233d21e2351c4f5ed7 100644 (file)
@@ -33,30 +33,33 @@ import SCons.dblite
 import SCons.SConsign
 
 class BuildInfo:
+    def merge(self, object):
+        pass
+
+class DummySConsignEntry:
     def __init__(self, name):
         self.name = name
+        self.binfo = BuildInfo()
     def convert_to_sconsign(self):
         self.c_to_s = 1
     def convert_from_sconsign(self, dir, name):
         self.c_from_s = 1
 
-class DummyModule:
-    def to_string(self, sig):
-        return str(sig)
-
-    def from_string(self, sig):
-        return int(sig)
-
 class FS:
     def __init__(self, top):
         self.Top = top
         self.Top.repositories = []
 
 class DummyNode:
-    def __init__(self, path='not_a_valid_path'):
+    def __init__(self, path='not_a_valid_path', binfo=None):
         self.path = path
         self.tpath = path
         self.fs = FS(self)
+        self.binfo = binfo
+    def get_stored_info(self):
+        return self.binfo
+    def get_binfo(self):
+        return self.binfo
 
 class SConsignTestCase(unittest.TestCase):
     def setUp(self):
@@ -70,17 +73,19 @@ class SConsignTestCase(unittest.TestCase):
 
 class BaseTestCase(SConsignTestCase):
 
-    def runTest(self):
-        aaa = BuildInfo('aaa')
-        bbb = BuildInfo('bbb')
+    def test_Base(self):
+        aaa = DummySConsignEntry('aaa')
+        bbb = DummySConsignEntry('bbb')
         bbb.arg1 = 'bbb arg1'
-        ccc = BuildInfo('ccc')
+        ccc = DummySConsignEntry('ccc')
         ccc.arg2 = 'ccc arg2'
 
         f = SCons.SConsign.Base()
         f.set_entry('aaa', aaa)
         f.set_entry('bbb', bbb)
 
+        #f.merge()
+
         e = f.get_entry('aaa')
         assert e == aaa, e
         assert e.name == 'aaa', e.name
@@ -92,17 +97,18 @@ class BaseTestCase(SConsignTestCase):
         assert not hasattr(e, 'arg2'), e
 
         f.set_entry('bbb', ccc)
+
         e = f.get_entry('bbb')
         assert e.name == 'ccc', e.name
         assert not hasattr(e, 'arg1'), e
         assert e.arg2 == 'ccc arg2', e.arg1
 
-        ddd = BuildInfo('ddd')
-        eee = BuildInfo('eee')
-        fff = BuildInfo('fff')
+        ddd = DummySConsignEntry('ddd')
+        eee = DummySConsignEntry('eee')
+        fff = DummySConsignEntry('fff')
         fff.arg = 'fff arg'
 
-        f = SCons.SConsign.Base(DummyModule())
+        f = SCons.SConsign.Base()
         f.set_entry('ddd', ddd)
         f.set_entry('eee', eee)
 
@@ -116,57 +122,149 @@ class BaseTestCase(SConsignTestCase):
         assert not hasattr(e, 'arg'), e
 
         f.set_entry('eee', fff)
+
+        e = f.get_entry('eee')
+        assert e.name == 'fff', e.name
+        assert e.arg == 'fff arg', e.arg
+
+    def test_store_info(self):
+        aaa = DummySConsignEntry('aaa')
+        bbb = DummySConsignEntry('bbb')
+        bbb.arg1 = 'bbb arg1'
+        ccc = DummySConsignEntry('ccc')
+        ccc.arg2 = 'ccc arg2'
+
+        f = SCons.SConsign.Base()
+        f.store_info('aaa', DummyNode('aaa', aaa))
+        f.store_info('bbb', DummyNode('bbb', bbb))
+
+        try:
+            e = f.get_entry('aaa')
+        except KeyError:
+            pass
+        else:
+            raise "unexpected entry %s" % e
+
+        try:
+            e = f.get_entry('bbb')
+        except KeyError:
+            pass
+        else:
+            raise "unexpected entry %s" % e
+
+        f.merge()
+
+        e = f.get_entry('aaa')
+        assert e == aaa, "aaa = %s, e = %s" % (aaa, e)
+        assert e.name == 'aaa', e.name
+
+        e = f.get_entry('bbb')
+        assert e == bbb, "bbb = %s, e = %s" % (bbb, e)
+        assert e.name == 'bbb', e.name
+        assert e.arg1 == 'bbb arg1', e.arg1
+        assert not hasattr(e, 'arg2'), e
+
+        f.store_info('bbb', DummyNode('bbb', ccc))
+
+        e = f.get_entry('bbb')
+        assert e == bbb, e
+        assert e.name == 'bbb', e.name
+        assert e.arg1 == 'bbb arg1', e.arg1
+        assert not hasattr(e, 'arg2'), e
+
+        f.merge()
+
+        e = f.get_entry('bbb')
+        assert e.name == 'ccc', e.name
+        assert not hasattr(e, 'arg1'), e
+        assert e.arg2 == 'ccc arg2', e.arg1
+
+        ddd = DummySConsignEntry('ddd')
+        eee = DummySConsignEntry('eee')
+        fff = DummySConsignEntry('fff')
+        fff.arg = 'fff arg'
+
+        f = SCons.SConsign.Base()
+        f.store_info('ddd', DummyNode('ddd', ddd))
+        f.store_info('eee', DummyNode('eee', eee))
+
+        f.merge()
+
+        e = f.get_entry('ddd')
+        assert e == ddd, e
+        assert e.name == 'ddd', e.name
+
+        e = f.get_entry('eee')
+        assert e == eee, e
+        assert e.name == 'eee', e.name
+        assert not hasattr(e, 'arg'), e
+
+        f.store_info('eee', DummyNode('eee', fff))
+
+        e = f.get_entry('eee')
+        assert e == eee, e
+        assert e.name == 'eee', e.name
+        assert not hasattr(e, 'arg'), e
+
+        f.merge()
+
         e = f.get_entry('eee')
         assert e.name == 'fff', e.name
         assert e.arg == 'fff arg', e.arg
 
 class SConsignDBTestCase(SConsignTestCase):
 
-    def runTest(self):
+    def test_SConsignDB(self):
         save_DataBase = SCons.SConsign.DataBase
         SCons.SConsign.DataBase = {}
         try:
             d1 = SCons.SConsign.DB(DummyNode('dir1'))
-            d1.set_entry('aaa', BuildInfo('aaa name'))
-            d1.set_entry('bbb', BuildInfo('bbb name'))
+            d1.set_entry('aaa', DummySConsignEntry('aaa name'))
+            d1.set_entry('bbb', DummySConsignEntry('bbb name'))
+
             aaa = d1.get_entry('aaa')
             assert aaa.name == 'aaa name'
             bbb = d1.get_entry('bbb')
             assert bbb.name == 'bbb name'
 
             d2 = SCons.SConsign.DB(DummyNode('dir2'))
-            d2.set_entry('ccc', BuildInfo('ccc name'))
-            d2.set_entry('ddd', BuildInfo('ddd name'))
+            d2.set_entry('ccc', DummySConsignEntry('ccc name'))
+            d2.set_entry('ddd', DummySConsignEntry('ddd name'))
+
             ccc = d2.get_entry('ccc')
             assert ccc.name == 'ccc name'
             ddd = d2.get_entry('ddd')
             assert ddd.name == 'ddd name'
 
             d31 = SCons.SConsign.DB(DummyNode('dir3/sub1'))
-            d31.set_entry('eee', BuildInfo('eee name'))
-            d31.set_entry('fff', BuildInfo('fff name'))
+            d31.set_entry('eee', DummySConsignEntry('eee name'))
+            d31.set_entry('fff', DummySConsignEntry('fff name'))
+
             eee = d31.get_entry('eee')
             assert eee.name == 'eee name'
             fff = d31.get_entry('fff')
             assert fff.name == 'fff name'
 
             d32 = SCons.SConsign.DB(DummyNode('dir3%ssub2' % os.sep))
-            d32.set_entry('ggg', BuildInfo('ggg name'))
-            d32.set_entry('hhh', BuildInfo('hhh name'))
+            d32.set_entry('ggg', DummySConsignEntry('ggg name'))
+            d32.set_entry('hhh', DummySConsignEntry('hhh name'))
+
             ggg = d32.get_entry('ggg')
             assert ggg.name == 'ggg name'
             hhh = d32.get_entry('hhh')
             assert hhh.name == 'hhh name'
+
         finally:
+
             SCons.SConsign.DataBase = save_DataBase
 
 class SConsignDirFileTestCase(SConsignTestCase):
 
-    def runTest(self):
-        bi_foo = BuildInfo('foo')
-        bi_bar = BuildInfo('bar')
+    def test_SConsignDirFile(self):
+        bi_foo = DummySConsignEntry('foo')
+        bi_bar = DummySConsignEntry('bar')
 
-        f = SCons.SConsign.DirFile(DummyNode(), DummyModule())
+        f = SCons.SConsign.DirFile(DummyNode())
         f.set_entry('foo', bi_foo)
         f.set_entry('bar', bi_bar)
 
@@ -174,18 +272,16 @@ class SConsignDirFileTestCase(SConsignTestCase):
         assert e == bi_foo, e
         assert e.name == 'foo', e.name
 
-        assert bi_foo.c_from_s, bi_foo.c_from_s
-
         e = f.get_entry('bar')
         assert e == bi_bar, e
         assert e.name == 'bar', e.name
         assert not hasattr(e, 'arg'), e
 
-        assert bi_bar.c_from_s, bi_bar.c_from_s
-
-        bbb = BuildInfo('bbb')
+        bbb = DummySConsignEntry('bbb')
         bbb.arg = 'bbb arg'
+
         f.set_entry('bar', bbb)
+
         e = f.get_entry('bar')
         assert e.name == 'bbb', e.name
         assert e.arg == 'bbb arg', e.arg
@@ -193,7 +289,7 @@ class SConsignDirFileTestCase(SConsignTestCase):
 
 class SConsignFileTestCase(SConsignTestCase):
 
-    def runTest(self):
+    def test_SConsignFile(self):
         test = self.test
         file = test.workpath('sconsign_file')
 
@@ -242,7 +338,7 @@ class SConsignFileTestCase(SConsignTestCase):
 
 class writeTestCase(SConsignTestCase):
 
-    def runTest(self):
+    def test_write(self):
 
         test = self.test
         file = test.workpath('sconsign_file')
@@ -263,10 +359,10 @@ class writeTestCase(SConsignTestCase):
         SCons.SConsign.DataBase = {}
         SCons.SConsign.File(file, fake_dbm)
 
-        f = SCons.SConsign.DB(DummyNode(), DummyModule())
+        f = SCons.SConsign.DB(DummyNode())
 
-        bi_foo = BuildInfo('foo')
-        bi_bar = BuildInfo('bar')
+        bi_foo = DummySConsignEntry('foo')
+        bi_bar = DummySConsignEntry('bar')
         f.set_entry('foo', bi_foo)
         f.set_entry('bar', bi_bar)
 
@@ -278,18 +374,18 @@ class writeTestCase(SConsignTestCase):
         assert fake_dbm.sync_count == 1, fake_dbm.sync_count
 
 
-def suite():
-    suite = unittest.TestSuite()
-    suite.addTest(BaseTestCase())
-    suite.addTest(SConsignDBTestCase())
-    suite.addTest(SConsignDirFileTestCase())
-    suite.addTest(SConsignFileTestCase())
-    suite.addTest(writeTestCase())
-    return suite
 
 if __name__ == "__main__":
-    runner = unittest.TextTestRunner()
-    result = runner.run(suite())
-    if not result.wasSuccessful():
+    suite = unittest.TestSuite()
+    tclasses = [
+        BaseTestCase,
+        SConsignDBTestCase,
+        SConsignDirFileTestCase,
+        SConsignFileTestCase,
+        writeTestCase,
+    ]
+    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 ae77908b9188d23286e495faf544a0d5e3607bd7..64d6d77dfc1119824c787ffd2c4a714a619b83c5 100644 (file)
@@ -26,7 +26,6 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 import sys
 import unittest
 import UserDict
-import SCons.Sig
 
 import SCons.Scanner
 
@@ -51,8 +50,6 @@ class DummyEnvironment(UserDict.UserDict):
         if type(path) != type([]):
             path = [path]
         return map(self.subst, path)
-    def get_calculator(self):
-        return SCons.Sig.default_calc
     def get_factory(self, factory):
         return factory or self.fs.File
 
@@ -399,7 +396,7 @@ class CurrentTestCase(unittest.TestCase):
         class MyNode:
             def __init__(self):
                 self.called_has_builder = None
-                self.called_current = None
+                self.called_is_up_to_date = None
                 self.func_called = None
             def rexists(self):
                 return 1
@@ -411,15 +408,15 @@ class CurrentTestCase(unittest.TestCase):
             def has_builder(self):
                 self.called_has_builder = 1
                 return 1
-            def current(self, sig):
-                self.called_current = 1
+            def is_up_to_date(self):
+                self.called_is_up_to_date = 1
                 return None
         class IsCurrent(MyNode):
             def has_builder(self):
                 self.called_has_builder = 1
                 return 1
-            def current(self, sig):
-                self.called_current = 1
+            def is_up_to_date(self):
+                self.called_is_up_to_date = 1
                 return 1
         def func(node, env, path):
             node.func_called = 1
@@ -430,17 +427,17 @@ class CurrentTestCase(unittest.TestCase):
         hnb = HasNoBuilder()
         s(hnb, env, path)
         self.failUnless(hnb.called_has_builder, "did not call has_builder()")
-        self.failUnless(not hnb.called_current, "did call current()")
+        self.failUnless(not hnb.called_is_up_to_date, "did call is_up_to_date()")
         self.failUnless(hnb.func_called, "did not call func()")
         inc = IsNotCurrent()
         s(inc, env, path)
         self.failUnless(inc.called_has_builder, "did not call has_builder()")
-        self.failUnless(inc.called_current, "did not call current()")
+        self.failUnless(inc.called_is_up_to_date, "did not call is_up_to_date()")
         self.failUnless(not inc.func_called, "did call func()")
         ic = IsCurrent()
         s(ic, env, path)
         self.failUnless(ic.called_has_builder, "did not call has_builder()")
-        self.failUnless(ic.called_current, "did not call current()")
+        self.failUnless(ic.called_is_up_to_date, "did not call is_up_to_date()")
         self.failUnless(ic.func_called, "did not call func()")
 
 class ClassicTestCase(unittest.TestCase):
index db93f615b7e8a6d9d38ca3cc72e5387768efe31c..c8ab1557f1ef580902d550d7fbeeace3d78566a5 100644 (file)
@@ -33,7 +33,6 @@ import re
 import string
 
 import SCons.Node.FS
-import SCons.Sig
 import SCons.Util
 
 
@@ -306,8 +305,7 @@ class Current(Base):
 
     def __init__(self, *args, **kw):
         def current_check(node, env):
-            c = not node.has_builder() or node.current(env.get_calculator())
-            return c
+            return not node.has_builder() or node.is_up_to_date()
         kw['scan_check'] = current_check
         apply(Base.__init__, (self,) + args, kw)
 
index a76165f6291fa61b2ca82afd3a39417749d11afa..36dc21e1e41007089f800f020930e4604c3bcc5c 100644 (file)
@@ -64,7 +64,6 @@ import SCons.Node
 import SCons.Node.FS
 import SCons.SConf
 import SCons.Script
-import SCons.Sig
 import SCons.Taskmaster
 import SCons.Util
 import SCons.Warnings
@@ -80,13 +79,77 @@ progress_display = SCons.Util.DisplayEngine()
 first_command_start = None
 last_command_end = None
 
+class Progressor:
+    prev = ''
+    count = 0
+    target_string = '$TARGET'
+
+    def __init__(self, obj, interval=1, file=None, overwrite=False):
+        if file is None:
+            file = sys.stdout
+
+        self.obj = obj
+        self.file = file
+        self.interval = interval
+        self.overwrite = overwrite
+
+        if callable(obj):
+            self.func = obj
+        elif SCons.Util.is_List(obj):
+            self.func = self.spinner
+        elif string.find(obj, self.target_string) != -1:
+            self.func = self.replace_string
+        else:
+            self.func = self.string
+
+    def write(self, s):
+        self.file.write(s)
+        self.file.flush()
+        self.prev = s
+
+    def erase_previous(self):
+        if self.prev:
+            length = len(self.prev)
+            if self.prev[-1] in ('\n', '\r'):
+                length = length - 1
+            self.write(' ' * length + '\r')
+            self.prev = ''
+
+    def spinner(self, node):
+        self.write(self.obj[self.count % len(self.obj)])
+
+    def string(self, node):
+        self.write(self.obj)
+
+    def replace_string(self, node):
+        self.write(string.replace(self.obj, self.target_string, str(node)))
+
+    def __call__(self, node):
+        self.count = self.count + 1
+        if (self.count % self.interval) == 0:
+            if self.overwrite:
+                self.erase_previous()
+            self.func(node)
+
+ProgressObject = SCons.Util.Null()
+
+def Progress(*args, **kw):
+    global ProgressObject
+    ProgressObject = apply(Progressor, args, kw)
+
 # Task control.
 #
 class BuildTask(SCons.Taskmaster.Task):
     """An SCons build task."""
+    progress = ProgressObject
+
     def display(self, message):
         display('scons: ' + message)
 
+    def prepare(self):
+        self.progress(self.targets[0])
+        return SCons.Taskmaster.Task.prepare(self)
+
     def execute(self):
         for target in self.targets:
             if target.get_state() == SCons.Node.up_to_date: 
@@ -276,6 +339,12 @@ class CleanTask(SCons.Taskmaster.Task):
 
     execute = remove
 
+    # We want the Taskmaster to update the Node states (and therefore
+    # handle reference counts, etc.), but we don't want to call
+    # back to the Node's post-build methods, which would do things
+    # we don't want, like store .sconsign information.
+    executed = SCons.Taskmaster.Task.executed_without_callbacks
+
     # Have the taskmaster arrange to "execute" all of the targets, because
     # we'll figure out ourselves (in remove() or show() above) whether
     # anything really needs to be done.
@@ -290,7 +359,8 @@ class QuestionTask(SCons.Taskmaster.Task):
         pass
     
     def execute(self):
-        if self.targets[0].get_state() != SCons.Node.up_to_date:
+        if self.targets[0].get_state() != SCons.Node.up_to_date or \
+           (self.top and not self.targets[0].exists()):
             global exit_status
             exit_status = 1
             self.tm.stop()
@@ -766,6 +836,12 @@ 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
     SCons.SConf.SetCacheMode(options.config)
     SCons.SConf.SetProgressDisplay(progress_display)
 
@@ -838,7 +914,8 @@ def _main(parser):
     memory_stats.append('after reading SConscript files:')
     count_stats.append(('post-', 'read'))
 
-    SCons.SConf.CreateConfigHBuilder(SCons.Defaults.DefaultEnvironment())
+    if not options.help:
+        SCons.SConf.CreateConfigHBuilder(SCons.Defaults.DefaultEnvironment())
 
     # Now re-parse the command-line options (any to the left of a '--'
     # argument, that is) with any user-defined command-line options that
@@ -972,6 +1049,8 @@ def _main(parser):
     except AttributeError:
         pass
 
+    task_class.progress = ProgressObject
+
     if options.random:
         def order(dependencies):
             """Randomize the dependencies."""
index a1856dab822b6932c13dcd81d2557e136e5199d6..5278d403e07f5425bdcd235a6f68d90f74c8ce03 100644 (file)
@@ -65,6 +65,9 @@ import UserList
 #CommandLineTargets = []
 #DefaultTargets = []
 
+class SConscriptReturn(Exception):
+    pass
+
 launch_dir = os.path.abspath(os.curdir)
 
 GlobalDict = None
@@ -125,6 +128,8 @@ class Frame:
         # make sure the sconscript attr is a Node.
         if isinstance(sconscript, SCons.Node.Node):
             self.sconscript = sconscript
+        elif sconscript == '-':
+            self.sconscript = None
         else:
             self.sconscript = fs.File(str(sconscript))
 
@@ -133,7 +138,7 @@ call_stack = []
 
 # For documentation on the methods in this file, see the scons man-page
 
-def Return(*vars):
+def Return(*vars, **kw):
     retval = []
     try:
         for var in vars:
@@ -147,6 +152,11 @@ def Return(*vars):
     else:
         call_stack[-1].retval = tuple(retval)
 
+    stop = kw.get('stop', True)
+
+    if stop:
+        raise SConscriptReturn
+
 
 stack_bottom = '% Stack boTTom %' # hard to define a variable w/this name :)
 
@@ -242,7 +252,10 @@ def _SConscript(fs, *files, **kw):
                     except KeyError:
                         pass
                     try:
-                        exec _file_ in call_stack[-1].globals
+                        try:
+                            exec _file_ in call_stack[-1].globals
+                        except SConscriptReturn:
+                            pass
                     finally:
                         if old_file is not None:
                             call_stack[-1].globals.update({__file__:old_file})
index 4010d80b1d0914bfab7a18cfe324e8be38cf3d9f..d1a115a2d49791e2f101f1028eef83988288ea9f 100644 (file)
@@ -110,6 +110,7 @@ QuestionTask            = Main.QuestionTask
 AddOption               = Main.AddOption
 GetOption               = Main.GetOption
 SetOption               = Main.SetOption
+Progress                = Main.Progress
 
 #keep_going_on_error     = Main.keep_going_on_error
 #print_dtree             = Main.print_dtree
@@ -260,7 +261,7 @@ def HelpFunction(text):
 sconscript_reading = 0
 
 #
-def Options(files=None, args=ARGUMENTS):
+def Options(files=[], args=ARGUMENTS):
     return SCons.Options.Options(files, args)
 
 # The list of global functions to add to the SConscript name space
@@ -288,6 +289,7 @@ GlobalDefaultEnvironmentFunctions = [
     'CacheDir',
     'Clean',
     #The Command() method is handled separately, below.
+    'Decider',
     'Depends',
     'Dir',
     'NoClean',
diff --git a/src/engine/SCons/Sig.py b/src/engine/SCons/Sig.py
new file mode 100644 (file)
index 0000000..80bb810
--- /dev/null
@@ -0,0 +1,57 @@
+#
+# __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__"
+
+__doc__ = """Place-holder for the old SCons.Sig module hierarchy
+
+This is no longer used, but code out there (such as the NSIS module on
+the SCons wiki) may try to import SCons.Sig.  If so, we generate a warning
+that points them to the line that caused the import, and don't die.
+
+If someone actually tried to use the sub-modules or functions within
+the package (for example, SCons.Sig.MD5.signature()), then they'll still
+get an AttributeError, but at least they'll know where to start looking.
+"""
+
+import SCons.Util
+import SCons.Warnings
+
+msg = 'The SCons.Sig module no longer exists.\n' \
+      '    Remove the following "import SCons.Sig" line to eliminate this warning:'
+
+SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning, msg)
+
+default_calc = None
+default_module = None
+
+class MD5Null(SCons.Util.Null):
+    def __repr__(self):
+        return "MD5Null()"
+
+class TimeStampNull(SCons.Util.Null):
+    def __repr__(self):
+        return "TimeStampNull()"
+
+MD5 = MD5Null()
+TimeStamp = TimeStampNull()
diff --git a/src/engine/SCons/Sig/.aeignore b/src/engine/SCons/Sig/.aeignore
deleted file mode 100644 (file)
index 22ebd62..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-*,D
-*.pyc
-.*.swp
-.consign
-.sconsign
diff --git a/src/engine/SCons/Sig/.cvsignore b/src/engine/SCons/Sig/.cvsignore
deleted file mode 100644 (file)
index 0d20b64..0000000
+++ /dev/null
@@ -1 +0,0 @@
-*.pyc
diff --git a/src/engine/SCons/Sig/MD5.py b/src/engine/SCons/Sig/MD5.py
deleted file mode 100644 (file)
index f8f349f..0000000
+++ /dev/null
@@ -1,104 +0,0 @@
-"""SCons.Sig.MD5
-
-The MD5 signature package for the SCons software construction
-utility.
-
-"""
-
-#
-# __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__"
-
-import imp
-import string
-
-# Force Python to load the builtin "md5" module.  If we do this with a
-# normal import statement, then case-insensitive systems (Windows) get
-# confused and thinks there's a case mismatch with *this* MD5.py module.
-file, name, desc = imp.find_module('md5')
-try:
-    md5 = imp.load_module('md5', file, name, desc)
-finally:
-    if file:
-        file.close()
-
-def current(new, old):
-    """Return whether a new signature is up-to-date with
-    respect to an old signature.
-    """
-    return new == old
-
-try:
-    md5.new('').hexdigest
-except AttributeError:
-    # The md5 objects created by the module have no native hexdigest()
-    # method (*cough* 1.5.2 *cough*) so provide an equivalent.
-    class new_md5:
-        def __init__(self, s):
-            self.m = md5.new(str(s))
-        #def copy(self):
-        #    return self.m.copy()
-        def digest(self):
-            return self.m.digest()
-        def hexdigest(self):
-            h = string.hexdigits
-            r = ''
-            for c in self.m.digest():
-                i = ord(c)
-                r = r + h[(i >> 4) & 0xF] + h[i & 0xF]
-            return r
-        def update(self, s):
-            return self.m.update(s)
-
-else:
-    new_md5 = lambda s: md5.new(str(s))
-
-def collect(signatures):
-    """
-    Collect a list of signatures into an aggregate signature.
-
-    signatures - a list of signatures
-    returns - the aggregate signature
-    """
-    if len(signatures) == 1:
-        return signatures[0]
-    else:
-        return new_md5(string.join(signatures, ', ')).hexdigest()
-
-def signature(obj):
-    """Generate a signature for an object
-    """
-    try:
-        gc = obj.get_contents
-    except AttributeError:
-        raise AttributeError, "unable to fetch contents of '%s'" % str(obj)
-    return new_md5(gc()).hexdigest()
-
-def to_string(signature):
-    """Convert a signature to a string"""
-    return signature
-
-def from_string(string):
-    """Convert a string to a signature"""
-    return string
diff --git a/src/engine/SCons/Sig/MD5Tests.py b/src/engine/SCons/Sig/MD5Tests.py
deleted file mode 100644 (file)
index ba18264..0000000
+++ /dev/null
@@ -1,113 +0,0 @@
-#
-# __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__"
-
-import sys
-import unittest
-import string
-
-from SCons.Sig.MD5 import current, collect, signature, to_string, from_string
-
-
-
-
-class my_obj:
-    """A dummy object class that satisfies the interface
-    requirements of the MD5 class.
-    """
-
-    def __init__(self, value = ""):
-        self.value = value
-        
-    def get_signature(self):
-        if not hasattr(self, "sig"):
-            self.sig = signature(self)
-        return self.sig
-
-    def get_contents(self):
-        return self.value
-
-
-
-class MD5TestCase(unittest.TestCase):
-
-    def test_current(self):
-        """Test deciding if an object is up-to-date
-
-        Simple comparison of different "signature" values.
-        """
-        obj = my_obj('111')
-        assert not current(obj.get_signature(), signature(my_obj('110')))
-        assert     current(obj.get_signature(), signature(my_obj('111')))
-        assert not current(obj.get_signature(), signature(my_obj('112')))
-
-    def test_collect(self):
-        """Test collecting a list of signatures into a new signature value
-        """
-        s = map(signature, map(my_obj, ('111', '222', '333')))
-        
-        assert '698d51a19d8a121ce581499d7b701668' == collect(s[0:1])
-        assert '8980c988edc2c78cc43ccb718c06efd5' == collect(s[0:2])
-        assert '53fd88c84ff8a285eb6e0a687e55b8c7' == collect(s)
-
-    def test_signature(self):
-        """Test generating a signature"""
-        o1 = my_obj(value = '111')
-        s = signature(o1)
-        assert '698d51a19d8a121ce581499d7b701668' == s, s
-
-        o2 = my_obj(value = 222)
-        s = signature(o2)
-        assert 'bcbe3365e6ac95ea2c0343a2395834dd' == s, s
-
-        try:
-            signature('string')
-        except AttributeError, e:
-            assert string.find(str(e), "unable to fetch contents") == 0, str(e)
-        else:
-            raise AttributeError, "unexpected get_contents() attribute"
-
-        # Make sure we don't eat AttributeErrors raised internally
-        # by the get_contents() method (or anything it calls).
-        caught = None
-        try:
-            class xxx:
-                def get_contents(self):
-                    raise AttributeError, "internal AttributeError"
-            signature(xxx())
-        except AttributeError, e:
-            assert str(e) == "internal AttributeError", e
-            caught = 1
-        assert caught, "did not catch expected AttributeError"
-
-    def test_to_string(self):
-        assert '698d51a19d8a121ce581499d7b701668' == to_string('698d51a19d8a121ce581499d7b701668')
-
-    def test_from_string(self):
-        assert '698d51a19d8a121ce581499d7b701668' == from_string('698d51a19d8a121ce581499d7b701668')
-
-if __name__ == "__main__":
-    suite = unittest.makeSuite(MD5TestCase, 'test_')
-    if not unittest.TextTestRunner().run(suite).wasSuccessful():
-        sys.exit(1)
diff --git a/src/engine/SCons/Sig/TimeStampTests.py b/src/engine/SCons/Sig/TimeStampTests.py
deleted file mode 100644 (file)
index 49c5cfb..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-#
-# __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__"
-
-import sys
-import unittest
-
-from SCons.Sig.TimeStamp import current, collect, signature, to_string, from_string
-
-
-
-class my_obj:
-    """A dummy object class that satisfies the interface
-    requirements of the TimeStamp class.
-    """
-
-    def __init__(self, value = 0):
-        self.value = value
-
-    def get_signature(self):
-        return self.value
-
-    def get_timestamp(self):
-        return self.value
-
-
-class TimeStampTestCase(unittest.TestCase):
-
-    def test_current(self):
-        """Test deciding if an object is up-to-date
-
-        Simple comparison of different timestamp values.
-        """
-        o1 = my_obj(value = 111)
-        assert not current(o1.get_signature(), 110)
-        assert current(o1.get_signature(), 111)
-        assert current(o1.get_signature(), 112)
-
-    def test_collect(self):
-        """Test collecting a list of signatures into a new signature value
-        into a new timestamp value.
-        """
-        
-        assert 111 == collect((111,))
-        assert 222 == collect((111, 222))
-        assert 333 == collect((333, 222, 111))
-
-    def test_signature(self):
-        """Test generating a signature"""
-        o1 = my_obj(value = 111)
-        assert 111 == signature(o1)
-
-    def test_to_string(self):
-        assert '111' == to_string(111)
-
-    def test_from_string(self):
-        assert 111 == from_string('111')
-
-
-if __name__ == "__main__":
-    suite = unittest.makeSuite(TimeStampTestCase, 'test_')
-    if not unittest.TextTestRunner().run(suite).wasSuccessful():
-        sys.exit(1)
index acb8c9b5844847ef50bc48f60418fb45bc2c94f1..598566e43e58d45ccc90d1e1d75f3802d74fff38 100644 (file)
@@ -205,20 +205,40 @@ class Task:
             raise SCons.Errors.TaskmasterException(self.targets[0],
                                                    sys.exc_info())
 
-    def executed(self):
+    def executed_without_callbacks(self):
         """
-        Called when the task has been successfully executed.
+        Called when the task has been successfully executed
+        and the Taskmaster instance doesn't want to call
+        the Node's callback methods.
+        """
+        for t in self.targets:
+            if t.get_state() == SCons.Node.executing:
+                for side_effect in t.side_effects:
+                    side_effect.set_state(SCons.Node.no_state)
+                t.set_state(SCons.Node.executed)
+
+    def executed_with_callbacks(self):
+        """
+        Called when the task has been successfully executed and
+        the Taskmaster instance wants to call the Node's callback
+        methods.
 
         This may have been a do-nothing operation (to preserve build
-        order), so we have to check the node's state before deciding
-        whether it was "built" or just "visited."
+        order), so we must check the node's state before deciding whether
+        it was "built", in which case we call the appropriate Node method.
+        In any event, we always call "visited()", which will handle any
+        post-visit actions that must take place regardless of whether
+        or not the target was an actual built target or a source Node.
         """
         for t in self.targets:
             if t.get_state() == SCons.Node.executing:
+                for side_effect in t.side_effects:
+                    side_effect.set_state(SCons.Node.no_state)
                 t.set_state(SCons.Node.executed)
                 t.built()
-            else:
-                t.visited()
+            t.visited()
+
+    executed = executed_with_callbacks
 
     def failed(self):
         """
@@ -275,9 +295,10 @@ class Task:
         """
         self.out_of_date = []
         for t in self.targets:
-            t.disambiguate()
             try:
-                is_up_to_date = not t.always_build and t.current()
+                t.disambiguate().make_ready()
+                is_up_to_date = not t.has_builder() or \
+                                (not t.always_build and t.is_up_to_date())
             except EnvironmentError, e:
                 raise SCons.Errors.BuildError(node=t, errstr=e.strerror, filename=e.filename)
             if is_up_to_date:
@@ -308,12 +329,14 @@ class Task:
         # back on the candidates list if the Node is also a waiting
         # parent.
 
+        targets = set(self.targets)
+
         parents = {}
-        for t in self.targets:
+        for t in targets:
             for p in t.waiting_parents.keys():
                 parents[p] = parents.get(p, 0) + 1
 
-        for t in self.targets:
+        for t in targets:
             for s in t.side_effects:
                 if s.get_state() == SCons.Node.executing:
                     s.set_state(SCons.Node.no_state)
@@ -329,7 +352,7 @@ class Task:
             if p.ref_count == 0:
                 self.tm.candidates.append(p)
 
-        for t in self.targets:
+        for t in targets:
             t.postprocess()
 
     # Exception handling subsystem.
@@ -403,8 +426,9 @@ class Taskmaster:
     """
 
     def __init__(self, targets=[], tasker=Task, order=None, trace=None):
-        self.top_targets = targets[:]
-        self.top_targets.reverse()
+        self.original_top = targets
+        self.top_targets_left = targets[:]
+        self.top_targets_left.reverse()
         self.candidates = []
         self.tasker = tasker
         if not order:
@@ -438,7 +462,7 @@ class Taskmaster:
         except IndexError:
             pass
         try:
-            node = self.top_targets.pop()
+            node = self.top_targets_left.pop()
         except IndexError:
             return None
         self.current_top = node
@@ -536,16 +560,14 @@ class Taskmaster:
                 c.sort()
                 T.write(' children:\n    %s\n   ' % c)
 
-            childinfo = map(lambda N: (N.get_state(),
-                                       N.is_derived() or N.is_pseudo_derived(),
-                                       N), children)
+            childstate = map(lambda N: (N, N.get_state()), children)
 
             # Skip this node if any of its children have failed.  This
             # catches the case where we're descending a top-level target
             # and one of our children failed while trying to be built
             # by a *previous* descent of an earlier top-level target.
-            failed_children = filter(lambda I: I[0] == SCons.Node.failed,
-                                     childinfo)
+            failed_children = filter(lambda I: I[1] == SCons.Node.failed,
+                                     childstate)
             if failed_children:
                 node.set_state(SCons.Node.failed)
                 if S: S.child_failed = S.child_failed + 1
@@ -556,76 +578,48 @@ class Taskmaster:
                 continue
 
             # Detect dependency cycles:
-            pending_nodes = filter(lambda I: I[0] == SCons.Node.pending, childinfo)
+            pending_nodes = filter(lambda I: I[1] == SCons.Node.pending, childstate)
             if pending_nodes:
                 for p in pending_nodes:
-                    cycle = find_cycle([p[2], node])
+                    cycle = find_cycle([p[0], node])
                     if cycle:
                         desc = "Dependency cycle: " + string.join(map(str, cycle), " -> ")
                         if T: T.write(' dependency cycle\n')
                         raise SCons.Errors.UserError, desc
 
-            # Select all of the dependencies that are derived targets
-            # (that is, children who have builders or are side effects).
-            derived_children = filter(lambda I: I[1], childinfo)
-
-            not_started = filter(lambda I: not I[0], derived_children)
-            if not_started:
-                not_started = map(lambda I: I[2], not_started)
-
-                # We're waiting on one more derived targets that have
-                # not yet started building.  Add this node to the
-                # waiting_parents lists of those derived files so that
-                # when they've finished building, our implicit dependency
-                # list will get cleared and we'll re-scan the newly-built
-                # file(s) for updated implicit dependencies.
-                added = map(lambda n, P=node: n.add_to_waiting_parents(P), not_started)
-                node.ref_count = node.ref_count + reduce(operator.add, added, 0)
-
-                # Now we add these derived targets to the candidates
-                # list so they can be examined and built.  We have to
-                # add ourselves back to the list first, though, so we get
-                # a chance to re-scan and build after the dependencies.
-                #
-                # We reverse the order in which the children are added
-                # to the candidates stack so the order in which they're
-                # popped matches the order in which they show up in our
-                # children's list.  This is more logical / efficient for
-                # builders with multiple targets, since the "primary"
-                # target will be examined first.
-                self.candidates.append(node)
-                not_started.reverse()
-                self.candidates.extend(self.order(not_started))
-
-                if S: S.not_started = S.not_started + 1
-                if T:
-                    c = map(str, not_started)
-                    c.sort()
-                    T.write(' waiting on unstarted children:\n    %s\n' % c)
-                continue
-
-            not_built = filter(lambda I: I[0] <= SCons.Node.executing, derived_children)
+            not_built = filter(lambda I: I[1] <= SCons.Node.executing, childstate)
             if not_built:
-                not_built = map(lambda I: I[2], not_built)
-
                 # We're waiting on one or more derived targets that have
-                # started building but not yet finished.  Add this node
-                # to the waiting parents lists of those derived files
-                # so that when they've finished building, our implicit
-                # dependency list will get cleared and we'll re-scan the
-                # newly-built file(s) for updated implicit dependencies.
-                added = map(lambda n, P=node: n.add_to_waiting_parents(P), not_built)
-                node.ref_count = node.ref_count + reduce(operator.add, added, 0)
+                # not yet finished building.
+
+                not_visited = filter(lambda I: not I[1], not_built)
+                if not_visited:
+                    # Some of them haven't even been visited yet.
+                    # Add them to the list so that on some next pass
+                    # we can take a stab at evaluating them (or
+                    # their children).
+                    not_visited = map(lambda I: I[0], not_visited)
+                    not_visited.reverse()
+                    self.candidates.extend(self.order(not_visited))
+
+                n_b_nodes = map(lambda I: I[0], not_built)
+
+                # Add this node to the waiting parents lists of anything
+                # we're waiting on, with a reference count so we can be
+                # put back on the list for re-evaluation when they've
+                # all finished.
+                map(lambda n, P=node: n.add_to_waiting_parents(P), n_b_nodes)
+                node.ref_count = len(set(n_b_nodes))
 
                 if S: S.not_built = S.not_built + 1
                 if T:
-                    c = map(str, not_built)
+                    c = map(str, n_b_nodes)
                     c.sort()
                     T.write(' waiting on unfinished children:\n    %s\n' % c)
                 continue
 
-            # Skip this node if it has side-effects that are currently being
-            # built themselves or waiting for something else being built.
+            # Skip this node if it has side-effects that are
+            # currently being built:
             side_effects = filter(lambda N:
                                   N.get_state() == SCons.Node.executing,
                                   node.side_effects)
@@ -660,7 +654,7 @@ class Taskmaster:
 
         tlist = node.get_executor().targets
 
-        task = self.tasker(self, tlist, node is self.current_top, node)
+        task = self.tasker(self, tlist, node in self.original_top, node)
         try:
             task.make_ready()
         except KeyboardInterrupt:
index 718851d79212aa8da1ef9d17027651f44f831327..c79fb9313cba5992c563fd742437dccb10a163ce 100644 (file)
@@ -74,10 +74,20 @@ class Node:
             cache_text.append(self.name + " retrieved")
         return self.cached
 
+    def make_ready(self):
+        pass
+
+    def prepare(self):
+        self.prepared = 1
+
     def build(self):
         global built_text
         built_text = self.name + " built"
 
+    def built(self):
+        global built_text
+        built_text = built_text + " really"
+
     def has_builder(self):
         return not self.builder is None
 
@@ -87,17 +97,10 @@ class Node:
     def alter_targets(self):
         return self.alttargets, None
 
-    def built(self):
-        global built_text
-        built_text = built_text + " really"
-
     def visited(self):
         global visited_nodes
         visited_nodes.append(self.name)
 
-    def prepare(self):
-        self.prepared = 1
-
     def children(self):
         if not self.scanned:
             self.scan()
@@ -145,18 +148,11 @@ class Node:
     def store_bsig(self):
         pass
 
-    def calculator(self):
-        class Calc:
-            def bsig(self, node):
-                return node._bsig_val
-            def current(self, node, sig):
-                return node._current_val
-        return Calc()
-
-    def current(self, calc=None):
-        if calc is None:
-            calc = self.calculator()
-        return calc.current(self, calc.bsig(self))
+    def is_pseudo_derived(self):
+        pass
+
+    def is_up_to_date(self):
+        return self._current_val
     
     def depends_on(self, nodes):
         for node in nodes:
@@ -713,7 +709,7 @@ class TaskmasterTestCase(unittest.TestCase):
         s = n1.get_state()
         assert s == SCons.Node.executed, s
         assert built_text == "xxx really", built_text
-        assert visited_nodes == [], visited_nodes
+        assert visited_nodes == ['n1'], visited_nodes
 
         n2 = Node("n2")
         tm = SCons.Taskmaster.Taskmaster([n2])
@@ -744,7 +740,7 @@ class TaskmasterTestCase(unittest.TestCase):
         assert s == SCons.Node.up_to_date, s
         s = n4.get_state()
         assert s == SCons.Node.executed, s
-        assert visited_nodes == ['n3'], visited_nodes
+        assert visited_nodes == ['n3', 'n4'], visited_nodes
 
     def test_prepare(self):
         """Test preparation of multiple Nodes for a task
@@ -1017,11 +1013,19 @@ class TaskmasterTestCase(unittest.TestCase):
         t = tm.next_task()
         t.prepare()
         t.execute()
+        t.postprocess()
         n1.set_state(SCons.Node.executed)
         t = tm.next_task()
         t.prepare()
         t.execute()
+        t.postprocess()
+        n2.set_state(SCons.Node.executed)
+        t = tm.next_task()
+        t.prepare()
+        t.execute()
+        t.postprocess()
         t = tm.next_task()
+        assert t is None
 
         value = trace.getvalue()
         expect = """\
@@ -1029,13 +1033,12 @@ Taskmaster: 'n1': evaluating n1
 Taskmaster: 'n1': already handled (executed)
 Taskmaster: 'n3': children:
     ['n1', 'n2']
-    waiting on unstarted children:
+    waiting on unfinished children:
     ['n2']
 Taskmaster: 'n2': evaluating n2
 Taskmaster: 'n3': children:
     ['n1', 'n2']
-    waiting on unfinished children:
-    ['n2']
+    evaluating n3
 """
         assert value == expect, value
 
index 5097c6733e1895a5b79a6152de5c37836687fa90..0991c372d0a2609a2a30f04dd9be12e22e57aa77 100644 (file)
@@ -56,7 +56,7 @@ if java_parsing:
     #     semi-colons;
     #     periods.
     _reToken = re.compile(r'(\n|\\\\|//|\\[\'"]|[\'"\{\}\;\.\(\)]|' +
-                          r'[A-Za-z_][\w\.]*|/\*|\*/|\[\])')
+                          r'[A-Za-z_][\w\$\.]*|/\*|\*/|\[\])')
 
     class OuterState:
         """The initial state for parsing a Java file for classes,
index 358f67577499c31df6bed8280ee372dc955247cf..1675190b6025b0c5a4ec87264fa700272d747dd3 100644 (file)
@@ -65,6 +65,21 @@ public class Foo
         assert classes == ['Foo'], classes
 
 
+
+    def test_dollar_sign(self):
+        """Test class names with $ in them"""
+
+        input = """\
+public class BadDep { 
+  public void new$rand () {}
+}
+"""
+        pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input)
+        assert pkg_dir == None, pkg_dir
+        assert classes == ['BadDep'], classes
+
+
+
     def test_inner_classes(self):
         """Test parsing various forms of inner classes"""
 
index 97a4e7028fcf9c2a5b909a3c034dba7b9f00bb07..d4e3815b3b025696bee709bd95bef5a577b54eea 100644 (file)
@@ -422,7 +422,54 @@ def CreateJavaFileBuilder(env):
         env['JAVASUFFIX'] = '.java'
     return java_file
 
-
+class ToolInitializer:
+    """
+    A class for delayed initialization of Tools modules.
+
+    This is intended to be added to a construction environment in
+    place of the method(s) normally called for a Builder (env.Object,
+    env.StaticObject, etc.).  When called, it searches the specified
+    list of tools, applies the first one that exists to the construction
+    environment, and calls whatever builder was (presumably) added the
+    construction environment in our place.
+    """
+    def __init__(self, name, tools):
+        """
+        Note:  we store the tool name as __name__ so it can be used by
+        the class that attaches this to a construction environment.
+        """
+        self.__name__ = name
+        if not SCons.Util.is_List(tools):
+            tools = [tools]
+        self.tools = tools
+    def __call__(self, env, *args, **kw):
+        for t in self.tools:
+            tool = SCons.Tool.Tool(t)
+            if tool.exists(env):
+                env.Tool(tool)
+                break
+
+        builder = getattr(env, self.__name__)
+        if builder is self:
+            # There was no Builder added, which means no valid Tool
+            # for this name was found (or possibly there's a mismatch
+            # between the name we were called by and the Builder name
+            # added by the Tool module).
+            #
+            # (Eventually this is where we'll put a more informative
+            # error message about the inability to find that tool
+            # as cut over more Builders+Tools to using this.
+            return [], []
+
+        # Let the construction environment remove the added method
+        # so we no longer copy and re-bind this method when the
+        # construction environment gets cloned.
+        env.RemoveMethod(self)
+        return apply(builder, args, kw)
+
+def Initializers(env):
+    env.AddMethod(ToolInitializer('Install', 'install'))
+    env.AddMethod(ToolInitializer('InstallAs', 'install'))
 
 def FindTool(tools, env):
     for tool in tools:
@@ -533,7 +580,7 @@ def tool_list(platform, env):
 
     other_tools = FindAllTools(['BitKeeper', 'CVS',
                                 'dmd',
-                                'install', 'filesystem',
+                                'filesystem',
                                 'dvipdf', 'dvips', 'gs',
                                 'jar', 'javac', 'javah',
                                 'latex', 'lex',
index 828bb9ee2ba78ad75c30b8a6bd830152e2a430c6..c7eee614b7b4f751418985ed3e42d86dbedaae47 100644 (file)
@@ -42,6 +42,7 @@ from SCons.Util import make_path_relative
 #
 # We keep track of *all* installed files.
 _INSTALLED_FILES = []
+_UNIQUE_INSTALLED_FILES = None
 
 #
 # Functions doing the actual work of the Install Builder.
@@ -101,12 +102,9 @@ def add_targets_to_INSTALLED_FILES(target, source, env):
     _INSTALLED_FILES global variable. This way all installed files of one
     scons call will be collected.
     """
-    global _INSTALLED_FILES
-    files = _INSTALLED_FILES
-    #files.extend( [ x for x in target if not x in files ] )
-    for x in target:
-        if not x in files:
-            files.append(x)
+    global _INSTALLED_FILES, _UNIQUE_INSTALLED_FILES
+    _INSTALLED_FILES.extend(target)
+    _UNIQUE_INSTALLED_FILES = None
     return (target, source)
 
 class DESTDIR_factory:
@@ -131,9 +129,42 @@ class DESTDIR_factory:
 install_action   = SCons.Action.Action(installFunc, stringFunc)
 installas_action = SCons.Action.Action(installFunc, stringFunc)
 
-InstallBuilder, InstallAsBuilder = None, None
 BaseInstallBuilder               = None
 
+def InstallBuilderWrapper(env, target, source, dir=None):
+    if target and dir:
+        raise SCons.Errors.UserError, "Both target and dir defined for Install(), only one may be defined."
+    if not dir:
+        dir=target
+
+    import SCons.Script
+    install_sandbox = SCons.Script.GetOption('install_sandbox')
+    if install_sandbox:
+        target_factory = DESTDIR_factory(env, install_sandbox)
+    else:
+        target_factory = env.fs
+
+    try:
+        dnodes = env.arg2nodes(dir, target_factory.Dir)
+    except TypeError:
+        raise SCons.Errors.UserError, "Target `%s' of Install() is a file, but should be a directory.  Perhaps you have the Install() arguments backwards?" % str(dir)
+    sources = env.arg2nodes(source, env.fs.Entry)
+    tgt = []
+    for dnode in dnodes:
+        for src in sources:
+            # Prepend './' so the lookup doesn't interpret an initial
+            # '#' on the file name portion as meaning the Node should
+            # be relative to the top-level SConstruct directory.
+            target = env.fs.Entry('.'+os.sep+src.name, dnode)
+            tgt.extend(BaseInstallBuilder(env, target, src))
+    return tgt
+
+def InstallAsBuilderWrapper(env, target, source):
+    result = []
+    for src, tgt in map(lambda x, y: (x, y), source, target):
+        result.extend(BaseInstallBuilder(env, tgt, src))
+    return result
+
 added = None
 
 def generate(env):
@@ -148,63 +179,31 @@ def generate(env):
                   action="store",
                   help='A directory under which all installed files will be placed.')
 
-    try:
-        env['BUILDERS']['Install']
-        env['BUILDERS']['InstallAs']
-
-    except KeyError, e:
+    global BaseInstallBuilder
+    if BaseInstallBuilder is None:
         install_sandbox = GetOption('install_sandbox')
         if install_sandbox:
             target_factory = DESTDIR_factory(env, install_sandbox)
         else:
             target_factory = env.fs
 
-        global BaseInstallBuilder
-        if BaseInstallBuilder is None:
-            BaseInstallBuilder = SCons.Builder.Builder(
-                                  action         = install_action,
-                                  target_factory = target_factory.Entry,
-                                  source_factory = env.fs.Entry,
-                                  multi          = 1,
-                                  emitter        = [ add_targets_to_INSTALLED_FILES, ],
-                                  name           = 'InstallBuilder')
-
-        global InstallBuilder
-        if InstallBuilder is None:
-            def InstallBuilderWrapper(env, target, source, dir=None, target_factory=target_factory):
-                if target and dir:
-                    raise SCons.Errors.UserError, "Both target and dir defined for Install(), only one may be defined."
-                if not dir:
-                    dir=target
-                try:
-                    dnodes = env.arg2nodes(dir, target_factory.Dir)
-                except TypeError:
-                    raise SCons.Errors.UserError, "Target `%s' of Install() is a file, but should be a directory.  Perhaps you have the Install() arguments backwards?" % str(dir)
-                sources = env.arg2nodes(source, env.fs.Entry)
-                tgt = []
-                for dnode in dnodes:
-                    for src in sources:
-                        # Prepend './' so the lookup doesn't interpret an initial
-                        # '#' on the file name portion as meaning the Node should
-                        # be relative to the top-level SConstruct directory.
-                        target = env.fs.Entry('.'+os.sep+src.name, dnode)
-                        tgt.extend(BaseInstallBuilder(env, target, src))
-                return tgt
-
-            InstallBuilder = InstallBuilderWrapper
-
-        global InstallAsBuilder
-        if InstallAsBuilder is None:
-            def InstallAsBuilderWrapper(env, target, source):
-                result = []
-                for src, tgt in map(lambda x, y: (x, y), source, target):
-                    result.extend(BaseInstallBuilder(env, tgt, src))
-                return result
-
-            InstallAsBuilder = InstallAsBuilderWrapper
-
-        env['BUILDERS']['Install']   = InstallBuilder
-        env['BUILDERS']['InstallAs'] = InstallAsBuilder
+        BaseInstallBuilder = SCons.Builder.Builder(
+                              action         = install_action,
+                              target_factory = target_factory.Entry,
+                              source_factory = env.fs.Entry,
+                              multi          = 1,
+                              emitter        = [ add_targets_to_INSTALLED_FILES, ],
+                              name           = 'InstallBuilder')
+
+    try:
+        env['BUILDERS']['Install']
+    except KeyError, e:
+        env['BUILDERS']['Install']   = InstallBuilderWrapper
+
+    try:
+        env['BUILDERS']['InstallAs']
+    except KeyError, e:
+        env['BUILDERS']['InstallAs'] = InstallAsBuilderWrapper
 
     # We'd like to initialize this doing something like the following,
     # but there isn't yet support for a ${SOURCE.type} expansion that
index 2d0a5a5ffa0f78ffd4f869550b9faafc93983315..673c8486dba3f921aa0faee30834d39d89831f09 100644 (file)
@@ -37,6 +37,9 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 import math, sys, os.path, glob, string, re
 
 is_windows = sys.platform == 'win32'
+is_win64 = is_windows and (os.environ['PROCESSOR_ARCHITECTURE'] == 'AMD64' or 
+                           (os.environ.has_key('PROCESSOR_ARCHITEW6432') and
+                            os.environ['PROCESSOR_ARCHITEW6432'] == 'AMD64'))
 is_linux = sys.platform == 'linux2'
 
 if is_windows:
@@ -138,7 +141,10 @@ def get_intel_registry_value(valuename, version=None, abi=None):
     Return a value from the Intel compiler registry tree. (Windows only)
     """
     # Open the key:
-    K = 'Software\\Intel\\Compilers\\C++\\' + version + '\\'+abi.upper()
+    if is_win64:
+        K = 'Software\\Wow6432Node\\Intel\\Compilers\\C++\\' + version + '\\'+abi.upper()
+    else:
+        K = 'Software\\Intel\\Compilers\\C++\\' + version + '\\'+abi.upper()
     try:
         k = SCons.Util.RegOpenKeyEx(SCons.Util.HKEY_LOCAL_MACHINE, K)
     except SCons.Util.RegError:
@@ -160,7 +166,10 @@ def get_all_compiler_versions():
     """
     versions=[]
     if is_windows:
-        keyname = 'Software\\Intel\\Compilers\\C++'
+        if is_win64:
+            keyname = 'Software\\WoW6432Node\\Intel\\Compilers\\C++'
+        else:
+            keyname = 'Software\\Intel\\Compilers\\C++'
         try:
             k = SCons.Util.RegOpenKeyEx(SCons.Util.HKEY_LOCAL_MACHINE,
                                         keyname)
@@ -178,7 +187,7 @@ def get_all_compiler_versions():
                 # than uninstalling properly), so the registry values
                 # are still there.
                 ok = False
-                for try_abi in ('IA32', 'IA32e',  'IA64'):
+                for try_abi in ('IA32', 'IA32e',  'IA64', 'EM64T'):
                     try:
                         d = get_intel_registry_value('ProductDir', subkey, try_abi)
                     except MissingRegistryError:
@@ -212,6 +221,7 @@ def get_intel_compiler_top(version, abi):
     The compiler will be in <top>/bin/icl.exe (icc on linux),
     the include dir is <top>/include, etc.
     """
+
     if is_windows:
         if not SCons.Util.can_read_reg:
             raise NoRegistryModuleError, "No Windows registry module was found"
@@ -282,8 +292,10 @@ def generate(env, version=None, abi=None, topdir=None, verbose=0):
             else:
                 abi = 'ia32'
         else:
-            # XXX: how would we do the same test on Windows?
-            abi = "ia32"
+            if is_win64:
+                abi = 'em64t'
+            else:
+                abi = 'ia32'
 
     if version and not topdir:
         try:
index 5703b2d1942345fd56fdddfc91534e3605161088..a0207272ac394ee0fe2d847b953b9422411eaed2 100644 (file)
@@ -133,20 +133,28 @@ JavaBuilder = SCons.Builder.Builder(action = JavaAction,
                     target_factory = SCons.Node.FS.Entry,
                     source_factory = SCons.Node.FS.Entry)
 
-def getClassPath(env,target, source, for_signature):
-    path = ""
-    if env.has_key('JAVACLASSPATH') and env['JAVACLASSPATH']:
-        path = SCons.Util.AppendPath(path, env['JAVACLASSPATH'])
-        return "-classpath %s" % (path)
-    else:
-        return ""
-
-def getSourcePath(env,target, source, for_signature):
-    path = ""
-    if env.has_key('JAVASOURCEPATH') and env['JAVASOURCEPATH']:
-        path = SCons.Util.AppendPath(path, env['JAVASOURCEPATH'])
-    path = SCons.Util.AppendPath(path,['${TARGET.attributes.java_sourcedir}'])
-    return "-sourcepath %s" % (path)
+class pathopt:
+    """
+    Callable object for generating javac-style path options from
+    a construction variable (e.g. -classpath, -sourcepath).
+    """
+    def __init__(self, opt, var, default=None):
+        self.opt = opt
+        self.var = var
+        self.default = default
+
+    def __call__(self, target, source, env, for_signature):
+        path = env[self.var]
+        if path and not SCons.Util.is_List(path):
+            path = [path]
+        if self.default:
+            path = path + [ env[self.default] ]
+        if path:
+            return [self.opt, string.join(path, os.pathsep)]
+            #return self.opt + " " + string.join(path, os.pathsep)
+        else:
+            return []
+            #return ""
 
 def Java(env, target, source, *args, **kw):
     """
@@ -195,16 +203,20 @@ def generate(env):
 
     env.AddMethod(Java)
 
-    env['JAVAC']            = 'javac'
-    env['JAVACFLAGS']       = SCons.Util.CLVar('')
-    env['JAVACLASSPATH']    = []
-    env['JAVASOURCEPATH']   = []
-    env['_JAVACLASSPATH']   = getClassPath
-    env['_JAVASOURCEPATH']  = getSourcePath
-    env['_JAVACCOM']        = '$JAVAC $JAVACFLAGS $_JAVACLASSPATH -d ${TARGET.attributes.java_classdir} $_JAVASOURCEPATH $SOURCES'
-    env['JAVACCOM']         = "${TEMPFILE('$_JAVACCOM')}"
-    env['JAVACLASSSUFFIX']  = '.class'
-    env['JAVASUFFIX']       = '.java'
+    env['JAVAC']                    = 'javac'
+    env['JAVACFLAGS']               = SCons.Util.CLVar('')
+    env['JAVABOOTCLASSPATH']        = []
+    env['JAVACLASSPATH']            = []
+    env['JAVASOURCEPATH']           = []
+    env['_javapathopt']             = pathopt
+    env['_JAVABOOTCLASSPATH']       = '${_javapathopt("-bootclasspath", "JAVABOOTCLASSPATH")} '
+    env['_JAVACLASSPATH']           = '${_javapathopt("-classpath", "JAVACLASSPATH")} '
+    env['_JAVASOURCEPATH']          = '${_javapathopt("-sourcepath", "JAVASOURCEPATH", "_JAVASOURCEPATHDEFAULT")} '
+    env['_JAVASOURCEPATHDEFAULT']   = '${TARGET.attributes.java_sourcedir}'
+    env['_JAVACCOM']                = '$JAVAC $JAVACFLAGS $_JAVABOOTCLASSPATH $_JAVACLASSPATH -d ${TARGET.attributes.java_classdir} $_JAVASOURCEPATH $SOURCES'
+    env['JAVACCOM']                 = "${TEMPFILE('$_JAVACCOM')}"
+    env['JAVACLASSSUFFIX']          = '.class'
+    env['JAVASUFFIX']               = '.java'
 
 def exists(env):
     return 1
index f5af975776ebb85e68abac07a4ca8f65b9ba1104..ee9368534a3b4297bebb0a3401f1af36dc442470 100644 (file)
@@ -14,6 +14,7 @@ JAVACFLAGS
 JAVACCOM
 JAVACLASSSUFFIX
 JAVASUFFIX
+JAVABOOTCLASSPATH
 JAVACLASSPATH
 JAVASOURCEPATH
 </sets>
@@ -70,6 +71,19 @@ env.Java(target = 'classes', source = ['File1.java', 'File2.java'])
 </summary>
 </builder>
 
+<cvar name="JAVABOOTCLASSPATH">
+<summary>
+Specifies the list of directories that
+will be added to the
+&javac; command line
+via the <option>-bootclasspath</option> option.
+The individual directory names will be
+separated by the operating system's path separate character
+(<filename>:</filename> on UNIX/Linux/POSIX,
+<filename>;</filename> on Windows).
+</summary>
+</cvar>
+
 <cvar name="JAVAC">
 <summary>
 The Java compiler.
index 811d573215234b491f334ebefeee5d2d779e1458..a02570118d19faebca63b6d0eaa6ed825704e2e6 100644 (file)
@@ -40,7 +40,6 @@ import SCons.Builder
 import SCons.Defaults
 import SCons.Scanner.IDL
 import SCons.Util
-import SCons.Tool.msvs
 
 def midl_emitter(target, source, env):
     """Produces a list of outputs from the MIDL compiler"""
@@ -80,6 +79,10 @@ def generate(env):
     env['BUILDERS']['TypeLibrary'] = midl_builder
 
 def exists(env):
+    if not env['PLATFORM'] in ('win32', 'cygwin'):
+        return 0
+
+    import SCons.Tool.msvs
     if SCons.Tool.msvs.is_msvs_installed():
         # there's at least one version of MSVS installed, which comes with midl:
         return 1
index 138f920c72085463a3472bf90dd4fb45955bd8b9..d4efa74d17ad72bc75ca7332a9938a730d4f4272 100644 (file)
@@ -1763,6 +1763,9 @@ def generate(env):
     env['SCONS_HOME'] = os.environ.get('SCONS_HOME')
 
 def exists(env):
+    if not env['PLATFORM'] in ('win32', 'cygwin'):
+        return 0
+
     try:
         v = SCons.Tool.msvs.get_visualstudio_versions()
     except (SCons.Util.RegError, SCons.Errors.InternalError):
index b1125e7164acff7b1a8771ee41eea629e94a80bb..829628719171412634f07fb0610425ddedfaa44a 100644 (file)
@@ -606,6 +606,7 @@ if __name__ == "__main__":
 
     # only makes sense to test this on win32
     if sys.platform != 'win32':
+        sys.stdout.write("NO RESULT for msvsTests.py:  '%s' is not win32\n" % sys.platform)
         sys.exit(0)
 
     SCons.Util.RegOpenKeyEx = DummyOpenKeyEx
index e8f6a0914edfea8444dfb1a00e03faff4d9cc21e..72bbff0026ad68c9fea827b818ac9df160327e67 100644 (file)
@@ -32,6 +32,7 @@ import SCons.Environment
 from SCons.Options import *
 from SCons.Errors import *
 from SCons.Util import is_List, make_path_relative
+from SCons.Warnings import warn, Warning
 
 import os, imp
 import SCons.Defaults
@@ -96,7 +97,11 @@ def Package(env, target=None, source=None, **kw):
     try: kw['PACKAGETYPE']=env['PACKAGETYPE']
     except KeyError: pass
 
-    if not kw.has_key('PACKAGETYPE') or kw['PACKAGETYPE']==None:
+    if not kw.get('PACKAGETYPE'):
+        from SCons.Script import GetOption
+        kw['PACKAGETYPE'] = GetOption('package_type')
+
+    if kw['PACKAGETYPE'] == None:
         if env['BUILDERS'].has_key('Tar'):
             kw['PACKAGETYPE']='targz'
         elif env['BUILDERS'].has_key('Zip'):
@@ -175,76 +180,48 @@ def Package(env, target=None, source=None, **kw):
     targets.extend(env.Alias( 'package', targets ))
     return targets
 
-def build_source(ss, sources):
-    for s in ss:
-        if s.__class__==SCons.Node.FS.Dir:
-            build_source(s.all_children())
-        elif not s.has_builder() and s.__class__==SCons.Node.FS.File:
-            sources.append(s)
-        else:
-            build_source(s.sources)
-
-def FindSourceFiles(env, target=None, source=None ):
-    """ returns a list of all children of the target nodes, which have no
-    children. This selects all leaves of the DAG that gets build by SCons for
-    handling dependencies.
-    """
-    if target==None: target = '.'
-
-    nodes = env.arg2nodes(target, env.fs.Entry)
-
-    sources = []
-    for node in nodes:
-        build_source(node.all_children(), sources)
-
-    # now strip the build_node from the sources by calling the srcnode
-    # function
-    def get_final_srcnode(file):
-        srcnode = file.srcnode()
-        while srcnode != file.srcnode():
-            srcnode = file.srcnode()
-        return srcnode
-
-    # get the final srcnode for all nodes, this means stripping any
-    # attached build node.
-    map( get_final_srcnode, sources )
-
-    # remove duplicates
-    return list(set(sources))
-
-def FindInstalledFiles(env, source=[], target=[]):
-    """ returns the list of all targets of the Install and InstallAs Builder.
-    """
-    from SCons.Tool import install
-    return install._INSTALLED_FILES
-
 #
 # SCons tool initialization functions
 #
+
+added = None
+
 def generate(env):
+    from SCons.Script import AddOption
+    global added
+    if not added:
+        added = 1
+        AddOption('--package-type',
+                  dest='package_type',
+                  default=None,
+                  type="string",
+                  action="store",
+                  help='The type of package to create.')
+
     try:
         env['BUILDERS']['Package']
         env['BUILDERS']['Tag']
-        env['BUILDERS']['FindSourceFiles']
-        env['BUILDERS']['FindInstalledFiles']
     except KeyError:
         env['BUILDERS']['Package'] = Package
         env['BUILDERS']['Tag'] = Tag
-        env['BUILDERS']['FindSourceFiles'] = FindSourceFiles
-        env['BUILDERS']['FindInstalledFiles'] = FindInstalledFiles
 
 def exists(env):
     return 1
 
+# XXX
 def options(opts):
     opts.AddOptions(
-        EnumOption( [ 'PACKAGETYPE', '--package-type' ],
+        EnumOption( 'PACKAGETYPE',
                     'the type of package to create.',
                     None, allowed_values=map( str, __all__ ),
                     ignorecase=2
                   )
     )
 
+#
+# Internal utility functions
+#
+
 def copy_attr(f1, f2):
     """ copies the special packaging file attributes from f1 to f2.
     """
@@ -254,79 +231,69 @@ def copy_attr(f1, f2):
     pattrs = filter(copyit, dir(f1))
     for attr in pattrs:
         setattr(f2, attr, getattr(f1, attr))
-#
-# Emitter functions which are reused by the various packagers
-#
-def packageroot_emitter(pkg_root, honor_install_location=1):
-    """ creates  the packageroot emitter.
-
-    The package root emitter uses the CopyAs builder to copy all source files
-    to the directory given in pkg_root.
+def putintopackageroot(target, source, env, pkgroot, honor_install_location=1):
+    """ Uses the CopyAs builder to copy all source files to the directory given
+    in pkgroot.
 
     If honor_install_location is set and the copied source file has an
     PACKAGING_INSTALL_LOCATION attribute, the PACKAGING_INSTALL_LOCATION is
-    used as the new name of the source file under pkg_root.
+    used as the new name of the source file under pkgroot.
 
-    The source file will not be copied if it is already under the the pkg_root
+    The source file will not be copied if it is already under the the pkgroot
     directory.
 
     All attributes of the source file will be copied to the new file.
     """
-    def package_root_emitter(target, source, env, pkg_root=pkg_root, honor_install_location=honor_install_location):
-        pkgroot = pkg_root
-        # make sure the packageroot is a Dir object.
-        if SCons.Util.is_String(pkgroot): pkgroot=env.Dir(pkgroot)
-
-        def copy_file_to_pkg_root(file, env=env, pkgroot=pkgroot, honor_install_location=honor_install_location):
-            if file.is_under(pkgroot):
-                return file
-            else:
-                if hasattr(file, 'PACKAGING_INSTALL_LOCATION') and\
-                   honor_install_location:
-                    new_name=make_path_relative(file.PACKAGING_INSTALL_LOCATION)
-                else:
-                    new_name=make_path_relative(file.get_path())
+    # make sure the packageroot is a Dir object.
+    if SCons.Util.is_String(pkgroot):  pkgroot=env.Dir(pkgroot)
+    if not SCons.Util.is_List(source): source=[source]
 
-                new_file=pkgroot.File(new_name)
-                new_file=env.CopyAs(new_file, file)[0]
+    new_source = []
+    for file in source:
+        if SCons.Util.is_String(file): file = env.File(file)
 
-                copy_attr(file, new_file)
+        if file.is_under(pkgroot):
+            new_source.append(file)
+        else:
+            if hasattr(file, 'PACKAGING_INSTALL_LOCATION') and\
+                       honor_install_location:
+                new_name=make_path_relative(file.PACKAGING_INSTALL_LOCATION)
+            else:
+                new_name=make_path_relative(file.get_path())
 
-                return new_file
-        return (target, map(copy_file_to_pkg_root, source))
-    return package_root_emitter
+            new_file=pkgroot.File(new_name)
+            new_file=env.CopyAs(new_file, file)[0]
+            copy_attr(file, new_file)
+            new_source.append(new_file)
 
-from SCons.Warnings import warn, Warning
+    return (target, new_source)
 
-def stripinstall_emitter():
-    """ create the a emitter which:
-     * strips of the Install Builder of the source target, and stores the
-       install location as the "PACKAGING_INSTALL_LOCATION" of the given source
-       File object. This effectively avoids having to execute the Install
-       Action while storing the needed install location.
-     * warns about files that are mangled by this emitter which have no
-       Install Builder.
+def stripinstallbuilder(target, source, env):
+    """ strips the install builder action from the source list and stores
+    the final installation location as the "PACKAGING_INSTALL_LOCATION" of
+    the source of the source file. This effectively removes the final installed
+    files from the source list while remembering the installation location.
+
+    It also warns about files which have no install builder attached.
     """
-    def strip_install_emitter(target, source, env):
-        def has_no_install_location(file):
-            return not (file.has_builder() and\
-                hasattr(file.builder, 'name') and\
-                (file.builder.name=="InstallBuilder" or\
-                 file.builder.name=="InstallAsBuilder"))
-
-        if len(filter(has_no_install_location, source)):
-            warn(Warning, "there are file to package which have no\
-            InstallBuilder attached, this might lead to irreproducible packages")
-
-        n_source=[]
-        for s in source:
-            if has_no_install_location(s):
-                n_source.append(s)
-            else:
-                for ss in s.sources:
-                    n_source.append(ss)
-                    copy_attr(s, ss)
-                    setattr(ss, 'PACKAGING_INSTALL_LOCATION', s.get_path())
+    def has_no_install_location(file):
+        return not (file.has_builder() and\
+            hasattr(file.builder, 'name') and\
+            (file.builder.name=="InstallBuilder" or\
+             file.builder.name=="InstallAsBuilder"))
+
+    if len(filter(has_no_install_location, source)):
+        warn(Warning, "there are files to package which have no\
+        InstallBuilder attached, this might lead to irreproducible packages")
+
+    n_source=[]
+    for s in source:
+        if has_no_install_location(s):
+            n_source.append(s)
+        else:
+            for ss in s.sources:
+                n_source.append(ss)
+                copy_attr(s, ss)
+                setattr(ss, 'PACKAGING_INSTALL_LOCATION', s.get_path())
 
-        return (target, n_source)
-    return strip_install_emitter
+    return (target, n_source)
index 267fe3c69edfdbdc369e402f656eef907c07c8b1..e3ae2775b7b6483bf4164c79cc85bbe371922eb6 100644 (file)
@@ -30,7 +30,7 @@ import SCons.Builder
 import SCons.Node.FS
 import os
 
-from SCons.Tool.packaging import stripinstall_emitter, packageroot_emitter
+from SCons.Tool.packaging import stripinstallbuilder, putintopackageroot
 
 def package(env, target, source, PACKAGEROOT, NAME, VERSION, DESCRIPTION,
             SUMMARY, X_IPK_PRIORITY, X_IPK_SECTION, SOURCE_URL,
@@ -42,8 +42,8 @@ def package(env, target, source, PACKAGEROOT, NAME, VERSION, DESCRIPTION,
 
     # setup the Ipkg builder
     bld = env['BUILDERS']['Ipkg']
-    bld.push_emitter(packageroot_emitter(PACKAGEROOT))
-    bld.push_emitter(stripinstall_emitter())
+    target, source = stripinstallbuilder(target, source, env)
+    target, source = putintopackageroot(target, source, env, PACKAGEROOT)
 
     # This should be overridable from the construction environment,
     # which it is by using ARCHITECTURE=.
@@ -85,7 +85,7 @@ def gen_ipk_dir(proot, source, env, kw):
     #  create the specfile builder
     s_bld=SCons.Builder.Builder(
         action  = build_specfiles,
-        emitter = [stripinstall_emitter(), packageroot_emitter(proot)])
+        )
 
     # create the specfile targets
     spec_target=[]
index 7fc7892a2132b533efa3bc3bb0855242fc690c86..1ce2b1a35bc67eb4d9dbc3097d58732ee588337d 100644 (file)
@@ -36,7 +36,7 @@ from SCons.Builder import Builder
 from xml.dom.minidom import *
 from xml.sax.saxutils import escape
 
-from SCons.Tool.packaging import stripinstall_emitter
+from SCons.Tool.packaging import stripinstallbuilder
 
 #
 # Utility functions
@@ -223,7 +223,7 @@ def build_wxsfile(target, source, env):
 #
 # setup function
 #
-def create_default_directory_layout(root, NAME, VERSION, vendor, filename_set):
+def create_default_directory_layout(root, NAME, VERSION, VENDOR, filename_set):
     """ Create the wix default target directory layout and return the innermost
     directory.
 
@@ -231,7 +231,7 @@ def create_default_directory_layout(root, NAME, VERSION, vendor, filename_set):
     the Product tag.
 
     Everything is put under the PFiles directory property defined by WiX.
-    After that a directory  with the 'vendor' tag is placed and then a
+    After that a directory  with the 'VENDOR' tag is placed and then a
     directory with the name of the project and its VERSION. This leads to the
     following TARGET Directory Layout:
     C:\<PFiles>\<Vendor>\<Projectname-Version>\
@@ -247,9 +247,9 @@ def create_default_directory_layout(root, NAME, VERSION, vendor, filename_set):
     d2.attributes['Name'] = 'PFiles'
 
     d3 = doc.createElement( 'Directory' )
-    d3.attributes['Id']       = 'vendor_folder'
-    d3.attributes['Name']     = escape( gen_dos_short_file_name( vendor, filename_set ) )
-    d3.attributes['LongName'] = escape( vendor )
+    d3.attributes['Id']       = 'VENDOR_folder'
+    d3.attributes['Name']     = escape( gen_dos_short_file_name( VENDOR, filename_set ) )
+    d3.attributes['LongName'] = escape( VENDOR )
 
     d4 = doc.createElement( 'Directory' )
     project_folder            = "%s-%s" % ( NAME, VERSION )
@@ -268,7 +268,7 @@ def create_default_directory_layout(root, NAME, VERSION, vendor, filename_set):
 #
 # mandatory and optional file tags
 #
-def build_wxsfile_file_section(root, files, NAME, VERSION, vendor, filename_set, id_set):
+def build_wxsfile_file_section(root, files, NAME, VERSION, VENDOR, filename_set, id_set):
     """ builds the Component sections of the wxs file with their included files.
 
     Files need to be specified in 8.3 format and in the long name format, long
@@ -276,7 +276,7 @@ def build_wxsfile_file_section(root, files, NAME, VERSION, vendor, filename_set,
 
     Features are specficied with the 'X_MSI_FEATURE' or 'DOC' FileTag.
     """
-    root       = create_default_directory_layout( root, NAME, VERSION, vendor, filename_set )
+    root       = create_default_directory_layout( root, NAME, VERSION, VENDOR, filename_set )
     components = create_feature_dict( files )
     factory    = Document()
 
@@ -470,7 +470,7 @@ def build_wxsfile_header_section(root, spec):
     # mandatory sections, will throw a KeyError if the tag is not available
     Product.attributes['Name']         = escape( spec['NAME'] )
     Product.attributes['Version']      = escape( spec['VERSION'] )
-    Product.attributes['Manufacturer'] = escape( spec['vendor'] )
+    Product.attributes['Manufacturer'] = escape( spec['VENDOR'] )
     Product.attributes['Language']     = escape( spec['X_MSI_LANGUAGE'] )
     Package.attributes['Description']  = escape( spec['SUMMARY'] )
 
@@ -490,12 +490,11 @@ def build_wxsfile_header_section(root, spec):
 
 # this builder is the entry-point for .wxs file compiler.
 wxs_builder = Builder(
-    action  = Action( build_wxsfile, string_wxsfile ),
-    emitter = stripinstall_emitter(),
-    suffix  = '.wxs' )
+    action         = Action( build_wxsfile, string_wxsfile ),
+    ensure_suffix  = '.wxs' )
 
 def package(env, target, source, PACKAGEROOT, NAME, VERSION,
-            DESCRIPTION, SUMMARY, **kw):
+            DESCRIPTION, SUMMARY, VENDOR, X_MSI_LANGUAGE, **kw):
     # make sure that the Wix Builder is in the environment
     SCons.Tool.Tool('wix').generate(env)
 
@@ -507,6 +506,9 @@ def package(env, target, source, PACKAGEROOT, NAME, VERSION,
     kw.update(loc)
     del kw['source'], kw['target'], kw['env']
 
+    # strip the install builder from the source files
+    target, source = stripinstallbuilder(target, source, env)
+
     # put the arguments into the env and call the specfile builder.
     env['msi_spec'] = kw
     specfile = apply( wxs_builder, [env, target, source], kw )
diff --git a/src/engine/SCons/Tool/packaging/packager.py b/src/engine/SCons/Tool/packaging/packager.py
deleted file mode 100644 (file)
index 7aca2b6..0000000
+++ /dev/null
@@ -1,218 +0,0 @@
-"""SCons.Tool.Packaging.packager
-"""
-
-#
-# __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__"
-
-import os
-import SCons.Defaults
-from SCons.Util import strip_abs_path
-
-class Packager:
-    """ abstract superclass of all packagers.
-
-    Defines the minimal set of function which need to be implemented in order
-    to create a new packager.
-    """
-    def __init__(self):
-        self.specfile_suffix = '.spec'
-
-    def create_builder(self, env, kw):
-        raise Exception( "%s does not implement create_builder()" % (self.__class__.__name_) )
-
-    def add_targets(self, kw):
-        """ In the absence of a target this method creates a default one of
-        the spec given in the kw argument.
-        """
-        if not kw.has_key('target'):
-            NAME, VERSION = kw['projectname'], kw['version']
-            kw['target'] = [ "%s-%s"%(NAME,VERSION) ]
-
-        return kw
-
-    def strip_install_emitter(self, target, source, env):
-        """ this emitter assert that all files in source have an InstallBuilder
-        attached. We take their sources and copy over the file tags, so the
-        the builder this emitter is attached to is independent of the *installed*
-        files.
-        """
-        tag_factories = [ LocationTagFactory() ]
-
-        def has_no_install_location(file):
-            return not ( file.has_builder() and (file.builder.name == 'InstallBuilder' or file.builder.name == 'InstallAsBuilder') )
-
-        # check if all source file belong into this package.
-        files = filter( has_no_install_location, source )
-        if len( files ) != 0:
-            raise SCons.Errors.UserError( "There are files which have no Install() Builder attached and are therefore not packageable\n%s" % map( lambda x: x.get_path(), files ) )
-
-        # All source files have an InstallBuilder attached and we don't want our
-        # package to be dependent on the *installed* files but only on the
-        # files that will be installed. Therefore we only care for sources of
-        # the files in the source list.
-        n_source = []
-
-        for s in source:
-            n_s = s.sources[0]
-            n_s.set_tags( s.get_tags(tag_factories) )
-            n_source.append( n_s )
-
-        return ( target, n_source )
-
-
-class BinaryPackager(Packager):
-    """ abstract superclass for all packagers creating a binary package.
-
-    Binary packagers are seperated from source packager by their requirement to
-    create a specfile or manifest file. This file contains the contents of the
-    binary packager together with some information about specific files.
-
-    This superclass provides two needed facilities:
-     * its specfile_emitter function sets up the correct list of source file
-       and warns about files with no InstallBuilder attached.
-    """
-    def create_specfile_targets(self, kw):
-        """ returns the specfile target name(s).
-
-        This function is called by specfile_emitter to find out the specfiles
-        target name.
-        """
-        p, v = kw['NAME'], kw['VERSION']
-        return '%s-%s' % ( p, v )
-
-    def specfile_emitter(self, target, source, env):
-        """ adds the to build specfile to the source list.
-        """
-        # create a specfile action that is executed for building the specfile
-        specfile_action = SCons.Action.Action( self.build_specfile,
-                                               self.string_specfile,
-                                               varlist=[ 'SPEC' ] )
-
-        # create a specfile Builder with the right sources attached.
-        specfile_builder = SCons.Builder.Builder( action = self.build_specfile,
-                                                  suffix = self.specfile_suffix )
-
-        specfile = apply( specfile_builder, [ env ], {
-                          'target' : self.create_specfile_targets(env),
-                          'source' : source } )
-
-        specfile.extend( source )
-        return ( target, specfile )
-
-    def string_specfile(self, target, source, env):
-        return "building specfile %s"%(target[0].abspath)
-
-    def build_specfile(self, target, source, env):
-        """ this function is called to build the specfile of name "target"
-        from the source list and the settings in "env"
-        """
-        raise Exception( 'class does not implement build_specfile()' )
-
-class SourcePackager(Packager):
-    """ abstract superclass for all packagers which generate a source package.
-
-    They are seperated from other packager by the their package_root attribute.
-    Since before a source package is created with the help of a Tar or Zip
-    builder their content needs to be moved to a package_root. For example the
-    project foo with VERSION 1.2.3, will get its files placed in foo-1.2.3/.
-    """
-    def create_package_root(self,kw):
-        """ creates the package_r oot for a given specification dict.
-        """
-        try:
-            return kw['package_root']
-        except KeyError:
-            NAME, VERSION = kw['projectname'], kw['version']
-            return "%s-%s"%(NAME,VERSION)
-
-    def package_root_emitter(self, pkg_root, honor_install_location=1):
-        def package_root_emitter(target, source, env):
-            """ This emitter copies the sources to the src_package_root directory:
-             * if a source has an install_location, not its original name is
-               used but the one specified in the 'install_location' tag.
-             * else its original name is used.
-             * if the source file is already in the src_package_root directory, 
-               nothing will be done.
-            """
-            new_source = []
-            for s in source:
-                if os.path.dirname(s.get_path()).rfind(pkg_root) != -1:
-                    new_source.append(s)
-                else:
-                    tags     = s.get_tags()
-                    new_s    = None
-
-                    if tags.has_key( 'install_location' ) and honor_install_location:
-                        my_target = strip_abs_path(tags['install_location'])
-                    else:
-                        my_target = strip_abs_path(s.get_path())
-
-                    new_s = env.CopyAs( os.path.join( pkg_root, my_target ), s )[0]
-
-                    # store the tags of our original file in the new file.
-                    new_s.set_tags( s.get_tags() )
-                    new_source.append( new_s )
-
-            return (target, new_source)
-
-        return package_root_emitter
-
-class TagFactory:
-    """An instance of this class has the responsibility to generate additional
-    tags for a SCons.Node.FS.File instance.
-
-    Subclasses have to be callable. This class definition is informally
-    describing the interface.
-    """
-
-    def __call__(self, file, current_tag_dict):
-        """ This call has to return additional tags in the form of a dict.
-        """
-        pass
-
-    def attach_additional_info(self, info=None):
-        pass
-
-class LocationTagFactory(TagFactory):
-    """ This class creates the "location" tag, which describes the install
-    location of a given file.
-
-    This is done by analyzing the builder of a given file for a InstallBuilder,
-    from this builder the install location is deduced.
-    """
-
-    def __call__(self, file, current_tag_dict):
-        if current_tag_dict.has_key('install_location'):
-            return {}
-
-        if file.has_builder() and\
-           (file.builder.name == "InstallBuilder" or\
-            file.builder.name == "InstallAsBuilder") and\
-           file.has_explicit_builder():
-            return { 'install_location' : file.get_path() }
-        else:
-            return {}
-
-
index 94b7b7aac23c4ab55c60e81bd32f2a831830645e..5f976088f21220e345f771802a681e6eb9f0ba33 100644 (file)
@@ -33,7 +33,9 @@ import string
 
 import SCons.Builder
 
-from SCons.Tool.packaging import stripinstall_emitter, packageroot_emitter, src_targz
+from SCons.Environment import OverrideEnvironment
+from SCons.Tool.packaging import stripinstallbuilder, src_targz
+from SCons.Errors import UserError
 
 def package(env, target, source, PACKAGEROOT, NAME, VERSION,
             PACKAGEVERSION, DESCRIPTION, SUMMARY, X_RPM_GROUP, LICENSE,
@@ -43,12 +45,13 @@ def package(env, target, source, PACKAGEROOT, NAME, VERSION,
 
     bld = env['BUILDERS']['Rpm']
 
-    bld.push_emitter(targz_emitter)
-    bld.push_emitter(specfile_emitter)
-    bld.push_emitter(stripinstall_emitter())
-
-    # override the default target, with the rpm specific ones.
-    if str(target[0])=="%s-%s"%(NAME, VERSION):
+    # Generate a UserError whenever the target name has been set explicitly,
+    # since rpm does not allow for controlling it. This is detected by
+    # checking if the target has been set to the default by the Package()
+    # Environment function.
+    if str(target[0])!="%s-%s"%(NAME, VERSION):
+        raise UserError( "Setting target is not supported for rpm." )
+    else:
         # This should be overridable from the construction environment,
         # which it is by using ARCHITECTURE=.
         # Guessing based on what os.uname() returns at least allows it
@@ -79,13 +82,19 @@ def package(env, target, source, PACKAGEROOT, NAME, VERSION,
 
     # if no "SOURCE_URL" tag is given add a default one.
     if not kw.has_key('SOURCE_URL'):
-        kw['SOURCE_URL']=(str(target[0])+".tar.gz").replace('.rpm', '')
+        #kw['SOURCE_URL']=(str(target[0])+".tar.gz").replace('.rpm', '')
+        kw['SOURCE_URL']=string.replace(str(target[0])+".tar.gz", '.rpm', '')
+
+    # mangle the source and target list for the rpmbuild
+    env = OverrideEnvironment(env, kw)
+    target, source = stripinstallbuilder(target, source, env)
+    target, source = addspecfile(target, source, env)
+    target, source = collectintargz(target, source, env)
 
     # now call the rpm builder to actually build the packet.
     return apply(bld, [env, target, source], kw)
 
-
-def targz_emitter(target, source, env):
+def collectintargz(target, source, env):
     """ Puts all source files into a tar.gz file. """
     # the rpm tool depends on a source package, until this is chagned
     # this hack needs to be here that tries to pack all sources in.
@@ -116,7 +125,7 @@ def targz_emitter(target, source, env):
 
     return (target, tarball)
 
-def specfile_emitter(target, source, env):
+def addspecfile(target, source, env):
     specfile = "%s-%s" % (env['NAME'], env['VERSION'])
 
     bld = SCons.Builder.Builder(action         = build_specfile,
@@ -180,7 +189,7 @@ def build_specfile_sections(spec):
     # Default prep, build, install and clean rules
     # TODO: optimize those build steps, to not compile the project a second time
     if not spec.has_key('X_RPM_PREP'):
-        spec['X_RPM_PREP'] = 'rm -rf "$RPM_BUILD_ROOT"' + '\n%setup -q'
+        spec['X_RPM_PREP'] = '[ -n "$RPM_BUILD_ROOT" -a "$RPM_BUILD_ROOT" != / ] && rm -rf "$RPM_BUILD_ROOT"' + '\n%setup -q'
 
     if not spec.has_key('X_RPM_BUILD'):
         spec['X_RPM_BUILD'] = 'mkdir "$RPM_BUILD_ROOT"'
@@ -189,7 +198,7 @@ def build_specfile_sections(spec):
         spec['X_RPM_INSTALL'] = 'scons --install-sandbox="$RPM_BUILD_ROOT" "$RPM_BUILD_ROOT"'
 
     if not spec.has_key('X_RPM_CLEAN'):
-        spec['X_RPM_CLEAN'] = 'rm -rf "$RPM_BUILD_ROOT"'
+        spec['X_RPM_CLEAN'] = '[ -n "$RPM_BUILD_ROOT" -a "$RPM_BUILD_ROOT" != / ] && rm -rf "$RPM_BUILD_ROOT"'
 
     str = str + SimpleTagCompiler(optional_sections, mandatory=0).compile( spec )
 
index 7d876e48c9f4dcbb9f425d3ee028694e124ca6b8..83231072d9ca16cae63e1dae6d23733b22ed5e20 100644 (file)
@@ -28,10 +28,10 @@ The tarbz2 SRC packager.
 
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
-from SCons.Tool.packaging import packageroot_emitter
+from SCons.Tool.packaging import putintopackageroot
 
 def package(env, target, source, PACKAGEROOT, **kw):
     bld = env['BUILDERS']['Tar']
     bld.set_suffix('.tar.bz2')
-    bld.push_emitter(packageroot_emitter(PACKAGEROOT, honor_install_location=0))
+    target, source = putintopackageroot(target, source, env, PACKAGEROOT, honor_install_location=0)
     return bld(env, target, source, TARFLAGS='-jc')
index d84976e36f2a8d9efeaa738273db8a006ce25c7d..db512798d95c7558c60dc0ab30d3c77dd0f4efcf 100644 (file)
@@ -28,10 +28,10 @@ The targz SRC packager.
 
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
-from SCons.Tool.packaging import packageroot_emitter
+from SCons.Tool.packaging import putintopackageroot
 
 def package(env, target, source, PACKAGEROOT, **kw):
     bld = env['BUILDERS']['Tar']
     bld.set_suffix('.tar.gz')
-    bld.push_emitter(packageroot_emitter(PACKAGEROOT, honor_install_location=0))
+    target, source = putintopackageroot(target, source, env, PACKAGEROOT, honor_install_location=0)
     return bld(env, target, source, TARFLAGS='-zc')
index d60fe85b5cb815278d7e24f8e005a40d1eec6bc7..01bd42e767e0d242a014c72d47eaf7b1cf979124 100644 (file)
@@ -28,10 +28,10 @@ The zip SRC packager.
 
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
-from SCons.Tool.packaging import packageroot_emitter
+from SCons.Tool.packaging import putintopackageroot
 
 def package(env, target, source, PACKAGEROOT, **kw):
     bld = env['BUILDERS']['Zip']
     bld.set_suffix('.zip')
-    bld.push_emitter(packageroot_emitter(PACKAGEROOT, honor_install_location=0))
+    target, source = putintopackageroot(target, source, env, PACKAGEROOT, honor_install_location=0)
     return bld(env, target, source)
index 7127896472fbd7793e55cdc8ad23c2962dec783d..52252da404336f90937e212addfc3697fa4cba38 100644 (file)
@@ -28,11 +28,11 @@ The tarbz2 SRC packager.
 
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
-from SCons.Tool.packaging import stripinstall_emitter, packageroot_emitter
+from SCons.Tool.packaging import stripinstallbuilder, putintopackageroot
 
 def package(env, target, source, PACKAGEROOT, **kw):
     bld = env['BUILDERS']['Tar']
     bld.set_suffix('.tar.gz')
-    bld.push_emitter(packageroot_emitter(PACKAGEROOT))
-    bld.push_emitter(stripinstall_emitter())
+    target, source = putintopackageroot(target, source, env, PACKAGEROOT)
+    target, source = stripinstallbuilder(target, source, env)
     return bld(env, target, source, TARFLAGS='-jc')
index 798e570f2915f36c61e67453932bf360a80aa262..47138406a6b6bf2db0ed25f0909156f5ec7055bd 100644 (file)
@@ -28,11 +28,11 @@ The targz SRC packager.
 
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
-from SCons.Tool.packaging import stripinstall_emitter, packageroot_emitter
+from SCons.Tool.packaging import stripinstallbuilder, putintopackageroot
 
 def package(env, target, source, PACKAGEROOT, **kw):
     bld = env['BUILDERS']['Tar']
     bld.set_suffix('.tar.gz')
-    bld.push_emitter(packageroot_emitter(PACKAGEROOT))
-    bld.push_emitter(stripinstall_emitter())
+    target, source = stripinstallbuilder(target, source, env)
+    target, source = putintopackageroot(target, source, env, PACKAGEROOT)
     return bld(env, target, source, TARFLAGS='-zc')
index 7663a4aa2d84d4c51e2ec1193e460c8bacd6eb95..639d5691eed06ae88c65361302e396b0c32d37ba 100644 (file)
@@ -28,11 +28,11 @@ The zip SRC packager.
 
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
-from SCons.Tool.packaging import stripinstall_emitter, packageroot_emitter
+from SCons.Tool.packaging import stripinstallbuilder, putintopackageroot
 
 def package(env, target, source, PACKAGEROOT, **kw):
     bld = env['BUILDERS']['Zip']
     bld.set_suffix('.zip')
-    bld.push_emitter(packageroot_emitter(PACKAGEROOT))
-    bld.push_emitter(stripinstall_emitter())
+    target, source = stripinstallbuilder(target, source, env)
+    target, source = putintopackageroot(target, source, env, PACKAGEROOT)
     return bld(env, target, source)
index 2fc4b58a2e71e418b0539ee250881904ff026a1e..47759ea41b8593a712a31ce987c2dd7ea6e38089 100644 (file)
@@ -103,7 +103,6 @@ def string_rpm(target, source, env):
 rpmAction = SCons.Action.Action(build_rpm, string_rpm)
 
 RpmBuilder = SCons.Builder.Builder(action = SCons.Action.Action('$RPMCOM', '$RPMCOMSTR'),
-                                   source_factory = SCons.Node.FS.Entry,
                                    source_scanner = SCons.Defaults.DirScanner,
                                    suffix = '$RPMSUFFIX')
 
index 5461c4dcab4f0e1eb6c9578421bab68a71f7adc8..930e29a3040cabfc09e2754f0512d424ebd2c4ea 100644 (file)
@@ -44,7 +44,7 @@ def generate(env):
 
     env['CXX']         = 'CC'
     env['CXXFLAGS']    = SCons.Util.CLVar('$CCFLAGS -LANG:std')
-    env['SHCXX']       = 'CC'
+    env['SHCXX']       = '$CXX'
     env['SHOBJSUFFIX'] = '.o'
     env['STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME'] = 1
     
index 0e3d4b1df4fff1b7cdfbc4475d6a08c05f9f135e..bbae25e0e9a6d6de2d3ba3c9a1bf3ce4792bd3ed 100644 (file)
@@ -51,6 +51,7 @@ undefined_references_str = '(^LaTeX Warning:.*undefined references)|(^Package \w
 undefined_references_re = re.compile(undefined_references_str, re.MULTILINE)
 
 openout_aux_re = re.compile(r"\\openout.*`(.*\.aux)'")
+openout_re = re.compile(r"\\openout.*`(.*)'")
 
 makeindex_re = re.compile(r"^[^%]*\\makeindex", re.MULTILINE)
 tableofcontents_re = re.compile(r"^[^%]*\\tableofcontents", re.MULTILINE)
@@ -193,17 +194,18 @@ def tex_emitter(target, source, env):
             env.Precious(base + '.bbl')
             target.append(base + '.blg')
 
-    # read log file to get all .aux files
+    # read log file to get all output file (include .aux files)
     logfilename = base + '.log'
     dir, base_nodir = os.path.split(base)
     if os.path.exists(logfilename):
         content = open(logfilename, "rb").read()
-        aux_files = openout_aux_re.findall(content)
-        aux_files = filter(lambda f, b=base_nodir+'.aux': f != b, aux_files)
-        aux_files = map(lambda f, d=dir: d+os.sep+f, aux_files)
-        target.extend(aux_files)
-        for a in aux_files:
-            env.Precious( a )
+        out_files = openout_re.findall(content)
+        out_files = filter(lambda f, b=base_nodir+'.aux': f != b, out_files)
+        if dir != '':
+            out_files = map(lambda f, d=dir: d+os.sep+f, out_files)
+        target.extend(out_files)
+        for f in out_files:
+            env.Precious( f )
 
     return (target, source)
 
index 3aa3375ea0e7b2763d8b9bedb8aa9b3f6ef60c1a..8d6d3fdb72716c15e62e231021056a7e9c6c5fe3 100644 (file)
@@ -50,12 +50,12 @@ def generate(env):
     env['WIXLIGHTCOM'] = "$WIXLIGHT $WIXLIGHTFLAGS -out ${TARGET} ${SOURCES}"
 
     object_builder = SCons.Builder.Builder(
-        action  = '$WIXCANDLECOM',
-        suffix  = '.wxiobj',
-        src_suffix = '.wxs')
+        action      = '$WIXCANDLECOM',
+        suffix      = '.wxiobj',
+        src_suffix  = '.wxs')
 
     linker_builder = SCons.Builder.Builder(
-        action = '$WIXLIGHTCOM',
+        action      = '$WIXLIGHTCOM',
         src_suffix  = '.wxiobj',
         src_builder = object_builder)
 
index 3dfa2872d6a3f719388bf51af0cd137aa8bccc43..04d263b815989b0ef33eba19755eecda1085f3bb 100644 (file)
@@ -51,6 +51,11 @@ ListType        = types.ListType
 StringType      = types.StringType
 TupleType       = types.TupleType
 
+def dictify(keys, values, result={}):
+    for k, v in zip(keys, values):
+        result[k] = v
+    return result
+
 _altsep = os.altsep
 if _altsep is None and sys.platform == 'win32':
     # My ActivePython 2.0.1 doesn't set os.altsep!  What gives?
@@ -300,7 +305,7 @@ def print_tree(root, child_func, prune=0, showtags=0, margin=[0], visited={}):
         tags.append(' S'[IDX(root.side_effect)])
         tags.append(' P'[IDX(root.precious)])
         tags.append(' A'[IDX(root.always_build)])
-        tags.append(' C'[IDX(root.current())])
+        tags.append(' C'[IDX(root.is_up_to_date())])
         tags.append(' N'[IDX(root.noclean)])
         tags.append(' H'[IDX(root.nocache)])
         tags.append(']')
@@ -886,17 +891,26 @@ else:
     def case_sensitive_suffixes(s1, s2):
         return (os.path.normcase(s1) != os.path.normcase(s2))
 
-def adjustixes(fname, pre, suf):
+def adjustixes(fname, pre, suf, ensure_suffix=False):
     if pre:
         path, fn = os.path.split(os.path.normpath(fname))
         if fn[:len(pre)] != pre:
             fname = os.path.join(path, pre + fn)
-    # Only append a suffix if the file does not have one.
-    if suf and not splitext(fname)[1] and fname[-len(suf):] != suf:
+    # Only append a suffix if the suffix we're going to add isn't already
+    # there, and if either we've been asked to ensure the specific suffix
+    # is present or there's no suffix on it at all.
+    if suf and fname[-len(suf):] != suf and \
+       (ensure_suffix or not splitext(fname)[1]):
             fname = fname + suf
     return fname
 
 
+
+# From Tim Peters,
+# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52560
+# ASPN: Python Cookbook: Remove duplicates from a sequence
+# (Also in the printed Python Cookbook.)
+
 def unique(s):
     """Return a list of the elements in s, but without duplicates.
 
@@ -967,6 +981,45 @@ def unique(s):
             u.append(x)
     return u
 
+
+
+# From Alex Martelli,
+# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52560
+# ASPN: Python Cookbook: Remove duplicates from a sequence
+# First comment, dated 2001/10/13.
+# (Also in the printed Python Cookbook.)
+
+def uniquer(seq, idfun=None):
+    if idfun is None:
+        def idfun(x): return x
+    seen = {}
+    result = []
+    for item in seq:
+        marker = idfun(item)
+        # in old Python versions:
+        # if seen.has_key(marker)
+        # but in new ones:
+        if marker in seen: continue
+        seen[marker] = 1
+        result.append(item)
+    return result
+
+# A more efficient implementation of Alex's uniquer(), this avoids the
+# idfun() argument and function-call overhead by assuming that all
+# items in the sequence are hashable.
+
+def uniquer_hashables(seq):
+    seen = {}
+    result = []
+    for item in seq:
+        #if not item in seen:
+        if not seen.has_key(item):
+            seen[item] = 1
+            result.append(item)
+    return result
+
+
+
 # Much of the logic here was originally based on recipe 4.9 from the
 # Python CookBook, but we had to dumb it way down for Python 1.5.2.
 class LogicalLines:
@@ -996,6 +1049,8 @@ class LogicalLines:
             result.append(line)
         return result
 
+
+
 class Unbuffered:
     """
     A proxy class that wraps a file object, flushing after every write,
@@ -1101,6 +1156,35 @@ def RenameFunction(function, name):
                         func_defaults)
 
 
+md5 = False
+def MD5signature(s):
+    return str(s)
+
+try:
+    import hashlib
+except ImportError:
+    pass
+else:
+    if hasattr(hashlib, 'md5'):
+        md5 = True
+        def MD5signature(s):
+            m = hashlib.md5()
+            m.update(str(s))
+            return m.hexdigest()
+
+def MD5collect(signatures):
+    """
+    Collects a list of signatures into an aggregate signature.
+
+    signatures - a list of signatures
+    returns - the aggregate signature
+    """
+    if len(signatures) == 1:
+        return signatures[0]
+    else:
+        return MD5signature(string.join(signatures, ', '))
+
+
 
 # From Dinu C. Gherman,
 # Python Cookbook, second edition, recipe 6.17, p. 277.
index 1149f35839a09d7450ae64ee782645a0207a93f4..3e8085bae8f58d1170d27818dba9cf9712a4ff53 100644 (file)
@@ -46,6 +46,18 @@ class OutBuffer:
     def write(self, str):
         self.buffer = self.buffer + str
 
+class dictifyTestCase(unittest.TestCase):
+    def test_dictify(self):
+        """Test the dictify() function"""
+        r = SCons.Util.dictify(['a', 'b', 'c'], [1, 2, 3])
+        assert r == {'a':1, 'b':2, 'c':3}, r
+
+        r = {}
+        SCons.Util.dictify(['a'], [1], r)
+        SCons.Util.dictify(['b'], [2], r)
+        SCons.Util.dictify(['c'], [3], r)
+        assert r == {'a':1, 'b':2, 'c':3}, r
+
 class UtilTestCase(unittest.TestCase):
     def test_splitext(self):
         assert splitext('foo') == ('foo','')
@@ -73,7 +85,7 @@ class UtilTestCase(unittest.TestCase):
             return 1
         def always_build(self):
             return 1
-        def current(self):
+        def is_up_to_date(self):
             return 1
         def noclean(self):
             return 1
@@ -687,7 +699,33 @@ bling
             'bling\n',
         ], lines
 
+class MD5TestCase(unittest.TestCase):
+
+    def test_collect(self):
+        """Test collecting a list of signatures into a new signature value
+        """
+        s = map(MD5signature, ('111', '222', '333'))
+        
+        assert '698d51a19d8a121ce581499d7b701668' == MD5collect(s[0:1])
+        assert '8980c988edc2c78cc43ccb718c06efd5' == MD5collect(s[0:2])
+        assert '53fd88c84ff8a285eb6e0a687e55b8c7' == MD5collect(s)
+
+    def test_MD5signature(self):
+        """Test generating a signature"""
+        s = MD5signature('111')
+        assert '698d51a19d8a121ce581499d7b701668' == s, s
+
+        s = MD5signature('222')
+        assert 'bcbe3365e6ac95ea2c0343a2395834dd' == s, s
+
 if __name__ == "__main__":
-    suite = unittest.makeSuite(UtilTestCase, 'test_')
+    suite = unittest.TestSuite()
+    tclasses = [ dictifyTestCase,
+                 MD5TestCase,
+                 UtilTestCase,
+               ]
+    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 0dfb18e08c7056c8d95e7cbce211480a2cd1db0c..5e095d1cb43989869366e197cbda386858c880c3 100644 (file)
@@ -60,7 +60,7 @@ _scons_subprocess.py is our compatibility module for subprocess) so
 that we can still try to import the real module name and fall back to
 our compatibility module if we get an ImportError.  The import_as()
 function defined below loads the module as the "real" name (without the
-underscore), after which all of the "import {module}" statements in the
+'_scons'), after which all of the "import {module}" statements in the
 rest of our code will find our pre-loaded compatibility module.
 """
 
@@ -79,6 +79,19 @@ def import_as(module, name):
 
 import builtins
 
+try:
+    import hashlib
+except ImportError:
+    # Pre-2.5 Python has no hashlib module.
+    try:
+        import_as('_scons_hashlib', 'hashlib')
+    except ImportError:
+        # If we failed importing our compatibility module, it probably
+        # means this version of Python has no md5 module.  Don't do
+        # anything and let the higher layer discover this fact, so it
+        # can fall back to using timestamp.
+        pass
+
 try:
     set
 except NameError:
@@ -112,6 +125,25 @@ except ImportError:
     # Pre-2.3 Python has no optparse module.
     import_as('_scons_optparse', 'optparse')
 
+import shlex
+try:
+    shlex.split
+except AttributeError:
+    # Pre-2.3 Python has no shlex.split function.
+    def split(s, comments=False):
+        import StringIO
+        lex = shlex.shlex(StringIO.StringIO(s))
+        lex.wordchars = lex.wordchars + '/\\-+,=:'
+        result = []
+        while True:
+            tt = lex.get_token()
+            if not tt:
+                break
+            result.append(tt)
+        return result
+    shlex.split = split
+    del split
+
 try:
     import subprocess
 except ImportError:
diff --git a/src/engine/SCons/compat/_scons_hashlib.py b/src/engine/SCons/compat/_scons_hashlib.py
new file mode 100644 (file)
index 0000000..9445ddb
--- /dev/null
@@ -0,0 +1,85 @@
+#
+# __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.
+#
+
+__doc__ = """
+hashlib backwards-compatibility module for older (pre-2.5) Python versions
+
+This does not not NOT (repeat, *NOT*) provide complete hashlib
+functionality.  It only wraps the portions of MD5 functionality used
+by SCons, in an interface that looks like hashlib (or enough for our
+purposes, anyway).  In fact, this module will raise an ImportError if
+the underlying md5 module isn't available.
+"""
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import md5
+import string
+
+class md5obj:
+
+    md5_module = md5
+
+    def __init__(self, name, string=''):
+        if not name in ('MD5', 'md5'):
+            raise ValueError, "unsupported hash type"
+        self.name = 'md5'
+        self.m = self.md5_module.md5()
+
+    def __repr__(self):
+        return '<%s HASH object @ %#x>' % (self.name, id(self))
+
+    def copy(self):
+        import copy
+        result = copy.copy(self)
+        result.m = self.m.copy()
+        return result
+
+    def digest(self):
+        return self.m.digest()
+
+    def update(self, arg):
+        return self.m.update(arg)
+
+    if hasattr(md5.md5(), 'hexdigest'):
+
+        def hexdigest(self):
+            return self.m.hexdigest()
+
+    else:
+
+        # Objects created by the underlying md5 module have no native
+        # hexdigest() method (*cough* 1.5.2 *cough*), so provide an
+        # equivalent lifted from elsewhere.
+        def hexdigest(self):
+            h = string.hexdigits
+            r = ''
+            for c in self.digest():
+                i = ord(c)
+                r = r + h[(i >> 4) & 0xF] + h[i & 0xF]
+            return r
+
+new = md5obj
+
+def md5(string=''):
+    return md5obj('md5', string)
index df7e7027f5bcf547499abf30d1a46b5f585f2f32..4cb9e303286e3fb4db9ebc71528d331edff5f5f3 100644 (file)
@@ -373,7 +373,13 @@ class CalledProcessError(Exception):
 
 
 if mswindows:
-    import threading
+    try:
+        import threading
+    except ImportError:
+        # SCons:  the threading module is only used by the communicate()
+        # method, which we don't actually use, so don't worry if we
+        # can't import it.
+        pass
     import msvcrt
     if 0: # <-- change this to use pywin32 instead of the _subprocess driver
         import pywintypes
index 66f8887d42c20dff3d8d48bb4814c14574179567..2aebf8545de3c59e9d657dfd3d2004347376c51e 100644 (file)
@@ -203,7 +203,6 @@ Print_Entries = []
 Print_Flags = Flagger()
 Verbose = 0
 Readable = 0
-Raw = 0
 
 def default_mapper(entry, name):
     try:
@@ -212,6 +211,14 @@ def default_mapper(entry, name):
         val = None
     return str(val)
 
+def map_action(entry, name):
+    try:
+        bact = entry.bact
+        bactsig = entry.bactsig
+    except AttributeError:
+        return None
+    return '%s [%s]' % (bactsig, bact)
+
 def map_timestamp(entry, name):
     try:
         timestamp = entry.timestamp
@@ -230,12 +237,13 @@ def map_bkids(entry, name):
         return None
     result = []
     for i in xrange(len(bkids)):
-        result.append("%s: %s" % (bkids[i], bkidsigs[i]))
+        result.append(nodeinfo_string(bkids[i], bkidsigs[i], "        "))
     if result == []:
         return None
     return string.join(result, "\n        ")
 
 map_field = {
+    'action'    : map_action,
     'timestamp' : map_timestamp,
     'bkids'     : map_bkids,
 }
@@ -255,52 +263,74 @@ def field(name, entry, verbose=Verbose):
     return val
 
 def nodeinfo_raw(name, ninfo, prefix=""):
-    # This does essentially what the pprint module does,
-    # except that it sorts the keys for deterministic output.
+    # This just formats the dictionary, which we would normally use str()
+    # to do, except that we want the keys sorted for deterministic output.
     d = ninfo.__dict__
-    keys = d.keys()
-    keys.sort()
+    try:
+        keys = ninfo.field_list + ['_version_id']
+    except AttributeError:
+        keys = d.keys()
+        keys.sort()
     l = []
     for k in keys:
-        l.append('%s: %s' % (repr(k), repr(d[k])))
+        l.append('%s: %s' % (repr(k), repr(d.get(k))))
     return name + ': {' + string.join(l, ', ') + '}'
 
-def nodeinfo_string(name, ninfo, prefix=""):
-    fieldlist = ["bsig", "csig", "timestamp", "size"]
+def nodeinfo_cooked(name, ninfo, prefix=""):
+    try:
+        field_list = ninfo.field_list
+    except AttributeError:
+        field_list = []
     f = lambda x, ni=ninfo, v=Verbose: field(x, ni, v)
-    outlist = [name+":"] + filter(None, map(f, fieldlist))
+    outlist = [name+':'] + filter(None, map(f, field_list))
     if Verbose:
-        sep = "\n    " + prefix
+        sep = '\n    ' + prefix
     else:
-        sep = " "
+        sep = ' '
     return string.join(outlist, sep)
 
-def printfield(name, entry, prefix=""):
-    if Raw:
-        print nodeinfo_raw(name, entry.ninfo, prefix)
-    else:
-        print nodeinfo_string(name, entry.ninfo, prefix)
+nodeinfo_string = nodeinfo_cooked
 
+def printfield(name, entry, prefix=""):
     outlist = field("implicit", entry, 0)
     if outlist:
         if Verbose:
             print "    implicit:"
         print "        " + outlist
+    outact = field("action", entry, 0)
+    if outact:
+        if Verbose:
+            print "    action: " + outact
+        else:
+            print "        " + outact
 
-def printentries(entries):
+def printentries(entries, location):
     if Print_Entries:
         for name in Print_Entries:
             try:
                 entry = entries[name]
             except KeyError:
-                sys.stderr.write("sconsign: no entry `%s' in `%s'\n" % (name, args[0]))
+                sys.stderr.write("sconsign: no entry `%s' in `%s'\n" % (name, location))
             else:
-                printfield(name, entry)
+                try:
+                    ninfo = entry.ninfo
+                except AttributeError:
+                    print name + ":"
+                else:
+                    print nodeinfo_string(name, entry.ninfo)
+                printfield(name, entry.binfo)
     else:
         names = entries.keys()
         names.sort()
         for name in names:
-            printfield(name, entries[name])
+            entry = entries[name]
+            try:
+                ninfo = entry.ninfo
+            except AttributeError:
+                print name + ":"
+            else:
+                print nodeinfo_string(name, entry.ninfo)
+            printfield(name, entry.binfo)
 
 class Do_SConsignDB:
     def __init__(self, dbm_name, dbm):
@@ -338,9 +368,14 @@ class Do_SConsignDB:
                     print_e = e
                 sys.stderr.write("sconsign: %s\n" % (print_e))
                 return
-        except:
+        except KeyboardInterrupt:
+            raise
+        except cPickle.UnpicklingError:
             sys.stderr.write("sconsign: ignoring invalid `%s' file `%s'\n" % (self.dbm_name, fname))
             return
+        except Exception, e:
+            sys.stderr.write("sconsign: ignoring invalid `%s' file `%s': %s\n" % (self.dbm_name, fname, e))
+            return
 
         if Print_Directories:
             for dir in Print_Directories:
@@ -358,7 +393,7 @@ class Do_SConsignDB:
 
     def printentries(self, dir, val):
         print '=== ' + dir + ':'
-        printentries(cPickle.loads(val))
+        printentries(cPickle.loads(val), dir)
 
 def Do_SConsignDir(name):
     try:
@@ -368,10 +403,15 @@ def Do_SConsignDir(name):
         return
     try:
         sconsign = SCons.SConsign.Dir(fp)
-    except:
-        sys.stderr.write("sconsign: ignoring invalid .sconsign file `%s'\n" % name)
+    except KeyboardInterrupt:
+        raise
+    except cPickle.UnpicklingError:
+        sys.stderr.write("sconsign: ignoring invalid .sconsign file `%s'\n" % (name))
+        return
+    except Exception, e:
+        sys.stderr.write("sconsign: ignoring invalid .sconsign file `%s': %s\n" % (name, e))
         return
-    printentries(sconsign.entries)
+    printentries(sconsign.entries, args[0])
 
 ##############################################################################
 
@@ -380,7 +420,7 @@ import getopt
 helpstr = """\
 Usage: sconsign [OPTIONS] FILE [...]
 Options:
-  -b, --bsig                  Print build signature information.
+  -a, --act, --action         Print build action information.
   -c, --csig                  Print content signature information.
   -d DIR, --dir=DIR           Print only info about DIR.
   -e ENTRY, --entry=ENTRY     Print only info about ENTRY.
@@ -394,16 +434,17 @@ Options:
   -v, --verbose               Verbose, describe each field.
 """
 
-opts, args = getopt.getopt(sys.argv[1:], "bcd:e:f:hirstv",
-                            ['bsig', 'csig', 'dir=', 'entry=',
+opts, args = getopt.getopt(sys.argv[1:], "acd:e:f:hirstv",
+                            ['act', 'action',
+                             'csig', 'dir=', 'entry=',
                              'format=', 'help', 'implicit',
                              'raw', 'readable',
                              'size', 'timestamp', 'verbose'])
 
 
 for o, a in opts:
-    if o in ('-b', '--bsig'):
-        Print_Flags['bsig'] = 1
+    if o in ('-a', '--act', '--action'):
+        Print_Flags['action'] = 1
     elif o in ('-c', '--csig'):
         Print_Flags['csig'] = 1
     elif o in ('-d', '--dir'):
@@ -430,7 +471,7 @@ for o, a in opts:
     elif o in ('-i', '--implicit'):
         Print_Flags['implicit'] = 1
     elif o in ('--raw',):
-        Raw = 1
+        nodeinfo_string = nodeinfo_raw
     elif o in ('-r', '--readable'):
         Readable = 1
     elif o in ('-s', '--size'):
index 7123304214248d95277c718bdcedd86d166c14d0..4660b2fc1035838b907abec098ff9fb14b3e290d 100644 (file)
@@ -380,7 +380,6 @@ arguments = {
                           "SCons.Platform",
                           "SCons.Scanner",
                           "SCons.Script",
-                          "SCons.Sig",
                           "SCons.Tool",
                           "SCons.Tool.packaging"],
     'package_dir'      : {'' : 'engine'},
index f97d4a3ebed15253b91b35be1620fb7a0c79c662..0a08cda615be5a5a20e91fa3d43192c4dcc45b50 100644 (file)
@@ -32,11 +32,20 @@ This is a test for fix of Issue 1004, reported by Matt Doar and
 packaged by Gary Oberbrunner.
 """
 
+import string
+
 import TestSCons
 
+_python_ = TestSCons._python_
 
 test = TestSCons.TestSCons()
 
+test.write('strip.py', """\
+import string
+import sys
+print "strip.py: %s" % string.join(sys.argv[1:])
+""")
+
 test.write('SConstruct', """\
 env = Environment()
 
@@ -44,10 +53,12 @@ mylib = env.StaticLibrary('mytest', 'test_lib.c')
 
 myprog = env.Program('test1.c',
                      LIBPATH = ['.'],
-                     LIBS = ['mytest'])
+                     LIBS = ['mytest'],
+                     OBJSUFFIX = '.obj',
+                     PROGSUFFIX = '.exe')
 if ARGUMENTS['case']=='2':
-  AddPostAction(myprog, Action('strip ' + myprog[0].abspath))
-""")
+  AddPostAction(myprog, Action(r'%(_python_)s strip.py ' + myprog[0].abspath))
+""" % locals())
 
 test.write('test1.c', """\
 extern void test_lib_fn();
@@ -69,8 +80,11 @@ test.run(arguments="-Q case=1", stderr=None)
 
 test.run(arguments="-Q -c case=1")
 
-test.must_not_exist('test1.o')
+test.must_not_exist('test1.obj')
 
 test.run(arguments="-Q case=2", stderr=None)
 
+expect = 'strip.py: %s' % test.workpath('test1.exe')
+test.fail_test(string.find(test.stdout(), expect) == -1)
+
 test.pass_test()
similarity index 50%
rename from src/engine/SCons/Sig/TimeStamp.py
rename to test/Actions/exitstatfunc.py
index 8cf23e89f627ee8ffe1acf4669fd64a5cefc6a9a..c52e1f50ad13487e54d55de0557a20aab8ba2797 100644 (file)
@@ -1,10 +1,4 @@
-"""SCons.Sig.TimeStamp
-
-The TimeStamp signature package for the SCons software construction
-utility.
-
-"""
-
+#!/usr/bin/env python
 #
 # __COPYRIGHT__
 #
@@ -30,46 +24,44 @@ utility.
 
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
-def current(new, old):
-    """Return whether a new timestamp is up-to-date with
-    respect to an old timestamp.
-    """
-    return not old is None and new <= old
+"""
+Verify that setting exitstatfunc on an Action works as advertised.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+def always_succeed(s):
+    # Always return 0, which indicates success.
+    return 0
+
+def copy_fail(target, source, env):
+    content = open(str(source[0]), 'rb').read()
+    open(str(target[0]), 'wb').write(content)
+    return 2
+
+a = Action(copy_fail, exitstatfunc=always_succeed)
+Alias('test1', Command('test1.out', 'test1.in', a))
 
-def collect(signatures):
-    """
-    Collect a list of timestamps, returning
-    the most-recent timestamp from the list 
+def fail(target, source, env):
+    return 2
 
-    signatures - a list of timestamps
-    returns - the most recent timestamp
-    """
+t2 = Command('test2.out', 'test2.in', Copy('$TARGET', '$SOURCE'))
+AddPostAction(t2, Action(fail, exitstatfunc=always_succeed))
+Alias('test2', t2)
+""")
 
-    if len(signatures) == 0:
-        return 0
-    elif len(signatures) == 1:
-        return signatures[0]
-    else:
-        return max(signatures)
+test.write('test1.in', "test1.in\n")
+test.write('test2.in', "test2.in\n")
 
-def signature(obj):
-    """Generate a timestamp.
-    """
-    return obj.get_timestamp()
+test.run(arguments = 'test1')
 
-def to_string(signature):
-    """Convert a timestamp to a string"""
-    return str(signature)
+test.must_match('test1.out', "test1.in\n")
 
-def from_string(string):
-    """Convert a string to a timestamp"""
-    try:
-        return int(string)
-    except ValueError:
-        # if the signature isn't an int, then
-        # the user probably just switched from
-        # MD5 signatures to timestamp signatures,
-        # so ignore the error:
-        return None
+test.run(arguments = 'test2')
 
+test.must_match('test2.out', "test2.in\n")
 
+test.pass_test()
diff --git a/test/Actions/timestamp.py b/test/Actions/timestamp.py
new file mode 100644 (file)
index 0000000..ee93596
--- /dev/null
@@ -0,0 +1,56 @@
+#!/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 changing an Action causes rebuilds when using timestamp
+signatures.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+env = Environment()
+env.Decider('timestamp-match')
+env.Command('file.out', 'file.in', Copy('$TARGET', '$SOURCE'))
+""")
+
+test.write('file.in', "file.in\n")
+
+test.not_up_to_date(arguments = 'file.out')
+
+test.write('SConstruct', """\
+def my_copy(target, source, env):
+    open(str(target[0]), 'w').write(open(str(source[0]), 'r').read())
+env = Environment()
+env.Decider('timestamp-match')
+env.Command('file.out', 'file.in', my_copy)
+""")
+
+test.not_up_to_date(arguments = 'file.out')
+
+test.pass_test()
index ef4a8d071d0431f8c2e4c2eb4db57078a2eed142..584ab4c10997e837e234ff94c9762689875681da 100644 (file)
@@ -36,7 +36,7 @@ test = TestSCons.TestSCons()
 
 test.write('SConstruct', """
 def foo(self):
-    return 'foo-' + env['FOO']
+    return 'foo-' + self['FOO']
 
 AddMethod(Environment, foo)
 env = Environment(FOO = '111')
@@ -46,14 +46,20 @@ env = Environment(FOO = '222')
 print env.foo()
 
 env.AddMethod(foo, 'bar')
-print env.bar()
+env['FOO'] = '333'
+
+e = env.Clone()
+e['FOO'] = '444'
 
+print env.bar()
+print e.bar()
 """)
 
 expect = """\
 foo-111
 foo-222
-foo-222
+foo-333
+foo-444
 """
 
 test.run(arguments = '-Q -q', stdout = expect)
diff --git a/test/Alias/Depends.py b/test/Alias/Depends.py
new file mode 100644 (file)
index 0000000..b4ea95c
--- /dev/null
@@ -0,0 +1,162 @@
+#!/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__"
+
+import os
+import sys
+import TestSCons
+import TestCmd
+
+_python_ = TestSCons._python_
+
+test = TestSCons.TestSCons(match=TestCmd.match_re)
+
+test.subdir('sub1', 'sub2')
+
+test.write('build.py', r"""
+import sys
+open(sys.argv[1], 'wb').write(open(sys.argv[2], 'rb').read())
+sys.exit(0)
+""")
+
+test.write('SConstruct', """
+B = Builder(action = r'%(_python_)s build.py $TARGET $SOURCES')
+env = Environment()
+env['BUILDERS']['B'] = B
+env.B(target = 'f1.out', source = 'f1.in')
+env.B(target = 'f2.out', source = 'f2.in')
+env.B(target = 'f3.out', source = 'f3.in')
+SConscript('sub1/SConscript', "env")
+SConscript('sub2/SConscript', "env")
+
+foo = Alias('foo')
+foo2 = env.Alias('foo', ['f2.out', 'sub1'])
+assert foo == foo2
+bar = Alias('bar', ['sub2', 'f3.out'])
+env.Alias('blat', ['sub2', 'f3.out'])
+env.Alias('blat', ['f2.out', 'sub1'])
+env.Depends('f1.out', 'bar')
+
+Alias('a1', 'a1-file.in')
+Depends(Alias('a2'), 'a1')
+env.B('a2-file.out', 'a2-file.in')
+Depends('a2-file.out', 'a2')
+""" % locals())
+
+test.write(['sub1', 'SConscript'], """
+Import("env")
+env.B(target = 'f4.out', source = 'f4.in')
+env.B(target = 'f5.out', source = 'f5.in')
+env.B(target = 'f6.out', source = 'f6.in')
+""")
+
+test.write(['sub2', 'SConscript'], """
+Import("env")
+env.B(target = 'f7.out', source = 'f7.in')
+env.B(target = 'f8.out', source = 'f8.in')
+env.B(target = 'f9.out', source = 'f9.in')
+""")
+
+test.write('f1.in', "f1.in\n")
+test.write('f2.in', "f2.in\n")
+test.write('f3.in', "f3.in\n")
+
+test.write(['sub1', 'f4.in'], "sub1/f4.in\n")
+test.write(['sub1', 'f5.in'], "sub1/f5.in\n")
+test.write(['sub1', 'f6.in'], "sub1/f6.in\n")
+
+test.write(['sub2', 'f7.in'], "sub2/f7.in\n")
+test.write(['sub2', 'f8.in'], "sub2/f8.in\n")
+test.write(['sub2', 'f9.in'], "sub2/f9.in\n")
+
+
+
+test.run(arguments = 'foo')
+
+test.must_not_exist(test.workpath('f1.out'))
+test.must_exist(test.workpath('f2.out'))
+test.must_not_exist(test.workpath('f3.out'))
+
+test.must_exist(test.workpath('sub1', 'f4.out'))
+test.must_exist(test.workpath('sub1', 'f5.out'))
+test.must_exist(test.workpath('sub1', 'f6.out'))
+
+test.must_not_exist(test.workpath('sub2', 'f7.out'))
+test.must_not_exist(test.workpath('sub2', 'f8.out'))
+test.must_not_exist(test.workpath('sub2', 'f9.out'))
+
+test.up_to_date(arguments = 'foo')
+
+test.write(['sub1', 'f5.in'], "sub1/f5.in 2\n")
+
+test.not_up_to_date(arguments = 'foo')
+
+
+
+test.run(arguments = 'f1.out')
+
+test.must_exist(test.workpath('f1.out'))
+test.must_exist(test.workpath('f3.out'))
+
+test.up_to_date(arguments = 'f1.out')
+
+
+
+os.unlink(test.workpath('f2.out'))
+os.unlink(test.workpath('f3.out'))
+
+test.run(arguments = 'blat')
+
+test.must_exist(test.workpath('f2.out'))
+test.must_exist(test.workpath('f3.out'))
+
+test.write('f3.in', "f3.in 2 \n")
+
+expect = """.* build.py f3.out f3.in
+.* build.py f1.out f1.in
+"""
+
+test.run(arguments = '-Q f1.out', stdout = expect)
+
+test.up_to_date(arguments = 'f1.out')
+
+
+
+test.write('a1-file.in', "a1-file.in\n")
+test.write('a2-file.in', "a2-file.in\n")
+
+test.run(arguments = 'a2-file.out')
+
+test.must_match(test.workpath('a2-file.out'), "a2-file.in\n")
+
+test.up_to_date(arguments = 'a2-file.out')
+
+test.write('a1-file.in', "a1-file.in 2\n")
+
+test.not_up_to_date(arguments = 'a2-file.out')
+
+
+
+test.pass_test()
index 44096a1c46b97ea6274d34800099efbac7f38d38..3954f2bf5b6480127e8cfd25f145b55f73531b16 100644 (file)
@@ -70,7 +70,6 @@ env = Environment(BUILDERS={'Build':Builder(action=cat)},
 # Do some Node test operations to ensure no side-effects cause failures
 File('file.in').exists()
 File('file.in').is_derived()
-File('file.in').is_pseudo_derived()
 
 env.Build('file.out', 'file.in')
 """)
diff --git a/test/Builder/ensure_suffix.py b/test/Builder/ensure_suffix.py
new file mode 100644 (file)
index 0000000..ee23635
--- /dev/null
@@ -0,0 +1,55 @@
+#!/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 ensure_suffix argument to causes us to add the suffix
+configured for the Builder even if it looks like the target already has
+a different suffix.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+env = Environment()
+
+tbuilder = Builder(action=Copy('$TARGET', '$SOURCE'),
+                   suffix='.dll',
+                   ensure_suffix=True)
+
+env['BUILDERS']['TBuilder'] = tbuilder
+
+env.TBuilder("aa.bb.cc.dd","aa.aa.txt")
+""")
+
+test.write('aa.aa.txt', "clean test\n")
+
+test.run(arguments = '.')
+
+test.must_match('aa.bb.cc.dd.dll', "clean test\n")
+
+test.pass_test()
diff --git a/test/CacheDir/multiple-targets.py b/test/CacheDir/multiple-targets.py
new file mode 100644 (file)
index 0000000..77b8f6f
--- /dev/null
@@ -0,0 +1,66 @@
+#!/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 multiple target files get retrieved from a CacheDir correctly.
+"""
+
+import os.path
+import shutil
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.subdir('cache')
+
+test.write('SConstruct', """\
+def touch(env, source, target):
+    open('foo', 'w').write("")
+    open('bar', 'w').write("")
+CacheDir(r'%s')
+env = Environment()
+env.Command(['foo', 'bar'], ['input'], touch)
+""" % (test.workpath('cache')))
+
+test.write('input', "multiple/input\n")
+
+test.run()
+
+test.must_exist(test.workpath('foo'))
+test.must_exist(test.workpath('bar'))
+
+test.run(arguments = '-c')
+
+test.must_not_exist(test.workpath('foo'))
+test.must_not_exist(test.workpath('bar'))
+
+test.run()
+
+test.must_exist(test.workpath('foo'))
+test.must_exist(test.workpath('bar'))
+
+test.pass_test()
diff --git a/test/CacheDir/scanner-target.py b/test/CacheDir/scanner-target.py
new file mode 100644 (file)
index 0000000..33930d8
--- /dev/null
@@ -0,0 +1,80 @@
+#!/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 the case (reported by Jeff Petkau, bug #694744) where a target
+is source for another target with a scanner, which used to cause us
+to push the file to the CacheDir after the build signature had already
+been cleared (as a sign that the built file should now be rescanned).
+"""
+
+import os.path
+import shutil
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.subdir('cache')
+
+test.write('SConstruct', """\
+import SCons
+
+CacheDir(r'%s')
+
+def docopy(target,source,env):
+    data = source[0].get_contents()
+    f = open(target[0].rfile().get_abspath(), "wb")
+    f.write(data)
+    f.close()
+
+def sillyScanner(node, env, dirs):
+    print 'This is never called (unless we build file.out)'
+    return []
+
+SillyScanner = SCons.Scanner.Base(function = sillyScanner, skeys = ['.res'])
+
+env = Environment(tools=[],
+                  SCANNERS = [SillyScanner],
+                  BUILDERS = {})
+
+r = env.Command('file.res', 'file.ma', docopy)
+
+env.Command('file.out', r, docopy)
+
+# make r the default. Note that we don't even try to build file.out,
+# and so SillyScanner never runs. The bug is the same if we build
+# file.out, though.
+Default(r)
+""" % test.workpath('cache'))
+
+test.write('file.ma', "file.ma\n")
+
+test.run()
+
+test.must_not_exist(test.workpath('cache', 'N', 'None'))
+
+test.pass_test()
similarity index 69%
rename from src/engine/SCons/Sig/SigTests.py
rename to test/CacheDir/timestamp.py
index 6b7c83fd86afd7f96f3b571104aaf2f43551748a..b0d45b63030ebf76fa2665ba9d3402bbc258a7e7 100644 (file)
@@ -1,3 +1,4 @@
+#!/usr/bin/env python
 #
 # __COPYRIGHT__
 #
 
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
-import unittest
-import SCons.Sig
-import sys
+"""
+Verify that CacheDir() works even when using timestamp signatures.
+"""
 
-class CalculatorTestCase(unittest.TestCase):
+import TestSCons
 
-    def runTest(self):
-        class MySigModule:
-            pass
-        calc = SCons.Sig.Calculator(MySigModule)
-        assert calc.module == MySigModule
+test = TestSCons.TestSCons()
 
+test.write('SConstruct', """
+SourceSignatures('timestamp')
+TargetSignatures('content')
+CacheDir('cache')
+Command('file.out', 'file.in', Copy('$TARGET', '$SOURCE'))
+""")
 
-def suite():
-    suite = unittest.TestSuite()
-    suite.addTest(CalculatorTestCase())
-    return suite
+test.write('file.in', "file.in\n")
 
-if __name__ == "__main__":
-    runner = unittest.TextTestRunner()
-    result = runner.run(suite())
-    if not result.wasSuccessful():
-        sys.exit(1)
+test.run()
 
+test.must_match('file.out', "file.in\n")
+
+test.pass_test()
diff --git a/test/Configure/clean.py b/test/Configure/clean.py
new file mode 100644 (file)
index 0000000..5b8e43d
--- /dev/null
@@ -0,0 +1,96 @@
+#!/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 don't perform Configure context actions when the
+-c or --clean options have been specified.
+"""
+
+import string
+
+import TestSCons
+
+test = TestSCons.TestSCons(match = TestSCons.match_re_dotall)
+
+test.write('SConstruct', """\
+env = Environment()
+import os
+env.AppendENVPath('PATH', os.environ['PATH'])
+conf = Configure(env)
+r1 = conf.CheckCHeader( 'math.h' )
+r2 = conf.CheckCHeader( 'no_std_c_header.h' ) # leads to compile error
+env = conf.Finish()
+Export( 'env' )
+SConscript( 'SConscript' )
+""")
+
+test.write('SConscript', """\
+Import( 'env' )
+env.Program( 'TestProgram', 'TestProgram.c' )
+""")
+
+test.write('TestProgram.c', """\
+#include <stdio.h>
+
+int main() {
+  printf( "Hello\\n" );
+}
+""")
+
+lines = [
+    "Checking for C header file math.h... ",
+    "Checking for C header file no_std_c_header.h... "
+]
+
+unexpected = []
+
+test.run(arguments = '-c')
+
+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')
+
+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.pass_test()
diff --git a/test/Configure/help.py b/test/Configure/help.py
new file mode 100644 (file)
index 0000000..f79831c
--- /dev/null
@@ -0,0 +1,109 @@
+#!/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 don't perform Configure context actions when the
+-H, -h or --help options have been specified.
+"""
+
+import string
+
+import TestSCons
+
+test = TestSCons.TestSCons(match = TestSCons.match_re_dotall)
+
+test.write('SConstruct', """\
+env = Environment()
+import os
+env.AppendENVPath('PATH', os.environ['PATH'])
+conf = Configure(env)
+r1 = conf.CheckCHeader( 'math.h' )
+r2 = conf.CheckCHeader( 'no_std_c_header.h' ) # leads to compile error
+env = conf.Finish()
+Export( 'env' )
+SConscript( 'SConscript' )
+""")
+
+test.write('SConscript', """\
+Import( 'env' )
+env.Program( 'TestProgram', 'TestProgram.c' )
+""")
+
+test.write('TestProgram.c', """\
+#include <stdio.h>
+
+int main() {
+  printf( "Hello\\n" );
+}
+""")
+
+lines = [
+    "Checking for C header file math.h... ",
+    "Checking for C header file no_std_c_header.h... "
+]
+
+unexpected = []
+
+test.run(arguments = '-H')
+
+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 = '-h')
+
+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.pass_test()
index 4da725e87e88e15a408244753a06d91e922ffd29..6659d934c44ccc1ff2fa55d33279452e8f364783 100644 (file)
@@ -79,6 +79,11 @@ test.write('f11.in', "f11.in\n")
 test.subdir('d5')
 test.write(['d5', 'f12.in'], "f12.in\n")
 
+d4_f10_in   = os.path.join('d4', 'f10.in')
+d4_f11_out  = os.path.join('d4', 'f11.out')
+d4_f12_out  = os.path.join('d4', 'f12.out')
+d5_f12_in   = os.path.join('d5', 'f12.in')
+
 expect = test.wrap_stdout(read_str = """\
 Copy("f1.out", "f1.in")
 Copy("d2.out", "d2.in")
@@ -89,14 +94,14 @@ cat(["bar.out"], ["bar.in"])
 Copy("f4.out", "f4.in")
 Copy("d5.out", "d5.in")
 Copy("d6.out", "f6.in")
-Copy file(s): "f10.in" to "d4/f10.in"
-Copy file(s): "f11.in" to "d4/f11.out"
-Copy file(s): "d5/f12.in" to "d4/f12.out"
+Copy file(s): "f10.in" to "%(d4_f10_in)s"
+Copy file(s): "f11.in" to "%(d4_f11_out)s"
+Copy file(s): "%(d5_f12_in)s" to "%(d4_f12_out)s"
 Copy("f7.out", "f7.in")
 cat(["f8.out"], ["f8.in"])
 cat(["f9.out"], ["f9.in"])
 Copy("f9.out-Copy", "f9.in")
-""")
+""" % locals())
 test.run(options = '-n', arguments = '.', stdout = expect)
 
 test.must_not_exist('f1.out')
diff --git a/test/Decider/Environment.py b/test/Decider/Environment.py
new file mode 100644 (file)
index 0000000..989e187
--- /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 use of an up-to-date Decider method through a construction
+environment.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """
+import os.path
+env = Environment()
+env.Command('file.out', 'file.in', Copy('$TARGET', '$SOURCE'))
+def my_decider(dependency, target, prev_ni):
+    return os.path.exists('has-changed')
+env.Decider(my_decider)
+""")
+
+test.write('file.in', "file.in\n")
+
+test.run(arguments = '.')
+
+test.up_to_date(arguments = '.')
+
+test.write('has-changed', "\n")
+
+test.not_up_to_date(arguments = '.')
+
+test.not_up_to_date(arguments = '.')
+
+test.unlink('has-changed')
+
+test.up_to_date(arguments = '.')
+
+test.pass_test()
diff --git a/test/Decider/MD5-timestamp.py b/test/Decider/MD5-timestamp.py
new file mode 100644 (file)
index 0000000..d31a2db
--- /dev/null
@@ -0,0 +1,81 @@
+#!/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 behavior of the MD5-timestamp Decider() setting.
+"""
+
+import os
+import stat
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+m = Environment()
+m.Decider('MD5-timestamp')
+m.Command('content1.out', 'content1.in', Copy('$TARGET', '$SOURCE'))
+m.Command('content2.out', 'content2.in', Copy('$TARGET', '$SOURCE'))
+m.Command('content3.out', 'content3.in', Copy('$TARGET', '$SOURCE'))
+""")
+
+test.write('content1.in', "content1.in 1\n")
+test.write('content2.in', "content2.in 1\n")
+test.write('content3.in', "content3.in 1\n")
+
+test.run(arguments = '.')
+
+test.up_to_date(arguments = '.')
+
+
+
+test.sleep()
+
+test.write('content1.in', "content1.in 2\n")
+
+test.touch('content2.in')
+
+time_content = os.stat('content3.in')[stat.ST_MTIME]
+test.write('content3.in', "content3.in 2\n")
+test.touch('content3.in', time_content)
+
+# We should only see content1.out rebuilt.  The timestamp of content2.in
+# has changed, but its content hasn't, so the follow-on content check says
+# to not rebuild it.  The content of content3.in has changed, but that's
+# masked by the fact that its timestamp is the same as the last run.
+
+expect = test.wrap_stdout("""\
+Copy("content1.out", "content1.in")
+""")
+
+test.run(arguments = '.', stdout=expect)
+
+test.up_to_date(arguments = '.')
+
+
+
+test.pass_test()
diff --git a/test/Decider/Node.py b/test/Decider/Node.py
new file mode 100644 (file)
index 0000000..b11e223
--- /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 use of an up-to-date Decider method on a Node.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """
+import os.path
+file_in = File('file.in')
+file_out = File('file.out')
+Command(file_out, file_in, Copy('$TARGET', '$SOURCE'))
+def my_decider(dependency, target, prev_ni):
+    return os.path.exists('has-changed')
+file_in.Decider(my_decider)
+""")
+
+test.write('file.in', "file.in\n")
+
+test.run(arguments = '.')
+
+test.up_to_date(arguments = '.')
+
+test.write('has-changed', "\n")
+
+test.not_up_to_date(arguments = '.')
+
+test.not_up_to_date(arguments = '.')
+
+test.unlink('has-changed')
+
+test.up_to_date(arguments = '.')
+
+test.pass_test()
diff --git a/test/Decider/default.py b/test/Decider/default.py
new file mode 100644 (file)
index 0000000..c2886fb
--- /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 use of a default up-to-date Decider method.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """
+import os.path
+Command('file.out', 'file.in', Copy('$TARGET', '$SOURCE'))
+def my_decider(dependency, target, prev_ni):
+    return os.path.exists('has-changed')
+Decider(my_decider)
+""")
+
+test.write('file.in', "file.in\n")
+
+test.run(arguments = '.')
+
+test.up_to_date(arguments = '.')
+
+test.write('has-changed', "\n")
+
+test.not_up_to_date(arguments = '.')
+
+test.not_up_to_date(arguments = '.')
+
+test.unlink('has-changed')
+
+test.up_to_date(arguments = '.')
+
+test.pass_test()
diff --git a/test/Decider/mixed.py b/test/Decider/mixed.py
new file mode 100644 (file)
index 0000000..7a83c74
--- /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.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify use of an up-to-date Decider method through a construction
+environment.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """
+import os.path
+denv = Environment()
+env = Environment()
+n1_in = File('n1.in')
+n2_in = File('n2.in')
+n3_in = File('n3.in')
+Command(     'ccc.out', 'ccc.in',   Copy('$TARGET', '$SOURCE'))
+Command(     'n1.out',  n1_in,      Copy('$TARGET', '$SOURCE'))
+denv.Command('ddd.out', 'ddd.in',   Copy('$TARGET', '$SOURCE'))
+denv.Command('n2.out',  n2_in,      Copy('$TARGET', '$SOURCE'))
+env.Command( 'eee.out', 'eee.in',   Copy('$TARGET', '$SOURCE'))
+env.Command( 'n3.out',  n3_in,      Copy('$TARGET', '$SOURCE'))
+def default_decider(dependency, target, prev_ni):
+    return os.path.exists('default-has-changed')
+def env_decider(dependency, target, prev_ni):
+    return os.path.exists('env-has-changed')
+def node_decider(dependency, target, prev_ni):
+    return os.path.exists('node-has-changed')
+Decider(default_decider)
+env.Decider(env_decider)
+n1_in.Decider(node_decider)
+n2_in.Decider(node_decider)
+n3_in.Decider(node_decider)
+""")
+
+test.write('ccc.in', "ccc.in\n")
+test.write('ddd.in', "ddd.in\n")
+test.write('eee.in', "eee.in\n")
+test.write('n1.in', "n1.in\n")
+test.write('n2.in', "n2.in\n")
+test.write('n3.in', "n3.in\n")
+
+
+
+test.run(arguments = '.')
+
+test.up_to_date(arguments = '.')
+
+
+
+test.write('env-has-changed', "\n")
+
+test.not_up_to_date(arguments = 'eee.out')
+test.up_to_date(arguments = 'ccc.out ddd.out n1.out n2.out n3.out')
+
+test.not_up_to_date(arguments = 'eee.out')
+test.up_to_date(arguments = 'ccc.out ddd.out n1.out n2.out n3.out')
+
+test.unlink('env-has-changed')
+
+
+
+test.write('default-has-changed', "\n")
+
+test.not_up_to_date(arguments = 'ccc.out ddd.out')
+test.up_to_date(arguments = 'eee.out n1.out n2.out n3.out')
+
+test.not_up_to_date(arguments = 'ccc.out ddd.out')
+test.up_to_date(arguments = 'eee.out n1.out n2.out n3.out')
+
+test.unlink('default-has-changed')
+
+
+
+test.up_to_date(arguments = '.')
+
+test.write('node-has-changed', "\n")
+
+test.not_up_to_date(arguments = 'n1.out n2.out n3.out')
+test.up_to_date(arguments = 'ccc.out ddd.out eee.out')
+
+test.not_up_to_date(arguments = 'n1.out n2.out n3.out')
+test.up_to_date(arguments = 'ccc.out ddd.out eee.out')
+
+test.unlink('node-has-changed')
+
+
+
+test.pass_test()
diff --git a/test/Decider/timestamp.py b/test/Decider/timestamp.py
new file mode 100644 (file)
index 0000000..6975607
--- /dev/null
@@ -0,0 +1,113 @@
+#!/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 various interactions of the timestamp-match and timestamp-newer
+Decider() settings:.
+"""
+
+import os
+import stat
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+m = Environment()
+m.Decider('timestamp-match')
+m.Command('match1.out', 'match1.in', Copy('$TARGET', '$SOURCE'))
+m.Command('match2.out', 'match2.in', Copy('$TARGET', '$SOURCE'))
+n = Environment()
+n.Decider('timestamp-newer')
+n.Command('newer1.out', 'newer1.in', Copy('$TARGET', '$SOURCE'))
+n.Command('newer2.out', 'newer2.in', Copy('$TARGET', '$SOURCE'))
+""")
+
+test.write('match1.in', "match1.in\n")
+test.write('match2.in', "match2.in\n")
+test.write('newer1.in', "newer1.in\n")
+test.write('newer2.in', "newer2.in\n")
+
+test.run(arguments = '.')
+
+test.up_to_date(arguments = '.')
+
+time_match = os.stat('match2.out')[stat.ST_MTIME]
+time_newer = os.stat('newer2.out')[stat.ST_MTIME]
+
+
+
+# Now make all the source files newer than (different timestamps from)
+# the last time the targets were built, and touch the target files
+# of match1.out and newer1.out to see the different effects.
+
+test.sleep()
+
+test.touch('match1.in')
+test.touch('newer1.in')
+test.touch('match2.in')
+test.touch('newer2.in')
+
+test.sleep()
+
+test.touch('match1.out')
+test.touch('newer1.out')
+
+# We should see both match1.out and match2.out rebuilt, because the
+# source file timestamps do not match the last time they were built,
+# but only newer2.out rebuilt.  newer1.out is *not* rebuilt because
+# the actual target file timestamp is, in fact, newer than the
+# source file (newer1.in) timestamp.
+
+expect = test.wrap_stdout("""\
+Copy("match1.out", "match1.in")
+Copy("match2.out", "match2.in")
+Copy("newer2.out", "newer2.in")
+""")
+
+test.run(arguments = '.', stdout=expect)
+
+# Now, for the somewhat pathological case, reset the match2.out and
+# newer2.out timestamps to the older timestamp when the targets were
+# first built.  This will cause newer2.out to be rebuilt, because
+# the newer1.in timestamp is now newer than the older, reset target
+# file timestamp, but match2.out is *not* rebuilt because its source
+# file (match2.in) timestamp still exactly matches the timestamp
+# recorded when the target file was last built.
+
+test.touch('match2.out', time_match)
+test.touch('newer2.out', time_newer)
+
+expect = test.wrap_stdout("""\
+Copy("newer2.out", "newer2.in")
+""")
+
+test.run(arguments = '.', stdout=expect)
+
+
+
+test.pass_test()
diff --git a/test/Decider/unknown.py b/test/Decider/unknown.py
new file mode 100644 (file)
index 0000000..b6d071d
--- /dev/null
@@ -0,0 +1,46 @@
+#!/usr/bin/env python\r
+#\r
+# __COPYRIGHT__\r
+#\r
+# Permission is hereby granted, free of charge, to any person obtaining\r
+# a copy of this software and associated documentation files (the\r
+# "Software"), to deal in the Software without restriction, including\r
+# without limitation the rights to use, copy, modify, merge, publish,\r
+# distribute, sublicense, and/or sell copies of the Software, and to\r
+# permit persons to whom the Software is furnished to do so, subject to\r
+# the following conditions:\r
+#\r
+# The above copyright notice and this permission notice shall be included\r
+# in all copies or substantial portions of the Software.\r
+#\r
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY\r
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE\r
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\r
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\r
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\r
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r
+#\r
+\r
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"\r
+\r
+"""\r
+Verify the error when the Decider() function is handed an unknown\r
+function string.\r
+"""\r
+\r
+import TestSCons\r
+\r
+test = TestSCons.TestSCons(match = TestSCons.match_re_dotall)\r
+\r
+test.write('SConstruct', """\\r
+Decider('fiddle-dee-dee')\r
+""")\r
+\r
+expect = r"""\r
+scons: \*\*\* Unknown Decider value 'fiddle-dee-dee'\r
+""" + TestSCons.file_expr\r
+\r
+test.run(arguments = '.', status = 2, stderr = expect)\r
+\r
+test.pass_test()\r
index 138dc950414c5601f4c4b2e519dccaf4edf0a2d1..31febbe072c24247a808877123ac5a252a671e75 100644 (file)
@@ -53,14 +53,18 @@ env.Command('f8.out', 'f8.in', [Delete("$FILE"), Delete("$DIR"), Cat])
 env.Command('f9.out', 'f9.in', [Cat,
                                 Delete("Delete-$SOURCE"),
                                 Delete("$TARGET-Delete")])
-env.Command('f10-nonexistent.out', 'f10.in', [Delete("$TARGET"),
-                                              Cat])
-env.Command('d11-nonexistent.out', 'd11.in', [Delete("$TARGET"),
-                                              Mkdir("$TARGET")])
-env.Command('f12-nonexistent.out', 'f12.in', [Delete("$TARGET", must_exist=0),
-                                              Cat])
-env.Command('d13-nonexistent.out', 'd13.in', [Delete("$TARGET", must_exist=0),
-                                              Mkdir("$TARGET")])
+
+env.Command('f10-nonexistent.out', 'f10.in',
+            [Delete("$TARGET"), Cat])
+
+env.Command(Dir('d11-nonexistent.out'), 'd11.in',
+            [Delete("$TARGET"), Mkdir("$TARGET")])
+
+env.Command('f12-nonexistent.out', 'f12.in',
+            [Delete("$TARGET", must_exist=0), Cat])
+
+env.Command(Dir('d13-nonexistent.out'), 'd13.in',
+            [Delete("$TARGET", must_exist=0), Mkdir("$TARGET")])
 """)
 
 test.write('f1', "f1\n")
index d0b21991fcc2d095daee18f0641c3d8cfaba1508..48707395e84684f93ddd6f6a67d52151b90101d2 100644 (file)
@@ -143,6 +143,9 @@ file2 = File('file2')
 env.Depends(file1, [[file2, 'file3']])
 """)
 
+test.write('file2', "file2\n")
+test.write('file3', "file3\n")
+
 test.up_to_date(arguments = '.')
 
 test.pass_test()
index fa07a8804efceea7f60496be48f33d18a8aa7b1b..985266051ee08d9d2d4745ea47dc583d23e57fec 100644 (file)
@@ -165,8 +165,6 @@ test.up_to_date(arguments='cmd-csig-noscan.out')
 
 test.write('junk.txt', 'junk.txt 2\n')
 test.not_up_to_date(arguments='bsig.out')
-# XXX For some reason, 'csig' is still reported as up to date.
-# XXX Comment out this test until someone can look at it.
-#test.not_up_to_date(arguments='csig.out')
+test.not_up_to_date(arguments='csig.out')
 
 test.pass_test()
index 3f9889477f33553634aad4efa6c6015dcd27bf75..9857f99093bc922e316b92df0e5768c13602a104 100644 (file)
@@ -35,19 +35,18 @@ import TestSCons
 
 test = TestSCons.TestSCons()
 
-install = test.workpath('install')
-install_file = test.workpath('install', 'file')
-work_file = test.workpath('work', 'file')
+work_file_out = test.workpath('work', 'file.out')
 
 test.subdir('install', 'work')
 
 test.write(['work', 'SConstruct'], """\
-Alias("install", Install(r"%(install)s", File('file')))
+file_out = Command('file.out', 'file.in', Copy('$TARGET', '$SOURCE'))
+Alias("install", file_out)
 
 # Make a directory where we expect the File() to be.  This causes an
 # IOError or OSError when we try to open it to read its signature.
 import os
-os.mkdir(r'%(work_file)s')
+os.mkdir('file.in')
 """ % locals())
 
 if sys.platform == 'win32':
@@ -56,7 +55,7 @@ else:
     error_message = "Is a directory"
 
 expect = """\
-scons: *** [%(install_file)s] %(work_file)s: %(error_message)s
+scons: *** [install] %(work_file_out)s: %(error_message)s
 """ % locals()
 
 test.run(chdir = 'work',
index f7f8e26eda09e9067d3145db252d4d220a7e1f0c..6d44c456251b048736309957b14d6b87ea0d94a2 100644 (file)
@@ -91,18 +91,18 @@ test.write(['work', 'sub', 'f4.in'], "sub/f4.in\n")
 test.write(f5_txt, "f5.txt\n")
 test.write(f6_txt, "f6.txt\n")
 
-test.run(chdir = 'work', arguments = '--debug=stacktrace .')
+test.run(chdir = 'work', arguments = '.')
 
-test.fail_test(test.read(f1_out) != "f1.in\n")
-test.fail_test(test.read(f2_out) != "f2.in\n")
-test.fail_test(test.read(f3_out) != "f3.in\n")
-test.fail_test(test.read(f4_out) != "sub/f4.in\n")
-test.fail_test(test.read(['work', 'f5.txt']) != "f5.txt\n")
-test.fail_test(test.read(['work', 'export', 'f5.txt']) != "f5.txt\n")
-test.fail_test(test.read(['work', 'f6.txt']) != "f6.txt\n")
-test.fail_test(test.read(['work', 'export', 'f6.txt']) != "f6.txt\n")
+test.must_match(f1_out,                         "f1.in\n")
+test.must_match(f2_out,                         "f2.in\n")
+test.must_match(f3_out,                         "f3.in\n")
+test.must_match(f4_out,                         "sub/f4.in\n")
+test.must_match(['work', 'f5.txt'],             "f5.txt\n")
+test.must_match(['work', 'export', 'f5.txt'],   "f5.txt\n")
+test.must_match(['work', 'f6.txt'],             "f6.txt\n")
+test.must_match(['work', 'export', 'f6.txt'],   "f6.txt\n")
 
-test.fail_test(test.read(['work', 'my_install.out']) != os.path.join('export', 'f3.out'))
+test.must_match(['work', 'my_install.out'], os.path.join('export', 'f3.out'))
 
 # make sure the programs didn't get rebuilt, because nothing changed:
 oldtime1 = os.path.getmtime(f1_out)
@@ -119,7 +119,7 @@ test.fail_test(oldtime2 != os.path.getmtime(f2_out))
 
 # Verify that we didn't link to the Installed file.
 open(f2_out, 'wb').write("xyzzy\n")
-test.fail_test(test.read(['work', 'f2.out']) != "f2.in\n")
+test.must_match(['work', 'f2.out'], "f2.in\n")
 
 # Verify that scons prints an error message
 # if a target can not be unlinked before building it:
similarity index 67%
rename from src/engine/SCons/Sig/__init__.py
rename to test/Install/tool.py
index cfad3e8230101f2a813885ca9a16bbac224977b7..4f09b73e12dd4d2dea138e3264f56c8a4b367a0b 100644 (file)
@@ -1,9 +1,4 @@
-"""SCons.Sig
-
-The Signature package for the scons software construction utility.
-
-"""
-
+#!/usr/bin/env python
 #
 # __COPYRIGHT__
 #
@@ -29,25 +24,28 @@ The Signature package for the scons software construction utility.
 
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
-try:
-    import MD5
-    default_module = MD5
-except ImportError:
-    import TimeStamp
-    default_module = TimeStamp
+"""
+Verify that we can still call Install() and InstallAs() even when
+no Tool modules have been loaded.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.subdir('iii')
+
+test.write('SConstruct', """
+env = Environment(tools = [])
+env.Install('iii', 'foo.in')
+env.InstallAs('foo.out', 'foo.in')
+""")
 
-class Calculator:
-    """
-    Encapsulates signature calculations and .sconsign file generating
-    for the build engine.
-    """
+test.write('foo.in', "foo.in\n")
 
-    def __init__(self, module=default_module):
-        """
-        Initialize the calculator.
+test.run(arguments = '.')
 
-        module - the signature module to use for signature calculations
-        """
-        self.module = module
+test.must_match(['iii', 'foo.in'], "foo.in\n")
+test.must_match('foo.out', "foo.in\n")
 
-default_calc = Calculator()
+test.pass_test()
diff --git a/test/Java/JAVABOOTCLASSPATH.py b/test/Java/JAVABOOTCLASSPATH.py
new file mode 100644 (file)
index 0000000..7723224
--- /dev/null
@@ -0,0 +1,108 @@
+#!/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 $JAVABOOTCLASSPATH sets the -bootclasspath option
+on javac compilations.
+"""
+
+import os
+import string
+
+import TestSCons
+
+_python_ = TestSCons._python_
+
+test = TestSCons.TestSCons()
+
+ENV = test.java_ENV()
+
+if test.detect_tool('javac', ENV=ENV):
+    where_javac = test.detect('JAVAC', 'javac', ENV=ENV)
+else:
+    where_javac = test.where_is('javac')
+if not where_javac:
+    test.skip_test("Could not find Java javac, skipping test(s).\n")
+
+if test.detect_tool('javah', ENV=ENV):
+    where_javah = test.detect('JAVAH', 'javah', ENV=ENV)
+else:
+    where_javah = test.where_is('javah')
+if not where_javah:
+    test.skip_test("Could not find Java javah, skipping test(s).\n")
+
+test.write('SConstruct', """
+env = Environment(tools = ['javac', 'javah'],
+                  JAVAC = r'%(where_javac)s',
+                  JAVABOOTCLASSPATH = ['dir1', 'dir2'])
+j1 = env.Java(target = 'class', source = 'com/Example1.java')
+j2 = env.Java(target = 'class', source = 'com/Example2.java')
+""" % locals())
+
+test.subdir('com')
+
+test.write(['com', 'Example1.java'], """\
+package com;
+
+public class Example1
+{
+
+     public static void main(String[] args)
+     {
+
+     }
+
+}
+""")
+
+test.write(['com', 'Example2.java'], """\
+package com;
+
+public class Example2
+{
+
+     public static void main(String[] args)
+     {
+
+     }
+
+}
+""")
+
+# Setting -bootclasspath messes with the Java runtime environment, so
+# we'll just take the easy way out and examine the -n output to see if
+# the expected option shows up on the command line.
+
+bootclasspath = string.join(['dir1', 'dir2'], os.pathsep)
+
+expect = """\
+%(where_javac)s -bootclasspath %(bootclasspath)s -d class -sourcepath com com/Example1.java
+%(where_javac)s -bootclasspath %(bootclasspath)s -d class -sourcepath com com/Example2.java
+""" % locals()
+
+test.run(arguments = '-Q -n .', stdout = expect)
+
+test.pass_test()
index 1b69838584d83c8f6b0c4203b40a209526e3a7cd..d185b4d56c4f51f186b3477bb668c1f8a3c4cc2b 100644 (file)
@@ -43,6 +43,13 @@ else:
 if not where_javac:
     test.skip_test("Could not find Java javac, skipping test(s).\n")
 
+if test.detect_tool('javah', ENV=ENV):
+    where_javah = test.detect('JAVAH', 'javah', ENV=ENV)
+else:
+    where_javah = test.where_is('javah')
+if not where_javah:
+    test.skip_test("Could not find Java javah, skipping test(s).\n")
+
 swig = test.where_is('swig')
 if not swig:
     test.skip_test('Can not find installed "swig", skipping test.\n')
@@ -70,7 +77,9 @@ test.subdir(['src'],
 
 test.write(['SConstruct'], """\
 import os,sys
-env=Environment(tools = ['default', 'javac', 'javah'])
+env=Environment(tools = ['default', 'javac', 'javah'],
+                JAVAC = r'%(where_javac)s',
+                JAVAH = r'%(where_javah)s')
 Export('env')
 env.PrependENVPath('PATH',os.environ.get('PATH',[]))
 env['INCPREFIX']='-I'
@@ -94,7 +103,7 @@ env.SConscript(['buildout/server/JavaSource/SConscript',
                 'buildout/HelloApplet/SConscript',
                 'buildout/jni/SConscript',
                 'buildout/javah/SConscript'])
-""")
+""" % locals())
 
 test.write(['src', 'HelloApplet', 'Hello.html'], """\
 <HTML>
@@ -559,9 +568,16 @@ test.must_exist(['buildout', 'jni', 'Sample.class'])
 test.must_exist(['buildout', 'jni', 'Sample.java'])
 test.must_exist(['buildout', 'jni', 'SampleJNI.class'])
 test.must_exist(['buildout', 'jni', 'SampleJNI.java'])
-test.must_exist(['buildout', 'jni', 'SampleTest.class'])
 test.must_exist(['buildout', 'jni', 'SampleTest.java'])
 
+# Some combinations of Java + SWIG apparently don't actually generate
+# a SampleTest.class file, while others do.  Only issue a warning if
+# it doesn't exist.
+p = test.workpath('buildout', 'jni', 'SampleTest.class')
+import os.path
+if not os.path.exists(p):
+    print 'Warning:  %s does not exist' % p
+
 test.up_to_date(arguments = '.')
 
 test.pass_test()
index c6961f2796ff2b4c90fdf55f4692300b8eb434c7..8df5e0965d7bef8de06acaf0a81da6c462613fbe 100644 (file)
@@ -35,6 +35,7 @@ import TestSCons
 test = TestSCons.TestSCons()
 
 ENV = test.java_ENV()
+
 if test.detect_tool('javac', ENV=ENV):
     where_javac = test.detect('JAVAC', 'javac', ENV=ENV)
 else:
@@ -42,6 +43,13 @@ else:
 if not where_javac:
     test.skip_test("Could not find Java javac, skipping test(s).\n")
 
+if test.detect_tool('javah', ENV=ENV):
+    where_javah = test.detect('JAVAH', 'javah', ENV=ENV)
+else:
+    where_javah = test.where_is('javah')
+if not where_javah:
+    test.skip_test("Could not find Java javah, skipping test(s).\n")
+
 if test.detect_tool('jar', ENV=ENV):
     where_jar = test.detect('JAR', 'jar', ENV=ENV)
 else:
@@ -57,7 +65,9 @@ test.subdir(['foo'],
 test.write(['SConstruct'], """\
 import os
 
-env = Environment(ENV = os.environ)
+env = Environment(ENV = os.environ,
+                  JAVAC = r'%(where_javac)s',
+                  JAVAH = r'%(where_javah)s')
 
 env.Append(CPPFLAGS = ' -g -Wall')
         
@@ -65,7 +75,7 @@ Export('env')
 
 SConscript('#foo/SConscript')
 SConscript('#java/SConscript')
-""")
+""" % locals())
 
 test.write(['foo', 'SConscript'], """\
 Import('env')
@@ -123,7 +133,10 @@ env['JARCHDIR'] = 'java/classes'
 foopack_jar = env.Jar(target = 'foopack.jar', source = 'classes')
 """)
 
-test.run(arguments = '.')
+# Disable looking at stderr because some combinations of SWIG/gcc
+# generate a warning about the sWIG_JavaThrowException() function
+# being defined but not used.
+test.run(arguments = '.', stderr=None)
 
 #test.must_exist(['java', 'classes', 'foopack', 'foopack.class'])
 #test.must_exist(['java', 'classes', 'foopack', 'foopackJNI.class'])
index 708ce6355bbad77cd7f1c9b51b4174095346be54..e9f654512344294f786f77a9d426c8d620b88500 100644 (file)
@@ -272,7 +272,7 @@ test.write(['src', 'component2', 'message2.c'], """\
 #include <stdio.h>
 #include "message1.h"
 
-int DisplayMessage2 (void)
+void DisplayMessage2 (void)
 {
     DisplayMessage1();
     printf ("src/component2/hello.c\\n");
index 2db056328a82080b26bc903828c94e5abdfd1de8..a272af742a8fb7ebb75ebddc78e7ca4e927841d9 100644 (file)
@@ -29,13 +29,15 @@ Test the ability to configure the $RCCOM construction variable
 when using MinGW.
 """
 
+import sys
 import TestSCons
 
 _python_ = TestSCons._python_
 
 test = TestSCons.TestSCons()
 
-
+if sys.platform in ('irix6',):
+    test.skip_test("Skipping mingw test on non-Windows %s platform."%sys.platform)
 
 test.write('myrc.py', """
 import sys
index 96565f14c80dc61d1e159a52be4ad9ca0440222c..d04c5d6f79c6ebe1d1aaa78714ba8a4800dbb4a1 100644 (file)
@@ -29,13 +29,15 @@ Test that the $RCCOMSTR construction variable allows you to customize
 the displayed string when rc is called.
 """
 
+import sys
 import TestSCons
 
 _python_ = TestSCons._python_
 
 test = TestSCons.TestSCons()
 
-
+if sys.platform in ('irix6',):
+    test.skip_test("Skipping mingw test on non-Windows %s platform."%sys.platform)
 
 test.write('myrc.py', """
 import sys
index 38692287935e5401197eb7b560a7f09807c92486..7e656f7ec6c8f3cd11f6ae09da80715b3cf8d146 100644 (file)
@@ -32,9 +32,6 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 #    However, this should *not* occur during a dryrun (-n).  When not
 #    performed during a dryrun, this should not affect buildability.
 # 2) Calling is_derived() should not affect buildability.
-# 3) Calling is_pseudo_derived() may cause the sbuilder to be set, and
-#    it may caues the builder to be set as well, but it should not
-#    adversely affect buildability.
 
 import sys
 import TestSCons
@@ -73,13 +70,11 @@ SConscript('bld/SConscript', ['Nodes'])
 if %(_E)s:
   import os
   derived = map(lambda N: N.is_derived(), Nodes)
-  p_derived = map(lambda N: N.is_pseudo_derived(), Nodes)
   real1 = map(lambda N: os.path.exists(str(N)), Nodes)
   exists = map(lambda N: N.exists(), Nodes)
   real2 = map(lambda N: os.path.exists(str(N)), Nodes)
-  for N,D,P,R,E,F in map(None, Nodes, derived, p_derived,
-                               real1, exists, real2):
-    print '%%s: %%s %%s %%s %%s %%s'%%(N,D,P,R,E,F)
+  for N,D,R,E,F in map(None, Nodes, derived, real1, exists, real2):
+    print '%%s: %%s %%s %%s %%s'%%(N,D,R,E,F)
 foo.SharedLibrary(target = 'foo', source = 'foo%(_obj)s')
 bar.SharedLibrary(target = 'bar', source = 'bar%(_obj)s')
 
@@ -147,7 +142,6 @@ def exists_test(node):
     via_node = node.exists()            # side effect causes copy from src
     after = os.path.exists(str(node))
     node.is_derived()
-    node.is_pseudo_derived()
     import SCons.Script
     if GetOption('no_exec'):
         if (before,via_node,after) != (False,False,False):
diff --git a/test/Parallel/duplicate-target.py b/test/Parallel/duplicate-target.py
new file mode 100644 (file)
index 0000000..5d4f5e1
--- /dev/null
@@ -0,0 +1,74 @@
+#!/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, when a file is specified multiple times in a target
+list, we still build all of the necessary targets.
+
+This used to cause a target to "disappear" from the DAG when its reference
+count dropped below 0, because we were subtracting the duplicated target
+mutiple times even though we'd only visit it once.  This was particularly
+hard to track down because the DAG itself (when printed with --tree=prune,
+for example) doesn't show the duplication in the *target* list.
+"""
+
+import TestSCons
+
+_python_ = TestSCons._python_
+
+test = TestSCons.TestSCons()
+
+test.subdir('work')
+
+tar_output = test.workpath('work.tar')
+
+test.write(['work', 'copy.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', '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')
+
+env.Tar(r'%(tar_output)s', Dir('.'))
+""" % locals())
+
+test.write(['work', 'f1.in'], "work/f1.in\n")
+test.write(['work', 'f2.in'], "work/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.pass_test()
diff --git a/test/Progress/TARGET.py b/test/Progress/TARGET.py
new file mode 100644 (file)
index 0000000..e45f859
--- /dev/null
@@ -0,0 +1,66 @@
+#!/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 substition of the $TARGET string in progress output, including
+overwriting it by setting the overwrite= keyword argument.
+"""
+
+import os
+import string
+
+import TestSCons
+
+test = TestSCons.TestSCons(universal_newlines=None)
+
+test.write('SConstruct', """\
+env = Environment()
+env['BUILDERS']['C'] = Builder(action=Copy('$TARGET', '$SOURCE'))
+Progress('$TARGET\\r', overwrite=True)
+env.C('S1.out', 'S1.in')
+env.C('S2.out', 'S2.in')
+env.C('S3.out', 'S3.in')
+env.C('S4.out', 'S4.in')
+""")
+
+test.write('S1.in', "S1.in\n")
+test.write('S2.in', "S2.in\n")
+test.write('S3.in', "S3.in\n")
+test.write('S4.in', "S4.in\n")
+
+expect = """\
+S1.in\r     \rS1.out\rCopy("S1.out", "S1.in")
+      \rS2.in\r     \rS2.out\rCopy("S2.out", "S2.in")
+      \rS3.in\r     \rS3.out\rCopy("S3.out", "S3.in")
+      \rS4.in\r     \rS4.out\rCopy("S4.out", "S4.in")
+      \rSConstruct\r          \r.\r"""
+
+if os.linesep != '\n':
+    expect = string.replace(expect, '\n', os.linesep)
+
+test.run(arguments = '-Q .', stdout=expect)
+
+test.pass_test()
diff --git a/test/Progress/dots.py b/test/Progress/dots.py
new file mode 100644 (file)
index 0000000..23b3e72
--- /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__"
+
+"""
+Test expected behavior of just telling a Progress() object to print
+a dot for every visited Node.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+env = Environment()
+env['BUILDERS']['C'] = Builder(action=Copy('$TARGET', '$SOURCE'))
+Progress('.')
+env.C('S1.out', 'S1.in')
+env.C('S2.out', 'S2.in')
+env.C('S3.out', 'S3.in')
+env.C('S4.out', 'S4.in')
+""")
+
+test.write('S1.in', "S1.in\n")
+test.write('S2.in', "S2.in\n")
+test.write('S3.in', "S3.in\n")
+test.write('S4.in', "S4.in\n")
+
+expect = """\
+..Copy("S1.out", "S1.in")
+..Copy("S2.out", "S2.in")
+..Copy("S3.out", "S3.in")
+..Copy("S4.out", "S4.in")
+.."""
+
+test.run(arguments = '-Q .', stdout=expect)
+
+test.pass_test()
diff --git a/test/Progress/file.py b/test/Progress/file.py
new file mode 100644 (file)
index 0000000..f0116c0
--- /dev/null
@@ -0,0 +1,81 @@
+#!/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 file= argument to Progress() allows us to redirect the
+progress output.
+"""
+
+import os
+import string
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+env = Environment()
+env['BUILDERS']['C'] = Builder(action=Copy('$TARGET', '$SOURCE'))
+Progress('stderr: $TARGET\\n', file=open('progress.out', 'w'))
+env.C('S1.out', 'S1.in')
+env.C('S2.out', 'S2.in')
+env.C('S3.out', 'S3.in')
+env.C('S4.out', 'S4.in')
+""")
+
+test.write('S1.in', "S1.in\n")
+test.write('S2.in', "S2.in\n")
+test.write('S3.in', "S3.in\n")
+test.write('S4.in', "S4.in\n")
+
+expect = """\
+Copy("S1.out", "S1.in")
+Copy("S2.out", "S2.in")
+Copy("S3.out", "S3.in")
+Copy("S4.out", "S4.in")
+"""
+
+test.run(arguments = '-Q .', stdout=expect)
+
+expect = """\
+stderr: S1.in
+stderr: S1.out
+stderr: S2.in
+stderr: S2.out
+stderr: S3.in
+stderr: S3.out
+stderr: S4.in
+stderr: S4.out
+stderr: SConstruct
+stderr: .
+"""
+
+if os.linesep != '\n':
+    expect = string.replace(expect, '\n', os.linesep)
+
+test.must_match('progress.out', expect)
+
+test.pass_test()
diff --git a/test/Progress/function.py b/test/Progress/function.py
new file mode 100644 (file)
index 0000000..2fcb671
--- /dev/null
@@ -0,0 +1,75 @@
+#!/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 behavior of passing our own function to Progress().
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+import sys
+env = Environment()
+env['BUILDERS']['C'] = Builder(action=Copy('$TARGET', '$SOURCE'))
+def my_progress_function(node, *args, **kw):
+    sys.stderr.write('mpf: %s\\n' % node)
+Progress(my_progress_function)
+env.C('S1.out', 'S1.in')
+env.C('S2.out', 'S2.in')
+env.C('S3.out', 'S3.in')
+env.C('S4.out', 'S4.in')
+""")
+
+test.write('S1.in', "S1.in\n")
+test.write('S2.in', "S2.in\n")
+test.write('S3.in', "S3.in\n")
+test.write('S4.in', "S4.in\n")
+
+expect_stdout = """\
+Copy("S1.out", "S1.in")
+Copy("S2.out", "S2.in")
+Copy("S3.out", "S3.in")
+Copy("S4.out", "S4.in")
+"""
+
+expect_stderr = """\
+mpf: S1.in
+mpf: S1.out
+mpf: S2.in
+mpf: S2.out
+mpf: S3.in
+mpf: S3.out
+mpf: S4.in
+mpf: S4.out
+mpf: SConstruct
+mpf: .
+"""
+
+test.run(arguments = '-Q .', stdout=expect_stdout, stderr=expect_stderr)
+
+test.pass_test()
diff --git a/test/Progress/interval.py b/test/Progress/interval.py
new file mode 100644 (file)
index 0000000..767bf20
--- /dev/null
@@ -0,0 +1,68 @@
+#!/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 "interval=" parameter to Progress skips Nodes.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+import sys
+env = Environment()
+env['BUILDERS']['C'] = Builder(action=Copy('$TARGET', '$SOURCE'))
+Progress('stderr: $TARGET\\n', file=sys.stderr, interval=2)
+env.C('S1.out', 'S1.in')
+env.C('S2.out', 'S2.in')
+env.C('S3.out', 'S3.in')
+env.C('S4.out', 'S4.in')
+""")
+
+test.write('S1.in', "S1.in\n")
+test.write('S2.in', "S2.in\n")
+test.write('S3.in', "S3.in\n")
+test.write('S4.in', "S4.in\n")
+
+expect_stdout = """\
+Copy("S1.out", "S1.in")
+Copy("S2.out", "S2.in")
+Copy("S3.out", "S3.in")
+Copy("S4.out", "S4.in")
+"""
+
+expect_stderr = """\
+stderr: S1.out
+stderr: S2.out
+stderr: S3.out
+stderr: S4.out
+stderr: .
+"""
+
+test.run(arguments = '-Q .', stdout=expect_stdout, stderr=expect_stderr)
+
+test.pass_test()
diff --git a/test/Progress/object.py b/test/Progress/object.py
new file mode 100644 (file)
index 0000000..4dad1a1
--- /dev/null
@@ -0,0 +1,78 @@
+#!/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 behavior of passing a callable object to Progress().
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+import sys
+env = Environment()
+env['BUILDERS']['C'] = Builder(action=Copy('$TARGET', '$SOURCE'))
+class my_progress:
+    count = 0
+    def __call__(self, node, *args, **kw):
+        self.count = self.count + 1
+        sys.stderr.write('%s: %s\\n' % (self.count, node))
+Progress(my_progress())
+env.C('S1.out', 'S1.in')
+env.C('S2.out', 'S2.in')
+env.C('S3.out', 'S3.in')
+env.C('S4.out', 'S4.in')
+""")
+
+test.write('S1.in', "S1.in\n")
+test.write('S2.in', "S2.in\n")
+test.write('S3.in', "S3.in\n")
+test.write('S4.in', "S4.in\n")
+
+expect_stdout = """\
+Copy("S1.out", "S1.in")
+Copy("S2.out", "S2.in")
+Copy("S3.out", "S3.in")
+Copy("S4.out", "S4.in")
+"""
+
+expect_stderr = """\
+1: S1.in
+2: S1.out
+3: S2.in
+4: S2.out
+5: S3.in
+6: S3.out
+7: S4.in
+8: S4.out
+9: SConstruct
+10: .
+"""
+
+test.run(arguments = '-Q .', stdout=expect_stdout, stderr=expect_stderr)
+
+test.pass_test()
diff --git a/test/Progress/spinner.py b/test/Progress/spinner.py
new file mode 100644 (file)
index 0000000..c600b67
--- /dev/null
@@ -0,0 +1,66 @@
+#!/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 output when a Progress() call is initialized with the list
+that represents a canonical "spinner" on the output.
+"""
+
+import os
+import string
+
+import TestSCons
+
+test = TestSCons.TestSCons(universal_newlines=None)
+
+test.write('SConstruct', r"""
+env = Environment()
+env['BUILDERS']['C'] = Builder(action=Copy('$TARGET', '$SOURCE'))
+Progress(['-\r', '\\\r', '|\r', '/\r'])
+env.C('S1.out', 'S1.in')
+env.C('S2.out', 'S2.in')
+env.C('S3.out', 'S3.in')
+env.C('S4.out', 'S4.in')
+""")
+
+test.write('S1.in', "S1.in\n")
+test.write('S2.in', "S2.in\n")
+test.write('S3.in', "S3.in\n")
+test.write('S4.in', "S4.in\n")
+
+expect = """\
+\\\r|\rCopy("S1.out", "S1.in")
+/\r-\rCopy("S2.out", "S2.in")
+\\\r|\rCopy("S3.out", "S3.in")
+/\r-\rCopy("S4.out", "S4.in")
+\\\r|\r"""
+
+if os.linesep != '\n':
+    expect = string.replace(expect, '\n', os.linesep)
+
+test.run(arguments = '-Q .', stdout=expect)
+
+test.pass_test()
index 8cb9ab193d7dded0a9b91dde7222586784e7b430..d746d9e66d0950016b1000e65dccb7cfa6009418 100644 (file)
@@ -191,10 +191,11 @@ if test.stdout() != "Hello World\n" or test.stderr() != '' or test.status:
     # If so, then print whatever it showed us (which is in and of itself
     # an indication that it built correctly) but don't fail the test.
     expect = 'cannot connect to X server'
-    test.fail_test(test.stdout() != '' or
-                   string.find(test.stderr(), expect) == -1 or \
-                   (test.status>>8) != 1)
-
+    test.fail_test(test.stdout())
+    test.fail_test(string.find(test.stderr(), expect) == -1)
+    if test.status != 1 and (test.status>>8) != 1:
+        sys.stdout.write('test_realqt returned status %s\n' % test.status)
+        test.fail_test()
 
 QTDIR = os.environ['QTDIR']
 PATH = os.environ['PATH']
index 1dbcd0f346a840a01371473ccb7d634c935f5158..28781369471dcdf03eb574a8f51a184b489cb093 100644 (file)
@@ -92,7 +92,6 @@ test.must_exist(test.workpath('build', moc))
 test.run(arguments = "build_dir=1 chdir=1 dup=0 " +
                      test.workpath('build_dup0', aaa_exe) )
 
-test.must_exist(['build_dup0', moc],
-                ['build_dup0', aaa_exe])
+test.must_exist(['build_dup0', moc])
 
 test.pass_test()
diff --git a/test/SConscript/Return.py b/test/SConscript/Return.py
new file mode 100644 (file)
index 0000000..453c0b8
--- /dev/null
@@ -0,0 +1,93 @@
+#!/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 Return() function stops processing the SConscript file
+at the point is called, unless the stop= keyword argument is supplied.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+SConscript('SConscript1')
+x = SConscript('SConscript2')
+y, z = SConscript('SConscript3')
+a4, b4 = SConscript('SConscript4')
+print "x =", x
+print "y =", y
+print "z =", z
+print "a4 =", a4
+print "b4 =", b4
+""")
+
+test.write('SConscript1', """\
+print "line 1"
+Return()
+print "line 2"
+""")
+
+test.write('SConscript2', """\
+print "line 3"
+x = 7
+Return('x')
+print "line 4"
+""")
+
+test.write('SConscript3', """\
+print "line 5"
+y = 8
+z = 9
+Return('y z')
+print "line 6"
+""")
+
+test.write('SConscript4', """\
+a4 = 'aaa'
+b4 = 'bbb'
+print "line 7"
+Return('a4', 'b4', stop=False)
+b4 = 'b-after'
+print "line 8"
+""")
+
+expect = """\
+line 1
+line 3
+line 5
+line 7
+line 8
+x = 7
+y = 8
+z = 9
+a4 = aaa
+b4 = bbb
+"""
+
+test.run(arguments = '-q -Q', stdout=expect)
+
+test.pass_test()
index 593a26a66f4c6ef86bcc0c11c1aabb4e6abfbf4c..58ad0cda53b57aedce2283141d32885f079e25fc 100644 (file)
@@ -37,6 +37,11 @@ import TestSCons
 
 test = TestSCons.TestSCons()
 
+swig = test.where_is('swig')
+
+if not swig:
+    test.skip_test('Can not find installed "swig", skipping test.\n')
+
 # swig-python expects specific filenames.
 # the platform specific suffix won't necessarily work.
 if sys.platform == 'win32':
@@ -129,9 +134,6 @@ public:
   %extend
   {
     const char* __str__() { return "linalg.Vector()"; }
-    int __len__() { return $self->size(); }
-    double __getitem__(int key) { return $self->operator[](key); }
-    void __setitem__(int key, double value) { $self->operator[](key) = value; }
     
     %pythoncode %{
     def __iter__(self):
index 2dfd32262be48c94bb077ba722b970c9c1cbc9d4..dd2c938cf496b8f1b60df64ad8094ab9d590bf7e 100644 (file)
@@ -411,18 +411,17 @@ int g_3()
 test.run(stderr=TestSCons.noisy_ar,
          match=TestSCons.match_re_dotall)
 
-# XXX Note that the generated .h files still get scanned twice,
-# once before they're generated and once after.  That's the
-# next thing to fix here.
+# Note that the generated .h files still get scanned twice,
+# but that's really once each as a child of libg_1.o and libg_2.o.
 
 test.must_match("MyCScan.out", """\
 libg_1.c: 1
 libg_2.c: 1
 libg_3.c: 1
-libg_gx.h: 1
+libg_gx.h: 2
 libg_gy.h: 1
 libg_gz.h: 1
-libg_w.h: 1
+libg_w.h: 2
 """)
 
 test.pass_test()
diff --git a/test/Sig.py b/test/Sig.py
new file mode 100644 (file)
index 0000000..8565735
--- /dev/null
@@ -0,0 +1,62 @@
+#!/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 generate the proper warning, but don't die, when someone
+tries to import the SCons.Sig module (which no longer exists) and
+use the things we used to define therein.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+SConstruct = test.workpath('SConstruct')
+
+test.write(SConstruct, """
+import SCons.Sig
+x = SCons.Sig.default_calc
+x = SCons.Sig.default_module
+x = SCons.Sig.MD5.current()
+x = SCons.Sig.MD5.collect()
+x = SCons.Sig.MD5.signature()
+x = SCons.Sig.MD5.to_string()
+x = SCons.Sig.MD5.from_string()
+x = SCons.Sig.TimeStamp.current()
+x = SCons.Sig.TimeStamp.collect()
+x = SCons.Sig.TimeStamp.signature()
+x = SCons.Sig.TimeStamp.to_string()
+x = SCons.Sig.TimeStamp.from_string()
+""")
+
+expect = """
+scons: warning: The SCons.Sig module no longer exists.
+    Remove the following "import SCons.Sig" line to eliminate this warning:
+""" + test.python_file_line(SConstruct, 2)
+
+test.run(arguments = '.', stderr=expect)
+
+test.pass_test()
index 86cc2a6c390c7d9b9d2ee2bf52cf6c775f3ae949..b74f8b1dc5bdb9dd04ea1385447b4746f56c4f26 100644 (file)
@@ -59,7 +59,7 @@ env = Environment(BUILDERS={'Cat':Builder(action=cat)}, SUBDIR='sub')
 env.SourceCode('$SUBDIR', Builder(action=sc_cat, env=env))
 env.Cat('aaa.out', 'sub/aaa.in')
 bbb_in = File('sub/bbb.in')
-bbb_in.is_pseudo_derived()
+bbb_in.is_derived()
 env.Cat('bbb.out', bbb_in)
 env.Cat('ccc.out', 'sub/ccc.in')
 env.Cat('all', ['aaa.out', 'bbb.out', 'ccc.out'])
diff --git a/test/SourceSignatures.py b/test/SourceSignatures.py
deleted file mode 100644 (file)
index b85f8ec..0000000
+++ /dev/null
@@ -1,268 +0,0 @@
-#!/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__"
-
-import imp
-import os
-import os.path
-import time
-
-import TestSCons
-
-test = TestSCons.TestSCons()
-
-test.write('SConstruct', """
-def build(env, target, source):
-    open(str(target[0]), 'wt').write(open(str(source[0]), 'rt').read())
-B = Builder(action = build)
-env = Environment(BUILDERS = { 'B' : B })
-env.B(target = 'f1.out', source = 'f1.in')
-env.B(target = 'f2.out', source = 'f2.in')
-env.B(target = 'f3.out', source = 'f3.in')
-env.B(target = 'f4.out', source = 'f4.in')
-
-SourceSignatures('timestamp')
-""")
-
-test.write('f1.in', "f1.in\n")
-test.write('f2.in', "f2.in\n")
-test.write('f3.in', "f3.in\n")
-test.write('f4.in', "f4.in\n")
-
-test.run(arguments = 'f1.out f3.out')
-
-test.run(arguments = 'f1.out f2.out f3.out f4.out',
-         stdout = test.wrap_stdout("""\
-scons: `f1.out' is up to date.
-build(["f2.out"], ["f2.in"])
-scons: `f3.out' is up to date.
-build(["f4.out"], ["f4.in"])
-"""))
-
-os.utime(test.workpath('f1.in'), 
-         (os.path.getatime(test.workpath('f1.in')),
-          os.path.getmtime(test.workpath('f1.in'))+10))
-os.utime(test.workpath('f3.in'), 
-         (os.path.getatime(test.workpath('f3.in')),
-          os.path.getmtime(test.workpath('f3.in'))+10))
-
-test.run(arguments = 'f1.out f2.out f3.out f4.out',
-         stdout = test.wrap_stdout("""\
-build(["f1.out"], ["f1.in"])
-scons: `f2.out' is up to date.
-build(["f3.out"], ["f3.in"])
-scons: `f4.out' is up to date.
-"""))
-
-test.write('SConstruct', """
-def build(env, target, source):
-    open(str(target[0]), 'wt').write(open(str(source[0]), 'rt').read())
-B = Builder(action = build)
-env = Environment(BUILDERS = { 'B' : B })
-env.B(target = 'f1.out', source = 'f1.in')
-env.B(target = 'f2.out', source = 'f2.in')
-env.B(target = 'f3.out', source = 'f3.in')
-env.B(target = 'f4.out', source = 'f4.in')
-
-SourceSignatures('MD5')
-""")
-
-test.write('f1.in', "f1.in\n")
-test.write('f2.in', "f2.in\n")
-test.write('f3.in', "f3.in\n")
-test.write('f4.in', "f4.in\n")
-
-test.run(arguments = 'f1.out f3.out')
-
-test.run(arguments = 'f1.out f2.out f3.out f4.out',
-         stdout = test.wrap_stdout("""\
-scons: `f1.out' is up to date.
-build(["f2.out"], ["f2.in"])
-scons: `f3.out' is up to date.
-build(["f4.out"], ["f4.in"])
-"""))
-
-os.utime(test.workpath('f1.in'), 
-         (os.path.getatime(test.workpath('f1.in')),
-          os.path.getmtime(test.workpath('f1.in'))+10))
-os.utime(test.workpath('f3.in'), 
-         (os.path.getatime(test.workpath('f3.in')),
-          os.path.getmtime(test.workpath('f3.in'))+10))
-
-test.up_to_date(arguments = 'f1.out f2.out f3.out f4.out')
-
-test.write('SConstruct', """
-def build(env, target, source):
-    open(str(target[0]), 'wt').write(open(str(source[0]), 'rt').read())
-B = Builder(action = build)
-env = Environment(BUILDERS = { 'B' : B })
-env.B(target = 'f1.out', source = 'f1.in')
-env.B(target = 'f2.out', source = 'f2.in')
-env.B(target = 'f3.out', source = 'f3.in')
-env.B(target = 'f4.out', source = 'f4.in')
-""")
-
-test.up_to_date(arguments = 'f1.out f2.out f3.out f4.out')
-
-test.write('SConstruct', """
-def build(env, target, source):
-    open(str(target[0]), 'wt').write(open(str(source[0]), 'rt').read())
-B = Builder(action = build)
-env = Environment(BUILDERS = { 'B' : B })
-env2 = env.Clone()
-env2.SourceSignatures('MD5')
-env.B(target = 'f5.out', source = 'f5.in')
-env.B(target = 'f6.out', source = 'f6.in')
-env2.B(target = 'f7.out', source = 'f7.in')
-env2.B(target = 'f8.out', source = 'f8.in')
-
-SourceSignatures('timestamp')
-""")
-
-test.write('f5.in', "f5.in\n")
-test.write('f6.in', "f6.in\n")
-test.write('f7.in', "f7.in\n")
-test.write('f8.in', "f8.in\n")
-
-test.run(arguments = 'f5.out f7.out')
-
-test.run(arguments = 'f5.out f6.out f7.out f8.out',
-         stdout = test.wrap_stdout("""\
-scons: `f5.out' is up to date.
-build(["f6.out"], ["f6.in"])
-scons: `f7.out' is up to date.
-build(["f8.out"], ["f8.in"])
-"""))
-
-os.utime(test.workpath('f5.in'), 
-         (os.path.getatime(test.workpath('f5.in')),
-          os.path.getmtime(test.workpath('f5.in'))+10))
-os.utime(test.workpath('f7.in'), 
-         (os.path.getatime(test.workpath('f7.in')),
-          os.path.getmtime(test.workpath('f7.in'))+10))
-
-test.run(arguments = 'f5.out f6.out f7.out f8.out',
-         stdout = test.wrap_stdout("""\
-build(["f5.out"], ["f5.in"])
-scons: `f6.out' is up to date.
-scons: `f7.out' is up to date.
-scons: `f8.out' is up to date.
-"""))
-
-test.up_to_date(arguments = 'f5.out f6.out f7.out f8.out')
-
-# Ensure that switching signature types causes a rebuild:
-test.write('SConstruct', """
-SourceSignatures('MD5')
-
-def build(env, target, source):
-    open(str(target[0]), 'wt').write(open(str(source[0]), 'rt').read())
-B = Builder(action = build)
-env = Environment(BUILDERS = { 'B' : B })
-env.B(target = 'switch.out', source = 'switch.in')
-""")
-
-test.write('switch.in', "switch.in\n")
-
-switch_out_switch_in = test.wrap_stdout('build(["switch.out"], ["switch.in"])\n')
-
-test.run(arguments = 'switch.out', stdout = switch_out_switch_in)
-
-test.up_to_date(arguments = 'switch.out')
-
-test.write('SConstruct', """
-SourceSignatures('timestamp')
-
-def build(env, target, source):
-    open(str(target[0]), 'wt').write(open(str(source[0]), 'rt').read())
-B = Builder(action = build)
-env = Environment(BUILDERS = { 'B' : B })
-env.B(target = 'switch.out', source = 'switch.in')
-""")
-
-test.run(arguments = 'switch.out', stdout = switch_out_switch_in)
-
-test.up_to_date(arguments = 'switch.out')
-
-test.write('SConstruct', """
-SourceSignatures('MD5')
-
-def build(env, target, source):
-    open(str(target[0]), 'wt').write(open(str(source[0]), 'rt').read())
-B = Builder(action = build)
-env = Environment(BUILDERS = { 'B' : B })
-env.B(target = 'switch.out', source = 'switch.in')
-""")
-
-test.run(arguments = 'switch.out', stdout = switch_out_switch_in)
-
-test.up_to_date(arguments = 'switch.out')
-
-test.write('switch.in', "switch.in 2\n")
-
-test.run(arguments = 'switch.out', stdout = switch_out_switch_in)
-
-
-# Test both implicit_cache and timestamp signatures at the same time:
-test.write('SConstruct', """
-SetOption('implicit_cache', 1)
-SourceSignatures('timestamp')
-
-def build(env, target, source):
-    open(str(target[0]), 'wt').write(open(str(source[0]), 'rt').read())
-B = Builder(action = build)
-env = Environment(BUILDERS = { 'B' : B })
-env.B(target = 'both.out', source = 'both.in')
-""")
-
-test.write('both.in', "both.in 1\n")
-
-both_out_both_in = test.wrap_stdout('build(["both.out"], ["both.in"])\n')
-
-test.run(arguments = 'both.out', stdout = both_out_both_in)
-
-time.sleep(2)
-
-test.write('both.in', "both.in 2\n")
-
-test.run(arguments = 'both.out', stdout = both_out_both_in)
-
-time.sleep(2)
-
-test.write('both.in', "both.in 3\n")
-
-test.run(arguments = 'both.out', stdout = both_out_both_in)
-
-time.sleep(2)
-
-test.write('both.in', "both.in 4\n")
-
-test.run(arguments = 'both.out', stdout = both_out_both_in)
-
-time.sleep(2)
-
-test.up_to_date(arguments = 'both.out')
-
-test.pass_test()
diff --git a/test/SourceSignatures/basic.py b/test/SourceSignatures/basic.py
new file mode 100644 (file)
index 0000000..7042fac
--- /dev/null
@@ -0,0 +1,124 @@
+#!/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__"
+
+import os
+import os.path
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+
+
+base_sconstruct_contents = """\
+def build(env, target, source):
+    open(str(target[0]), 'wt').write(open(str(source[0]), 'rt').read())
+B = Builder(action = build)
+env = Environment(BUILDERS = { 'B' : B })
+env.B(target = 'f1.out', source = 'f1.in')
+env.B(target = 'f2.out', source = 'f2.in')
+env.B(target = 'f3.out', source = 'f3.in')
+env.B(target = 'f4.out', source = 'f4.in')
+"""
+
+def write_SConstruct(test, sigtype):
+    contents = base_sconstruct_contents
+    if sigtype:
+         contents = contents + ("\nSourceSignatures('%s')\n" % sigtype)
+    test.write('SConstruct', contents)
+
+
+
+write_SConstruct(test, 'timestamp')
+
+test.write('f1.in', "f1.in\n")
+test.write('f2.in', "f2.in\n")
+test.write('f3.in', "f3.in\n")
+test.write('f4.in', "f4.in\n")
+
+test.run(arguments = 'f1.out f3.out')
+
+test.run(arguments = 'f1.out f2.out f3.out f4.out',
+         stdout = test.wrap_stdout("""\
+scons: `f1.out' is up to date.
+build(["f2.out"], ["f2.in"])
+scons: `f3.out' is up to date.
+build(["f4.out"], ["f4.in"])
+"""))
+
+
+
+os.utime(test.workpath('f1.in'), 
+         (os.path.getatime(test.workpath('f1.in')),
+          os.path.getmtime(test.workpath('f1.in'))+10))
+os.utime(test.workpath('f3.in'), 
+         (os.path.getatime(test.workpath('f3.in')),
+          os.path.getmtime(test.workpath('f3.in'))+10))
+
+test.run(arguments = 'f1.out f2.out f3.out f4.out',
+         stdout = test.wrap_stdout("""\
+build(["f1.out"], ["f1.in"])
+scons: `f2.out' is up to date.
+build(["f3.out"], ["f3.in"])
+scons: `f4.out' is up to date.
+"""))
+
+
+
+# Switching to content signatures from timestamps should rebuild,
+# because we didn't record the content signatures last time.
+
+write_SConstruct(test, 'MD5')
+
+test.not_up_to_date(arguments = 'f1.out f2.out f3.out f4.out')
+
+
+
+test.sleep()
+
+test.write('f1.in', "f1.in\n")
+test.write('f2.in', "f2.in\n")
+test.write('f3.in', "f3.in\n")
+test.write('f4.in', "f4.in\n")
+
+test.up_to_date(arguments = 'f1.out f2.out f3.out f4.out')
+
+
+
+test.touch('f1.in', os.path.getmtime(test.workpath('f1.in'))+10)
+test.touch('f3.in', os.path.getmtime(test.workpath('f3.in'))+10)
+
+test.up_to_date(arguments = 'f1.out f2.out f3.out f4.out')
+
+
+
+write_SConstruct(test, None)
+
+test.up_to_date(arguments = 'f1.out f2.out f3.out f4.out')
+
+
+
+test.pass_test()
diff --git a/test/SourceSignatures/env.py b/test/SourceSignatures/env.py
new file mode 100644 (file)
index 0000000..3bef2a6
--- /dev/null
@@ -0,0 +1,96 @@
+#!/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 use of env.SourceSignatures() correctly overrides the
+default behavior.
+"""
+
+import os
+import os.path
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+base_sconstruct_contents = """\
+def build(env, target, source):
+    open(str(target[0]), 'wt').write(open(str(source[0]), 'rt').read())
+B = Builder(action = build)
+env = Environment(BUILDERS = { 'B' : B })
+env2 = env.Copy()
+env2.SourceSignatures('%s')
+env.B(target = 'f1.out', source = 'f1.in')
+env.B(target = 'f2.out', source = 'f2.in')
+env2.B(target = 'f3.out', source = 'f3.in')
+env2.B(target = 'f4.out', source = 'f4.in')
+
+SourceSignatures('%s')
+"""
+
+def write_SConstruct(test, env_sigtype, default_sigtype):
+    contents = base_sconstruct_contents % (env_sigtype, default_sigtype)
+    test.write('SConstruct', contents)
+
+
+
+write_SConstruct(test, 'MD5', 'timestamp')
+
+test.write('f1.in', "f1.in\n")
+test.write('f2.in', "f2.in\n")
+test.write('f3.in', "f3.in\n")
+test.write('f4.in', "f4.in\n")
+
+test.run(arguments = 'f1.out f3.out')
+
+test.run(arguments = 'f1.out f2.out f3.out f4.out',
+         stdout = test.wrap_stdout("""\
+scons: `f1.out' is up to date.
+build(["f2.out"], ["f2.in"])
+scons: `f3.out' is up to date.
+build(["f4.out"], ["f4.in"])
+"""))
+
+
+
+test.sleep()
+
+test.touch('f1.in')
+test.touch('f3.in')
+
+test.run(arguments = 'f1.out f2.out f3.out f4.out',
+         stdout = test.wrap_stdout("""\
+build(["f1.out"], ["f1.in"])
+scons: `f2.out' is up to date.
+scons: `f3.out' is up to date.
+scons: `f4.out' is up to date.
+"""))
+
+test.up_to_date(arguments = 'f1.out f2.out f3.out f4.out')
+
+
+
+test.pass_test()
diff --git a/test/SourceSignatures/implicit-cache.py b/test/SourceSignatures/implicit-cache.py
new file mode 100644 (file)
index 0000000..de66b72
--- /dev/null
@@ -0,0 +1,87 @@
+#!/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 the simultaneous use of implicit_cache and
+SourceSignatures('timestamp')
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+SetOption('implicit_cache', 1)
+SourceSignatures('timestamp')
+
+def build(env, target, source):
+    open(str(target[0]), 'wt').write(open(str(source[0]), 'rt').read())
+B = Builder(action = build)
+env = Environment(BUILDERS = { 'B' : B })
+env.B(target = 'both.out', source = 'both.in')
+""")
+
+both_out_both_in = test.wrap_stdout('build(["both.out"], ["both.in"])\n')
+
+
+
+test.write('both.in', "both.in 1\n")
+
+test.run(arguments = 'both.out', stdout = both_out_both_in)
+
+
+
+test.sleep(2)
+
+test.write('both.in', "both.in 2\n")
+
+test.run(arguments = 'both.out', stdout = both_out_both_in)
+
+
+
+test.sleep(2)
+
+test.write('both.in', "both.in 3\n")
+
+test.run(arguments = 'both.out', stdout = both_out_both_in)
+
+
+
+test.sleep(2)
+
+test.write('both.in', "both.in 4\n")
+
+test.run(arguments = 'both.out', stdout = both_out_both_in)
+
+
+
+test.sleep(2)
+
+test.up_to_date(arguments = 'both.out')
+
+
+
+test.pass_test()
diff --git a/test/SourceSignatures/no-csigs.py b/test/SourceSignatures/no-csigs.py
new file mode 100644 (file)
index 0000000..3702901
--- /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__"
+
+import os
+import os.path
+
+import TestSConsign
+
+test = TestSConsign.TestSConsign(match = TestSConsign.match_re)
+
+
+test.write('SConstruct', """\
+def build(env, target, source):
+    open(str(target[0]), 'wt').write(open(str(source[0]), 'rt').read())
+B = Builder(action = build)
+env = Environment(BUILDERS = { 'B' : B })
+env.B(target = 'f1.out', source = 'f1.in')
+env.B(target = 'f2.out', source = 'f2.in')
+SourceSignatures('timestamp')
+""")
+
+test.write('f1.in', "f1.in\n")
+test.write('f2.in', "f2.in\n")
+
+test.run(arguments = '.')
+
+
+
+expect = r"""=== .:
+SConstruct: None \d+ \d+
+f1.in: None \d+ \d+
+f1.out: \S+ \d+ \d+
+        f1.in: None \d+ \d+
+        \S+ \[build\(target, source, env\)\]
+f2.in: None \d+ \d+
+f2.out: \S+ \d+ \d+
+        f2.in: None \d+ \d+
+        \S+ \[build\(target, source, env\)\]
+"""
+
+test.run_sconsign(arguments = test.workpath('.sconsign'),
+                  stdout = expect)
+
+
+
+test.pass_test()
diff --git a/test/SourceSignatures/overrides.py b/test/SourceSignatures/overrides.py
new file mode 100644 (file)
index 0000000..cf83488
--- /dev/null
@@ -0,0 +1,56 @@
+#!/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__"
+
+"""
+Make sure that SourceSignatures() works when overrides are used on a
+Builder call.  (Previous implementations used methods that would stay
+bound to the underlying construction environment, which in this case
+meant ignoring the 'timestamp' setting and still using the underlying
+content signature.)
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+DefaultEnvironment().SourceSignatures('MD5')
+env = Environment()
+env.SourceSignatures('timestamp')
+env.Command('foo.out', 'foo.in', Copy('$TARGET', '$SOURCE'), FOO=1)
+""")
+
+test.write('foo.in', "foo.in 1\n")
+
+test.run(arguments = 'foo.out')
+
+test.sleep()
+
+test.write('foo.in', "foo.in 1\n")
+
+test.not_up_to_date(arguments = 'foo.out')
+
+test.pass_test()
diff --git a/test/SourceSignatures/switch-rebuild.py b/test/SourceSignatures/switch-rebuild.py
new file mode 100644 (file)
index 0000000..85c2b22
--- /dev/null
@@ -0,0 +1,81 @@
+#!/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 switching SourceSignature() types no longer causes rebuilds.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+
+base_sconstruct_contents = """\
+SourceSignatures('%s')
+
+def build(env, target, source):
+    open(str(target[0]), 'wt').write(open(str(source[0]), 'rt').read())
+B = Builder(action = build)
+env = Environment(BUILDERS = { 'B' : B })
+env.B(target = 'switch.out', source = 'switch.in')
+"""
+
+def write_SConstruct(test, sig_type):
+    contents = base_sconstruct_contents % sig_type
+    test.write('SConstruct', contents)
+
+
+write_SConstruct(test, 'MD5')
+
+test.write('switch.in', "switch.in\n")
+
+switch_out_switch_in = test.wrap_stdout('build(["switch.out"], ["switch.in"])\n')
+
+test.run(arguments = 'switch.out', stdout = switch_out_switch_in)
+
+test.up_to_date(arguments = 'switch.out')
+
+
+
+write_SConstruct(test, 'timestamp')
+
+test.up_to_date(arguments = 'switch.out')
+
+
+
+write_SConstruct(test, 'MD5')
+
+test.not_up_to_date(arguments = 'switch.out')
+
+
+
+test.write('switch.in', "switch.in 2\n")
+
+test.run(arguments = 'switch.out', stdout = switch_out_switch_in)
+
+
+
+test.pass_test()
index e189d5745b3912abb163b978aa04e0b50d0820c8..8df2e79f72ed1d49e13a14895b2a98c4bacb833d 100644 (file)
@@ -141,7 +141,7 @@ if pdf_output_1 != pdf_output_2:
     import sys
     test.write(['build', 'docs', 'test.normalized.1.pdf'], pdf_output_1)
     test.write(['build', 'docs', 'test.normalized.2.pdf'], pdf_output_2)
-    sys.stdout.write("***** 1 and 2 are different!\n")
+    sys.stdout.write("***** 1.pdf and 2.pdf are different!\n")
     sys.stdout.write(test.diff_substr(pdf_output_1, pdf_output_2, 80, 80) + '\n')
     sys.stdout.write("Output from run 1:\n")
     sys.stdout.write(test.stdout(-1) + '\n')
@@ -150,7 +150,16 @@ if pdf_output_1 != pdf_output_2:
     sys.stdout.flush()
     test.fail_test()
 
-assert ps_output_1 == ps_output_2,      test.diff_substr(ps_output_1, ps_output_2, 80, 80)
+if ps_output_1 != ps_output_2:
+    import sys
+    sys.stdout.write("***** 1.ps and 2.ps are different!\n")
+    sys.stdout.write(test.diff_substr(ps_output_1, ps_output_2, 80, 80) + '\n')
+    sys.stdout.write("Output from run 1:\n")
+    sys.stdout.write(test.stdout(-1) + '\n')
+    sys.stdout.write("Output from run 2:\n")
+    sys.stdout.write(test.stdout() + '\n')
+    sys.stdout.flush()
+    test.fail_test()
 
 
 
diff --git a/test/TEX/clean.py b/test/TEX/clean.py
new file mode 100644 (file)
index 0000000..6615fc9
--- /dev/null
@@ -0,0 +1,94 @@
+#!/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__"
+
+"""
+Check that all auxilary files created by LaTeX are properly cleaned by scons -c.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+latex = test.where_is('latex')
+
+if not latex:
+    test.skip_test("Could not find tex or latex; skipping test(s).\n")
+
+# package hyperref generates foo.out
+# package comment generates comment.cut
+# todo: add makeindex etc.
+input_file = r"""
+\documentclass{article}
+\usepackage{hyperref}
+\usepackage{comment}
+\specialcomment{foocom}{}{}
+\begin{document}
+\begin{foocom}
+Hi
+\end{foocom}
+As stated in \cite{X}, this is a bug-a-boo.
+\bibliography{fooref}
+\bibliographystyle{plain}
+\end{document}
+"""
+
+bibfile = r"""
+@Article{X,
+  author =      "Mr. X",
+  title =       "A determination of bug-a-boo-ness",
+  journal =     "Journal of B.a.B.",
+  year =        1920,
+  volume =      62,
+  pages =       291
+}
+"""
+
+test.write('SConstruct', """\
+DVI( "foo.ltx" )
+""")
+
+test.write('foo.ltx', input_file)
+test.write('fooref.bib', bibfile)
+
+test.run()
+
+test.must_exist('foo.log')
+test.must_exist('foo.aux')
+test.must_exist('foo.bbl')
+test.must_exist('foo.blg')
+test.must_exist('comment.cut')
+test.must_exist('foo.out')
+
+test.run(arguments = '-c')
+
+test.must_not_exist('foo.log')
+test.must_not_exist('foo.aux')
+test.must_not_exist('foo.bbl')
+test.must_not_exist('foo.blg')
+test.must_not_exist('comment.cut')
+test.must_not_exist('foo.out')
+
+test.pass_test()
similarity index 56%
rename from test/TargetSignatures.py
rename to test/TargetSignatures/build-content.py
index c3b506b596974048a2378171333d51a216ec049f..2cd7a89a6db43f9c0710efafaccc1dfb03787293 100644 (file)
 
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
+"""
+Verify basic interaction of the historic TargetSignatures('build')
+and TargetSignatures('content') settings, overriding one with
+the other in specific construction environments.
+"""
+
 import TestSCons
 
 test = TestSCons.TestSCons()
 
-test.write('SConstruct', """
+
+
+sconstruct_contents = """\
 env = Environment()
 
 def copy1(env, source, target):
     open(str(target[0]), 'wb').write(open(str(source[0]), 'rb').read())
 
 def copy2(env, source, target):
+    %s
     return copy1(env, source, target)
 
 env['BUILDERS']['Copy1'] = Builder(action=copy1)
@@ -44,12 +53,20 @@ env.Copy2('foo.mid', 'foo.in')
 env.Copy1('foo.out', 'foo.mid')
 
 env2 = env.Clone()
-env2.TargetSignatures('build')
+env2.TargetSignatures('%s')
 env2.Copy2('bar.mid', 'bar.in')
 env2.Copy1('bar.out', 'bar.mid')
 
-TargetSignatures('content')
-""")
+TargetSignatures('%s')
+"""
+
+def write_SConstruct(test, *args):
+    contents = sconstruct_contents % args
+    test.write('SConstruct', contents)
+
+
+
+write_SConstruct(test, '', 'build', 'content')
 
 test.write('foo.in', 'foo.in')
 test.write('bar.in', 'bar.in')
@@ -64,29 +81,12 @@ copy1(["foo.out"], ["foo.mid"])
 
 test.up_to_date(arguments='bar.out foo.out')
 
-test.write('SConstruct', """
-env = Environment()
-
-def copy1(env, source, target):
-    open(str(target[0]), 'wb').write(open(str(source[0]), 'rb').read())
-
-def copy2(env, source, target):
-    x = 2 # added this line
-    return copy1(env, source, target)
-
-env['BUILDERS']['Copy1'] = Builder(action=copy1)
-env['BUILDERS']['Copy2'] = Builder(action=copy2)
 
-env.Copy2('foo.mid', 'foo.in')
-env.Copy1('foo.out', 'foo.mid')
 
-env2 = env.Clone()
-env2.TargetSignatures('build')
-env2.Copy2('bar.mid', 'bar.in')
-env2.Copy1('bar.out', 'bar.mid')
+# Change the code in the the copy2() function, which should change
+# its content and trigger a rebuild of the targets built with it.
 
-TargetSignatures('content')
-""")
+write_SConstruct(test, 'x = 2 # added this line', 'build', 'content')
 
 test.run(arguments="bar.out foo.out",
          stdout=test.wrap_stdout("""\
@@ -96,58 +96,21 @@ copy2(["foo.mid"], ["foo.in"])
 scons: `foo.out' is up to date.
 """))
 
-test.write('SConstruct', """
-env = Environment()
-
-def copy1(env, source, target):
-    open(str(target[0]), 'wb').write(open(str(source[0]), 'rb').read())
-
-def copy2(env, source, target):
-    x = 2 # added this line
-    return copy1(env, source, target)
-
-env['BUILDERS']['Copy1'] = Builder(action=copy1)
-env['BUILDERS']['Copy2'] = Builder(action=copy2)
-
-env.Copy2('foo.mid', 'foo.in')
-env.Copy1('foo.out', 'foo.mid')
-
-env2 = env.Copy()
-env2.TargetSignatures('content')
-env2.Copy2('bar.mid', 'bar.in')
-env2.Copy1('bar.out', 'bar.mid')
-
-TargetSignatures('build')
-""")
 
-test.run(arguments="bar.out foo.out",
-         stdout=test.wrap_stdout("""\
-copy1(["bar.out"], ["bar.mid"])
-copy1(["foo.out"], ["foo.mid"])
-"""))
 
-test.write('SConstruct', """
-env = Environment()
+# Swapping content and build signatures no longer causes a rebuild
+# because we record the right underlying information regardless.
 
-def copy1(env, source, target):
-    open(str(target[0]), 'wb').write(open(str(source[0]), 'rb').read())
+write_SConstruct(test, 'x = 2 # added this line', 'content', 'build')
 
-def copy2(env, source, target):
-    return copy1(env, source, target)
+test.up_to_date(arguments="bar.out foo.out")
 
-env['BUILDERS']['Copy1'] = Builder(action=copy1)
-env['BUILDERS']['Copy2'] = Builder(action=copy2)
 
-env.Copy2('foo.mid', 'foo.in')
-env.Copy1('foo.out', 'foo.mid')
 
-env2 = env.Copy()
-env2.TargetSignatures('content')
-env2.Copy2('bar.mid', 'bar.in')
-env2.Copy1('bar.out', 'bar.mid')
+# Change the code in the the copy2() function back again, which should
+# trigger another rebuild of the targets built with it.
 
-TargetSignatures('build')
-""")
+write_SConstruct(test, '', 'content', 'build')
 
 test.run(arguments='bar.out foo.out',
          stdout=test.wrap_stdout("""\
@@ -158,4 +121,5 @@ copy1(["foo.out"], ["foo.mid"])
 """))
 
 
+
 test.pass_test()
diff --git a/test/TargetSignatures/content.py b/test/TargetSignatures/content.py
new file mode 100644 (file)
index 0000000..8d9f213
--- /dev/null
@@ -0,0 +1,81 @@
+#!/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 use of the TargetSignatures('content') setting to override
+SourceSignatures('timestamp') settings.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+
+
+test.write('SConstruct', """\
+env = Environment()
+
+def copy(env, source, target):
+    fp = open(str(target[0]), 'wb')
+    for s in source:
+       fp.write(open(str(s), 'rb').read())
+    fp.close()
+
+copyAction = Action(copy, "Copying $TARGET")
+
+SourceSignatures('timestamp')
+
+env['BUILDERS']['Copy'] = Builder(action=copyAction)
+
+env.Copy('foo.out', 'foo.in')
+
+env2 = env.Clone()
+env2.TargetSignatures('content')
+env2.Copy('bar.out', 'bar.in')
+AlwaysBuild('bar.out')
+
+env.Copy('final', ['foo.out', 'bar.out', 'extra.in'])
+env.Ignore('final', 'extra.in')
+""")
+
+test.write('foo.in', "foo.in\n")
+test.write('bar.in', "bar.in\n")
+test.write('extra.in', "extra.in 1\n")
+
+test.run()
+
+test.must_match('final', "foo.in\nbar.in\nextra.in 1\n")
+
+test.sleep()
+test.write('extra.in', "extra.in 2\n")
+
+test.run()
+
+test.must_match('final', "foo.in\nbar.in\nextra.in 1\n")
+
+
+
+test.pass_test()
diff --git a/test/TargetSignatures/overrides.py b/test/TargetSignatures/overrides.py
new file mode 100644 (file)
index 0000000..5d9dd99
--- /dev/null
@@ -0,0 +1,52 @@
+#!/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__"
+
+"""
+Make sure that TargetSignatures() works when overrides are used on a
+Builder call.  Previous implementations used methods that would stay
+bound to the underlying construction environment and cause weird
+behavior like infinite recursion.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+env = Environment()
+env.TargetSignatures('content')
+env.Command('foo.out', 'foo.mid', Copy('$TARGET', '$SOURCE'), FOO=1)
+env.Command('foo.mid', 'foo.in', Copy('$TARGET', '$SOURCE'), FOO=2)
+""")
+
+test.write('foo.in', "foo.in\n")
+
+test.run(arguments = '.')
+
+test.must_match('foo.mid', "foo.in\n")
+test.must_match('foo.out', "foo.in\n")
+
+test.pass_test()
index 85fcbd1969ac8de38fc9266fa5129a1ab0bb831b..fd7afb71c5fc6accd186c80cf9247629d376ec2e 100644 (file)
@@ -35,13 +35,9 @@ _python_ = TestSCons._python_
 
 test = TestSCons.TestSCons(match=TestCmd.match_re)
 
-# Run all of the tests with both types of source signature
-# to make sure there's no difference in behavior.
-for source_signature in ['MD5', 'timestamp']:
+python = TestSCons.python
 
-    print "Testing Value node with source signatures:", source_signature
-
-    test.write('SConstruct', """
+SConstruct_content = """
 SourceSignatures(r'%(source_signature)s')
 
 class Custom:
@@ -57,7 +53,7 @@ def create(target, source, env):
 
 env = Environment()
 env['BUILDERS']['B'] = Builder(action = create)
-env['BUILDERS']['S'] = Builder(action = '%(_python_)s put $SOURCES into $TARGET')
+env['BUILDERS']['S'] = Builder(action = r'%(_python_)s put.py $SOURCES into $TARGET')
 env.B('f1.out', Value(P))
 env.B('f2.out', env.Value(L))
 env.B('f3.out', Value(C))
@@ -75,15 +71,23 @@ env['BUILDERS']['B3'] = Builder(action = create_value_file)
 V = Value('my value')
 env.B2(V, 'f3.out')
 env.B3('f5.out', V)
-""" % locals())
+"""
 
-    test.write('put', """
+test.write('put.py', """\
 import os
 import string
 import sys
 open(sys.argv[-1],'wb').write(string.join(sys.argv[1:-2]))
 """)
 
+# Run all of the tests with both types of source signature
+# to make sure there's no difference in behavior.
+for source_signature in ['MD5', 'timestamp']:
+
+    print "Testing Value node with source signatures:", source_signature
+
+    test.write('SConstruct', SConstruct_content % locals())
+
     test.run(arguments='-c')
     test.run()
 
index a642a7c7fdbbe902495a18b4ae5762c29ddc34d1..f07da2bee79684ea8cef3c9e4c628d85d7931995 100644 (file)
@@ -93,7 +93,7 @@ test.fail_test(test.read('aaa.out') != "aaa.in\n")
 # becomes an issue or some refactoring restores the old behavior.
 
 test.run(arguments = bad_drive + 'not_mentioned',
-         stderr = "scons: *** No drive `%s' for target `%snot_mentioned'.  Stop.\n" % (bad_drive, bad_drive),
+         stderr = "scons: *** Do not know how to make target `%snot_mentioned'.  Stop.\n" % (bad_drive),
          status = 2)
 
 test.run(arguments = bad_drive + 'no_target_1',
index 9d873475502aa3c5f6edf4cb3a7865f547606d60..6ec9d1cb647299ddc6225fa5cff4b000f84bdb48 100644 (file)
@@ -59,6 +59,7 @@ test.write(['w1', 'foo.in'], "foo.in 1")
 test.run(chdir='w1',
          arguments="--max-drift=0 -f SConstruct1 foo.mid",
          stdout = test.wrap_stdout('build(["foo.mid"], ["foo.in"])\n'))
+
 test.run(chdir='w1',
          arguments="--max-drift=0 -f SConstruct2 foo.out",
          stdout = test.wrap_stdout('build(["foo.out"], ["foo.mid"])\n'))
@@ -66,6 +67,7 @@ test.run(chdir='w1',
 test.up_to_date(chdir='w1',
                 options="--max-drift=0 -f SConstruct1",
                 arguments="foo.mid")
+
 test.up_to_date(chdir='w1',
                 options="--max-drift=0 -f SConstruct2",
                 arguments="foo.out")
@@ -73,28 +75,23 @@ test.up_to_date(chdir='w1',
 test.sleep()  # make sure foo.in rewrite has new mod-time
 test.write(['w1', 'foo.in'], "foo.in 2")
 
-test.run(chdir='w1',
-         arguments="--max-drift=0 -f SConstruct1 foo.mid",
-         stdout = test.wrap_stdout('build(["foo.mid"], ["foo.in"])\n'))
 # Because we're using --max-drift=0, we use the cached csig value
-# and think that foo.mid hasn't changed even though it has on disk.
+# and think that foo.in hasn't changed even though it has on disk.
 test.up_to_date(chdir='w1',
-                options="--max-drift=0 -f SConstruct2",
-                arguments="foo.out")
+         options="--max-drift=0 -f SConstruct1",
+         arguments="foo.mid")
 
+# Now try with --max-drift disabled.  The build of foo.out should still
+# be considered up-to-date, but the build of foo.mid now detects the
+# change and rebuilds, too, which then causes a rebuild of foo.out.
 test.up_to_date(chdir='w1',
-                options="--max-drift=0 -f SConstruct1",
-                arguments="foo.mid")
-test.up_to_date(chdir='w1',
-                options="--max-drift=0 -f SConstruct2",
+                options="--max-drift=-1 -f SConstruct2",
                 arguments="foo.out")
 
-# Now try with --max-drift disabled.  The build of foo.mid should still
-# be considered up-to-date, but the build of foo.out now detects the
-# change and rebuilds, too.
-test.up_to_date(chdir='w1',
-                options="--max-drift=-1 -f SConstruct1",
-                arguments="foo.mid")
+test.run(chdir='w1',
+         arguments="--max-drift=-1 -f SConstruct1 foo.mid",
+         stdout = test.wrap_stdout('build(["foo.mid"], ["foo.in"])\n'))
+
 test.run(chdir='w1',
          arguments="--max-drift=-1 -f SConstruct2 foo.out",
          stdout = test.wrap_stdout('build(["foo.out"], ["foo.mid"])\n'))
index 34c24f45bb45a3af6b414abed438b1f9f8b12b14..c4bfb378b9fbeb043fef9bd4a836822590812dd5 100644 (file)
@@ -65,7 +65,7 @@ test.run(arguments = "-j2 foo.out", stderr = expected_stderr, status = 2)
 
 
 # Verify that exceptions caused by exit values of builder actions are
-# correectly signalled, for both Serial and Parallel jobs.
+# correctly signalled, for both Serial and Parallel jobs.
 
 test.write('myfail.py', r"""\
 import sys
@@ -75,12 +75,12 @@ sys.exit(1)
 test.write(SConstruct_path, """
 Fail = Builder(action = r'%(_python_)s myfail.py $TARGETS $SOURCE')
 env = Environment(BUILDERS = { 'Fail' : Fail })
-env.Fail(target = 'f1', source = 'f1.in')
+env.Fail(target = 'out.f1', source = 'in.f1')
 """ % locals())
 
-test.write('f1.in', "f1.in\n")
+test.write('in.f1', "in.f1\n")
 
-expected_stderr = "scons: \*\*\* \[f1\] Error 1\n"
+expected_stderr = "scons: \\*\\*\\* \\[out.f1\\] Error 1\n"
 
 test.run(arguments = '.', status = 2, stderr = expected_stderr)
 test.run(arguments = '-j2 .', status = 2, stderr = expected_stderr)
@@ -93,13 +93,14 @@ test.run(arguments = '-j2 .', status = 2, stderr = expected_stderr)
 test.write(SConstruct_path, """
 Fail = Builder(action = r'%(_python_)s myfail.py $TARGETS $SOURCE')
 env = Environment(BUILDERS = { 'Fail' : Fail })
-env.Fail(target = 'f1', source = 'f1.in')
-env.Fail(target = 'f2', source = 'f2.in')
-env.Fail(target = 'f3', source = 'f3.in')
+env.Fail(target = 'out.f1', source = 'in.f1')
+env.Fail(target = 'out.f2', source = 'in.f2')
+env.Fail(target = 'out.f3', source = 'in.f3')
 """ % locals())
 
-# f2.in is not created to cause a Task.prepare exception
-test.write('f3.in', 'f3.in\n')
+# in.f2 is not created to cause a Task.prepare exception
+test.write('in.f1', 'in.f1\n')
+test.write('in.f3', 'in.f3\n')
 
 # In Serial task mode, get the first exception and stop
 test.run(arguments = '.', status = 2, stderr = expected_stderr)
@@ -107,24 +108,32 @@ test.run(arguments = '.', status = 2, stderr = expected_stderr)
 # In Parallel task mode, we will get all three exceptions.
 
 expected_stderr_list = [
-  expected_stderr,
-  "scons: \*\*\* Source `f2\.in' not found, needed by target `f2'\.  Stop\.\n",
-  string.replace(expected_stderr, 'f1', 'f3')
-  ]
-  
-# Unfortunately, we aren't guaranteed what order we will get the
-# exceptions in...
-orders = [ (1,2,3), (1,3,2), (2,1,3), (2,3,1), (3,1,2), (3,2,1) ]
-otexts = []
-for A,B,C in orders:
-    otexts.append("%s%s%s"%(expected_stderr_list[A-1],
-                            expected_stderr_list[B-1],
-                            expected_stderr_list[C-1]))
-                           
-                      
-expected_stderrs = "(" + string.join(otexts, "|") + ")"
-
-test.run(arguments = '-j3 .', status = 2, stderr = expected_stderrs)
+    "scons: *** [out.f1] Error 1\n",
+    "scons: *** Source `in.f2' not found, needed by target `out.f2'.  Stop.\n",
+    "scons: *** [out.f3] Error 1\n",
+]
+
+# To get all three exceptions simultaneously, we execute -j7 to create
+# one thread each for the SConstruct file and {in,out}.f[123].  Note that
+# it's important that the input (source) files sort earlier alphabetically
+# than the output files, so they're visited first in the dependency graph
+# walk of '.' and are already considered up-to-date when we kick off the
+# "simultaneous" builds of the output (target) files.
+
+test.run(arguments = '-j7 .', status = 2, stderr = None)
+
+missing = []
+for es in expected_stderr_list:
+    if string.find(test.stderr(), es) == -1:
+        missing.append(es)
+
+if missing:
+    sys.stderr.write("Missing the following lines from stderr:\n")
+    for m in missing:
+        sys.stderr.write(m)
+    sys.stderr.write('STDERR ===============================================\n')
+    sys.stderr.write(test.stderr())
+    test.fail_test(1)
 
 
 test.pass_test()
diff --git a/test/explain/get_csig.py b/test/explain/get_csig.py
new file mode 100644 (file)
index 0000000..4bf4981
--- /dev/null
@@ -0,0 +1,75 @@
+#!/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 call get_csig() from a function action without
+causing problems.  (This messed up a lot of internal state before
+the Big Signature Refactoring.)
+
+Test case courtesy of Damyan Pepper.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+args = "--debug=explain"
+
+test.write('SConstruct', """\
+env = Environment()
+
+def action( source, target, env ):
+    target[0].get_csig()
+    f = open( str(target[0]), 'w' )
+    for s in source:
+        f.write( s.get_contents() )
+    f.close()
+
+builder = env.Builder( action=action )
+
+builder( env, target = "target.txt", source = "source.txt" )
+""")
+
+test.write("source.txt", "source.txt 1\n")
+
+test.run(arguments=args)
+
+
+
+test.write("source.txt", "source.txt 2")
+
+
+
+expect_rebuild = test.wrap_stdout("""\
+scons: rebuilding `target.txt' because `source.txt' changed
+action(["target.txt"], ["source.txt"])
+""")
+
+test.not_up_to_date(arguments=args)
+
+
+
+test.pass_test()
index 0c9196cfcb2a9d8145817dd3f8a9f023fcdc84ba..265f589ba2d430031fdd741eb5b4a4596752153b 100644 (file)
@@ -41,7 +41,13 @@ test = TestSCons.TestSCons()
 
 test.subdir('include', 'subdir', ['subdir', 'include'], 'inc2')
 
+# Set TargetSignatures('build') because a lot of the test below expect
+# the old behavior of non-essential changes in .h files will propagate
+# and cause the executable file to be re-linked as well (even if the
+# object file was rebuilt to the exact same contents as last time).
+
 test.write('SConstruct', """
+TargetSignatures('build')
 env = Environment(CPPPATH = Split('inc2 include'))
 obj = env.Object(target='prog', source='subdir/prog.c')
 env.Program(target='prog', source=obj)
diff --git a/test/implicit/IMPLICIT_COMMAND_DEPENDENCIES.py b/test/implicit/IMPLICIT_COMMAND_DEPENDENCIES.py
new file mode 100644 (file)
index 0000000..47acc2f
--- /dev/null
@@ -0,0 +1,125 @@
+#!/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 $IMPLICIT_COMMAND_DEPENDENCIES variable controls
+whether or not the implicit dependency on executed commands
+is added to targets.
+"""
+
+import TestSCons
+
+python = TestSCons.python
+_python_ = TestSCons._python_
+
+test = TestSCons.TestSCons()
+
+generate_build_py_py_contents = """\
+#!%(python)s
+import os
+import sys
+
+open(sys.argv[1], 'w').write('''\
+#!/usr/bin/env %(python)s
+import os.path
+import string
+import sys
+fp = open(sys.argv[1], 'wb')
+args = [os.path.split(sys.argv[0])[1]] + sys.argv[1:]
+fp.write(string.join(args) + '\\\\n' + '%(extra)s')
+for infile in sys.argv[2:]:
+    fp.write(open(infile, 'rb').read())
+fp.close()
+''')
+os.chmod(sys.argv[1], 0755)
+
+"""
+
+extra = ''
+test.write('generate_build_py.py', generate_build_py_py_contents % locals())
+
+test.write('SConstruct', """
+generate = Builder(action = r'%(_python_)s $GENERATE $TARGET')
+build = Builder(action = r'$BUILD_PY $TARGET $SOURCES')
+env = Environment(BUILDERS = {
+                        'GenerateBuild' : generate,
+                        'BuildFile' : build,
+                  },
+                  GENERATE = 'generate_build_py.py',
+                  BUILD_PY = 'build.py',
+                  )
+env.PrependENVPath('PATH', '.')
+env.PrependENVPath('PATHEXT', '.PY')
+env0        = env.Clone(IMPLICIT_COMMAND_DEPENDENCIES = 0)
+env1        = env.Clone(IMPLICIT_COMMAND_DEPENDENCIES = 1)
+envNone     = env.Clone(IMPLICIT_COMMAND_DEPENDENCIES = None)
+envFalse    = env.Clone(IMPLICIT_COMMAND_DEPENDENCIES = False)
+envTrue     = env.Clone(IMPLICIT_COMMAND_DEPENDENCIES = True)
+
+build_py = env.GenerateBuild('${BUILD_PY}', [])
+AlwaysBuild(build_py)
+
+env.BuildFile('file.out',               'file.in')
+env0.BuildFile('file0.out',             'file.in')
+env1.BuildFile('file1.out',             'file.in')
+envNone.BuildFile('fileNone.out',       'file.in')
+envFalse.BuildFile('fileFalse.out',     'file.in')
+envTrue.BuildFile('fileTrue.out',       'file.in')
+""" % locals())
+
+
+
+test.write('file.in',     "file.in\n")
+
+test.run(arguments = '--tree=all .')
+
+expect_none = 'build.py %s file.in\nfile.in\n'
+
+test.must_match('file.out',         expect_none % 'file.out')
+test.must_match('file0.out',        expect_none % 'file0.out')
+test.must_match('file1.out',        expect_none % 'file1.out')
+test.must_match('fileNone.out',     expect_none % 'fileNone.out')
+test.must_match('fileFalse.out',    expect_none % 'fileFalse.out')
+test.must_match('fileTrue.out',     expect_none % 'fileTrue.out')
+
+
+
+extra = 'xyzzy\\\\n'
+test.write('generate_build_py.py', generate_build_py_py_contents % locals())
+
+test.run(arguments = '--tree=all .')
+
+expect_extra = 'build.py %s file.in\nxyzzy\nfile.in\n'
+
+test.must_match('file.out',         expect_extra % 'file.out')
+test.must_match('file0.out',        expect_none % 'file0.out')
+test.must_match('file1.out',        expect_extra % 'file1.out')
+test.must_match('fileNone.out',     expect_none % 'fileNone.out')
+test.must_match('fileFalse.out',    expect_none % 'fileFalse.out')
+test.must_match('fileTrue.out',     expect_extra % 'fileTrue.out')
+
+
+test.pass_test()
diff --git a/test/implicit/asynchronous-modification.py b/test/implicit/asynchronous-modification.py
new file mode 100644 (file)
index 0000000..d10f823
--- /dev/null
@@ -0,0 +1,89 @@
+#!/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 expected behavior when an implicit dependency is modified
+asynchronously (that is, mid-build and without our knowledge).
+
+Test case courtesy Greg Noel.
+"""
+
+import TestSCons
+
+_python_ = TestSCons._python_
+
+test = TestSCons.TestSCons()
+
+test.write(['SConstruct'], """\
+import SCons.Defaults
+env = Environment()
+env['BUILDERS']['C'] = Builder(action = Copy('$TARGET', '$SOURCE'),
+                               source_scanner = SCons.Defaults.CScan)
+env['BUILDERS']['Mod'] = Builder(action = r'%(_python_)s mod.py')
+Alias('seq', env.C('one.c'))
+Alias('seq', env.Mod('mod', 'mod.py'))
+Alias('seq', env.C('two.c'))
+Default('seq')
+""" % locals())
+
+test.write(['hdr.h'], """\
+/* empty header */
+""")
+
+test.write(['mod.py'], """\
+open('mod', 'w').write(open('mod.py', 'r').read())
+open('hdr.h', 'w').write("/* modified */\\n")
+""")
+
+test.write(['one.c'], """\
+#include "hdr.h"
+""")
+
+test.write(['two.c'], """\
+#include "hdr.h"
+""")
+
+# The first run builds the file 'one', then runs the 'mod' script
+# (which update modifies the 'hdr.h' file) then builds the file 'two'.
+test.run(arguments = 'seq')
+
+# The 'hdr.h' file had its original contents when 'one' was built,
+# and modified contents when 'two' was built.  Because we took a
+# look at 'hdr.h' once, up front, we think both files are out of
+# date and will rebuild both (even though 'two' is really up to date).
+#
+# A future enhancement might add some sort of verification mode that
+# would examine 'hdr.h' again when 'two' was built, thereby avoiding
+# the unnecessary rebuild.  In that case, the second line below
+# will need to change to "test.up_to_date(...)".
+test.not_up_to_date(arguments = 'one')
+test.not_up_to_date(arguments = 'two')
+
+# Regardless of what happened on the middle run(s), both files should
+# be up to date now.
+test.up_to_date(arguments = 'seq')
+
+test.pass_test()
index 9b5e88cfcaabb9ff4360a0d6276ddc410872402c..439daa2b2de1514752e10e61092ec4befb4e15a6 100644 (file)
@@ -73,7 +73,6 @@ env.Cat(target = 'f4a.out', source = 'f4a.in')
 f4b_in = File('dir/f4b.in')
 f4b_in.exists()
 f4b_in.is_derived()
-f4b_in.is_pseudo_derived()
 env.Cat(target = 'dir/f4b.out', source = f4b_in)
 """)
 
index 33f0f4d11f475267a1edb92bf762af40c8b50650..8c8b2cf29ab35759b6c27b24d893f228ab822432 100644 (file)
@@ -69,7 +69,6 @@ expect = [
     "Base.stat()",
     "Dir.srcdir_list()",
     "File.exists()",
-    "FS._doLookup()",
     "Node._children_get()",
 ]
 
index 21ca38661df9394818614145de5e01629a36f49e..d25b7fa2741bc4f45fbe9a7cf95f3ce6a7ec73d1 100644 (file)
@@ -37,6 +37,10 @@ import time
 
 test = TestSCons.TestSCons()
 
+CC = test.detect('CC')
+LINK = test.detect('LINK')
+if LINK is None: LINK = CC
+
 test.write('SConstruct', """
 env = Environment(OBJSUFFIX = '.ooo', PROGSUFFIX = '.xxx')
 env.Program('foo', Split('foo.c bar.c'))
@@ -75,14 +79,17 @@ test.write('bar.h', """
 stree = """
 [E B   C  ]+-foo.xxx
 [E B   C  ]  +-foo.ooo
-[E        ]  | +-foo.c
-[E        ]  | +-foo.h
-[E        ]  | +-bar.h
+[E     C  ]  | +-foo.c
+[E     C  ]  | +-foo.h
+[E     C  ]  | +-bar.h
+[E     C  ]  | +-%(CC)s
 [E B   C  ]  +-bar.ooo
-[E        ]    +-bar.c
-[E        ]    +-bar.h
-[E        ]    +-foo.h
-"""
+[E     C  ]  | +-bar.c
+[E     C  ]  | +-bar.h
+[E     C  ]  | +-foo.h
+[E     C  ]  | +-%(CC)s
+[E     C  ]  +-%(LINK)s
+""" % locals()
 
 test.run(arguments = "--debug=stree foo.xxx")
 test.fail_test(string.find(test.stdout(), stree) == -1)
@@ -101,14 +108,17 @@ stree2 = """
 
 [  B      ]+-foo.xxx
 [  B      ]  +-foo.ooo
-[E        ]  | +-foo.c
-[E        ]  | +-foo.h
-[E        ]  | +-bar.h
+[E     C  ]  | +-foo.c
+[E     C  ]  | +-foo.h
+[E     C  ]  | +-bar.h
+[E     C  ]  | +-%(CC)s
 [  B      ]  +-bar.ooo
-[E        ]    +-bar.c
-[E        ]    +-bar.h
-[E        ]    +-foo.h
-"""
+[E     C  ]  | +-bar.c
+[E     C  ]  | +-bar.h
+[E     C  ]  | +-foo.h
+[E     C  ]  | +-%(CC)s
+[E     C  ]  +-%(LINK)s
+""" % locals()
 
 test.run(arguments = '-c foo.xxx')
 
index e8873cfb513f898cdba221c7f6e776eb07313281..8a975a372cdd154737f92a599133db37eba4cb5a 100644 (file)
@@ -64,6 +64,12 @@ test.write('f4.in', "f4.in\n")
 
 
 
+# Before anything else, make sure we get valid --debug=time results
+# when just running the help option.
+test.run(arguments = "-h --debug=time")
+
+
+
 def num(s, match):
     return float(re.search(match, s).group(1))
 
index 4f025c2864b1e7096511be195b34f6fe6ea6f945..09cdffb7ca063bdf6656d9d95bf62d2caf285b5e 100644 (file)
@@ -37,6 +37,10 @@ import time
 
 test = TestSCons.TestSCons()
 
+CC = test.detect('CC')
+LINK = test.detect('LINK')
+if LINK is None: LINK = CC
+
 test.write('SConstruct', """
 env = Environment(OBJSUFFIX = '.ooo', PROGSUFFIX = '.xxx')
 env.Program('Foo', Split('Foo.c Bar.c'))
@@ -82,40 +86,57 @@ tree1 = """
   | +-Foo.c
   | +-Foo.h
   | +-Bar.h
+  | +-%(CC)s
   +-Bar.ooo
-    +-Bar.c
-    +-Bar.h
-    +-Foo.h
-"""
+  | +-Bar.c
+  | +-Bar.h
+  | +-Foo.h
+  | +-%(CC)s
+  +-%(LINK)s
+""" % locals()
 
 test.run(arguments = "--debug=tree Foo.xxx")
-test.fail_test(string.find(test.stdout(), tree1) == -1)
+if string.find(test.stdout(), tree1) == -1:
+    sys.stdout.write('Did not find expected tree in the following output:\n')
+    sys.stdout.write(test.stdout())
+    test.fail_test()
 
 tree2 = """
 +-.
   +-Bar.c
+  +-Bar.h
   +-Bar.ooo
   | +-Bar.c
   | +-Bar.h
   | +-Foo.h
+  | +-%(CC)s
   +-Foo.c
+  +-Foo.h
   +-Foo.ooo
   | +-Foo.c
   | +-Foo.h
   | +-Bar.h
+  | +-%(CC)s
   +-Foo.xxx
   | +-Foo.ooo
   | | +-Foo.c
   | | +-Foo.h
   | | +-Bar.h
+  | | +-%(CC)s
   | +-Bar.ooo
-  |   +-Bar.c
-  |   +-Bar.h
-  |   +-Foo.h
+  | | +-Bar.c
+  | | +-Bar.h
+  | | +-Foo.h
+  | | +-%(CC)s
+  | +-%(LINK)s
   +-SConstruct
-"""
+""" % locals()
+
 test.run(arguments = "--debug=tree .")
-test.fail_test(string.find(test.stdout(), tree2) == -1)
+if string.find(test.stdout(), tree2) == -1:
+    sys.stdout.write('Did not find expected tree in the following output:\n')
+    sys.stdout.write(test.stdout())
+    test.fail_test()
 
 # Make sure we print the debug stuff even if there's a build failure.
 test.write('Bar.h', """
@@ -129,6 +150,9 @@ THIS SHOULD CAUSE A BUILD FAILURE
 test.run(arguments = "--debug=tree Foo.xxx",
          status = 2,
          stderr = None)
-test.fail_test(string.find(test.stdout(), tree1) == -1)
+if string.find(test.stdout(), tree1) == -1:
+    sys.stdout.write('Did not find expected tree in the following output:\n')
+    sys.stdout.write(test.stdout())
+    test.fail_test()
 
 test.pass_test()
index 30faceddd684352188028a1eed1ad693b9390442..31395046f16c15952ba871cf707cc8b5bb575e07 100644 (file)
@@ -34,29 +34,35 @@ test = TestSCons.TestSCons()
 
 test.write('SConstruct', """
 env = Environment()
-env.Command('file.out', 'file.mid', Copy('$TARGET', '$SOURCE'))
-env.Command('file.mid', 'file.in', Copy('$TARGET', '$SOURCE'))
+
+# We name the files 'Tfile' so that they will sort after the SConstruct
+# file regardless of whether the test is being run on a case-sensitive
+# or case-insensitive system.
+
+env.Command('Tfile.out', 'Tfile.mid', Copy('$TARGET', '$SOURCE'))
+env.Command('Tfile.mid', 'Tfile.in', Copy('$TARGET', '$SOURCE'))
 """)
 
-test.write('file.in', "file.in\n")
+test.write('Tfile.in', "Tfile.in\n")
 
 expect_stdout = test.wrap_stdout("""\
 Taskmaster: '.': children:
-    ['SConstruct', 'file.in', 'file.mid', 'file.out']
-    waiting on unstarted children:
-    ['file.mid', 'file.out']
-Taskmaster: 'file.mid': children:
-    ['file.in']
-    evaluating file.mid
-Copy("file.mid", "file.in")
-Taskmaster: 'file.out': children:
-    ['file.mid']
-    evaluating file.out
-Copy("file.out", "file.mid")
+    ['SConstruct', 'Tfile.in', 'Tfile.mid', 'Tfile.out']
+    waiting on unfinished children:
+    ['SConstruct', 'Tfile.in', 'Tfile.mid', 'Tfile.out']
+Taskmaster: 'SConstruct': evaluating SConstruct
+Taskmaster: 'Tfile.in': evaluating Tfile.in
+Taskmaster: 'Tfile.mid': children:
+    ['Tfile.in']
+    evaluating Tfile.mid
+Copy("Tfile.mid", "Tfile.in")
+Taskmaster: 'Tfile.out': children:
+    ['Tfile.mid']
+    evaluating Tfile.out
+Copy("Tfile.out", "Tfile.mid")
 Taskmaster: '.': children:
-    ['SConstruct', 'file.in', 'file.mid', 'file.out']
+    ['SConstruct', 'Tfile.in', 'Tfile.mid', 'Tfile.out']
     evaluating .
-Taskmaster: '.': already handled (executed)
 """)
 
 test.run(arguments='--taskmastertrace=- .', stdout=expect_stdout)
@@ -68,27 +74,28 @@ test.run(arguments='-c .')
 
 
 expect_stdout = test.wrap_stdout("""\
-Copy("file.mid", "file.in")
-Copy("file.out", "file.mid")
+Copy("Tfile.mid", "Tfile.in")
+Copy("Tfile.out", "Tfile.mid")
 """)
 
 test.run(arguments='--taskmastertrace=trace.out .', stdout=expect_stdout)
 
 expect_trace = """\
 Taskmaster: '.': children:
-    ['SConstruct', 'file.in', 'file.mid', 'file.out']
-    waiting on unstarted children:
-    ['file.mid', 'file.out']
-Taskmaster: 'file.mid': children:
-    ['file.in']
-    evaluating file.mid
-Taskmaster: 'file.out': children:
-    ['file.mid']
-    evaluating file.out
+    ['SConstruct', 'Tfile.in', 'Tfile.mid', 'Tfile.out']
+    waiting on unfinished children:
+    ['SConstruct', 'Tfile.in', 'Tfile.mid', 'Tfile.out']
+Taskmaster: 'SConstruct': evaluating SConstruct
+Taskmaster: 'Tfile.in': evaluating Tfile.in
+Taskmaster: 'Tfile.mid': children:
+    ['Tfile.in']
+    evaluating Tfile.mid
+Taskmaster: 'Tfile.out': children:
+    ['Tfile.mid']
+    evaluating Tfile.out
 Taskmaster: '.': children:
-    ['SConstruct', 'file.in', 'file.mid', 'file.out']
+    ['SConstruct', 'Tfile.in', 'Tfile.mid', 'Tfile.out']
     evaluating .
-Taskmaster: '.': already handled (executed)
 """
 
 test.must_match('trace.out', expect_trace)
index ec7c7d898e50b8b7c0943c0ef41d60dfe35fab52..7940d471d355421b220b7fb92375275ac7010c41 100644 (file)
@@ -37,6 +37,10 @@ import time
 
 test = TestSCons.TestSCons()
 
+CC = test.detect('CC')
+LINK = test.detect('LINK')
+if LINK is None: LINK = CC
+
 test.write('SConstruct', """
 env = Environment(OBJSUFFIX = '.ooo', PROGSUFFIX = '.xxx')
 env.Program('Foo', Split('Foo.c Bar.c'))
@@ -82,65 +86,92 @@ tree1 = """
   | +-Foo.c
   | +-Foo.h
   | +-Bar.h
+  | +-%(CC)s
   +-Bar.ooo
-    +-Bar.c
-    +-Bar.h
-    +-Foo.h
-"""
+  | +-Bar.c
+  | +-Bar.h
+  | +-Foo.h
+  | +-%(CC)s
+  +-%(LINK)s
+""" % locals()
 
 test.run(arguments = "--tree=all Foo.xxx")
-test.fail_test(string.find(test.stdout(), tree1) == -1)
+if string.find(test.stdout(), tree1) == -1:
+    sys.stdout.write('Did not find expected tree in the following output:\n')
+    sys.stdout.write(test.stdout())
+    test.fail_test()
 
 tree2 = """
 +-.
   +-Bar.c
+  +-Bar.h
   +-Bar.ooo
   | +-Bar.c
   | +-Bar.h
   | +-Foo.h
+  | +-%(CC)s
   +-Foo.c
+  +-Foo.h
   +-Foo.ooo
   | +-Foo.c
   | +-Foo.h
   | +-Bar.h
+  | +-%(CC)s
   +-Foo.xxx
   | +-Foo.ooo
   | | +-Foo.c
   | | +-Foo.h
   | | +-Bar.h
+  | | +-%(CC)s
   | +-Bar.ooo
-  |   +-Bar.c
-  |   +-Bar.h
-  |   +-Foo.h
+  | | +-Bar.c
+  | | +-Bar.h
+  | | +-Foo.h
+  | | +-%(CC)s
+  | +-%(LINK)s
   +-SConstruct
-"""
+""" % locals()
 
 test.run(arguments = "--tree=all .")
-test.fail_test(string.find(test.stdout(), tree2) == -1)
+if string.find(test.stdout(), tree2) == -1:
+    sys.stdout.write('Did not find expected tree in the following output:\n')
+    sys.stdout.write(test.stdout())
+    test.fail_test()
 
 tree3 = """
 +-.
   +-Bar.c
+  +-Bar.h
   +-Bar.ooo
   | +-Bar.c
   | +-Bar.h
   | +-Foo.h
+  | +-%(CC)s
   +-Foo.c
+  +-Foo.h
   +-Foo.ooo
   | +-Foo.c
   | +-Foo.h
   | +-Bar.h
+  | +-%(CC)s
   +-Foo.xxx
   | +-[Foo.ooo]
   | +-[Bar.ooo]
+  | +-%(LINK)s
   +-SConstruct
-"""
+""" % locals()
 
 test.run(arguments = "--tree=all,prune .")
-test.fail_test(string.find(test.stdout(), tree3) == -1)
+if string.find(test.stdout(), tree3) == -1:
+    sys.stdout.write('Did not find expected tree in the following output:\n')
+    sys.stdout.write(test.stdout())
+    test.fail_test()
 
 test.run(arguments = "--tree=prune .")
-test.fail_test(string.find(test.stdout(), tree3) == -1)
+if string.find(test.stdout(), tree3) == -1:
+    sys.stdout.write('Did not find expected tree in the following output:\n')
+    sys.stdout.write(test.stdout())
+    test.fail_test()
 
 tree4 = """
  E         = exists
@@ -156,22 +187,31 @@ tree4 = """
 
 [  B      ]+-Foo.xxx
 [  B      ]  +-Foo.ooo
-[E        ]  | +-Foo.c
-[E        ]  | +-Foo.h
-[E        ]  | +-Bar.h
+[E     C  ]  | +-Foo.c
+[E     C  ]  | +-Foo.h
+[E     C  ]  | +-Bar.h
+[E     C  ]  | +-%(CC)s
 [  B      ]  +-Bar.ooo
-[E        ]    +-Bar.c
-[E        ]    +-Bar.h
-[E        ]    +-Foo.h
-"""
+[E     C  ]  | +-Bar.c
+[E     C  ]  | +-Bar.h
+[E     C  ]  | +-Foo.h
+[E     C  ]  | +-%(CC)s
+[E     C  ]  +-%(LINK)s
+""" % locals()
 
 test.run(arguments = '-c Foo.xxx')
 
 test.run(arguments = "--no-exec --tree=all,status Foo.xxx")
-test.fail_test(string.find(test.stdout(), tree4) == -1)
+if string.find(test.stdout(), tree4) == -1:
+    sys.stdout.write('Did not find expected tree in the following output:\n')
+    sys.stdout.write(test.stdout())
+    test.fail_test()
 
 test.run(arguments = "--no-exec --tree=status Foo.xxx")
-test.fail_test(string.find(test.stdout(), tree4) == -1)
+if string.find(test.stdout(), tree4) == -1:
+    sys.stdout.write('Did not find expected tree in the following output:\n')
+    sys.stdout.write(test.stdout())
+    test.fail_test()
 
 # Make sure we print the debug stuff even if there's a build failure.
 test.write('Bar.h', """
@@ -185,6 +225,9 @@ THIS SHOULD CAUSE A BUILD FAILURE
 test.run(arguments = "--tree=all Foo.xxx",
          status = 2,
          stderr = None)
-test.fail_test(string.find(test.stdout(), tree1) == -1)
+if string.find(test.stdout(), tree1) == -1:
+    sys.stdout.write('Did not find expected tree in the following output:\n')
+    sys.stdout.write(test.stdout())
+    test.fail_test()
 
 test.pass_test()
diff --git a/test/option/tree-lib.py b/test/option/tree-lib.py
new file mode 100644 (file)
index 0000000..fc29d50
--- /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__"
+
+"""
+Make sure that --tree=derived output with a library dependency shows
+the dependency on the library.  (On earlier versions of the Microsoft
+toolchain this wouldn't show up unless the library already existed
+on disk.)
+
+Issue 1363:  http://scons.tigris.org/issues/show_bug.cgi?id=1363
+"""
+
+import string
+import sys
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """
+env = Environment(LIBPREFIX='',
+                  LIBSUFFIX='.lib',
+                  OBJSUFFIX='.obj',
+                  EXESUFFIX='.exe')
+env.AppendENVPath('PATH', '.')
+l = env.Library( 'util.lib', 'util.c' )
+p = env.Program( 'test.exe', 'main.c', LIBS=l )
+env.Command( 'foo.h', p, '$SOURCE > $TARGET')
+""")
+
+test.write('main.c', """\
+#include <stdlib.h>
+#include <stdio.h>
+int
+main(int argc, char *argv)
+{
+    printf("#define    FOO_H   \\"foo.h\\"\\n");
+    return (0);
+}
+""")
+
+test.write('util.c', """\
+void
+util(void)
+{
+    ;
+}
+""")
+
+expect = """
+  +-test.exe
+    +-main.obj
+    +-util.lib
+      +-util.obj
+"""
+
+test.run(arguments = '--tree=derived foo.h')
+if string.find(test.stdout(), expect) == -1:
+    sys.stdout.write('Did not find expected tree in the following output:\n')
+    sys.stdout.write(test.stdout())
+    test.fail_test()
+
+test.up_to_date(arguments = 'foo.h')
+
+test.pass_test()
diff --git a/test/packaging/convenience-functions.py b/test/packaging/convenience-functions.py
new file mode 100644 (file)
index 0000000..6eae270
--- /dev/null
@@ -0,0 +1,76 @@
+#!/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 the FindInstalledFiles() and the FindSourceFiles() functions.
+"""
+
+import os.path
+import string
+import TestSCons
+
+python = TestSCons.python
+test = TestSCons.TestSCons()
+
+test.write( "f1", "" )
+test.write( "f2", "" )
+test.write( "f3", "" )
+
+test.write( 'SConstruct', r"""
+env  = Environment(tools=['default', 'packaging'])
+prog = env.Install( 'bin/', ["f1", "f2"] )
+env.File( "f3" )
+
+src_files = map(str, env.FindSourceFiles())
+oth_files = map(str, env.FindInstalledFiles())
+src_files.sort()
+oth_files.sort()
+
+print src_files
+print oth_files
+""")
+
+bin_f1 = os.path.join('bin', 'f1')
+bin_f2 = os.path.join('bin', 'f2')
+
+bin__f1 = string.replace(bin_f1, '\\', '\\\\')
+bin__f2 = string.replace(bin_f2, '\\', '\\\\')
+
+expect_read = """\
+['SConstruct', 'f1', 'f2', 'f3']
+['%(bin__f1)s', '%(bin__f2)s']
+""" % locals()
+
+expect_build = """\
+Install file: "f1" as "%(bin_f1)s"
+Install file: "f2" as "%(bin_f2)s"
+""" % locals()
+
+expected = test.wrap_stdout(read_str = expect_read, build_str = expect_build)
+
+test.run(stdout=expected)
+
+test.pass_test()
diff --git a/test/packaging/msi/explicit-target.py b/test/packaging/msi/explicit-target.py
new file mode 100644 (file)
index 0000000..745f0c1
--- /dev/null
@@ -0,0 +1,92 @@
+#!/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 the ability to use a explicit target package name and the use
+of FindInstalledFiles() in conjuction with .msi packages.
+"""
+
+import os
+import TestSCons
+
+python = TestSCons.python
+
+test = TestSCons.TestSCons()
+
+try:
+    from xml.dom.minidom import *
+except ImportError:
+    test.skip_test('Canoot import xml.dom.minidom skipping test\n')
+
+wix = test.Environment().WhereIs('candle')
+
+if not wix:
+    test.skip_test("No 'candle' found; skipping test\n")
+
+#
+# build with minimal tag set and test for the given package meta-data
+#
+test.write( 'file1.exe', "file1" )
+test.write( 'file2.exe', "file2" )
+
+test.write('SConstruct', """
+import os
+
+env  = Environment(tools=['default', 'packaging'])
+
+f1  = env.Install( '/usr/' , 'file1.exe'  )
+f2  = env.Install( '/usr/' , 'file2.exe'  )
+
+env.Alias( 'install', [ f1, f2 ] )
+
+env.Package( NAME         = 'foo',
+             VERSION      = '1.2',
+             PACKAGETYPE  = 'msi',
+             SUMMARY      = 'balalalalal',
+             DESCRIPTION  = 'this should be reallly really long',
+             VENDOR       = 'Nanosoft_2000',
+             source       = env.FindInstalledFiles(),
+             target       = "mypackage.msi",
+            )
+""")
+
+test.run(arguments='', stderr = None)
+
+test.must_exist( 'foo-1.2.wxs' )
+test.must_exist( 'foo-1.2.msi' )
+
+dom     = parse( test.workpath( 'foo-1.2.wxs' ) )
+Product = dom.getElementsByTagName( 'Product' )[0]
+Package = dom.getElementsByTagName( 'Package' )[0]
+
+test.fail_test( not Product.attributes['Manufacturer'].value == 'Nanosoft_2000' )
+test.fail_test( not Product.attributes['Version'].value      == '1.2' )
+test.fail_test( not Product.attributes['Name'].value         == 'foo' )
+
+test.fail_test( not Package.attributes['Description'].value == 'balalalalal' )
+test.fail_test( not Package.attributes['Comments'].value    == 'this should be reallly really long' )
+
+test.pass_test()
diff --git a/test/packaging/multiple-packages-at-once.py b/test/packaging/multiple-packages-at-once.py
new file mode 100644 (file)
index 0000000..3151c05
--- /dev/null
@@ -0,0 +1,82 @@
+#!/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__"
+
+"""
+See if the packaging tool is able to build multiple packages at once.
+
+TODO: test if the packages are clean versions (i.e. do not contain files
+      added by different packager runs)
+"""
+
+import TestSCons
+
+python = TestSCons.python
+
+test = TestSCons.TestSCons()
+
+zip = test.detect('ZIP', 'zip')
+
+if not zip:
+    test.skip_test('zip not found, skipping test\n')
+
+test.subdir('src')
+
+test.write( [ 'src', 'main.c' ], r"""
+int main( int argc, char* argv[] )
+{
+  return 0;
+}
+""")
+
+test.write('SConstruct', """
+Program( 'src/main.c' )
+env=Environment(tools=['default', 'packaging'])
+env.Package( PACKAGETYPE  = ['src_zip', 'src_targz'],
+             target       = ['src.zip', 'src.tar.gz'],
+             PACKAGEROOT  = 'test',
+             source       = [ 'src/main.c', 'SConstruct' ] )
+""")
+
+test.run(arguments='', stderr = None)
+
+test.must_exist( 'src.zip' )
+test.must_exist( 'src.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' ] )
+""")
+
+test.run(arguments='', stderr = None)
+
+test.must_exist( 'src-1.0.zip' )
+test.must_exist( 'src-1.0.tar.gz' )
+
+test.pass_test()
diff --git a/test/packaging/multiple-subdirs.py b/test/packaging/multiple-subdirs.py
new file mode 100644 (file)
index 0000000..80758ab
--- /dev/null
@@ -0,0 +1,80 @@
+#!/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 build packages in different subdirectories.
+
+Test case courtesy Andrew Smith.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+tar = test.detect('TAR', 'tar')
+
+if not tar:
+    test.skip_test('No TAR executable found; skipping test\n')
+
+test.subdir('one', 'two', 'three')
+
+test.write('SConstruct', """\
+env = Environment(tools=['default', 'packaging'])
+Export('env')
+SConscript(dirs = ['one', 'two', 'three'])
+""")
+
+SConscript_template = """\
+Import('*')
+
+files = env.Install('/usr/bin', '%s.sh')
+
+pkg = env.Package(NAME          = '%s',
+                  VERSION       = '1.0.0',
+                  PACKAGETYPE   = 'targz',
+                  source        = [files]
+                  )
+"""
+
+test.write(['one',   'SConscript'], SConscript_template % ('one', 'one'))
+test.write(['two',   'SConscript'], SConscript_template % ('two', 'two'))
+test.write(['three', 'SConscript'], SConscript_template % ('three', 'three'))
+
+test.write(['one',   'one.sh'],     "one/one.sh\n")
+test.write(['two',   'two.sh'],     "two/two.sh\n")
+test.write(['three', 'three.sh'],   "three/three.sh\n")
+
+test.run(arguments = '.')
+
+test.must_match(['one', 'one-1.0.0', 'usr', 'bin', 'one.sh'], "one/one.sh\n")
+test.must_match(['two', 'two-1.0.0', 'usr', 'bin', 'two.sh'], "two/two.sh\n")
+test.must_match(['three', 'three-1.0.0', 'usr', 'bin', 'three.sh'], "three/three.sh\n")
+
+test.must_exist(['one', 'one-1.0.0.tar.gz'])
+test.must_exist(['two', 'two-1.0.0.tar.gz'])
+test.must_exist(['three', 'three-1.0.0.tar.gz'])
+
+test.pass_test()
index 00a569e02e32e179a8ab315ef1c8fc1c625555ec..ce16e95274ccc4999f80e1dea9c3f235bc3b22ec 100644 (file)
@@ -67,14 +67,21 @@ env.Package( NAME           = 'foo',
             )
 """ % locals())
 
-test.run(arguments='package PACKAGETYPE=rpm', stderr = None)
-
 src_rpm = 'foo-1.2.3-0.src.rpm'
 machine_rpm = 'foo-1.2.3-0.%s.rpm' % machine
 
+test.run(arguments='package PACKAGETYPE=rpm', stderr = None)
+
 test.must_exist( src_rpm )
 test.must_exist( machine_rpm )
+test.must_not_exist( 'bin/main.c' )
+test.must_not_exist( '/bin/main.c' )
+
+test.run(arguments='-c package PACKAGETYPE=rpm', stderr = None)
 
+test.run(arguments='package --package-type=rpm', stderr = None)
+test.must_exist( src_rpm )
+test.must_exist( machine_rpm )
 test.must_not_exist( 'bin/main.c' )
 test.must_not_exist( '/bin/main.c' )
 
index d9758a1829f4eac28eac1cde9fbec0752dc90f71..66bdd5398098a7e44eb5fe202022816fa0694973 100644 (file)
@@ -38,7 +38,7 @@ test = TestSCons.TestSCons()
 tar = test.detect('TAR', 'tar')
 
 if not tar:
-    test.skipt_test('tar not found, skipping test\n')
+    test.skip_test('tar not found, skipping test\n')
 
 #
 # TEST: subdir creation and file copying
index 5472fbbcbea600ea9bdd08017a80f545a1d426db..11f6645c4562b236add6522fe4ec9550ffdb402b 100644 (file)
@@ -83,9 +83,11 @@ env.Alias( 'install', prog )
 """ % locals())
 
 # first run: build the package
-# second run: test if the intermediate files have been cleaned
-test.run( arguments='' )
-test.run( arguments='-c' )
+# second run: make sure everything is up-to-date (sanity check)
+# third run: test if the intermediate files have been cleaned
+test.run( arguments='.' )
+test.up_to_date( arguments='.' )
+test.run( arguments='-c .' )
 
 src_rpm     = 'foo-1.2.3-0.src.rpm'
 machine_rpm = 'foo-1.2.3-0.%s.rpm' % machine
diff --git a/test/packaging/rpm/explicit-target.py b/test/packaging/rpm/explicit-target.py
new file mode 100644 (file)
index 0000000..12a6c6c
--- /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__"
+
+"""
+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()
+
+scons = test.program
+
+rpm = test.Environment().WhereIs('rpm')
+
+if not rpm:
+    test.skip_test('rpm not found, skipping test\n')
+
+rpm_build_root = test.workpath('rpm_build_root')
+
+test.subdir('src')
+
+test.write( [ 'src', 'main.c' ], r"""
+int main( int argc, char* argv[] )
+{
+  return 0;
+}
+""")
+
+test.write('SConstruct', """
+import os
+
+env=Environment(tools=['default', 'packaging'])
+
+env.Prepend(RPM = 'TAR_OPTIONS=--wildcards ')
+env.Append(RPMFLAGS = r' --buildroot %(rpm_build_root)s')
+
+prog = env.Install( '/bin/' , Program( 'src/main.c')  )
+
+env.Alias( 'install', prog )
+
+env.Package( NAME           = 'foo',
+             VERSION        = '1.2.3',
+             PACKAGEVERSION = 0,
+             PACKAGETYPE    = 'rpm',
+             LICENSE        = 'gpl',
+             SUMMARY        = 'balalalalal',
+             X_RPM_GROUP    = 'Application/fu',
+             X_RPM_INSTALL  = r'%(_python_)s %(scons)s --debug=tree --install-sandbox="$RPM_BUILD_ROOT" "$RPM_BUILD_ROOT"',
+             DESCRIPTION    = 'this should be really really long',
+             source         = [ prog ],
+             target         = "my_rpm_package.rpm",
+             SOURCE_URL     = 'http://foo.org/foo-1.2.3.tar.gz'
+        )
+""" % locals())
+
+test.run(arguments='', stderr = None)
+
+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.pass_test()
index af0bc75b21f99f7c1c192c413e8c67a4d71fd875..24d8ddd7b172ae2a91465af7646811c923a8ed31 100644 (file)
@@ -104,12 +104,10 @@ test.must_not_exist( 'bin/main' )
 
 cmd = 'rpm -qp --queryformat \'%%{GROUP}-%%{SUMMARY}-%%{DESCRIPTION}\' %s'
 
-os.environ['LC_ALL']   = 'de_DE.utf8'
 os.environ['LANGUAGE'] = 'de'
 out = os.popen( cmd % test.workpath(machine_rpm) ).read()
 test.fail_test( out != 'Applikation/büro-hallo-das sollte wirklich lang sein' )
 
-os.environ['LC_ALL']   = 'fr_FR.utf8'
 os.environ['LANGUAGE'] = 'fr'
 out = os.popen( cmd % test.workpath(machine_rpm) ).read()
 test.fail_test( out != 'Application/bureau-bonjour-ceci devrait Ãªtre vraiment long' )
diff --git a/test/packaging/rpm/multipackage.py b/test/packaging/rpm/multipackage.py
new file mode 100644 (file)
index 0000000..5b85db3
--- /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.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Test the ability to create more than rpm file with different package root
+from one SCons environment.
+"""
+
+import os
+import TestSCons
+
+machine = TestSCons.machine
+_python_ = TestSCons._python_
+
+test = TestSCons.TestSCons()
+
+scons = test.program
+
+rpm = test.Environment().WhereIs('rpm')
+
+if not rpm:
+    test.skip_test('rpm not found, skipping test\n')
+
+rpm_build_root = test.workpath('rpm_build_root')
+
+test.subdir('src')
+
+test.write( [ 'src', 'main.c' ], r"""
+int main( int argc, char* argv[] )
+{
+  return 0;
+}
+""")
+
+test.write('SConstruct', """
+import os
+
+env=Environment(tools=['default', 'packaging'])
+
+env.Prepend(RPM = 'TAR_OPTIONS=--wildcards ')
+env.Append(RPMFLAGS = r' --buildroot %(rpm_build_root)s')
+
+prog = env.Install( '/bin/' , Program( 'src/main.c')  )
+
+env.Package( NAME           = 'foo',
+             VERSION        = '1.2.3',
+             PACKAGEVERSION = 0,
+             PACKAGETYPE    = 'rpm',
+             LICENSE        = 'gpl',
+             SUMMARY        = 'balalalalal',
+             X_RPM_GROUP    = 'Application/fu',
+             X_RPM_INSTALL  = r'%(_python_)s %(scons)s --debug=tree --install-sandbox="$RPM_BUILD_ROOT" "$RPM_BUILD_ROOT"',
+             DESCRIPTION    = 'this should be really really long',
+             source         = [ prog ],
+             SOURCE_URL     = 'http://foo.org/foo-1.2.3.tar.gz'
+        )
+
+env.Package( NAME           = 'foo2',
+             VERSION        = '1.2.3',
+             PACKAGEVERSION = 0,
+             PACKAGETYPE    = 'rpm',
+             LICENSE        = 'gpl',
+             SUMMARY        = 'balalalalal',
+             X_RPM_GROUP    = 'Application/fu',
+             X_RPM_INSTALL  = r'%(_python_)s %(scons)s --debug=tree --install-sandbox="$RPM_BUILD_ROOT" "$RPM_BUILD_ROOT"',
+             DESCRIPTION    = 'this should be really really long',
+             source         = [ prog ],
+        )
+
+
+
+env.Alias( 'install', prog )
+""" % locals())
+
+test.run(arguments='', stderr = None)
+
+src_rpm      = 'foo-1.2.3-0.src.rpm'
+machine_rpm  = 'foo-1.2.3-0.%s.rpm' % machine
+src_rpm2     = 'foo2-1.2.3-0.src.rpm'
+machine_rpm2 = 'foo2-1.2.3-0.%s.rpm' % machine
+
+test.must_exist( machine_rpm )
+test.must_exist( src_rpm )
+
+test.must_exist( machine_rpm2 )
+test.must_exist( src_rpm2 )
+
+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.pass_test()
index 65b6a615bced95a03872fec0aedfd9374aa8549b..ec4022015a84bdd18f718b88007d250f895fc267 100644 (file)
@@ -34,6 +34,11 @@ python = TestSCons.python
 
 test = TestSCons.TestSCons()
 
+tar = test.detect('TAR', 'tar')
+
+if not tar:
+    test.skip_test('tar not found, skipping test\n')
+
 test.write( 'main.c', '' )
 test.write('SConstruct', """
 prog = Install( '/bin', 'main.c' )
similarity index 53%
rename from test/option-q.py
rename to test/question/Configure.py
index 9b67d0a552c03387951903dcbc9a2cab0b27259b..aeaba8ada90c9b53a4ea2e9f417faf1190915d4b 100644 (file)
 
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
+"""
+Verify operation of the -q (--question) option in conjunction
+with Configure tests.
+
+(This was originally mostly copied and pasted from test/option-n.py.)
+"""
+
 import os.path
 import re
-import string
-import sys
 
 import TestCmd
 import TestSCons
 
-test = TestSCons.TestSCons()
-
-_python_ = TestSCons._python_
-
-test.write('build.py', r"""
-import sys
-contents = open(sys.argv[2], 'rb').read()
-file = open(sys.argv[1], 'wb')
-file.write(contents)
-file.close()
-""")
-
-test.write('SConstruct', """
-B = Builder(action=r'%(_python_)s build.py $TARGET $SOURCES')
-env = Environment(BUILDERS = { 'B' : B })
-env.B(target = 'aaa.out', source = 'aaa.in')
-env.B(target = 'bbb.out', source = 'bbb.in')
-""" % locals())
-
-test.write('aaa.in', "aaa.in\n")
-test.write('bbb.in', "bbb.in\n")
-
-test.run(arguments = '-q aaa.out', status = 1)
-
-test.fail_test(os.path.exists(test.workpath('aaa.out')))
-
-test.run(arguments = 'aaa.out')
-
-test.fail_test(test.read('aaa.out') != "aaa.in\n")
-
-test.run(arguments = '-q aaa.out', status = 0)
+test = TestSCons.TestSCons(match = TestCmd.match_re_dotall)
 
-test.run(arguments = '--question bbb.out', status = 1)
+test.write('aaa.in', 'Hello world\n')
 
-test.fail_test(os.path.exists(test.workpath('bbb.out')))
-
-test.run(arguments = 'bbb.out')
-
-test.fail_test(test.read('bbb.out') != "bbb.in\n")
-
-test.run(arguments = '--question bbb.out', status = 0)
-
-
-# test -q in conjunction with Configure Tests
-# mostly copy&paste from test/option-n.py
-test.subdir('configure')
-test.match_func = TestCmd.match_re_dotall
-test.write('configure/aaa.in', 'Hello world')
-test.write('configure/SConstruct',
-"""def userAction(target,source,env):
+test.write('SConstruct', """\
+def userAction(target,source,env):
     import shutil
     shutil.copyfile( str(source[0]), str(target[0]))
 
@@ -108,15 +69,16 @@ else:
 
 env.B(target='aaa.out', source='aaa.in')
 """)
+
 # test that conf_dir isn't created and an error is raised
 stderr=r"""
 scons: \*\*\* Cannot create configure directory "config\.test" within a dry-run\.
 File \S+, line \S+, in \S+
 """
-test.run(arguments="-q aaa.out",stderr=stderr,status=2,
-         chdir=test.workpath("configure"))
-test.fail_test(os.path.exists(test.workpath("configure", "config.test")))
-test.fail_test(os.path.exists(test.workpath("configure", "config.log")))
+test.run(arguments="-q aaa.out",stderr=stderr,status=2)
+
+test.must_not_exist(test.workpath("config.test"))
+test.must_not_exist(test.workpath("config.log"))
 
 # test that targets are not built, if conf_dir exists.
 # verify that .cache and config.log are not created.
@@ -125,27 +87,28 @@ stderr=r"""
 scons: \*\*\* Cannot update configure test "%s" within a dry-run\.
 File \S+, line \S+, in \S+
 """ % re.escape(os.path.join("config.test", "conftest_0.in"))
-test.subdir(['configure','config.test'])
-test.run(arguments="-q aaa.out",stderr=stderr,status=2,
-         chdir=test.workpath("configure"))
-test.fail_test(os.path.exists(test.workpath("configure", "config.test",
-                                            ".cache")))
-test.fail_test(os.path.exists(test.workpath("configure", "config.test",
-                                            "conftest_0")))
-test.fail_test(os.path.exists(test.workpath("configure", "config.test",
-                                            "conftest_0.in")))
-test.fail_test(os.path.exists(test.workpath("configure", "config.log")))
+
+test.subdir('config.test')
+
+test.run(arguments="-q aaa.out",stderr=stderr,status=2)
+
+test.must_not_exist(test.workpath("config.test", ".cache"))
+test.must_not_exist(test.workpath("config.test", "conftest_0"))
+test.must_not_exist(test.workpath("config.test", "conftest_0.in"))
+test.must_not_exist(test.workpath("config.log"))
 
 # test that no error is raised, if all targets are up-to-date. In this
 # case .cache and config.log shouldn't be created
 stdout=test.wrap_stdout(build_str='cp aaa.in aaa.out\n',
                         read_str="""Executing Custom Test ... yes
 """)
-test.run(stdout=stdout,arguments="aaa.out",status=0,chdir=test.workpath("configure"))
-log1_mtime = os.path.getmtime(test.workpath("configure","config.log"))
-test.run(arguments="-q aaa.out",status=0,
-         chdir=test.workpath("configure"))
-log2_mtime = os.path.getmtime(test.workpath("configure","config.log"))
+
+test.run(stdout=stdout,arguments="aaa.out",status=0)
+
+log1_mtime = os.path.getmtime(test.workpath("config.log"))
+
+test.run(arguments="-q aaa.out",status=0)
+log2_mtime = os.path.getmtime(test.workpath("config.log"))
 test.fail_test( log1_mtime != log2_mtime )
 
 test.pass_test()
diff --git a/test/question/basic.py b/test/question/basic.py
new file mode 100644 (file)
index 0000000..dc07e48
--- /dev/null
@@ -0,0 +1,75 @@
+#!/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 -q (--question) option.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+_python_ = TestSCons._python_
+
+test.write('build.py', r"""
+import sys
+contents = open(sys.argv[2], 'rb').read()
+file = open(sys.argv[1], 'wb')
+file.write(contents)
+file.close()
+""")
+
+test.write('SConstruct', """
+B = Builder(action=r'%(_python_)s build.py $TARGET $SOURCES')
+env = Environment(BUILDERS = { 'B' : B })
+env.B(target = 'aaa.out', source = 'aaa.in')
+env.B(target = 'bbb.out', source = 'bbb.in')
+""" % locals())
+
+test.write('aaa.in', "aaa.in\n")
+test.write('bbb.in', "bbb.in\n")
+
+test.run(arguments = '-q aaa.out', status = 1)
+
+test.must_not_exist(test.workpath('aaa.out'))
+
+test.run(arguments = 'aaa.out')
+
+test.must_match('aaa.out', "aaa.in\n")
+
+test.run(arguments = '-q aaa.out', status = 0)
+
+test.run(arguments = '--question bbb.out', status = 1)
+
+test.must_not_exist(test.workpath('bbb.out'))
+
+test.run(arguments = 'bbb.out')
+
+test.must_match('bbb.out', "bbb.in\n")
+
+test.run(arguments = '--question bbb.out', status = 0)
+
+test.pass_test()
diff --git a/test/question/no-builder.py b/test/question/no-builder.py
new file mode 100644 (file)
index 0000000..d038724
--- /dev/null
@@ -0,0 +1,45 @@
+#!/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 operation of the -q (--question) option when a specified
+target has no builder.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+_python_ = TestSCons._python_
+
+test.write('SConstruct', """
+""")
+
+test.run(arguments = '-q no_such_target', status = 1)
+
+test.run(arguments = '--question no_such_target', status = 1)
+
+test.pass_test()
index 90cd4f2cb8fea01378d6e8edd0a21e31ffb04c21..8b6ae4210010a7add263917129fdb6771290aef0 100644 (file)
@@ -68,12 +68,20 @@ if re.search('\s', python):
 else:
     expect_python = python
 
+def escape(s):
+    return string.replace(s, '\\', '\\\\')
+
+expect_python                  = escape(expect_python)
+expect_workpath_pass_py                = escape(workpath_pass_py)
+expect_workpath_fail_py                = escape(workpath_fail_py)
+expect_workpath_no_result_py   = escape(workpath_no_result_py)
+
 expect_stdout = """\
-%(expect_python)s -tt %(workpath_fail_py)s
+%(expect_python)s -tt %(expect_workpath_fail_py)s
 FAILING TEST STDOUT
-%(expect_python)s -tt %(workpath_no_result_py)s
+%(expect_python)s -tt %(expect_workpath_no_result_py)s
 NO RESULT TEST STDOUT
-%(expect_python)s -tt %(workpath_pass_py)s
+%(expect_python)s -tt %(expect_workpath_pass_py)s
 PASSING TEST STDOUT
 
 Failed the following test:
index c44212580da57b2c3b5b2dcf7387818da864a421..f2bf6eb93f04f7d526bf59dd961f2147c26e4f9c 100644 (file)
@@ -59,12 +59,20 @@ if re.search('\s', python):
 else:
     expect_python = python
 
+def escape(s):
+    return string.replace(s, '\\', '\\\\')
+
+expect_python                  = escape(expect_python)
+expect_workpath_pass_py                = escape(workpath_pass_py)
+expect_workpath_fail_py                = escape(workpath_fail_py)
+expect_workpath_no_result_py   = escape(workpath_no_result_py)
+
 expect_stdout = """\
-%(expect_python)s -tt %(workpath_fail_py)s
+%(expect_python)s -tt %(expect_workpath_fail_py)s
 FAILING TEST STDOUT
-%(expect_python)s -tt %(workpath_no_result_py)s
+%(expect_python)s -tt %(expect_workpath_no_result_py)s
 NO RESULT TEST STDOUT
-%(expect_python)s -tt %(workpath_pass_py)s
+%(expect_python)s -tt %(expect_workpath_pass_py)s
 PASSING TEST STDOUT
 
 Failed the following test:
index 1af32ddd4532d31703d9a6bca46b595ecfa33ba6..95b5f0f4f41dcd7884adc7d76585b01d9d52dca6 100644 (file)
@@ -46,17 +46,11 @@ head, dir = os.path.split(head)
 
 mypython = os.path.join(head, dir, os.path.pardir, dir, python)
 
-if re.search('\s', mypython):
-    _mypython_ = '"' + mypython + '"'
-else:
-    _mypython_ = mypython
-
 test.subdir('test')
 
 test.write_passing_test(['test', 'pass.py'])
 
-# NOTE:  The "test/fail.py : FAIL" and "test/pass.py : PASS" lines both
-# have spaces at the end.
+# NOTE:  The "test/pass.py : PASS" line has spaces at the end.
 
 expect = r"""qmtest.py run --output results.qmr --format none --result-stream="scons_tdb.AegisChangeStream" --context python="%(mypython)s" test
 --- TEST RESULTS -------------------------------------------------------------
@@ -75,6 +69,6 @@ expect = r"""qmtest.py run --output results.qmr --format none --result-stream="s
        1 (100%%) tests PASS
 """ % locals()
 
-test.run(arguments = '-P %s test' % _mypython_, stdout = expect)
+test.run(arguments = ['-P', mypython, 'test'], stdout = expect)
 
 test.pass_test()
diff --git a/test/sconsign/script/Configure.py b/test/sconsign/script/Configure.py
new file mode 100644 (file)
index 0000000..8e17e95
--- /dev/null
@@ -0,0 +1,87 @@
+#!/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 print .sconsign files with Configure context
+info in them (which have different BuildInfo entries).
+"""
+
+import os.path
+
+import TestSCons
+import TestSConsign
+
+_obj = TestSCons._obj
+
+test = TestSConsign.TestSConsign(match = TestSConsign.match_re)
+
+CC = test.detect('CC', norm=1)
+CC_dir, CC_file = os.path.split(CC)
+
+# 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.
+
+_sconf_temp_conftest_0_c = '.sconf_temp/conftest_0.c'
+
+test.write('SConstruct', """
+env = Environment()
+import os
+env.AppendENVPath('PATH', os.environ['PATH'])
+conf = Configure(env)
+r1 = conf.CheckCHeader( 'math.h' )
+env = conf.Finish()
+""")
+
+test.run(arguments = '.')
+
+sig_re = r'[0-9a-fA-F]{32}'
+date_re = r'\S+ \S+ [ \d]\d \d\d:\d\d:\d\d \d\d\d\d'
+
+# Note:  There's a space at the end of the '.*': line, because the
+# Value node being printed actually begins with a newline.  It would
+# probably be good to change that to a repr() of the contents.
+expect = r"""=== .:
+SConstruct: None \d+ \d+
+=== .sconf_temp:
+conftest_0.c:
+        '.*': 
+#include "math.h"
+
+
+        %(sig_re)s \[.*\]
+conftest_0%(_obj)s:
+        %(_sconf_temp_conftest_0_c)s: %(sig_re)s \d+ \d+
+        %(CC)s: %(sig_re)s \d+ \d+
+        %(sig_re)s \[.*\]
+=== %(CC_dir)s:
+%(CC_file)s: %(sig_re)s \d+ \d+
+""" % locals()
+
+test.run_sconsign(arguments = ".sconsign",
+                  stdout = expect)
+
+test.pass_test()
index 2f5ddf3a272349dc3bfb15520ce210220c548d03..99845e3ba660308f76e20e8d8cc476f7c6159c6a 100644 (file)
@@ -29,12 +29,30 @@ Verify that the sconsign script works with files generated when
 using the signatures in an SConsignFile().
 """
 
+import os.path
+
 import TestSConsign
 
 test = TestSConsign.TestSConsign(match = TestSConsign.match_re)
 
+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.subdir('sub1', 'sub2')
 
+# 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.
+
+sub1_hello_c    = 'sub1/hello.c'
+sub1_hello_obj  = 'sub1/hello.obj'
+sub2_hello_c    = 'sub2/hello.c'
+sub2_hello_obj  = 'sub2/hello.obj'
+sub2_inc1_h     = 'sub2/inc1.h'
+sub2_inc2_h     = 'sub2/inc2.h'
+
 test.write(['SConstruct'], """\
 SConsignFile()
 env1 = Environment(PROGSUFFIX = '.exe', OBJSUFFIX = '.obj')
@@ -79,182 +97,328 @@ test.write(['sub2', 'inc2.h'], r"""\
 
 test.run(arguments = '--implicit-cache .')
 
+sig_re = r'[0-9a-fA-F]{32}'
+
 test.run_sconsign(arguments = ".sconsign",
-         stdout = """\
+         stdout = r"""=== .:
+SConstruct: None \d+ \d+
+=== %(CC_dir)s:
+%(CC_file)s: %(sig_re)s \d+ \d+
 === sub1:
-hello.exe: \S+ None \d+ \d+
-        hello.obj: \S+
-hello.obj: \S+ None \d+ \d+
-        hello.c: \S+
+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.exe: \S+ None \d+ \d+
-        hello.obj: \S+
-hello.obj: \S+ None \d+ \d+
-        hello.c: \S+
-        inc1.h: \S+
-        inc2.h: \S+
-""")
+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+
+""" % locals())
 
 test.run_sconsign(arguments = "--raw .sconsign",
-         stdout = """\
+         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.exe: {'bsig': '\S+', 'size': \d+L?, 'timestamp': \d+}
-        hello.obj: \S+
-hello.obj: {'bsig': '\S+', 'size': \d+L?, 'timestamp': \d+}
-        hello.c: \S+
+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.exe: {'bsig': '\S+', 'size': \d+L?, 'timestamp': \d+}
-        hello.obj: \S+
-hello.obj: {'bsig': '\S+', 'size': \d+L?, 'timestamp': \d+}
-        hello.c: \S+
-        inc1.h: \S+
-        inc2.h: \S+
-""")
+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}
+""" % locals())
 
-test.run_sconsign(arguments = "-v .sconsign",
-         stdout = """\
+expect = r"""=== .:
+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
+    timestamp: \d+
+    size: \d+
 hello.exe:
-    bsig: \S+
-    csig: None
+    csig: %(sig_re)s
     timestamp: \d+
     size: \d+
     implicit:
-        hello.obj: \S+
+        %(sub1_hello_obj)s:
+            csig: %(sig_re)s
+            timestamp: \d+
+            size: \d+
+        %(LINK)s:
+            csig: %(sig_re)s
+            timestamp: \d+
+            size: \d+
+    action: %(sig_re)s \[.*\]
 hello.obj:
-    bsig: \S+
-    csig: None
+    csig: %(sig_re)s
     timestamp: \d+
     size: \d+
     implicit:
-        hello.c: \S+
+        %(sub1_hello_c)s:
+            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:
+    csig: %(sig_re)s
+    timestamp: \d+
+    size: \d+
 hello.exe:
-    bsig: \S+
-    csig: None
+    csig: %(sig_re)s
     timestamp: \d+
     size: \d+
     implicit:
-        hello.obj: \S+
+        %(sub2_hello_obj)s:
+            csig: %(sig_re)s
+            timestamp: \d+
+            size: \d+
+        %(LINK)s:
+            csig: %(sig_re)s
+            timestamp: \d+
+            size: \d+
+    action: %(sig_re)s \[.*\]
 hello.obj:
-    bsig: \S+
-    csig: None
+    csig: %(sig_re)s
     timestamp: \d+
     size: \d+
     implicit:
-        hello.c: \S+
-        inc1.h: \S+
-        inc2.h: \S+
-""")
+        %(sub2_hello_c)s:
+            csig: %(sig_re)s
+            timestamp: \d+
+            size: \d+
+        %(sub2_inc1_h)s:
+            csig: %(sig_re)s
+            timestamp: \d+
+            size: \d+
+        %(sub2_inc2_h)s:
+            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
+    timestamp: \d+
+    size: \d+
+inc2.h:
+    csig: %(sig_re)s
+    timestamp: \d+
+    size: \d+
+""" % locals()
 
-test.run_sconsign(arguments = "-b -v .sconsign",
-         stdout = """\
-=== sub1:
-hello.exe:
-    bsig: \S+
-hello.obj:
-    bsig: \S+
-=== sub2:
-hello.exe:
-    bsig: \S+
-hello.obj:
-    bsig: \S+
-""")
+test.run_sconsign(arguments = "-v .sconsign", stdout=expect)
 
 test.run_sconsign(arguments = "-c -v .sconsign",
-         stdout = """\
+         stdout = r"""=== .:
+SConstruct:
+    csig: None
+=== %(CC_dir)s:
+%(CC_file)s:
+    csig: %(sig_re)s
 === sub1:
+hello.c:
+    csig: %(sig_re)s
 hello.exe:
-    csig: None
+    csig: %(sig_re)s
 hello.obj:
-    csig: None
+    csig: %(sig_re)s
 === sub2:
+hello.c:
+    csig: %(sig_re)s
 hello.exe:
-    csig: None
+    csig: %(sig_re)s
 hello.obj:
-    csig: None
-""")
+    csig: %(sig_re)s
+inc1.h:
+    csig: %(sig_re)s
+inc2.h:
+    csig: %(sig_re)s
+""" % locals())
 
 test.run_sconsign(arguments = "-s -v .sconsign",
-         stdout = """\
+         stdout = r"""=== .:
+SConstruct:
+    size: \d+
+=== %(CC_dir)s:
+%(CC_file)s:
+    size: \d+
 === sub1:
+hello.c:
+    size: \d+
 hello.exe:
     size: \d+
 hello.obj:
     size: \d+
 === sub2:
+hello.c:
+    size: \d+
 hello.exe:
     size: \d+
 hello.obj:
     size: \d+
-""")
+inc1.h:
+    size: \d+
+inc2.h:
+    size: \d+
+""" % locals())
 
 test.run_sconsign(arguments = "-t -v .sconsign",
-         stdout = """\
+         stdout = r"""=== .:
+SConstruct:
+    timestamp: \d+
+=== %(CC_dir)s:
+%(CC_file)s:
+    timestamp: \d+
 === sub1:
+hello.c:
+    timestamp: \d+
 hello.exe:
     timestamp: \d+
 hello.obj:
     timestamp: \d+
 === sub2:
+hello.c:
+    timestamp: \d+
 hello.exe:
     timestamp: \d+
 hello.obj:
     timestamp: \d+
-""")
+inc1.h:
+    timestamp: \d+
+inc2.h:
+    timestamp: \d+
+""" % locals())
 
 test.run_sconsign(arguments = "-e hello.obj .sconsign",
-         stdout = """\
+         stdout = r"""=== .:
+=== %(CC_dir)s:
 === sub1:
-hello.obj: \S+ None \d+ \d+
-        hello.c: \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: \S+ None \d+ \d+
-        hello.c: \S+
-        inc1.h: \S+
-        inc2.h: \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.obj' in `%(CC_dir)s'
+""" % locals())
 
 test.run_sconsign(arguments = "-e hello.obj -e hello.exe -e hello.obj .sconsign",
-         stdout = """\
+         stdout = r"""=== .:
+=== %(CC_dir)s:
 === sub1:
-hello.obj: \S+ None \d+ \d+
-        hello.c: \S+
-hello.exe: \S+ None \d+ \d+
-        hello.obj: \S+
-hello.obj: \S+ None \d+ \d+
-        hello.c: \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 \[.*\]
+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: \S+ None \d+ \d+
-        hello.c: \S+
-        inc1.h: \S+
-        inc2.h: \S+
-hello.exe: \S+ None \d+ \d+
-        hello.obj: \S+
-hello.obj: \S+ None \d+ \d+
-        hello.c: \S+
-        inc1.h: \S+
-        inc2.h: \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 \[.*\]
+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",
-#         stdout = """\
-#=== sub1:
+#         stdout = r"""=== sub1:
 #hello.exe:
 #    implicit:
-#        hello.obj: \S+
+#        hello.obj: %(sig_re)s
 #hello.obj:
 #    implicit:
-#        hello.c: \S+
+#        hello.c: %(sig_re)s
 #=== sub2:
 #hello.exe:
 #    implicit:
-#        hello.obj: \S+
+#        hello.obj: %(sig_re)s
 #hello.obj:
 #    implicit:
-#        hello.c: \S+
-#        inc1.h: \S+
-#        inc2.h: \S+
-#""")
+#        hello.c: %(sig_re)s
+#        inc1.h: %(sig_re)s
+#        inc2.h: %(sig_re)s
+#inc1.h: %(sig_re)s
+#inc2.h: %(sig_re)s
+#""" % locals())
 
 test.pass_test()
index 9a3ce6201d313df7c281c010952ff7538f13e5fc..fc85133e5b5662b9baf1052bbc328e0c18b26653 100644 (file)
@@ -35,6 +35,17 @@ import TestSConsign
 
 test = TestSConsign.TestSConsign(match = TestSConsign.match_re)
 
+CC = test.detect('CC', norm=1)
+LINK = test.detect('LINK', norm=1)
+if LINK is None: LINK = CC
+
+# 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.
+
+sub1_hello_c    = 'sub1/hello.c'
+sub1_hello_obj  = 'sub1/hello.obj'
+
 def re_sep(*args):
     import os.path
     import re
@@ -90,20 +101,29 @@ test.sleep()
 
 test.run(arguments = '. --max-drift=1')
 
+sig_re = r'[0-9a-fA-F]{32}'
+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 = """\
-hello.exe: \S+ None \d+ \d+
-        hello.obj: \S+
-hello.obj: \S+ None \d+ \d+
-        hello.c: \S+
-""")
+         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 = """\
-hello.exe: \S+ None '\S+ \S+ [ \d]\d \d\d:\d\d:\d\d \d\d\d\d' \d+
-        hello.obj: \S+
-hello.obj: \S+ None '\S+ \S+ [ \d]\d \d\d:\d\d:\d\d \d\d\d\d' \d+
-        hello.c: \S+
-""")
+         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())
 
 test.pass_test()
index fe49df61ecb58b400e49d52346e1551f8d766dfa..1b359e238c08d08dfea05b5f95d5da330ce47bb1 100644 (file)
@@ -33,8 +33,19 @@ import TestSConsign
 
 test = TestSConsign.TestSConsign(match = TestSConsign.match_re)
 
+CC = test.detect('CC', norm=1)
+LINK = test.detect('LINK', norm=1)
+if LINK is None: LINK = CC
+
 test.subdir('sub1', 'sub2')
 
+# 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.
+
+sub1_hello_c    = 'sub1/hello.c'
+sub1_hello_obj  = 'sub1/hello.obj'
+
 test.write('SConstruct', """
 SConsignFile('my_sconsign')
 SourceSignatures('timestamp')
@@ -83,21 +94,30 @@ test.sleep()
 
 test.run(arguments = '. --max-drift=1')
 
-expect = """\
-=== sub1:
-hello.exe: \d+ None \d+ \d+
-        hello.obj: \d+
-hello.obj: \d+ None \d+ \d+
-        hello.c: \d+
-"""
-
-expect_r = """\
-=== sub1:
-hello.exe: \d+ None '\S+ \S+ [ \d]\d \d\d:\d\d:\d\d \d\d\d\d' \d+
-        hello.obj: \d+
-hello.obj: \d+ None '\S+ \S+ [ \d]\d \d\d:\d\d:\d\d \d\d\d\d' \d+
-        hello.c: \d+
-"""
+sig_re = r'[0-9a-fA-F]{32}'
+date_re = r'\S+ \S+ [ \d]\d \d\d:\d\d:\d\d \d\d\d\d'
+
+expect = r"""=== sub1:
+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()
+
+expect_r = """=== sub1:
+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()
 
 common_flags = '-e hello.exe -e hello.obj -d sub1'
 
index b8607706e8865f78d239b54119160ef57fd4e9a4..1fcfbfda853319b8aae3642929b593c9ffa93e32 100644 (file)
@@ -33,13 +33,23 @@ import TestSConsign
 
 test = TestSConsign.TestSConsign(match = TestSConsign.match_re)
 
-def re_sep(*args):
-    import os.path
-    import re
-    return re.escape(apply(os.path.join, args))
+CC = test.detect('CC', norm=1)
+LINK = test.detect('LINK', norm=1)
+if LINK is None: LINK = CC
 
 test.subdir('sub1', 'sub2')
 
+# 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.
+
+sub1_hello_c    = 'sub1/hello.c'
+sub1_hello_obj  = 'sub1/hello.obj'
+sub2_hello_c    = 'sub2/hello.c'
+sub2_hello_obj  = 'sub2/hello.obj'
+sub2_inc1_h     = 'sub2/inc1.h'
+sub2_inc2_h     = 'sub2/inc2.h'
+
 test.write(['SConstruct'], """
 SConsignFile(None)
 env1 = Environment(PROGSUFFIX = '.exe', OBJSUFFIX = '.obj')
@@ -82,124 +92,160 @@ test.write(['sub2', 'inc2.h'], r"""\
 #define STRING2 "inc2.h"
 """)
 
-test.run(arguments = '--implicit-cache .')
+test.run(arguments = '--implicit-cache --tree=prune .')
 
-test.run_sconsign(arguments = "sub1/.sconsign",
-         stdout = """\
-hello.exe: \S+ None \d+ \d+
-        hello.obj: \S+
-hello.obj: \S+ None \d+ \d+
-        hello.c: \S+
-""")
+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()
+
+test.run_sconsign(arguments = "sub1/.sconsign", stdout=expect)
+#test.run_sconsign(arguments = "sub1/.sconsign")
+#print test.stdout()
 
 test.run_sconsign(arguments = "--raw sub1/.sconsign",
-         stdout = """\
-hello.exe: {'bsig': '\S+', 'size': \d+L?, 'timestamp': \d+}
-        hello.obj: \S+
-hello.obj: {'bsig': '\S+', 'size': \d+L?, 'timestamp': \d+}
-        hello.c: \S+
-""")
+         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())
 
 test.run_sconsign(arguments = "-v sub1/.sconsign",
-         stdout = """\
+         stdout = r"""hello.c:
+    csig: %(sig_re)s
+    timestamp: \d+
+    size: \d+
 hello.exe:
-    bsig: \S+
-    csig: None
+    csig: %(sig_re)s
     timestamp: \d+
     size: \d+
     implicit:
-        hello.obj: \S+
+        %(sub1_hello_obj)s:
+            csig: %(sig_re)s
+            timestamp: \d+
+            size: \d+
+        %(LINK)s:
+            csig: %(sig_re)s
+            timestamp: \d+
+            size: \d+
+    action: %(sig_re)s \[.*\]
 hello.obj:
-    bsig: \S+
-    csig: None
+    csig: %(sig_re)s
     timestamp: \d+
     size: \d+
     implicit:
-        hello.c: \S+
-""")
-
-test.run_sconsign(arguments = "-b -v sub1/.sconsign",
-         stdout = """\
-hello.exe:
-    bsig: \S+
-hello.obj:
-    bsig: \S+
-""")
+        %(sub1_hello_c)s:
+            csig: %(sig_re)s
+            timestamp: \d+
+            size: \d+
+        %(CC)s:
+            csig: %(sig_re)s
+            timestamp: \d+
+            size: \d+
+    action: %(sig_re)s \[.*\]
+""" % locals())
 
 test.run_sconsign(arguments = "-c -v sub1/.sconsign",
-         stdout = """\
+         stdout = r"""hello.c:
+    csig: %(sig_re)s
 hello.exe:
-    csig: None
+    csig: %(sig_re)s
 hello.obj:
-    csig: None
-""")
+    csig: %(sig_re)s
+""" % locals())
 
 test.run_sconsign(arguments = "-s -v sub1/.sconsign",
-         stdout = """\
+         stdout = r"""hello.c:
+    size: \d+
 hello.exe:
     size: \d+
 hello.obj:
     size: \d+
-""")
+""" % locals())
 
 test.run_sconsign(arguments = "-t -v sub1/.sconsign",
-         stdout = """\
+         stdout = r"""hello.c:
+    timestamp: \d+
 hello.exe:
     timestamp: \d+
 hello.obj:
     timestamp: \d+
-""")
+""" % locals())
 
 test.run_sconsign(arguments = "-e hello.obj sub1/.sconsign",
-         stdout = """\
-hello.obj: \S+ None \d+ \d+
-        hello.c: \S+
-""")
+         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 = """\
-hello.obj: \S+ None \d+ \d+
-        hello.c: \S+
-hello.exe: \S+ None \d+ \d+
-        hello.obj: \S+
-hello.obj: \S+ None \d+ \d+
-        hello.c: \S+
-""")
-
-# XXX NOT SURE IF THIS IS RIGHT!
-sub2_inc1_h = re_sep('sub2', 'inc1.h')
-sub2_inc2_h = re_sep('sub2', 'inc2.h')
+         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())
 
 test.run_sconsign(arguments = "sub2/.sconsign",
-         stdout = """\
-hello.exe: \S+ None \d+ \d+
-        hello.obj: \S+
-hello.obj: \S+ None \d+ \d+
-        hello.c: \S+
-        inc1.h: \S+
-        inc2.h: \S+
-""")
+         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+
+""" % locals())
 
 #test.run_sconsign(arguments = "-i -v sub2/.sconsign",
-#         stdout = """\
-#hello.exe:
+#         stdout = r"""hello.c: %(sig_re)s \d+ \d+
+#hello.exe: %(sig_re)s \d+ \d+
 #    implicit:
-#        hello.obj: \S+ None \d+ \d+
-#hello.obj:
+#        hello.obj: %(sig_re)s \d+ \d+
+#hello.obj: %(sig_re)s \d+ \d+
 #    implicit:
-#        hello.c: None \S+ \d+ \d+
-#        inc1.h: None \S+ \d+ \d+
-#        inc2.h: None \S+ \d+ \d+
-#""")
+#        hello.c: %(sig_re)s \d+ \d+
+#        inc1.h: %(sig_re)s \d+ \d+
+#        inc2.h: %(sig_re)s \d+ \d+
+#""" % locals())
 
 test.run_sconsign(arguments = "-e hello.obj sub2/.sconsign sub1/.sconsign",
-         stdout = """\
-hello.obj: \S+ None \d+ \d+
-        hello.c: \S+
-        inc1.h: \S+
-        inc2.h: \S+
-hello.obj: \S+ None \d+ \d+
-        hello.c: \S+
-""")
+         stdout = r"""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.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.pass_test()
index 75760233ae5b807576e4ca2e74ac4a3efb5819a9..9d89d704e7a8c74bbc54d7a6cf8befda538cf23b 100644 (file)
 
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
+"""
+Verify falling back to 'timestamp' behavior if there is no native
+hashlib and no underlying md5 module available.
+"""
+
 import imp
 import os
 import os.path
@@ -32,6 +37,15 @@ import TestSCons
 
 test = TestSCons.TestSCons()
 
+try:
+    file, name, desc = imp.find_module('hashlib')
+except ImportError:
+    pass
+else:
+    msg = "This version of Python has a 'hashlib' module.\n" + \
+          "Skipping test of falling back to timestamps.\n"
+    test.skip_test(msg)
+
 try:
     file, name, desc = imp.find_module('md5')
 except ImportError:
index cec59525ef29ee71720983412f7df011baee9c1b..366d017ec8f4abcb634cfd81f867ff8230cb3141 100644 (file)
 
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
-import os.path
+"""
+Verify appropriate printing of "is up to date" messages.
+"""
+
 import string
-import sys
+
 import TestSCons
 
 _python_ = TestSCons._python_
@@ -66,5 +69,29 @@ scons: `f3.out' is up to date.
 
 test.run(arguments = 'f1.out f2.out f3.out f4.out', stdout = expect)
 
-test.pass_test()
+# Make sure all of the "up to date" messages get printed even when -j
+# is used.  This broke during signature refactoring development.
+expected_lines = [
+    "scons: `f1.out' is up to date.",
+    "scons: `f2.out' is up to date.",
+    "scons: `f3.out' is up to date.",
+    "scons: `f4.out' is up to date.",
+]
 
+test.run(options = '-j4 f1.out f2.out f3.out f4.out')
+stdout = test.stdout()
+
+missing = []
+for line in expected_lines:
+    if string.find(stdout, line) == -1:
+        missing.append(line)
+
+if missing:
+    print "Missing the following expected lines:"
+    for line in missing:
+        print line
+    print "STDOUT =========="
+    print stdout
+    test.fail_test()
+
+test.pass_test()