From d43c3fa04d636344c9d38794182970914a85ce0b Mon Sep 17 00:00:00 2001 From: Steven Knight Date: Wed, 19 Sep 2007 12:52:29 +0000 Subject: Merged revisions 2302-2362,2364-2452 via svnmerge from 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. ................ --- .svnt/conf | 2 + QMTest/TestCmd.py | 181 ++-- QMTest/TestCommon.py | 4 +- QMTest/TestSCons.py | 7 +- SConstruct | 19 +- bench/dependency-func.py | 98 +++ doc/man/scons.1 | 708 ++++++++++++++- doc/man/sconsign.1 | 23 +- runtest.py | 1 + src/CHANGES.txt | 76 ++ src/RELEASE.txt | 86 +- src/engine/MANIFEST.in | 6 +- src/engine/SCons/Action.py | 32 +- src/engine/SCons/Action.xml | 35 + src/engine/SCons/Builder.py | 28 +- src/engine/SCons/CacheDir.py | 26 +- src/engine/SCons/CacheDirTests.py | 31 +- src/engine/SCons/Debug.py | 1 + src/engine/SCons/Defaults.py | 39 +- src/engine/SCons/Environment.py | 304 +++++-- src/engine/SCons/EnvironmentTests.py | 147 +++- src/engine/SCons/Executor.py | 52 +- src/engine/SCons/ExecutorTests.py | 48 +- src/engine/SCons/JobTests.py | 14 +- src/engine/SCons/Node/Alias.py | 38 +- src/engine/SCons/Node/FS.py | 1097 +++++++++++++++--------- src/engine/SCons/Node/FSTests.py | 250 +++--- src/engine/SCons/Node/NodeTests.py | 182 ++-- src/engine/SCons/Node/Python.py | 34 +- src/engine/SCons/Node/PythonTests.py | 2 +- src/engine/SCons/Node/__init__.py | 483 ++++++----- src/engine/SCons/Options/__init__.py | 20 +- src/engine/SCons/SConf.py | 106 ++- src/engine/SCons/SConfTests.py | 21 +- src/engine/SCons/SConsign.py | 179 ++-- src/engine/SCons/SConsignTests.py | 198 +++-- src/engine/SCons/Scanner/ScannerTests.py | 19 +- src/engine/SCons/Scanner/__init__.py | 4 +- src/engine/SCons/Script/Main.py | 85 +- src/engine/SCons/Script/SConscript.py | 17 +- src/engine/SCons/Script/__init__.py | 4 +- src/engine/SCons/Sig.py | 57 ++ src/engine/SCons/Sig/.aeignore | 5 - src/engine/SCons/Sig/.cvsignore | 1 - src/engine/SCons/Sig/MD5.py | 104 --- src/engine/SCons/Sig/MD5Tests.py | 113 --- src/engine/SCons/Sig/SigTests.py | 49 -- src/engine/SCons/Sig/TimeStamp.py | 75 -- src/engine/SCons/Sig/TimeStampTests.py | 84 -- src/engine/SCons/Sig/__init__.py | 53 -- src/engine/SCons/Taskmaster.py | 142 ++- src/engine/SCons/TaskmasterTests.py | 51 +- src/engine/SCons/Tool/JavaCommon.py | 2 +- src/engine/SCons/Tool/JavaCommonTests.py | 15 + src/engine/SCons/Tool/__init__.py | 51 +- src/engine/SCons/Tool/install.py | 115 ++- src/engine/SCons/Tool/intelc.py | 22 +- src/engine/SCons/Tool/javac.py | 60 +- src/engine/SCons/Tool/javac.xml | 14 + src/engine/SCons/Tool/midl.py | 5 +- src/engine/SCons/Tool/msvs.py | 3 + src/engine/SCons/Tool/msvsTests.py | 1 + src/engine/SCons/Tool/packaging/__init__.py | 187 ++-- src/engine/SCons/Tool/packaging/ipk.py | 8 +- src/engine/SCons/Tool/packaging/msi.py | 28 +- src/engine/SCons/Tool/packaging/packager.py | 218 ----- src/engine/SCons/Tool/packaging/rpm.py | 35 +- src/engine/SCons/Tool/packaging/src_tarbz2.py | 4 +- src/engine/SCons/Tool/packaging/src_targz.py | 4 +- src/engine/SCons/Tool/packaging/src_zip.py | 4 +- src/engine/SCons/Tool/packaging/tarbz2.py | 6 +- src/engine/SCons/Tool/packaging/targz.py | 6 +- src/engine/SCons/Tool/packaging/zip.py | 6 +- src/engine/SCons/Tool/rpm.py | 1 - src/engine/SCons/Tool/sgic++.py | 2 +- src/engine/SCons/Tool/tex.py | 16 +- src/engine/SCons/Tool/wix.py | 8 +- src/engine/SCons/Util.py | 92 +- src/engine/SCons/UtilTests.py | 42 +- src/engine/SCons/compat/__init__.py | 34 +- src/engine/SCons/compat/_scons_hashlib.py | 85 ++ src/engine/SCons/compat/_scons_subprocess.py | 8 +- src/script/sconsign.py | 105 ++- src/setup.py | 1 - test/Actions/addpost-link.py | 22 +- test/Actions/exitstatfunc.py | 67 ++ test/Actions/timestamp.py | 56 ++ test/AddMethod.py | 12 +- test/Alias/Depends.py | 162 ++++ test/BuildDir/errors.py | 1 - test/Builder/ensure_suffix.py | 55 ++ test/CacheDir/multiple-targets.py | 66 ++ test/CacheDir/scanner-target.py | 80 ++ test/CacheDir/timestamp.py | 48 ++ test/Configure/clean.py | 96 +++ test/Configure/help.py | 109 +++ test/Copy.py | 13 +- test/Decider/Environment.py | 61 ++ test/Decider/MD5-timestamp.py | 81 ++ test/Decider/Node.py | 61 ++ test/Decider/default.py | 59 ++ test/Decider/mixed.py | 115 +++ test/Decider/timestamp.py | 113 +++ test/Decider/unknown.py | 46 + test/Delete.py | 20 +- test/Depends.py | 3 + test/Dir/source.py | 4 +- test/Errors/preparation.py | 11 +- test/Install/Install.py | 22 +- test/Install/tool.py | 51 ++ test/Java/JAVABOOTCLASSPATH.py | 108 +++ test/Java/multi-step.py | 22 +- test/Java/swig-dependencies.py | 19 +- test/LIBS.py | 2 +- test/MinGW/RCCOM.py | 4 +- test/MinGW/RCCOMSTR.py | 4 +- test/NodeOps.py | 10 +- test/Parallel/duplicate-target.py | 74 ++ test/Progress/TARGET.py | 66 ++ test/Progress/dots.py | 60 ++ test/Progress/file.py | 81 ++ test/Progress/function.py | 75 ++ test/Progress/interval.py | 68 ++ test/Progress/object.py | 78 ++ test/Progress/spinner.py | 66 ++ test/QT/installed.py | 9 +- test/QT/moc-from-header.py | 3 +- test/SConscript/Return.py | 93 ++ test/SWIG/build-dir.py | 8 +- test/Scanner/generated.py | 9 +- test/Sig.py | 62 ++ test/SourceCode.py | 2 +- test/SourceSignatures.py | 268 ------ test/SourceSignatures/basic.py | 124 +++ test/SourceSignatures/env.py | 96 +++ test/SourceSignatures/implicit-cache.py | 87 ++ test/SourceSignatures/no-csigs.py | 70 ++ test/SourceSignatures/overrides.py | 56 ++ test/SourceSignatures/switch-rebuild.py | 81 ++ test/TEX/auxiliaries.py | 13 +- test/TEX/clean.py | 94 ++ test/TargetSignatures.py | 161 ---- test/TargetSignatures/build-content.py | 125 +++ test/TargetSignatures/content.py | 81 ++ test/TargetSignatures/overrides.py | 52 ++ test/Value.py | 22 +- test/bad-drive.py | 2 +- test/chained-build.py | 29 +- test/changed-node.py | 142 --- test/exceptions.py | 63 +- test/explain/get_csig.py | 75 ++ test/implicit-cache/basic.py | 6 + test/implicit/IMPLICIT_COMMAND_DEPENDENCIES.py | 125 +++ test/implicit/asynchronous-modification.py | 89 ++ test/implicit/changed-node.py | 142 +++ test/option-q.py | 151 ---- test/option-u.py | 1 - test/option/debug-memoizer.py | 1 - test/option/debug-stree.py | 38 +- test/option/debug-time.py | 6 + test/option/debug-tree.py | 46 +- test/option/taskmastertrace.py | 65 +- test/option/tree-all.py | 89 +- test/option/tree-lib.py | 88 ++ test/packaging/convenience-functions.py | 76 ++ test/packaging/msi/explicit-target.py | 92 ++ test/packaging/multiple-packages-at-once.py | 82 ++ test/packaging/multiple-subdirs.py | 80 ++ test/packaging/option--package-type.py | 11 +- test/packaging/place-files-in-subdirectory.py | 2 +- test/packaging/rpm/cleanup.py | 8 +- test/packaging/rpm/explicit-target.py | 95 ++ test/packaging/rpm/internationalization.py | 2 - test/packaging/rpm/multipackage.py | 115 +++ test/packaging/strip-install-dir.py | 5 + test/question/Configure.py | 114 +++ test/question/basic.py | 75 ++ test/question/no-builder.py | 45 + test/runtest/fallback.py | 14 +- test/runtest/noqmtest.py | 14 +- test/runtest/python.py | 10 +- test/sconsign/script/Configure.py | 87 ++ test/sconsign/script/SConsignFile.py | 362 +++++--- test/sconsign/script/Signatures.py | 44 +- test/sconsign/script/dblite.py | 50 +- test/sconsign/script/no-SConsignFile.py | 210 +++-- test/timestamp-fallback.py | 14 + test/up-to-date.py | 33 +- 188 files changed, 9478 insertions(+), 3636 deletions(-) create mode 100644 bench/dependency-func.py create mode 100644 src/engine/SCons/Sig.py delete mode 100644 src/engine/SCons/Sig/.aeignore delete mode 100644 src/engine/SCons/Sig/.cvsignore delete mode 100644 src/engine/SCons/Sig/MD5.py delete mode 100644 src/engine/SCons/Sig/MD5Tests.py delete mode 100644 src/engine/SCons/Sig/SigTests.py delete mode 100644 src/engine/SCons/Sig/TimeStamp.py delete mode 100644 src/engine/SCons/Sig/TimeStampTests.py delete mode 100644 src/engine/SCons/Sig/__init__.py delete mode 100644 src/engine/SCons/Tool/packaging/packager.py create mode 100644 src/engine/SCons/compat/_scons_hashlib.py create mode 100644 test/Actions/exitstatfunc.py create mode 100644 test/Actions/timestamp.py create mode 100644 test/Alias/Depends.py create mode 100644 test/Builder/ensure_suffix.py create mode 100644 test/CacheDir/multiple-targets.py create mode 100644 test/CacheDir/scanner-target.py create mode 100644 test/CacheDir/timestamp.py create mode 100644 test/Configure/clean.py create mode 100644 test/Configure/help.py create mode 100644 test/Decider/Environment.py create mode 100644 test/Decider/MD5-timestamp.py create mode 100644 test/Decider/Node.py create mode 100644 test/Decider/default.py create mode 100644 test/Decider/mixed.py create mode 100644 test/Decider/timestamp.py create mode 100644 test/Decider/unknown.py create mode 100644 test/Install/tool.py create mode 100644 test/Java/JAVABOOTCLASSPATH.py create mode 100644 test/Parallel/duplicate-target.py create mode 100644 test/Progress/TARGET.py create mode 100644 test/Progress/dots.py create mode 100644 test/Progress/file.py create mode 100644 test/Progress/function.py create mode 100644 test/Progress/interval.py create mode 100644 test/Progress/object.py create mode 100644 test/Progress/spinner.py create mode 100644 test/SConscript/Return.py create mode 100644 test/Sig.py delete mode 100644 test/SourceSignatures.py create mode 100644 test/SourceSignatures/basic.py create mode 100644 test/SourceSignatures/env.py create mode 100644 test/SourceSignatures/implicit-cache.py create mode 100644 test/SourceSignatures/no-csigs.py create mode 100644 test/SourceSignatures/overrides.py create mode 100644 test/SourceSignatures/switch-rebuild.py create mode 100644 test/TEX/clean.py delete mode 100644 test/TargetSignatures.py create mode 100644 test/TargetSignatures/build-content.py create mode 100644 test/TargetSignatures/content.py create mode 100644 test/TargetSignatures/overrides.py delete mode 100644 test/changed-node.py create mode 100644 test/explain/get_csig.py create mode 100644 test/implicit/IMPLICIT_COMMAND_DEPENDENCIES.py create mode 100644 test/implicit/asynchronous-modification.py create mode 100644 test/implicit/changed-node.py delete mode 100644 test/option-q.py create mode 100644 test/option/tree-lib.py create mode 100644 test/packaging/convenience-functions.py create mode 100644 test/packaging/msi/explicit-target.py create mode 100644 test/packaging/multiple-packages-at-once.py create mode 100644 test/packaging/multiple-subdirs.py create mode 100644 test/packaging/rpm/explicit-target.py create mode 100644 test/packaging/rpm/multipackage.py create mode 100644 test/question/Configure.py create mode 100644 test/question/basic.py create mode 100644 test/question/no-builder.py create mode 100644 test/sconsign/script/Configure.py diff --git a/.svnt/conf b/.svnt/conf index dece324..d4ffcb7 100644 --- a/.svnt/conf +++ b/.svnt/conf @@ -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 = [ diff --git a/QMTest/TestCmd.py b/QMTest/TestCmd.py index 7a668e8..08642cb 100644 --- a/QMTest/TestCmd.py +++ b/QMTest/TestCmd.py @@ -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 " -__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 diff --git a/QMTest/TestCommon.py b/QMTest/TestCommon.py index b8fb5cd..f4c7c54 100644 --- a/QMTest/TestCommon.py +++ b/QMTest/TestCommon.py @@ -80,8 +80,8 @@ The TestCommon module also provides the following variables # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. __author__ = "Steven Knight " -__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 diff --git a/QMTest/TestSCons.py b/QMTest/TestSCons.py index 9c270fa..b1fdbc1 100644 --- a/QMTest/TestSCons.py +++ b/QMTest/TestSCons.py @@ -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): """ diff --git a/SConstruct b/SConstruct index e00e109..b35e3b0 100644 --- a/SConstruct +++ b/SConstruct @@ -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 index 0000000..0af411d --- /dev/null +++ b/bench/dependency-func.py @@ -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',), + {}, + ), +] diff --git a/doc/man/scons.1 b/doc/man/scons.1 index a20a0f6..6f87b20 100644 --- a/doc/man/scons.1 +++ b/doc/man/scons.1 @@ -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 @@ -2795,6 +2814,163 @@ env.SourceCode('.', env.CVS('/usr/local/CVSROOT', 'foo/bar')) '\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" .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 ) .TP .RI env.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,12 +3292,78 @@ 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 ) Returns a function (actually a callable Python object) @@ -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')) @@ -3707,6 +3968,124 @@ 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 ", ...)" .TP .RI env.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. diff --git a/doc/man/sconsign.1 b/doc/man/sconsign.1 index 3d01cf4..7c80327 100644 --- a/doc/man/sconsign.1 +++ b/doc/man/sconsign.1 @@ -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 diff --git a/runtest.py b/runtest.py index f99f5a1..a3cf016 100644 --- a/runtest.py +++ b/runtest.py @@ -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 diff --git a/src/CHANGES.txt b/src/CHANGES.txt index 3699e95..8886204 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -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 diff --git a/src/RELEASE.txt b/src/RELEASE.txt index 195697e..23c5638 100644 --- a/src/RELEASE.txt +++ b/src/RELEASE.txt @@ -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: diff --git a/src/engine/MANIFEST.in b/src/engine/MANIFEST.in index 9eccbdb..52323d2 100644 --- a/src/engine/MANIFEST.in +++ b/src/engine/MANIFEST.in @@ -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 diff --git a/src/engine/SCons/Action.py b/src/engine/SCons/Action.py index 04e68a3..bdedc99 100644 --- a/src/engine/SCons/Action.py +++ b/src/engine/SCons/Action.py @@ -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 diff --git a/src/engine/SCons/Action.xml b/src/engine/SCons/Action.xml index da2f3e3..a82b038 100644 --- a/src/engine/SCons/Action.xml +++ b/src/engine/SCons/Action.xml @@ -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. --> + + +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 +PATH +variable in the +ENV +environment used to execute the command. + +If the construction variable +&cv-IMPLICIT_COMMAND_DEPENDENCIES; +is set to a false value +(None, +False, +0, +etc.), +then the implicit dependency will +not be added to the targets +built with that construction environment. + + +env = Environment(IMPLICIT_COMMAND_DEPENDENCIES = 0) + + + + A Python function used to print the command lines as they are executed diff --git a/src/engine/SCons/Builder.py b/src/engine/SCons/Builder.py index ff31c5e..6164a55 100644 --- a/src/engine/SCons/Builder.py +++ b/src/engine/SCons/Builder.py @@ -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. diff --git a/src/engine/SCons/CacheDir.py b/src/engine/SCons/CacheDir.py index 6a1cc04..e3730a4 100644 --- a/src/engine/SCons/CacheDir.py +++ b/src/engine/SCons/CacheDir.py @@ -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 diff --git a/src/engine/SCons/CacheDirTests.py b/src/engine/SCons/CacheDirTests.py index 564b762..e31f67b 100644 --- a/src/engine/SCons/CacheDirTests.py +++ b/src/engine/SCons/CacheDirTests.py @@ -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.""" diff --git a/src/engine/SCons/Debug.py b/src/engine/SCons/Debug.py index 47d2134..c54eae2 100644 --- a/src/engine/SCons/Debug.py +++ b/src/engine/SCons/Debug.py @@ -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() diff --git a/src/engine/SCons/Defaults.py b/src/engine/SCons/Defaults.py index 29868ec..9308051 100644 --- a/src/engine/SCons/Defaults.py +++ b/src/engine/SCons/Defaults.py @@ -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() diff --git a/src/engine/SCons/Environment.py b/src/engine/SCons/Environment.py index 012c36c..2f4c34e 100644 --- a/src/engine/SCons/Environment.py +++ b/src/engine/SCons/Environment.py @@ -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 '' % 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 diff --git a/src/engine/SCons/EnvironmentTests.py b/src/engine/SCons/EnvironmentTests.py index 82f220a..630f594 100644 --- a/src/engine/SCons/EnvironmentTests.py +++ b/src/engine/SCons/EnvironmentTests.py @@ -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 diff --git a/src/engine/SCons/Executor.py b/src/engine/SCons/Executor.py index 7d8df68..88a46cc 100644 --- a/src/engine/SCons/Executor.py +++ b/src/engine/SCons/Executor.py @@ -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): diff --git a/src/engine/SCons/ExecutorTests.py b/src/engine/SCons/ExecutorTests.py index 8aedb76..59deca5 100644 --- a/src/engine/SCons/ExecutorTests.py +++ b/src/engine/SCons/ExecutorTests.py @@ -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 diff --git a/src/engine/SCons/JobTests.py b/src/engine/SCons/JobTests.py index 5a47da7..5f056e8 100644 --- a/src/engine/SCons/JobTests.py +++ b/src/engine/SCons/JobTests.py @@ -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.") diff --git a/src/engine/SCons/Node/Alias.py b/src/engine/SCons/Node/Alias.py index 6d4440b..15de664 100644 --- a/src/engine/SCons/Node/Alias.py +++ b/src/engine/SCons/Node/Alias.py @@ -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) diff --git a/src/engine/SCons/Node/FS.py b/src/engine/SCons/Node/FS.py index 74dc655..964af62 100644 --- a/src/engine/SCons/Node/FS.py +++ b/src/engine/SCons/Node/FS.py @@ -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 diff --git a/src/engine/SCons/Node/FSTests.py b/src/engine/SCons/Node/FSTests.py index ed8d6ec..225226d 100644 --- a/src/engine/SCons/Node/FSTests.py +++ b/src/engine/SCons/Node/FSTests.py @@ -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, diff --git a/src/engine/SCons/Node/NodeTests.py b/src/engine/SCons/Node/NodeTests.py index 09ab5c7..fe42035 100644 --- a/src/engine/SCons/Node/NodeTests.py +++ b/src/engine/SCons/Node/NodeTests.py @@ -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): diff --git a/src/engine/SCons/Node/Python.py b/src/engine/SCons/Node/Python.py index a639aee..7cdea14 100644 --- a/src/engine/SCons/Node/Python.py +++ b/src/engine/SCons/Node/Python.py @@ -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 diff --git a/src/engine/SCons/Node/PythonTests.py b/src/engine/SCons/Node/PythonTests.py index 62bcf8b..1827a30 100644 --- a/src/engine/SCons/Node/PythonTests.py +++ b/src/engine/SCons/Node/PythonTests.py @@ -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') diff --git a/src/engine/SCons/Node/__init__.py b/src/engine/SCons/Node/__init__.py index f550b5b..7ddca37 100644 --- a/src/engine/SCons/Node/__init__.py +++ b/src/engine/SCons/Node/__init__.py @@ -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: diff --git a/src/engine/SCons/Options/__init__.py b/src/engine/SCons/Options/__init__.py index ebd6052..e2ad80f 100644 --- a/src/engine/SCons/Options/__init__.py +++ b/src/engine/SCons/Options/__init__.py @@ -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: diff --git a/src/engine/SCons/SConf.py b/src/engine/SCons/SConf.py index 21305b9..47a552a 100644 --- a/src/engine/SCons/SConf.py +++ b/src/engine/SCons/SConf.py @@ -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: diff --git a/src/engine/SCons/SConfTests.py b/src/engine/SCons/SConfTests.py index d4ed346..2c35730 100644 --- a/src/engine/SCons/SConfTests.py +++ b/src/engine/SCons/SConfTests.py @@ -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: diff --git a/src/engine/SCons/SConsign.py b/src/engine/SCons/SConsign.py index dcd6979..bd42706 100644 --- a/src/engine/SCons/SConsign.py +++ b/src/engine/SCons/SConsign.py @@ -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 diff --git a/src/engine/SCons/SConsignTests.py b/src/engine/SCons/SConsignTests.py index e292aed..2a04c89 100644 --- a/src/engine/SCons/SConsignTests.py +++ b/src/engine/SCons/SConsignTests.py @@ -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) - diff --git a/src/engine/SCons/Scanner/ScannerTests.py b/src/engine/SCons/Scanner/ScannerTests.py index ae77908..64d6d77 100644 --- a/src/engine/SCons/Scanner/ScannerTests.py +++ b/src/engine/SCons/Scanner/ScannerTests.py @@ -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): diff --git a/src/engine/SCons/Scanner/__init__.py b/src/engine/SCons/Scanner/__init__.py index db93f61..c8ab155 100644 --- a/src/engine/SCons/Scanner/__init__.py +++ b/src/engine/SCons/Scanner/__init__.py @@ -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) diff --git a/src/engine/SCons/Script/Main.py b/src/engine/SCons/Script/Main.py index a76165f..36dc21e 100644 --- a/src/engine/SCons/Script/Main.py +++ b/src/engine/SCons/Script/Main.py @@ -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.""" diff --git a/src/engine/SCons/Script/SConscript.py b/src/engine/SCons/Script/SConscript.py index a1856da..5278d40 100644 --- a/src/engine/SCons/Script/SConscript.py +++ b/src/engine/SCons/Script/SConscript.py @@ -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}) diff --git a/src/engine/SCons/Script/__init__.py b/src/engine/SCons/Script/__init__.py index 4010d80..d1a115a 100644 --- a/src/engine/SCons/Script/__init__.py +++ b/src/engine/SCons/Script/__init__.py @@ -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 index 0000000..80bb810 --- /dev/null +++ b/src/engine/SCons/Sig.py @@ -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 index 22ebd62..0000000 --- a/src/engine/SCons/Sig/.aeignore +++ /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 index 0d20b64..0000000 --- a/src/engine/SCons/Sig/.cvsignore +++ /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 index f8f349f..0000000 --- a/src/engine/SCons/Sig/MD5.py +++ /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 index ba18264..0000000 --- a/src/engine/SCons/Sig/MD5Tests.py +++ /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/SigTests.py b/src/engine/SCons/Sig/SigTests.py deleted file mode 100644 index 6b7c83f..0000000 --- a/src/engine/SCons/Sig/SigTests.py +++ /dev/null @@ -1,49 +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 unittest -import SCons.Sig -import sys - -class CalculatorTestCase(unittest.TestCase): - - def runTest(self): - class MySigModule: - pass - calc = SCons.Sig.Calculator(MySigModule) - assert calc.module == MySigModule - - -def suite(): - suite = unittest.TestSuite() - suite.addTest(CalculatorTestCase()) - return suite - -if __name__ == "__main__": - runner = unittest.TextTestRunner() - result = runner.run(suite()) - if not result.wasSuccessful(): - sys.exit(1) - diff --git a/src/engine/SCons/Sig/TimeStamp.py b/src/engine/SCons/Sig/TimeStamp.py deleted file mode 100644 index 8cf23e8..0000000 --- a/src/engine/SCons/Sig/TimeStamp.py +++ /dev/null @@ -1,75 +0,0 @@ -"""SCons.Sig.TimeStamp - -The TimeStamp 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__" - -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 - -def collect(signatures): - """ - Collect a list of timestamps, returning - the most-recent timestamp from the list - - signatures - a list of timestamps - returns - the most recent timestamp - """ - - if len(signatures) == 0: - return 0 - elif len(signatures) == 1: - return signatures[0] - else: - return max(signatures) - -def signature(obj): - """Generate a timestamp. - """ - return obj.get_timestamp() - -def to_string(signature): - """Convert a timestamp to a string""" - return str(signature) - -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 - - diff --git a/src/engine/SCons/Sig/TimeStampTests.py b/src/engine/SCons/Sig/TimeStampTests.py deleted file mode 100644 index 49c5cfb..0000000 --- a/src/engine/SCons/Sig/TimeStampTests.py +++ /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) diff --git a/src/engine/SCons/Sig/__init__.py b/src/engine/SCons/Sig/__init__.py deleted file mode 100644 index cfad3e8..0000000 --- a/src/engine/SCons/Sig/__init__.py +++ /dev/null @@ -1,53 +0,0 @@ -"""SCons.Sig - -The 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__" - -try: - import MD5 - default_module = MD5 -except ImportError: - import TimeStamp - default_module = TimeStamp - -class Calculator: - """ - Encapsulates signature calculations and .sconsign file generating - for the build engine. - """ - - def __init__(self, module=default_module): - """ - Initialize the calculator. - - module - the signature module to use for signature calculations - """ - self.module = module - -default_calc = Calculator() diff --git a/src/engine/SCons/Taskmaster.py b/src/engine/SCons/Taskmaster.py index acb8c9b..598566e 100644 --- a/src/engine/SCons/Taskmaster.py +++ b/src/engine/SCons/Taskmaster.py @@ -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: diff --git a/src/engine/SCons/TaskmasterTests.py b/src/engine/SCons/TaskmasterTests.py index 718851d..c79fb93 100644 --- a/src/engine/SCons/TaskmasterTests.py +++ b/src/engine/SCons/TaskmasterTests.py @@ -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 diff --git a/src/engine/SCons/Tool/JavaCommon.py b/src/engine/SCons/Tool/JavaCommon.py index 5097c67..0991c37 100644 --- a/src/engine/SCons/Tool/JavaCommon.py +++ b/src/engine/SCons/Tool/JavaCommon.py @@ -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, diff --git a/src/engine/SCons/Tool/JavaCommonTests.py b/src/engine/SCons/Tool/JavaCommonTests.py index 358f675..1675190 100644 --- a/src/engine/SCons/Tool/JavaCommonTests.py +++ b/src/engine/SCons/Tool/JavaCommonTests.py @@ -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""" diff --git a/src/engine/SCons/Tool/__init__.py b/src/engine/SCons/Tool/__init__.py index 97a4e70..d4e3815 100644 --- a/src/engine/SCons/Tool/__init__.py +++ b/src/engine/SCons/Tool/__init__.py @@ -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', diff --git a/src/engine/SCons/Tool/install.py b/src/engine/SCons/Tool/install.py index 828bb9e..c7eee61 100644 --- a/src/engine/SCons/Tool/install.py +++ b/src/engine/SCons/Tool/install.py @@ -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 diff --git a/src/engine/SCons/Tool/intelc.py b/src/engine/SCons/Tool/intelc.py index 2d0a5a5..673c848 100644 --- a/src/engine/SCons/Tool/intelc.py +++ b/src/engine/SCons/Tool/intelc.py @@ -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 /bin/icl.exe (icc on linux), the include dir is /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: diff --git a/src/engine/SCons/Tool/javac.py b/src/engine/SCons/Tool/javac.py index 5703b2d..a020727 100644 --- a/src/engine/SCons/Tool/javac.py +++ b/src/engine/SCons/Tool/javac.py @@ -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 diff --git a/src/engine/SCons/Tool/javac.xml b/src/engine/SCons/Tool/javac.xml index f5af975..ee93685 100644 --- a/src/engine/SCons/Tool/javac.xml +++ b/src/engine/SCons/Tool/javac.xml @@ -14,6 +14,7 @@ JAVACFLAGS JAVACCOM JAVACLASSSUFFIX JAVASUFFIX +JAVABOOTCLASSPATH JAVACLASSPATH JAVASOURCEPATH @@ -70,6 +71,19 @@ env.Java(target = 'classes', source = ['File1.java', 'File2.java']) + + +Specifies the list of directories that +will be added to the +&javac; command line +via the option. +The individual directory names will be +separated by the operating system's path separate character +(: on UNIX/Linux/POSIX, +; on Windows). + + + The Java compiler. diff --git a/src/engine/SCons/Tool/midl.py b/src/engine/SCons/Tool/midl.py index 811d573..a025701 100644 --- a/src/engine/SCons/Tool/midl.py +++ b/src/engine/SCons/Tool/midl.py @@ -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 diff --git a/src/engine/SCons/Tool/msvs.py b/src/engine/SCons/Tool/msvs.py index 138f920..d4efa74 100644 --- a/src/engine/SCons/Tool/msvs.py +++ b/src/engine/SCons/Tool/msvs.py @@ -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): diff --git a/src/engine/SCons/Tool/msvsTests.py b/src/engine/SCons/Tool/msvsTests.py index b1125e7..8296287 100644 --- a/src/engine/SCons/Tool/msvsTests.py +++ b/src/engine/SCons/Tool/msvsTests.py @@ -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 diff --git a/src/engine/SCons/Tool/packaging/__init__.py b/src/engine/SCons/Tool/packaging/__init__.py index e8f6a09..72bbff0 100644 --- a/src/engine/SCons/Tool/packaging/__init__.py +++ b/src/engine/SCons/Tool/packaging/__init__.py @@ -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) diff --git a/src/engine/SCons/Tool/packaging/ipk.py b/src/engine/SCons/Tool/packaging/ipk.py index 267fe3c..e3ae277 100644 --- a/src/engine/SCons/Tool/packaging/ipk.py +++ b/src/engine/SCons/Tool/packaging/ipk.py @@ -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=[] diff --git a/src/engine/SCons/Tool/packaging/msi.py b/src/engine/SCons/Tool/packaging/msi.py index 7fc7892..1ce2b1a 100644 --- a/src/engine/SCons/Tool/packaging/msi.py +++ b/src/engine/SCons/Tool/packaging/msi.py @@ -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:\\\\ @@ -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 index 7aca2b6..0000000 --- a/src/engine/SCons/Tool/packaging/packager.py +++ /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 {} - - diff --git a/src/engine/SCons/Tool/packaging/rpm.py b/src/engine/SCons/Tool/packaging/rpm.py index 94b7b7a..5f97608 100644 --- a/src/engine/SCons/Tool/packaging/rpm.py +++ b/src/engine/SCons/Tool/packaging/rpm.py @@ -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 ) diff --git a/src/engine/SCons/Tool/packaging/src_tarbz2.py b/src/engine/SCons/Tool/packaging/src_tarbz2.py index 7d876e4..8323107 100644 --- a/src/engine/SCons/Tool/packaging/src_tarbz2.py +++ b/src/engine/SCons/Tool/packaging/src_tarbz2.py @@ -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') diff --git a/src/engine/SCons/Tool/packaging/src_targz.py b/src/engine/SCons/Tool/packaging/src_targz.py index d84976e..db51279 100644 --- a/src/engine/SCons/Tool/packaging/src_targz.py +++ b/src/engine/SCons/Tool/packaging/src_targz.py @@ -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') diff --git a/src/engine/SCons/Tool/packaging/src_zip.py b/src/engine/SCons/Tool/packaging/src_zip.py index d60fe85..01bd42e 100644 --- a/src/engine/SCons/Tool/packaging/src_zip.py +++ b/src/engine/SCons/Tool/packaging/src_zip.py @@ -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) diff --git a/src/engine/SCons/Tool/packaging/tarbz2.py b/src/engine/SCons/Tool/packaging/tarbz2.py index 7127896..52252da 100644 --- a/src/engine/SCons/Tool/packaging/tarbz2.py +++ b/src/engine/SCons/Tool/packaging/tarbz2.py @@ -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') diff --git a/src/engine/SCons/Tool/packaging/targz.py b/src/engine/SCons/Tool/packaging/targz.py index 798e570..4713840 100644 --- a/src/engine/SCons/Tool/packaging/targz.py +++ b/src/engine/SCons/Tool/packaging/targz.py @@ -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') diff --git a/src/engine/SCons/Tool/packaging/zip.py b/src/engine/SCons/Tool/packaging/zip.py index 7663a4a..639d569 100644 --- a/src/engine/SCons/Tool/packaging/zip.py +++ b/src/engine/SCons/Tool/packaging/zip.py @@ -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) diff --git a/src/engine/SCons/Tool/rpm.py b/src/engine/SCons/Tool/rpm.py index 2fc4b58..47759ea 100644 --- a/src/engine/SCons/Tool/rpm.py +++ b/src/engine/SCons/Tool/rpm.py @@ -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') diff --git a/src/engine/SCons/Tool/sgic++.py b/src/engine/SCons/Tool/sgic++.py index 5461c4d..930e29a 100644 --- a/src/engine/SCons/Tool/sgic++.py +++ b/src/engine/SCons/Tool/sgic++.py @@ -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 diff --git a/src/engine/SCons/Tool/tex.py b/src/engine/SCons/Tool/tex.py index 0e3d4b1..bbae25e 100644 --- a/src/engine/SCons/Tool/tex.py +++ b/src/engine/SCons/Tool/tex.py @@ -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) diff --git a/src/engine/SCons/Tool/wix.py b/src/engine/SCons/Tool/wix.py index 3aa3375..8d6d3fd 100644 --- a/src/engine/SCons/Tool/wix.py +++ b/src/engine/SCons/Tool/wix.py @@ -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) diff --git a/src/engine/SCons/Util.py b/src/engine/SCons/Util.py index 3dfa287..04d263b 100644 --- a/src/engine/SCons/Util.py +++ b/src/engine/SCons/Util.py @@ -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. diff --git a/src/engine/SCons/UtilTests.py b/src/engine/SCons/UtilTests.py index 1149f35..3e8085b 100644 --- a/src/engine/SCons/UtilTests.py +++ b/src/engine/SCons/UtilTests.py @@ -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) diff --git a/src/engine/SCons/compat/__init__.py b/src/engine/SCons/compat/__init__.py index 0dfb18e..5e095d1 100644 --- a/src/engine/SCons/compat/__init__.py +++ b/src/engine/SCons/compat/__init__.py @@ -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. """ @@ -80,6 +80,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: # Pre-2.4 Python has no native set type @@ -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 index 0000000..9445ddb --- /dev/null +++ b/src/engine/SCons/compat/_scons_hashlib.py @@ -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) diff --git a/src/engine/SCons/compat/_scons_subprocess.py b/src/engine/SCons/compat/_scons_subprocess.py index df7e702..4cb9e30 100644 --- a/src/engine/SCons/compat/_scons_subprocess.py +++ b/src/engine/SCons/compat/_scons_subprocess.py @@ -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 diff --git a/src/script/sconsign.py b/src/script/sconsign.py index 66f8887..2aebf85 100644 --- a/src/script/sconsign.py +++ b/src/script/sconsign.py @@ -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'): diff --git a/src/setup.py b/src/setup.py index 7123304..4660b2f 100644 --- a/src/setup.py +++ b/src/setup.py @@ -380,7 +380,6 @@ arguments = { "SCons.Platform", "SCons.Scanner", "SCons.Script", - "SCons.Sig", "SCons.Tool", "SCons.Tool.packaging"], 'package_dir' : {'' : 'engine'}, diff --git a/test/Actions/addpost-link.py b/test/Actions/addpost-link.py index f97d4a3..0a08cda 100644 --- a/test/Actions/addpost-link.py +++ b/test/Actions/addpost-link.py @@ -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() diff --git a/test/Actions/exitstatfunc.py b/test/Actions/exitstatfunc.py new file mode 100644 index 0000000..c52e1f5 --- /dev/null +++ b/test/Actions/exitstatfunc.py @@ -0,0 +1,67 @@ +#!/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 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 fail(target, source, env): + return 2 + +t2 = Command('test2.out', 'test2.in', Copy('$TARGET', '$SOURCE')) +AddPostAction(t2, Action(fail, exitstatfunc=always_succeed)) +Alias('test2', t2) +""") + +test.write('test1.in', "test1.in\n") +test.write('test2.in', "test2.in\n") + +test.run(arguments = 'test1') + +test.must_match('test1.out', "test1.in\n") + +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 index 0000000..ee93596 --- /dev/null +++ b/test/Actions/timestamp.py @@ -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() diff --git a/test/AddMethod.py b/test/AddMethod.py index ef4a8d0..584ab4c 100644 --- a/test/AddMethod.py +++ b/test/AddMethod.py @@ -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 index 0000000..b4ea95c --- /dev/null +++ b/test/Alias/Depends.py @@ -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() diff --git a/test/BuildDir/errors.py b/test/BuildDir/errors.py index 44096a1..3954f2b 100644 --- a/test/BuildDir/errors.py +++ b/test/BuildDir/errors.py @@ -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 index 0000000..ee23635 --- /dev/null +++ b/test/Builder/ensure_suffix.py @@ -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 index 0000000..77b8f6f --- /dev/null +++ b/test/CacheDir/multiple-targets.py @@ -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 index 0000000..33930d8 --- /dev/null +++ b/test/CacheDir/scanner-target.py @@ -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() diff --git a/test/CacheDir/timestamp.py b/test/CacheDir/timestamp.py new file mode 100644 index 0000000..b0d45b6 --- /dev/null +++ b/test/CacheDir/timestamp.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python +# +# __COPYRIGHT__ +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +""" +Verify that CacheDir() works even when using timestamp signatures. +""" + +import TestSCons + +test = TestSCons.TestSCons() + +test.write('SConstruct', """ +SourceSignatures('timestamp') +TargetSignatures('content') +CacheDir('cache') +Command('file.out', 'file.in', Copy('$TARGET', '$SOURCE')) +""") + +test.write('file.in', "file.in\n") + +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 index 0000000..5b8e43d --- /dev/null +++ b/test/Configure/clean.py @@ -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 + +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 index 0000000..f79831c --- /dev/null +++ b/test/Configure/help.py @@ -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 + +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() diff --git a/test/Copy.py b/test/Copy.py index 4da725e..6659d93 100644 --- a/test/Copy.py +++ b/test/Copy.py @@ -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 index 0000000..989e187 --- /dev/null +++ b/test/Decider/Environment.py @@ -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 index 0000000..d31a2db --- /dev/null +++ b/test/Decider/MD5-timestamp.py @@ -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 index 0000000..b11e223 --- /dev/null +++ b/test/Decider/Node.py @@ -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 index 0000000..c2886fb --- /dev/null +++ b/test/Decider/default.py @@ -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 index 0000000..7a83c74 --- /dev/null +++ b/test/Decider/mixed.py @@ -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 index 0000000..6975607 --- /dev/null +++ b/test/Decider/timestamp.py @@ -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 index 0000000..b6d071d --- /dev/null +++ b/test/Decider/unknown.py @@ -0,0 +1,46 @@ +#!/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 error when the Decider() function is handed an unknown +function string. +""" + +import TestSCons + +test = TestSCons.TestSCons(match = TestSCons.match_re_dotall) + +test.write('SConstruct', """\ +Decider('fiddle-dee-dee') +""") + +expect = r""" +scons: \*\*\* Unknown Decider value 'fiddle-dee-dee' +""" + TestSCons.file_expr + +test.run(arguments = '.', status = 2, stderr = expect) + +test.pass_test() diff --git a/test/Delete.py b/test/Delete.py index 138dc95..31febbe 100644 --- a/test/Delete.py +++ b/test/Delete.py @@ -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") diff --git a/test/Depends.py b/test/Depends.py index d0b2199..4870739 100644 --- a/test/Depends.py +++ b/test/Depends.py @@ -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() diff --git a/test/Dir/source.py b/test/Dir/source.py index fa07a88..9852660 100644 --- a/test/Dir/source.py +++ b/test/Dir/source.py @@ -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() diff --git a/test/Errors/preparation.py b/test/Errors/preparation.py index 3f98894..9857f99 100644 --- a/test/Errors/preparation.py +++ b/test/Errors/preparation.py @@ -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', diff --git a/test/Install/Install.py b/test/Install/Install.py index f7f8e26..6d44c45 100644 --- a/test/Install/Install.py +++ b/test/Install/Install.py @@ -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: diff --git a/test/Install/tool.py b/test/Install/tool.py new file mode 100644 index 0000000..4f09b73 --- /dev/null +++ b/test/Install/tool.py @@ -0,0 +1,51 @@ +#!/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 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') +""") + +test.write('foo.in', "foo.in\n") + +test.run(arguments = '.') + +test.must_match(['iii', 'foo.in'], "foo.in\n") +test.must_match('foo.out', "foo.in\n") + +test.pass_test() diff --git a/test/Java/JAVABOOTCLASSPATH.py b/test/Java/JAVABOOTCLASSPATH.py new file mode 100644 index 0000000..7723224 --- /dev/null +++ b/test/Java/JAVABOOTCLASSPATH.py @@ -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() diff --git a/test/Java/multi-step.py b/test/Java/multi-step.py index 1b69838..d185b4d 100644 --- a/test/Java/multi-step.py +++ b/test/Java/multi-step.py @@ -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'], """\ @@ -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() diff --git a/test/Java/swig-dependencies.py b/test/Java/swig-dependencies.py index c6961f2..8df5e09 100644 --- a/test/Java/swig-dependencies.py +++ b/test/Java/swig-dependencies.py @@ -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']) diff --git a/test/LIBS.py b/test/LIBS.py index 708ce63..e9f6545 100644 --- a/test/LIBS.py +++ b/test/LIBS.py @@ -272,7 +272,7 @@ test.write(['src', 'component2', 'message2.c'], """\ #include #include "message1.h" -int DisplayMessage2 (void) +void DisplayMessage2 (void) { DisplayMessage1(); printf ("src/component2/hello.c\\n"); diff --git a/test/MinGW/RCCOM.py b/test/MinGW/RCCOM.py index 2db0563..a272af7 100644 --- a/test/MinGW/RCCOM.py +++ b/test/MinGW/RCCOM.py @@ -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 diff --git a/test/MinGW/RCCOMSTR.py b/test/MinGW/RCCOMSTR.py index 96565f1..d04c5d6 100644 --- a/test/MinGW/RCCOMSTR.py +++ b/test/MinGW/RCCOMSTR.py @@ -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 diff --git a/test/NodeOps.py b/test/NodeOps.py index 3869228..7e656f7 100644 --- a/test/NodeOps.py +++ b/test/NodeOps.py @@ -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 index 0000000..5d4f5e1 --- /dev/null +++ b/test/Parallel/duplicate-target.py @@ -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 index 0000000..e45f859 --- /dev/null +++ b/test/Progress/TARGET.py @@ -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 index 0000000..23b3e72 --- /dev/null +++ b/test/Progress/dots.py @@ -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 index 0000000..f0116c0 --- /dev/null +++ b/test/Progress/file.py @@ -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 index 0000000..2fcb671 --- /dev/null +++ b/test/Progress/function.py @@ -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 index 0000000..767bf20 --- /dev/null +++ b/test/Progress/interval.py @@ -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 index 0000000..4dad1a1 --- /dev/null +++ b/test/Progress/object.py @@ -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 index 0000000..c600b67 --- /dev/null +++ b/test/Progress/spinner.py @@ -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() diff --git a/test/QT/installed.py b/test/QT/installed.py index 8cb9ab1..d746d9e 100644 --- a/test/QT/installed.py +++ b/test/QT/installed.py @@ -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'] diff --git a/test/QT/moc-from-header.py b/test/QT/moc-from-header.py index 1dbcd0f..2878136 100644 --- a/test/QT/moc-from-header.py +++ b/test/QT/moc-from-header.py @@ -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 index 0000000..453c0b8 --- /dev/null +++ b/test/SConscript/Return.py @@ -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() diff --git a/test/SWIG/build-dir.py b/test/SWIG/build-dir.py index 593a26a..58ad0cd 100644 --- a/test/SWIG/build-dir.py +++ b/test/SWIG/build-dir.py @@ -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): diff --git a/test/Scanner/generated.py b/test/Scanner/generated.py index 2dfd322..dd2c938 100644 --- a/test/Scanner/generated.py +++ b/test/Scanner/generated.py @@ -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 index 0000000..8565735 --- /dev/null +++ b/test/Sig.py @@ -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() diff --git a/test/SourceCode.py b/test/SourceCode.py index 86cc2a6..b74f8b1 100644 --- a/test/SourceCode.py +++ b/test/SourceCode.py @@ -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 index b85f8ec..0000000 --- a/test/SourceSignatures.py +++ /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 index 0000000..7042fac --- /dev/null +++ b/test/SourceSignatures/basic.py @@ -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 index 0000000..3bef2a6 --- /dev/null +++ b/test/SourceSignatures/env.py @@ -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 index 0000000..de66b72 --- /dev/null +++ b/test/SourceSignatures/implicit-cache.py @@ -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 index 0000000..3702901 --- /dev/null +++ b/test/SourceSignatures/no-csigs.py @@ -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 index 0000000..cf83488 --- /dev/null +++ b/test/SourceSignatures/overrides.py @@ -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 index 0000000..85c2b22 --- /dev/null +++ b/test/SourceSignatures/switch-rebuild.py @@ -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() diff --git a/test/TEX/auxiliaries.py b/test/TEX/auxiliaries.py index e189d57..8df2e79 100644 --- a/test/TEX/auxiliaries.py +++ b/test/TEX/auxiliaries.py @@ -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 index 0000000..6615fc9 --- /dev/null +++ b/test/TEX/clean.py @@ -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() diff --git a/test/TargetSignatures.py b/test/TargetSignatures.py deleted file mode 100644 index c3b506b..0000000 --- a/test/TargetSignatures.py +++ /dev/null @@ -1,161 +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 TestSCons - -test = TestSCons.TestSCons() - -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): - 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') - -TargetSignatures('content') -""") - -test.write('foo.in', 'foo.in') -test.write('bar.in', 'bar.in') - -test.run(arguments="bar.out foo.out", - stdout=test.wrap_stdout("""\ -copy2(["bar.mid"], ["bar.in"]) -copy1(["bar.out"], ["bar.mid"]) -copy2(["foo.mid"], ["foo.in"]) -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') - -TargetSignatures('content') -""") - -test.run(arguments="bar.out foo.out", - stdout=test.wrap_stdout("""\ -copy2(["bar.mid"], ["bar.in"]) -copy1(["bar.out"], ["bar.mid"]) -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() - -def copy1(env, source, target): - open(str(target[0]), 'wb').write(open(str(source[0]), 'rb').read()) - -def copy2(env, source, target): - 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("""\ -copy2(["bar.mid"], ["bar.in"]) -scons: `bar.out' is up to date. -copy2(["foo.mid"], ["foo.in"]) -copy1(["foo.out"], ["foo.mid"]) -""")) - - -test.pass_test() diff --git a/test/TargetSignatures/build-content.py b/test/TargetSignatures/build-content.py new file mode 100644 index 0000000..2cd7a89 --- /dev/null +++ b/test/TargetSignatures/build-content.py @@ -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 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() + + + +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) +env['BUILDERS']['Copy2'] = Builder(action=copy2) + +env.Copy2('foo.mid', 'foo.in') +env.Copy1('foo.out', 'foo.mid') + +env2 = env.Clone() +env2.TargetSignatures('%s') +env2.Copy2('bar.mid', 'bar.in') +env2.Copy1('bar.out', 'bar.mid') + +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') + +test.run(arguments="bar.out foo.out", + stdout=test.wrap_stdout("""\ +copy2(["bar.mid"], ["bar.in"]) +copy1(["bar.out"], ["bar.mid"]) +copy2(["foo.mid"], ["foo.in"]) +copy1(["foo.out"], ["foo.mid"]) +""")) + +test.up_to_date(arguments='bar.out foo.out') + + + +# Change the code in the the copy2() function, which should change +# its content and trigger a rebuild of the targets built with it. + +write_SConstruct(test, 'x = 2 # added this line', 'build', 'content') + +test.run(arguments="bar.out foo.out", + stdout=test.wrap_stdout("""\ +copy2(["bar.mid"], ["bar.in"]) +copy1(["bar.out"], ["bar.mid"]) +copy2(["foo.mid"], ["foo.in"]) +scons: `foo.out' is up to date. +""")) + + + +# Swapping content and build signatures no longer causes a rebuild +# because we record the right underlying information regardless. + +write_SConstruct(test, 'x = 2 # added this line', 'content', 'build') + +test.up_to_date(arguments="bar.out foo.out") + + + +# Change the code in the the copy2() function back again, which should +# trigger another rebuild of the targets built with it. + +write_SConstruct(test, '', 'content', 'build') + +test.run(arguments='bar.out foo.out', + stdout=test.wrap_stdout("""\ +copy2(["bar.mid"], ["bar.in"]) +scons: `bar.out' is up to date. +copy2(["foo.mid"], ["foo.in"]) +copy1(["foo.out"], ["foo.mid"]) +""")) + + + +test.pass_test() diff --git a/test/TargetSignatures/content.py b/test/TargetSignatures/content.py new file mode 100644 index 0000000..8d9f213 --- /dev/null +++ b/test/TargetSignatures/content.py @@ -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 index 0000000..5d9dd99 --- /dev/null +++ b/test/TargetSignatures/overrides.py @@ -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() diff --git a/test/Value.py b/test/Value.py index 85fcbd1..fd7afb7 100644 --- a/test/Value.py +++ b/test/Value.py @@ -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() diff --git a/test/bad-drive.py b/test/bad-drive.py index a642a7c..f07da2b 100644 --- a/test/bad-drive.py +++ b/test/bad-drive.py @@ -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', diff --git a/test/chained-build.py b/test/chained-build.py index 9d87347..6ec9d1c 100644 --- a/test/chained-build.py +++ b/test/chained-build.py @@ -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')) diff --git a/test/changed-node.py b/test/changed-node.py deleted file mode 100644 index 99a16ac..0000000 --- a/test/changed-node.py +++ /dev/null @@ -1,142 +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__" - -""" -Verify that we don't throw an exception if a stored implicit -dependency has changed -""" - -import TestSCons - -test = TestSCons.TestSCons() - - - -test.subdir('d', - ['d', '1'], - ['d', '2'], - ['d', '3']) - -test.write('SConstruct', """\ -SetOption('implicit_cache', 1) -SetOption('max_drift', 1) -SourceCode('.', None) - -def lister(target, source, env): - import os - fp = open(str(target[0]), 'w') - s = str(source[0]) - if os.path.isdir(s): - for l in os.listdir(str(source[0])): - fp.write(l + '\\n') - else: - fp.write(s + '\\n') - fp.close() - -builder = Builder(action=lister, - source_factory=Dir, - source_scanner=DirScanner) -env = Environment(tools=[]) -env['BUILDERS']['builder'] = builder -env.builder('d/xfactor', 'd/1') -env.builder('a', 'd') -""") - -test.write(['d', '1', 'x'], "d/1/x\n") -test.write(['d', '1', 'y'], "d/1/y\n") -test.write(['d', '1', 'z'], "d/1/z\n") -test.write(['d', '2', 'x'], "d/2/x\n") -test.write(['d', '2', 'y'], "d/2/y\n") -test.write(['d', '2', 'z'], "d/2/x\n") -test.write(['d', '3', 'x'], "d/3/x\n") -test.write(['d', '3', 'y'], "d/3/y\n") -test.write(['d', '3', 'z'], "d/3/z\n") - -test.run('--debug=stacktrace') - - - -test.write('SConstruct', """\ -SetOption('implicit_cache', 1) -SetOption('max_drift', 1) -SourceCode('.', None) - -def lister(target, source, env): - import os.path - fp = open(str(target[0]), 'w') - s = str(source[0]) - if os.path.isdir(s): - for l in os.listdir(str(source[0])): - fp.write(l + '\\n') - else: - fp.write(s + '\\n') - fp.close() - -builder = Builder(action=lister, - source_factory=File) -env = Environment(tools=[]) -env['BUILDERS']['builder'] = builder - -env.builder('a', 'SConstruct') -""") - -test.run('--debug=stacktrace') - -test.pass_test() - - - - -#from os import system, rmdir, remove, mkdir, listdir -#from os.path import exists, isdir -#import sys -# -# -#def setfile(f, content): -# f = open(f, 'w') -# try: f.write(content) -# finally: f.close() -# -#def checkfile(f, content): -# assert open(f).read().strip() == content -# -#def rm(f): -# if exists(f): -# if isdir(f): -# for name in listdir(f): -# rm(f+'/'+name) -# rmdir(f) -# else: remove(f) -#def clean(full=0): -# for f in ('d','b','a','SConstruct'): -# rm(f) -# if full: -# for f in ('.sconsign.dblite', 'build.py'): -# rm(f) -# -#clean(1) -# -#clean(1) diff --git a/test/exceptions.py b/test/exceptions.py index 34c24f4..c4bfb37 100644 --- a/test/exceptions.py +++ b/test/exceptions.py @@ -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 index 0000000..4bf4981 --- /dev/null +++ b/test/explain/get_csig.py @@ -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() diff --git a/test/implicit-cache/basic.py b/test/implicit-cache/basic.py index 0c9196c..265f589 100644 --- a/test/implicit-cache/basic.py +++ b/test/implicit-cache/basic.py @@ -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 index 0000000..47acc2f --- /dev/null +++ b/test/implicit/IMPLICIT_COMMAND_DEPENDENCIES.py @@ -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 index 0000000..d10f823 --- /dev/null +++ b/test/implicit/asynchronous-modification.py @@ -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() diff --git a/test/implicit/changed-node.py b/test/implicit/changed-node.py new file mode 100644 index 0000000..99a16ac --- /dev/null +++ b/test/implicit/changed-node.py @@ -0,0 +1,142 @@ +#!/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 throw an exception if a stored implicit +dependency has changed +""" + +import TestSCons + +test = TestSCons.TestSCons() + + + +test.subdir('d', + ['d', '1'], + ['d', '2'], + ['d', '3']) + +test.write('SConstruct', """\ +SetOption('implicit_cache', 1) +SetOption('max_drift', 1) +SourceCode('.', None) + +def lister(target, source, env): + import os + fp = open(str(target[0]), 'w') + s = str(source[0]) + if os.path.isdir(s): + for l in os.listdir(str(source[0])): + fp.write(l + '\\n') + else: + fp.write(s + '\\n') + fp.close() + +builder = Builder(action=lister, + source_factory=Dir, + source_scanner=DirScanner) +env = Environment(tools=[]) +env['BUILDERS']['builder'] = builder +env.builder('d/xfactor', 'd/1') +env.builder('a', 'd') +""") + +test.write(['d', '1', 'x'], "d/1/x\n") +test.write(['d', '1', 'y'], "d/1/y\n") +test.write(['d', '1', 'z'], "d/1/z\n") +test.write(['d', '2', 'x'], "d/2/x\n") +test.write(['d', '2', 'y'], "d/2/y\n") +test.write(['d', '2', 'z'], "d/2/x\n") +test.write(['d', '3', 'x'], "d/3/x\n") +test.write(['d', '3', 'y'], "d/3/y\n") +test.write(['d', '3', 'z'], "d/3/z\n") + +test.run('--debug=stacktrace') + + + +test.write('SConstruct', """\ +SetOption('implicit_cache', 1) +SetOption('max_drift', 1) +SourceCode('.', None) + +def lister(target, source, env): + import os.path + fp = open(str(target[0]), 'w') + s = str(source[0]) + if os.path.isdir(s): + for l in os.listdir(str(source[0])): + fp.write(l + '\\n') + else: + fp.write(s + '\\n') + fp.close() + +builder = Builder(action=lister, + source_factory=File) +env = Environment(tools=[]) +env['BUILDERS']['builder'] = builder + +env.builder('a', 'SConstruct') +""") + +test.run('--debug=stacktrace') + +test.pass_test() + + + + +#from os import system, rmdir, remove, mkdir, listdir +#from os.path import exists, isdir +#import sys +# +# +#def setfile(f, content): +# f = open(f, 'w') +# try: f.write(content) +# finally: f.close() +# +#def checkfile(f, content): +# assert open(f).read().strip() == content +# +#def rm(f): +# if exists(f): +# if isdir(f): +# for name in listdir(f): +# rm(f+'/'+name) +# rmdir(f) +# else: remove(f) +#def clean(full=0): +# for f in ('d','b','a','SConstruct'): +# rm(f) +# if full: +# for f in ('.sconsign.dblite', 'build.py'): +# rm(f) +# +#clean(1) +# +#clean(1) diff --git a/test/option-q.py b/test/option-q.py deleted file mode 100644 index 9b67d0a..0000000 --- a/test/option-q.py +++ /dev/null @@ -1,151 +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 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.run(arguments = '--question bbb.out', status = 1) - -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): - import shutil - shutil.copyfile( str(source[0]), str(target[0])) - -def strAction(target,source,env): - return "cp " + str(source[0]) + " " + str(target[0]) - -def CustomTest(context): - context.Message("Executing Custom Test ... " ) - (ok, msg) = context.TryAction(Action(userAction,strAction), - "Hello World", ".in") - context.Result(ok) - return ok - -env = Environment(BUILDERS={'B' : Builder(action=Action(userAction,strAction))}) - -conf = Configure( env, - custom_tests={'CustomTest':CustomTest}, - conf_dir="config.test", - log_file="config.log") -if not conf.CustomTest(): - Exit(1) -else: - env = conf.Finish() - -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 that targets are not built, if conf_dir exists. -# verify that .cache and config.log are not created. -# an error should be raised -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 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.fail_test( log1_mtime != log2_mtime ) - -test.pass_test() diff --git a/test/option-u.py b/test/option-u.py index 9b5e88c..439daa2 100644 --- a/test/option-u.py +++ b/test/option-u.py @@ -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) """) diff --git a/test/option/debug-memoizer.py b/test/option/debug-memoizer.py index 33f0f4d..8c8b2cf 100644 --- a/test/option/debug-memoizer.py +++ b/test/option/debug-memoizer.py @@ -69,7 +69,6 @@ expect = [ "Base.stat()", "Dir.srcdir_list()", "File.exists()", - "FS._doLookup()", "Node._children_get()", ] diff --git a/test/option/debug-stree.py b/test/option/debug-stree.py index 21ca386..d25b7fa 100644 --- a/test/option/debug-stree.py +++ b/test/option/debug-stree.py @@ -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') diff --git a/test/option/debug-time.py b/test/option/debug-time.py index e8873cf..8a975a3 100644 --- a/test/option/debug-time.py +++ b/test/option/debug-time.py @@ -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)) diff --git a/test/option/debug-tree.py b/test/option/debug-tree.py index 4f025c2..09cdffb 100644 --- a/test/option/debug-tree.py +++ b/test/option/debug-tree.py @@ -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() diff --git a/test/option/taskmastertrace.py b/test/option/taskmastertrace.py index 30faced..3139504 100644 --- a/test/option/taskmastertrace.py +++ b/test/option/taskmastertrace.py @@ -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) diff --git a/test/option/tree-all.py b/test/option/tree-all.py index ec7c7d8..7940d47 100644 --- a/test/option/tree-all.py +++ b/test/option/tree-all.py @@ -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 index 0000000..fc29d50 --- /dev/null +++ b/test/option/tree-lib.py @@ -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 +#include +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 index 0000000..6eae270 --- /dev/null +++ b/test/packaging/convenience-functions.py @@ -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 index 0000000..745f0c1 --- /dev/null +++ b/test/packaging/msi/explicit-target.py @@ -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 index 0000000..3151c05 --- /dev/null +++ b/test/packaging/multiple-packages-at-once.py @@ -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 index 0000000..80758ab --- /dev/null +++ b/test/packaging/multiple-subdirs.py @@ -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() diff --git a/test/packaging/option--package-type.py b/test/packaging/option--package-type.py index 00a569e..ce16e95 100644 --- a/test/packaging/option--package-type.py +++ b/test/packaging/option--package-type.py @@ -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' ) diff --git a/test/packaging/place-files-in-subdirectory.py b/test/packaging/place-files-in-subdirectory.py index d9758a1..66bdd53 100644 --- a/test/packaging/place-files-in-subdirectory.py +++ b/test/packaging/place-files-in-subdirectory.py @@ -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 diff --git a/test/packaging/rpm/cleanup.py b/test/packaging/rpm/cleanup.py index 5472fbb..11f6645 100644 --- a/test/packaging/rpm/cleanup.py +++ b/test/packaging/rpm/cleanup.py @@ -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 index 0000000..12a6c6c --- /dev/null +++ b/test/packaging/rpm/explicit-target.py @@ -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() diff --git a/test/packaging/rpm/internationalization.py b/test/packaging/rpm/internationalization.py index af0bc75..24d8ddd 100644 --- a/test/packaging/rpm/internationalization.py +++ b/test/packaging/rpm/internationalization.py @@ -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 index 0000000..5b85db3 --- /dev/null +++ b/test/packaging/rpm/multipackage.py @@ -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() diff --git a/test/packaging/strip-install-dir.py b/test/packaging/strip-install-dir.py index 65b6a61..ec40220 100644 --- a/test/packaging/strip-install-dir.py +++ b/test/packaging/strip-install-dir.py @@ -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' ) diff --git a/test/question/Configure.py b/test/question/Configure.py new file mode 100644 index 0000000..aeaba8a --- /dev/null +++ b/test/question/Configure.py @@ -0,0 +1,114 @@ +#!/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 in conjunction +with Configure tests. + +(This was originally mostly copied and pasted from test/option-n.py.) +""" + +import os.path +import re + +import TestCmd +import TestSCons + +test = TestSCons.TestSCons(match = TestCmd.match_re_dotall) + +test.write('aaa.in', 'Hello world\n') + +test.write('SConstruct', """\ +def userAction(target,source,env): + import shutil + shutil.copyfile( str(source[0]), str(target[0])) + +def strAction(target,source,env): + return "cp " + str(source[0]) + " " + str(target[0]) + +def CustomTest(context): + context.Message("Executing Custom Test ... " ) + (ok, msg) = context.TryAction(Action(userAction,strAction), + "Hello World", ".in") + context.Result(ok) + return ok + +env = Environment(BUILDERS={'B' : Builder(action=Action(userAction,strAction))}) + +conf = Configure( env, + custom_tests={'CustomTest':CustomTest}, + conf_dir="config.test", + log_file="config.log") +if not conf.CustomTest(): + Exit(1) +else: + env = conf.Finish() + +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) + +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. +# an error should be raised +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('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) + +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 index 0000000..dc07e48 --- /dev/null +++ b/test/question/basic.py @@ -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 index 0000000..d038724 --- /dev/null +++ b/test/question/no-builder.py @@ -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() diff --git a/test/runtest/fallback.py b/test/runtest/fallback.py index 90cd4f2..8b6ae42 100644 --- a/test/runtest/fallback.py +++ b/test/runtest/fallback.py @@ -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: diff --git a/test/runtest/noqmtest.py b/test/runtest/noqmtest.py index c442125..f2bf6eb 100644 --- a/test/runtest/noqmtest.py +++ b/test/runtest/noqmtest.py @@ -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: diff --git a/test/runtest/python.py b/test/runtest/python.py index 1af32dd..95b5f0f 100644 --- a/test/runtest/python.py +++ b/test/runtest/python.py @@ -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 index 0000000..8e17e95 --- /dev/null +++ b/test/sconsign/script/Configure.py @@ -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() diff --git a/test/sconsign/script/SConsignFile.py b/test/sconsign/script/SConsignFile.py index 2f5ddf3..99845e3 100644 --- a/test/sconsign/script/SConsignFile.py +++ b/test/sconsign/script/SConsignFile.py @@ -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() diff --git a/test/sconsign/script/Signatures.py b/test/sconsign/script/Signatures.py index 9a3ce62..fc85133 100644 --- a/test/sconsign/script/Signatures.py +++ b/test/sconsign/script/Signatures.py @@ -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() diff --git a/test/sconsign/script/dblite.py b/test/sconsign/script/dblite.py index fe49df6..1b359e2 100644 --- a/test/sconsign/script/dblite.py +++ b/test/sconsign/script/dblite.py @@ -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' diff --git a/test/sconsign/script/no-SConsignFile.py b/test/sconsign/script/no-SConsignFile.py index b860770..1fcfbfd 100644 --- a/test/sconsign/script/no-SConsignFile.py +++ b/test/sconsign/script/no-SConsignFile.py @@ -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() diff --git a/test/timestamp-fallback.py b/test/timestamp-fallback.py index 7576023..9d89d70 100644 --- a/test/timestamp-fallback.py +++ b/test/timestamp-fallback.py @@ -24,6 +24,11 @@ __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 @@ -33,6 +38,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: pass diff --git a/test/up-to-date.py b/test/up-to-date.py index cec5952..366d017 100644 --- a/test/up-to-date.py +++ b/test/up-to-date.py @@ -24,9 +24,12 @@ __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() -- cgit v0.12