diff options
author | Steven Knight <knight@baldmt.com> | 2007-12-13 04:25:43 (GMT) |
---|---|---|
committer | Steven Knight <knight@baldmt.com> | 2007-12-13 04:25:43 (GMT) |
commit | 5c650fb05bcd2e0091ced1ad94b8aa55173af805 (patch) | |
tree | 86299c32c336859f1bf05c6e3800d8c54282ccd8 | |
parent | 2e7721d8c9ad57dcd48c28403178f8986943868d (diff) | |
download | SCons-5c650fb05bcd2e0091ced1ad94b8aa55173af805.zip SCons-5c650fb05bcd2e0091ced1ad94b8aa55173af805.tar.gz SCons-5c650fb05bcd2e0091ced1ad94b8aa55173af805.tar.bz2 |
Merged revisions 2454-2525 via svnmerge from
http://scons.tigris.org/svn/scons/branches/core
........
r2455 | stevenknight | 2007-09-20 01:27:23 -0500 (Thu, 20 Sep 2007) | 2 lines
Use ${TARGET.base} to make sure $TARGET attributes stay fixed.
........
r2456 | stevenknight | 2007-09-25 11:52:30 -0500 (Tue, 25 Sep 2007) | 5 lines
Issue 1734: Avoid having content signature calculation of Alias Nodes
consume excessive amounts of memory by having an Alias' "contents" be a
concatenation of the children's signatures, not the children's contents.
(Ken Deeter)
........
r2457 | stevenknight | 2007-09-26 12:18:49 -0500 (Wed, 26 Sep 2007) | 2 lines
Add an Options.UnknownOptions() method.
........
r2458 | stevenknight | 2007-09-26 16:26:05 -0500 (Wed, 26 Sep 2007) | 2 lines
Add a compatibility fnmatch.filter() function.
........
r2459 | stevenknight | 2007-09-27 18:26:03 -0500 (Thu, 27 Sep 2007) | 3 lines
Add a new Glob() function that matches in-memory Nodes as well as on-disk
files (including matching repository and source directories).
........
r2460 | stevenknight | 2007-09-28 15:01:37 -0500 (Fri, 28 Sep 2007) | 5 lines
Issue 1020: fix use of Clean() for files created by "side effect"
in BuildDir() by removing the file by absolute path, not by what str()
returns. (It will think that the file is a source file and return a
path to the source directory.)
........
r2461 | stevenknight | 2007-09-29 05:39:09 -0500 (Sat, 29 Sep 2007) | 2 lines
Update to TestCmd 0.28 modules.
........
r2462 | stevenknight | 2007-09-29 05:49:29 -0500 (Sat, 29 Sep 2007) | 3 lines
The RPM packaging can no longer take a "target" argument and produces
an appropriate error message. Update the test accordingly.
........
r2463 | pscholl | 2007-09-30 08:57:01 -0500 (Sun, 30 Sep 2007) | 3 lines
fix documentation issues (issue 1736 on the bugtracker)
........
r2464 | pscholl | 2007-09-30 09:39:49 -0500 (Sun, 30 Sep 2007) | 3 lines
fix target set up if multiple package builders are specified at once.
........
r2465 | stevenknight | 2007-10-01 13:00:44 -0500 (Mon, 01 Oct 2007) | 3 lines
Update to TestCmd 0.29, with new methods for searching for a list
of lines in output.
........
r2466 | stevenknight | 2007-10-01 13:58:41 -0500 (Mon, 01 Oct 2007) | 4 lines
Issue 1737: Fix use of Configure() contexts with the -c (clean) and -h
(help) options by supporting the ability to *configure* whether or no
configure context tests are executed during those modes.
........
r2467 | stevenknight | 2007-10-01 16:58:21 -0500 (Mon, 01 Oct 2007) | 2 lines
Update to TestCmd 0.30, with a new TestCmd.rmdir() method.
........
r2468 | stevenknight | 2007-10-01 17:05:44 -0500 (Mon, 01 Oct 2007) | 4 lines
Issue 1586: Capture a test script for "ghost" entries in .sconsign files.
Test cases by Morten Elo Peterson and Jason Orendorff, packaged by
Gary Oberbrunner.
........
r2469 | stevenknight | 2007-10-04 11:21:12 -0500 (Thu, 04 Oct 2007) | 4 lines
When cloning a construction environment, have the clone record the
re-binding of the methods that were added to the original construction
environment, so that further clones have their methods re-bound as well.
........
r2470 | stevenknight | 2007-10-05 13:02:34 -0500 (Fri, 05 Oct 2007) | 3 lines
Refactor the Glob() code for efficiency and readability. (Greg Noel)
Refactor Glob() unit tests for platform-independence.
........
r2471 | stevenknight | 2007-10-09 10:49:15 -0500 (Tue, 09 Oct 2007) | 2 lines
Back out Glob() refactoring to avoid Repository breakage.
........
r2472 | stevenknight | 2007-10-09 12:16:33 -0500 (Tue, 09 Oct 2007) | 3 lines
Fix ToolInitializer-related infinite recursion when the BUILDERS dict
and the environment attributes can get out of sync.
........
r2473 | stevenknight | 2007-10-10 14:39:19 -0500 (Wed, 10 Oct 2007) | 4 lines
Fix a race condition in the -j sub-test by using marker directories to
make sure (?) that the two build scripts are actually executed in parallel
(regardless of system load).
........
r2474 | stevenknight | 2007-10-11 12:32:07 -0500 (Thu, 11 Oct 2007) | 4 lines
Re-fix globbing on case-insensitive systems like Windows. Slight
efficiency improvements as well (avoiding unnecessary calls to
fnmatch.filter()).
........
r2475 | stevenknight | 2007-10-11 15:04:42 -0500 (Thu, 11 Oct 2007) | 3 lines
Refactor the Node lookup logic to fix handling Windows drive letters
after an initial '#'.
........
r2476 | stevenknight | 2007-10-12 00:01:31 -0500 (Fri, 12 Oct 2007) | 2 lines
Fix nested scope issues (for the benefit of older Python versions).
........
r2477 | stevenknight | 2007-10-12 11:36:21 -0500 (Fri, 12 Oct 2007) | 2 lines
Issue 1743: Document '#' interpretation, with examples.
........
r2478 | stevenknight | 2007-10-12 12:17:50 -0500 (Fri, 12 Oct 2007) | 3 lines
Fix the ability of our default ActionFactory function to handle Nodes
as input.
........
r2479 | stevenknight | 2007-10-12 13:53:37 -0500 (Fri, 12 Oct 2007) | 4 lines
Enhance Options() file execution to add the file's directory to sys.path
(and remove it afterwards) and to add a __name__ variable that can be
used for introspecting on the file's location.
........
r2480 | stevenknight | 2007-10-14 17:57:09 -0500 (Sun, 14 Oct 2007) | 4 lines
Remove unnecessary os.path.normpath() calls when looking up directories
or files by checking for whether we can just tack on a single entry name
to the already-normalized lookup path of the directory Node.
........
r2481 | stevenknight | 2007-10-17 01:08:47 -0500 (Wed, 17 Oct 2007) | 2 lines
Add a GetBuildFailures() function.
........
r2482 | stevenknight | 2007-10-17 09:56:18 -0500 (Wed, 17 Oct 2007) | 2 lines
Fix the GetBuildFailures() example in the man page.
........
r2483 | stevenknight | 2007-10-17 10:54:21 -0500 (Wed, 17 Oct 2007) | 3 lines
Use sys.exitfunc if there's no atexit module (Python 1.5.2).
Sort the failure list for deterministic build output under system load.
........
r2484 | stevenknight | 2007-10-20 12:33:07 -0500 (Sat, 20 Oct 2007) | 2 lines
Use more efficient Decider() defaults instead of {Target,Source}Signatures().
........
r2485 | stevenknight | 2007-10-20 17:42:27 -0500 (Sat, 20 Oct 2007) | 2 lines
Windows portability in GetBuildFailures() test scripts.
........
r2486 | stevenknight | 2007-10-20 20:46:26 -0500 (Sat, 20 Oct 2007) | 2 lines
Windows portability: rename internal copy.py script, use a stub instead of tar.
........
r2487 | stevenknight | 2007-10-24 23:36:24 -0500 (Wed, 24 Oct 2007) | 4 lines
Whenever a script configures SConsignFile(None), make sure it uses
stub compiler and linker scripts, not the system ones, to avoid writing
(or trying to write) .sconsign files in system directories.
........
r2488 | stevenknight | 2007-10-26 13:57:38 -0500 (Fri, 26 Oct 2007) | 2 lines
Issue 1764: Fix test-script portability issues on Solaris.
........
r2489 | stevenknight | 2007-10-27 07:37:37 -0500 (Sat, 27 Oct 2007) | 3 lines
Issue 1757: add a CheckTypeSize() call to Configure contexts
(David Cournapeau).
........
r2490 | stevenknight | 2007-10-28 07:58:30 -0500 (Sun, 28 Oct 2007) | 2 lines
Python 1.5.2 compatibility: no use of +=.
........
r2491 | stevenknight | 2007-10-29 12:13:35 -0500 (Mon, 29 Oct 2007) | 3 lines
Issue 1758: Fix the SCons packaging build for use with shared-lib
versions of Python and to avoid .egg-info naming issues.
........
r2492 | stevenknight | 2007-10-29 14:09:57 -0500 (Mon, 29 Oct 2007) | 2 lines
Document the "expect" argument to CheckTypeSize(). (David Cournapeau)
........
r2493 | stevenknight | 2007-11-05 20:57:27 -0600 (Mon, 05 Nov 2007) | 2 lines
Fix use of Glob() when a pattern is below an explicitly-named subdirectory.
........
r2495 | stevenknight | 2007-11-12 22:58:12 -0600 (Mon, 12 Nov 2007) | 5 lines
Add a get_sources() access method to avoid an O(n^2) problem when adding
sources to an Executor object. The old code weeded out duplicates
whenever a new source was added; the new code only does that when the
source is list going to be used.
........
r2496 | stevenknight | 2007-11-15 12:19:55 -0600 (Thu, 15 Nov 2007) | 4 lines
Redefine the $WINDOWSPROGMANIFESTSUFFIX and
$WINDOWSSHLIBMANIFESTSUFFIX variables so they pick up changes to
the underlying $SHLIBSUFFIX and $PROGSUFFIX variables.
........
r2497 | stevenknight | 2007-11-18 17:11:52 -0600 (Sun, 18 Nov 2007) | 4 lines
Support .status and .command attributes of BuildError exceptions.
Change Action objects to return BuildError objects (not raise them)
when an action fails.
........
r2498 | stevenknight | 2007-11-19 07:27:16 -0600 (Mon, 19 Nov 2007) | 3 lines
When converting .sconsign paths to Nodes, use the more efficient
_lookup_abs() method.
........
r2499 | stevenknight | 2007-11-25 00:18:20 -0600 (Sun, 25 Nov 2007) | 3 lines
Move the reflection-checking is_under() logic from the .srcdir_list()
method to the .srcdir_duplicate() method.
........
r2500 | stevenknight | 2007-11-25 00:31:33 -0600 (Sun, 25 Nov 2007) | 2 lines
Have the .srcnode() method use the .srcdir_list() method.
........
r2501 | stevenknight | 2007-11-28 22:56:39 -0600 (Wed, 28 Nov 2007) | 3 lines
Issue 1845: Have single-source Builders (like Object()) return
NodeList objects even when called with multiple files.
........
r2502 | stevenknight | 2007-11-28 23:00:46 -0600 (Wed, 28 Nov 2007) | 2 lines
Issue 1845: Document the NodeList behavior w.r.t Python's += operator.
........
r2503 | stevenknight | 2007-11-29 09:35:31 -0600 (Thu, 29 Nov 2007) | 3 lines
Issue 1840: Fix a lot of typos in the man page and Users' Guide.
(Malte Helmert)
........
r2504 | stevenknight | 2007-11-29 10:41:44 -0600 (Thu, 29 Nov 2007) | 3 lines
Issue 1841: Fix --implicit-cache spurious rebuilds and inefficiency
when using Builders that produce multiple targets. (Benoit Belley)
........
r2505 | stevenknight | 2007-11-30 17:36:03 -0600 (Fri, 30 Nov 2007) | 3 lines
Unit test fix for Python 1.5.2, which can't .extend() lists with
UserList objects.
........
r2506 | stevenknight | 2007-11-30 20:37:14 -0600 (Fri, 30 Nov 2007) | 2 lines
Python 1.5.2 portability: use string.join(), not ' '.join().
........
r2507 | stevenknight | 2007-11-30 21:42:19 -0600 (Fri, 30 Nov 2007) | 3 lines
When searching directory lists like $CPPPATH, don't make Dir Nodes for
directories that don't exist on disk.
........
r2508 | stevenknight | 2007-12-01 00:14:35 -0600 (Sat, 01 Dec 2007) | 2 lines
Add a Requires() function for specifying order-only prerequisites.
........
r2509 | stevenknight | 2007-12-01 07:32:24 -0600 (Sat, 01 Dec 2007) | 3 lines
Handle absolute paths without infinite recursion in the new code that
searches for implicit dependencies without creating unnecessary Dir Nodes.
........
r2510 | stevenknight | 2007-12-03 15:11:56 -0600 (Mon, 03 Dec 2007) | 2 lines
Restore the rel_path() method, for the benefit of SConscript files using it.
........
r2511 | stevenknight | 2007-12-04 00:34:02 -0600 (Tue, 04 Dec 2007) | 4 lines
User's Guide updates for the Big Signature refactoring, capturing mention
of things that still need documenting, and other changes from re-running
the examples through the latest code.
........
r2512 | stevenknight | 2007-12-04 08:48:51 -0600 (Tue, 04 Dec 2007) | 3 lines
Issue 1846: allow building only part of the dependency graph when
BuildDir(duplicate=0) is used. (Benoit Belley)
........
r2513 | stevenknight | 2007-12-06 05:02:56 -0600 (Thu, 06 Dec 2007) | 3 lines
Have the code that avoids creating unnecessary Dir Nodes when searching
$*PATH variables handle absolute paths with Windows drive letters.
........
r2514 | stevenknight | 2007-12-08 07:44:15 -0600 (Sat, 08 Dec 2007) | 6 lines
Issue 1852: Make the default behavior of
{Source,Target}Signatures('timestamp') equivalent to
'timestamp-match', not 'timestamp-newer'. Fix use of CacheDir with
Decider('timestamp-newer') by updating the modification time when copying
files from the cache.
........
r2515 | stevenknight | 2007-12-08 08:23:02 -0600 (Sat, 08 Dec 2007) | 4 lines
Update the mock compiler inin/sconsoutput to use $CPPPATH.
Capture the ripple effect in the Troubleshooting appendix.
Also add a -t option to the mock "touch" command.
........
r2516 | stevenknight | 2007-12-08 09:16:11 -0600 (Sat, 08 Dec 2007) | 3 lines
Update the Dependencies chapter for use of the Decider() function,
and to now discourage use of SourceSignatures() and TargetSignatures().
........
r2517 | stevenknight | 2007-12-08 12:31:10 -0600 (Sat, 08 Dec 2007) | 4 lines
Issue 1721: On Windows, wrap __builtin__.close() and __builtin__.file()
to disable file handle inheritance on any files opened by SCons during
the run.
........
r2518 | stevenknight | 2007-12-11 17:33:20 -0600 (Tue, 11 Dec 2007) | 4 lines
Prevent the _get_str() method from causing underlying stat() values
to be cached if we're not yet saving the string representations of
FS.Base() Nodes.
........
r2519 | stevenknight | 2007-12-11 23:27:05 -0600 (Tue, 11 Dec 2007) | 4 lines
Add a warning about the unreliability of -j if the pywin32 modules aren't
available or are old and can't suppress file handle inheritance.
Add a release note about the change to open() and file().
........
r2520 | stevenknight | 2007-12-11 23:28:05 -0600 (Tue, 11 Dec 2007) | 2 lines
Add an overlooked update, fix spelling.
........
r2521 | stevenknight | 2007-12-12 09:12:42 -0600 (Wed, 12 Dec 2007) | 3 lines
Use &TargetSignatures; (replace missing ampersand) in the title of
that section. Move the &Depends; section to before the &Ignore; section.
........
r2522 | stevenknight | 2007-12-12 09:20:46 -0600 (Wed, 12 Dec 2007) | 3 lines
Final documentation update for checkpoint release: propagate .in changes
to .xml files.
........
r2523 | stevenknight | 2007-12-12 09:29:11 -0600 (Wed, 12 Dec 2007) | 2 lines
Update release lines for new checkpoint release.
........
138 files changed, 6239 insertions, 1168 deletions
diff --git a/QMTest/TestCmd.py b/QMTest/TestCmd.py index 08642cb..f5e1c71 100644 --- a/QMTest/TestCmd.py +++ b/QMTest/TestCmd.py @@ -48,6 +48,8 @@ things. Here is an overview of them: test.subdir('subdir', ...) + test.rmdir('subdir', ...) + test.write('file', "contents\n") test.write(['subdir', 'file'], "contents\n") @@ -179,8 +181,8 @@ version. # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. __author__ = "Steven Knight <knight at baldmt dot com>" -__revision__ = "TestCmd.py 0.26.D001 2007/08/20 21:58:58 knight" -__version__ = "0.26" +__revision__ = "TestCmd.py 0.30.D001 2007/10/01 16:53:55 knight" +__version__ = "0.30" import os import os.path @@ -690,6 +692,17 @@ class TestCmd: raise ValueError, "mode must begin with 'r'" return open(file, mode).read() + def rmdir(self, dir): + """Removes the specified dir name. + The dir name may be a list, in which case the elements are + concatenated with the os.path.join() method. The dir is + assumed to be under the temporary working directory unless it + is an absolute path name. + The dir must be empty. + """ + dir = self.canonicalize(dir) + os.rmdir(dir) + def run(self, program = None, interpreter = None, arguments = None, diff --git a/QMTest/TestCommon.py b/QMTest/TestCommon.py index f4c7c54..d6b21ad 100644 --- a/QMTest/TestCommon.py +++ b/QMTest/TestCommon.py @@ -36,12 +36,16 @@ provided by the TestCommon class: test.must_contain('file', 'required text\n') + test.must_contain_lines(lines, output) + test.must_exist('file1', ['file2', ...]) test.must_match('file', "expected contents\n") test.must_not_be_writable('file1', ['file2', ...]) + test.must_not_contain_lines(lines, output) + test.must_not_exist('file1', ['file2', ...]) test.run(options = "options to be prepended to arguments", @@ -80,8 +84,8 @@ The TestCommon module also provides the following variables # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. __author__ = "Steven Knight <knight at baldmt dot com>" -__revision__ = "TestCommon.py 0.26.D001 2007/08/20 21:58:58 knight" -__version__ = "0.26" +__revision__ = "TestCommon.py 0.30.D001 2007/10/01 16:53:55 knight" +__version__ = "0.30" import os import os.path @@ -95,8 +99,6 @@ from TestCmd import * from TestCmd import __all__ __all__.extend([ 'TestCommon', - 'TestFailed', - 'TestNoResult', 'exe_suffix', 'obj_suffix', 'shobj_suffix', @@ -196,24 +198,16 @@ def separate_files(flist): missing.append(f) return existing, missing -class TestFailed(Exception): - def __init__(self, args=None): - self.args = args - -class TestNoResult(Exception): - def __init__(self, args=None): - self.args = args - if os.name == 'posix': def _failed(self, status = 0): if self.status is None or status is None: return None - if os.WIFSIGNALED(self.status): - return None return _status(self) != status def _status(self): if os.WIFEXITED(self.status): return os.WEXITSTATUS(self.status) + elif os.WIFSIGNALED(self.status): + return os.WTERMSIG(self.status) else: return None elif os.name == 'nt': @@ -301,6 +295,19 @@ class TestCommon(TestCmd): print file_contents self.fail_test(not contains) + def must_contain_lines(self, lines, output, title=None): + if title is None: + title = 'output' + + missing = filter(lambda l, o=output: string.find(o, l) == -1, lines) + + if missing: + print "Missing lines from %s:" % title + print string.join(missing, '\n') + print "%s ============================================================" % title + print output + self.fail_test() + def must_exist(self, *files): """Ensures that the specified file(s) must exist. An individual file be specified as a list of directory names, in which case the @@ -329,6 +336,19 @@ class TestCommon(TestCmd): self.diff(expect, file_contents, 'contents ') raise + def must_not_contain_lines(self, lines, output=None, title=None): + if title is None: + title = 'output' + + unexpected = filter(lambda l, o=output: string.find(o, l) != -1, lines) + + if unexpected: + print "Unexpected lines in %s:" % title + print string.join(unexpected, '\n') + print "%s ============================================================" % title + print output + self.fail_test() + def must_not_exist(self, *files): """Ensures that the specified file(s) must not exist. An individual file be specified as a list of directory names, in @@ -399,12 +419,18 @@ class TestCommon(TestCmd): apply(TestCmd.run, [self], kw) except KeyboardInterrupt: raise - except: + except Exception, e: print self.banner('STDOUT ') - print self.stdout() + try: + print self.stdout() + except IndexError: + pass print self.banner('STDERR ') - print self.stderr() - raise + try: + print self.stderr() + except IndexError: + pass + raise e if _failed(self, status): expect = '' if status != 0: @@ -414,19 +440,19 @@ class TestCommon(TestCmd): print self.stdout() print self.banner('STDERR ') print self.stderr() - raise TestFailed + self.fail_test() if not stdout is None and not match(self.stdout(), stdout): self.diff(stdout, self.stdout(), 'STDOUT ') stderr = self.stderr() if stderr: print self.banner('STDERR ') print stderr - raise TestFailed + self.fail_test() if not stderr is None and not match(self.stderr(), stderr): print self.banner('STDOUT ') print self.stdout() self.diff(stderr, self.stderr(), 'STDERR ') - raise TestFailed + self.fail_test() def skip_test(self, message="Skipping test.\n"): """Skips a test. @@ -42,6 +42,7 @@ import re import stat import string import sys +import tempfile project = 'scons' default_version = '0.97.0' @@ -139,8 +140,11 @@ python_ver = sys.version[0:3] platform = distutils.util.get_platform() +# Re-exporting LD_LIBRARY_PATH is necessary if the Python version was +# built with the --enable-shared option. + ENV = { 'PATH' : os.environ['PATH'] } -for key in ['LOGNAME', 'PYTHONPATH']: +for key in ['LOGNAME', 'PYTHONPATH', 'LD_LIBRARY_PATH']: if os.environ.has_key(key): ENV[key] = os.environ[key] @@ -481,18 +485,54 @@ python_scons = { }, } -# The RPM spec file we generate will just execute "python", not -# necessarily the one in sys.executable. If that version of python has -# a distutils that knows about Python eggs, then setup.py will generate -# a .egg-info file. Check for whether or not to add it to the expected -# RPM files by executing "python" in a subshell. +# Figure out the name of a .egg-info file that might be generated +# as part of the RPM package. There are two complicating factors. +# +# First, the RPM spec file we generate will just execute "python", not +# necessarily the one in sys.executable. If *that* version of python has +# a distutils that knows about Python eggs, then setup.py will generate a +# .egg-info file, so we have to execute any distutils logic in a subshell. +# +# Second, we can't just have the subshell check for the existence of the +# distutils.command.install_egg_info module and generate the expected +# file name by hand, the way we used to, because different systems can +# have slightly different .egg-info naming conventions. (Specifically, +# Ubuntu overrides the default behavior to remove the Python version +# string from the .egg-info file name.) The right way to do this is to +# actually call into the install_egg_info() class to have it generate +# the expected name for us. +# +# This is all complicated enough that we do it by writing an in-line +# script to a temporary file and then feeding it to a separate invocation +# of "python" to tell us the actual name of the generated .egg-info file. -cmd = "python -c 'import distutils.command.install_egg_info' > /dev/null 2>&1" -import_egg_error = os.system(cmd) +print_egg_info_name = """ +try: + from distutils.dist import Distribution + from distutils.command.install_egg_info import install_egg_info +except ImportError: + pass +else: + dist = Distribution({'name' : "scons", 'version' : '%s'}) + i = install_egg_info(dist) + i.finalize_options() + import os.path + print os.path.split(i.outputs[0])[1] +""" % version -if not import_egg_error: - egg_info_file = 'scons-' + version + '.egg-info' - python_scons['extra_rpm_files'].append(egg_info_file) +try: + fd, tfname = tempfile.mkstemp() + tfp = os.fdopen(fd, "w") + tfp.write(print_egg_info_name) + tfp.close() + egg_info_file = os.popen("python %s" % tfname).read()[:-1] + if egg_info_file: + python_scons['extra_rpm_files'].append(egg_info_file) +finally: + try: + os.unlink(tfname) + except EnvironmentError: + pass # # The original packaging scheme would have have required us to push diff --git a/bin/sconsoutput.py b/bin/sconsoutput.py index 585d3c1..6340bc1 100644 --- a/bin/sconsoutput.py +++ b/bin/sconsoutput.py @@ -280,12 +280,23 @@ def Cat(target, source, env): def CCCom(target, source, env): target = str(target[0]) - f = open(target, "wb") + fp = open(target, "wb") + def process(source_file, fp=fp): + for line in open(source_file, "rb").readlines(): + m = re.match(r'#include\s[<"]([^<"]+)[>"]', line) + if m: + include = m.group(1) + for d in [str(env.Dir('$CPPPATH')), '.']: + f = os.path.join(d, include) + if os.path.exists(f): + process(f) + break + elif line[:11] != "STRIP CCCOM": + fp.write(line) for src in map(str, source): - lines = open(src, "rb").readlines() - lines = filter(lambda l: l[:11] != "STRIP CCCOM", lines) - f.write(string.join(lines)) - f.close() + process(src) + fp.write('debug = ' + ARGUMENTS.get('debug', '0') + '\\n') + fp.close() public_class_re = re.compile('^public class (\S+)', re.MULTILINE) @@ -432,13 +443,19 @@ def command_scons(args, c, test, dict): return lines def command_touch(args, c, test, dict): - time.sleep(1) + if args[0] == '-t': + t = int(time.mktime(time.strptime(args[1], '%Y%m%d%H%M'))) + times = (t, t) + args = args[2:] + else: + time.sleep(1) + times = None for file in args: if not os.path.isabs(file): file = os.path.join(test.workpath('WORK'), file) if not os.path.exists(file): open(file, 'wb') - os.utime(file, None) + os.utime(file, times) return [] def command_edit(args, c, test, dict): @@ -728,6 +745,7 @@ class MySGML(sgmllib.SGMLParser): elif lines: content = string.join(lines, '\n' + p) if content: + content = re.sub(' at 0x[0-9a-fA-F]*\>', ' at 0x700000>', content) content = string.replace(content, '<', '<') content = string.replace(content, '>', '>') sys.stdout.write(p + content + '\n') diff --git a/doc/man/scons.1 b/doc/man/scons.1 index 6f87b20..526a5a3 100644 --- a/doc/man/scons.1 +++ b/doc/man/scons.1 @@ -264,8 +264,8 @@ scons foo bar in which case only the specified targets will be built (along with any derived files on which they depend). -Specifying "cleanup" targets in SConscript files is not -necessary. The +Specifying "cleanup" targets in SConscript files is not usually necessary. +The .B -c flag removes all files necessary to build the specified target: @@ -1271,7 +1271,7 @@ These warnings are disabled by default. Search the specified repository for any input and target files not found in the local directory hierarchy. Multiple .B -Y -options may specified, in which case the +options may be specified, in which case the repositories are searched in the order specified. .SH CONFIGURATION FILE REFERENCE @@ -1340,7 +1340,7 @@ env = Environment(platform = my_platform) Additionally, a specific set of tools with which to initialize the environment -may specified as an optional keyword argument: +may be specified as an optional keyword argument: .ES env = Environment(tools = ['msvc', 'lex']) @@ -1530,10 +1530,12 @@ Build rules are specified by calling a construction environment's builder methods. The arguments to the builder methods are .B target -(a list of target files) +(a list of targets to be built, +usually file names) and .B source -(a list of source files). +(a list of sources to be built, +usually file names). Because long lists of file names can lead to a lot of quoting, @@ -1569,6 +1571,58 @@ env.Program(target = 'bar', env.Split('bar.c foo.c')) env.Program('bar', source = string.split('bar.c foo.c')) .EE +Target and source file names +that are not absolute path names +(that is, do not begin with +.B / +on POSIX systems +or +.B \\ +on Windows systems, +with or without +an optional drive letter) +are interpreted relative to the directory containing the +.B SConscript +file being read. +An initial +.B # +(hash mark) +on a path name means that the rest of the file name +is interpreted relative to +the directory containing +the top-level +.B SConstruct +file, +even if the +.B # +is followed by a directory separator character +(slash or backslash). + +Examples: + +.ES +# The comments describing the targets that will be built +# assume these calls are in a SConscript file in the +# a subdirectory named "subdir". + +# Builds the program "subdir/foo" from "subdir/foo.c": +env.Program('foo', 'foo.c') + +# Builds the program "/tmp/bar" from "subdir/bar.c": +env.Program('/tmp/bar', 'bar.c') + +# An initial '#' or '#/' are equivalent; the following +# calls build the programs "foo" and "bar" (in the +# top-level SConstruct directory) from "subdir/foo.c" and +# "subdir/bar.c", respectively: +env.Program('#foo', 'foo.c') +env.Program('#/bar', 'bar.c') + +# Builds the program "other/foo" (relative to the top-level +# SConstruct directory) from "subdir/foo.c": +env.Program('#other/foo', 'foo.c') +.EE + When the target shares the same base name as the source and only the suffix varies, and if the builder method has a suffix defined for the target file type, @@ -1665,15 +1719,16 @@ to the Python module: from SCons.Script import * .EE -All builder methods return a list of Nodes -that represent the target or targets that will be built. +All builder methods return a list-like object +containing Nodes that +represent the target or targets that will be built. A .I Node is an internal SCons object which represents build targets or sources. -The returned Node(s) +The returned Node-list object can be passed to other builder methods as source(s) or passed to any SCons function or method where a filename would normally be accepted. @@ -1712,7 +1767,7 @@ for object in objects: Or you can use the .BR Flatten () -supplied by scons +function supplied by scons to create a list containing just the Nodes, which may be more convenient: @@ -1724,6 +1779,40 @@ for object in objects: print str(object) .EE +Note also that because Builder calls return +a list-like object, not an actual Python list, +you should +.I not +use the Python +.B += +operator to append Builder results to a Python list. +Because the list and the object are different types, +Python will not update the original list in place, +but will instead create a new Node-list object +containing the concatenation of the list +elements and the Builder results. +This will cause problems for any other Python variables +in your SCons configuration +that still hold on to a reference to the original list. +Instead, use the Python +.B .extend() +method to make sure the list is updated in-place. +Example: + +.ES +object_files = [] + +# Do NOT use += as follows: +# +# object_files += Object('bar.c') +# +# It will not update the object_files list in place. +# +# Instead, use the .extend() method: +object_files.extend(Object('bar.c')) + +.EE + The path name for a Node's file may be used by passing the Node to the Python-builtin .B str() @@ -3303,14 +3392,14 @@ foo = env.FindFile('foo', ['dir1', 'dir2']) .RI FindInstalledFiles( ) .TP .RI env.FindInstalledFiles( ) -Returns the list of targets setup by the +Returns the list of targets set up by the .B Install() or .B InstallAs() builders. This function serves as a convenient method to select the contents of -a Binary Package. +a binary package. Example: @@ -3338,7 +3427,7 @@ Returns the list of nodes which serve as the source of the built files. It does so by inspecting the dependency tree starting at the optional argument .B node -which defaults to the '"."'-node. It will then return all leafs of +which defaults to the '"."'-node. It will then return all leaves of .B node. These are all children which have no further children. @@ -3453,6 +3542,111 @@ for object in Flatten(objects): .EE '\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI GetBuildFailures() +Returns a list of exceptions for the +actions that failed while +attempting to build targets. +Each element in the returned list is a +.B BuildError +object +with the following attributes +that record various aspects +of the build failure: + +.B .node +The node that was being built +when the build failure occurred. + +.B .status +The numeric exit status +returned by the command or Python function +that failed when trying to build the +specified Node. + +.B .errstr +The SCons error string +describing the build failure. +(This is often a generic +message like "Error 2" +to indicate that an executed +command exited with a status of 2.) + +.B .filename +The name of the file or +directory that actually caused the failure. +This may be different from the +.B .node +attribute. +For example, +if an attempt to build a target named +.B sub/dir/target +fails because the +.B sub/dir +directory could not be created, +then the +.B .node +attribute will be +.B sub/dir/target +but the +.B .filename +attribute will be +.BR sub/dir . + +.B .executor +The SCons Executor object +for the target Node +being built. +This can be used to retrieve +the construction environment used +for the failed action. + +.B .action +The actual SCons Action object that failed. +This will be one specific action +out of the possible list of +actions that would have been +executed to build the target. + +.B .command +The actual expanded command that was executed and failed, +after expansion of +.BR $TARGET , +.BR $SOURCE , +and other construction variables. + +Note that the +.BR GetBuildFailures () +function +will always return an empty list +until any build failure has occurred, +which means that +.BR GetBuildFailures () +will always return an empty list +while the +.B SConscript +files are being read. +Its primary intended use is +for functions that will be +executed before SCons exits +by passing them to the +standard Python +.BR atexit.register () +function. +Example: + +.ES +import atexit + +def print_build_failures(): + from SCons.Script import GetBuildFailures + for bf in GetBuildFailures(): + print "%s failed: %s" % (bf.node, bf.errstr) + +atexit.register(print_build_failures) +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" .TP .RI GetBuildPath( file ", [" ... ]) .TP @@ -3498,6 +3692,111 @@ options from a SConscript file. See .IR SetOption () for a description of the options available. +"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI Glob( pattern ", [" ondisk ", " source ", " strings ]) +.TP +.RI env.Glob( pattern ", [" ondisk ", " source ", " strings ]) +Returns Nodes (or strings) that match the specified +.IR pattern , +relative to the directory of the current +.B SConscript +file. +The +.BR env.Glob () +form performs string substition on +.I pattern +and returns whatever matches +the resulting expanded pattern. + +The specified +.I pattern +uses Unix shell style metacharacters for matching: + +.ES + * matches everything + ? matches any single character + [seq] matches any character in seq + [!seq] matches any char not in seq +.EE + +Character matches do +.I not +span directory separators. + +The +.BR Glob () +knows about +repositories +(see the +.BR Repository () +function) +and source directories +(see the +.BR BuildDir () +function) +and +returns a Node (or string, if so configured) +in the local (SConscript) directory +if matching Node is found +anywhere in a corresponding +repository or source directory. + +The +.B ondisk +argument may be set to +.B False +(or any other non-true value) +to disable the search for matches on disk, +thereby only returning matches among +already-configured File or Dir Nodes. +The default behavior is to +return corresponding Nodes +for any on-disk matches found. + +The +.B source +argument may be set to +.B True +(or any equivalent value) +to specify that, +when the local directory is a +.BR BuildDir (), +the returned Nodes should be from the +corresponding source directory, +not the local directory. + +The +.B strings +argument may be set to +.B True +(or any equivalent value) +to have the +.BR Glob () +function return strings, not Nodes, +that represent the matched files or directories. +The returned strings will be relative to +the local (SConscript) directory. +(Note that This may make it easier to perform +arbitrary manipulation of file names, +but if the returned strings are +passed to a different +.B SConscript +file, +any Node translation will be relative +to the other +.B SConscript +directory, +not the original +.B SConscript +directory.) + +Example: + +.ES +Program('foo', Glob('*.c')) +.EE + '\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" '\".TP '\".RI GlobalBuilders( flag ) @@ -4267,6 +4566,29 @@ method. '\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" .TP +.RI Requires( target ", " prerequisite ) +.TP +.RI env.Requires( target ", " prerequisite ) +Specifies an order-only relationship +between the specified target file(s) +and the specified prerequisite file(s). +The prerequisite file(s) +will be (re)built, if necessary, +.I before +the target file(s), +but the target file(s) do not actually +depend on the prerequisites +and will not be rebuilt simply because +the prerequisite file(s) change. + +Example: + +.ES +env.Requires('foo', 'file-that-must-be-built-before-foo') +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP .RI Return([ vars "... , " stop= ]) By default, this stops processing the current SConscript @@ -4875,7 +5197,16 @@ source_nodes = env.subst('$EXPAND_TO_NODELIST', .RI SourceSignatures( type ) .TP .RI env.SourceSignatures( type ) -This function tells +Note: Although it is not yet officially deprecated, +use of this function is discouraged. +See the +.BR Decider () +function for a more flexible and straightforward way +to configure SCons' decision-making. + +The +.BR SourceSignatures () +function tells .B scons how to decide if a source file (a file that is not built from any other files) @@ -4906,8 +5237,12 @@ the last time it was used to rebuild a particular target file. means .B scons decides that a source file has changed -if its timestamp (modification time) is newer than +if its timestamp (modification time) has changed since the last time it was used to rebuild a particular target file. +(Note that although this is similar to the behavior of Make, +by default it will also rebuild if the dependency is +.I older +than the last time it was used to rebuild the target file.) There is no different between the two behaviors for Python @@ -4993,7 +5328,16 @@ Tag( 'file2.txt', DOC ) .RI TargetSignatures( type ) .TP .RI env.TargetSignatures( type ) -This function tells +Note: Although it is not yet officially deprecated, +use of this function is discouraged. +See the +.BR Decider () +function for a more flexible and straightforward way +to configure SCons' decision-making. + +The +.BR TargetSignatures () +function tells .B scons how to decide if a target file (a file that @@ -5040,8 +5384,12 @@ rebuilt with exactly the same contents as the last time. means .B scons decides that a target file has changed -if its timestamp (modification time) is newer than +if its timestamp (modification time) has changed since the last time it was used to rebuild some other target file. +(Note that although this is similar to the behavior of Make, +by default it will also rebuild if the dependency is +.I older +than the last time it was used to rebuild the target file.) .B "source" means @@ -5476,7 +5824,7 @@ defined construction variables: '\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" '\" BEGIN GENERATED CONSTRUCTION VARIABLE DESCRIPTIONS '\" -'\" The descriptions below of the various SCons contruction variables +'\" The descriptions below of the various SCons construction variables '\" are generated from the .xml files that live next to the various '\" Python modules in the build enginer library. If you're reading '\" this [gnt]roff file with an eye towards patching this man page, @@ -5491,7 +5839,7 @@ defined construction variables: '\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" '\" END GENERATED CONSTRUCTION VARIABLE DESCRIPTIONS '\" -'\" The descriptions above of the various SCons contruction variables +'\" The descriptions above of the various SCons construction variables '\" are generated from the .xml files that live next to the various '\" Python modules in the build enginer library. If you're reading '\" this [gnt]roff file with an eye towards patching this man page, @@ -5554,9 +5902,9 @@ command line option. The following methods can be used to perform checks: .TP -.RI Configure( env ", [" custom_tests ", " conf_dir ", " log_file ", " config_h ]) +.RI Configure( env ", [" custom_tests ", " conf_dir ", " log_file ", " config_h ", " clean ", " help]) .TP -.RI env.Configure([ custom_tests ", " conf_dir ", " log_file ", " config_h ]) +.RI env.Configure([ custom_tests ", " conf_dir ", " log_file ", " config_h ", " clean ", " help]) This creates a configure context, which can be used to perform checks. .I env specifies the environment for building the tests. @@ -5604,6 +5952,30 @@ and some target that depends on the .I config_h file is being built. +The optional +.B clean +and +.B help +arguments can be used to suppress execution of the configuration +tests when the +.B -c/--clean +or +.B -H/-h/--help +options are used, respectively. +The default behavior is always to execute +configure context tests, +since the results of the tests may +affect the list of targets to be cleaned +or the help text. +If the configure tests do not affect these, +then you may add the +.B clean=False +or +.B help=False +arguments +(or both) +to avoid unnecessary test execution. + .EE A created .B Configure @@ -5843,6 +6215,42 @@ if conf.CheckLibWithHeader( 'qt', 'qapp.h', 'c++', 'QApplication qapp(0,0);' ): env = conf.Finish() .EE +.TP +.RI Configure.CheckTypeSize( self ", " type_name ", [" header ", " language ", " expect ]) +Checks for the size of a type defined by +.BR typedef . +.I type_name +specifies the typedef name to check for. +The optional +.I header +argument is a string +that will be +placed at the top +of the test file +that will be compiled +to check if the function exists; +the default is empty. +The optional +.I language +argument should be +.B C +or +.B C++ +and selects the compiler to be used for the check; +the default is "C". +The optional +.I expect +argument should be an integer. +If this argument is used, +the function will only check whether the type +given in type_name has the expected size (in bytes). +For example, +.B "CheckTypeSize('short', expect = 2)" +will return success only if short is two bytes. + +.ES +.EE + .EE You can define your own custom checks. in addition to the predefined checks. @@ -5990,9 +6398,13 @@ written. For example, libraries needed for the build may be in non-standard locations, or site-specific compiler options may need to be passed to the compiler. .B scons -provides a mechanism for overridding construction variables from the -command line or a text-based SConscript file through an Options -object. To create an Options object, call the Options() function: +provides an Options object for overridding construction variables +on the command line: +.ES +$ scons VARIABLE=foo +.EE +The variable values can also be specified in a text-based SConscript file. +To create an Options object, call the Options() function: .TP .RI Options([ files "], [" args ]) @@ -6091,8 +6503,17 @@ opt.AddOptions( .RI Update( env ", [" args ]) This updates a construction environment .I env -with the customized construction variables. Normally this method is not -called directly, but is called indirectly by passing the Options object to +with the customized construction variables. +Any specified variables that are +.I not +configured for the Options object +will be saved and may be +retrieved with the +.BR UnknownOptions () +method, below. + +Normally this method is not called directly, +but is called indirectly by passing the Options object to the Environment() function: .ES @@ -6105,6 +6526,7 @@ when the Options object was created are executed as Python scripts, and the values of (global) Python variables set in the file are added to the construction environment. + Example: .ES @@ -6112,6 +6534,21 @@ CC = 'my_cc' .EE .TP +.RI UnknownOptions( ) +Returns a dictionary containing any +variables that were specified +either in the files or the dictionary +with which the Options object was intialized, +but for which the Options object was +not configured. + +.ES +env = Environment(options=opts) +for key, value in opts.UnknownOptions(): + print "unknown variable: %s=%s" % (key, value) +.EE + +.TP .RI Save( filename ", " env ) This saves the currently set options into a script file named .I filename @@ -8436,7 +8873,7 @@ prefix and suffix for the current platform (for example, 'liba.a' on POSIX systems, 'a.lib' on Windows). -.SS Customizing contruction variables from the command line. +.SS Customizing construction variables from the command line. The following would allow the C compiler to be specified on the command line or in the file custom.py. diff --git a/doc/scons.mod b/doc/scons.mod index f3c1a86..c23e6ae 100644 --- a/doc/scons.mod +++ b/doc/scons.mod @@ -162,6 +162,7 @@ <!ENTITY Command "<function>Command</function>"> <!ENTITY Configure "<function>Configure</function>"> <!ENTITY Copy "<function>Copy</function>"> +<!ENTITY Decider "<function>Decider</function>"> <!ENTITY Default "<function>Default</function>"> <!ENTITY DefaultRules "<function>DefaultRules</function>"> <!ENTITY Delete "<function>Delete</function>"> diff --git a/doc/user/ENV.in b/doc/user/ENV.in index 3c8cfd5..3d8bc4b 100644 --- a/doc/user/ENV.in +++ b/doc/user/ENV.in @@ -196,7 +196,7 @@ The drawback is that the build can behave differently if it's run by people with different &PATH; values in their environment--for example, - both the <literal>/bin</literal> and + if both the <literal>/bin</literal> and <literal>/usr/local/bin</literal> directories have different &cc; commands, then which one will be used to compile programs diff --git a/doc/user/ENV.xml b/doc/user/ENV.xml index d843276..9fa2ec1 100644 --- a/doc/user/ENV.xml +++ b/doc/user/ENV.xml @@ -196,7 +196,7 @@ The drawback is that the build can behave differently if it's run by people with different &PATH; values in their environment--for example, - both the <literal>/bin</literal> and + if both the <literal>/bin</literal> and <literal>/usr/local/bin</literal> directories have different &cc; commands, then which one will be used to compile programs diff --git a/doc/user/builders-writing.in b/doc/user/builders-writing.in index 7497277..2285ac8 100644 --- a/doc/user/builders-writing.in +++ b/doc/user/builders-writing.in @@ -263,7 +263,7 @@ This functionality could be invoked as in the following example: <para> - To be able use both our own defined &Builder; objects + To be able to use both our own defined &Builder; objects and the default &Builder; objects in the same &consenv;, you can either add to the &cv-BUILDERS; variable using the &Append; function: diff --git a/doc/user/builders-writing.xml b/doc/user/builders-writing.xml index 50f6556..8ab4fca 100644 --- a/doc/user/builders-writing.xml +++ b/doc/user/builders-writing.xml @@ -239,7 +239,7 @@ This functionality could be invoked as in the following example: <para> - To be able use both our own defined &Builder; objects + To be able to use both our own defined &Builder; objects and the default &Builder; objects in the same &consenv;, you can either add to the &cv-BUILDERS; variable using the &Append; function: diff --git a/doc/user/command-line.in b/doc/user/command-line.in index b2cb888..dbbddaf 100644 --- a/doc/user/command-line.in +++ b/doc/user/command-line.in @@ -53,7 +53,7 @@ <para> If, for example, - and you're using a POSIX shell that's + you're using a POSIX shell that's compatible with the Bourne shell, and you always want &SCons; to use the <literal>-Q</literal> option, @@ -96,7 +96,7 @@ <para> - Windows users may typically want to set this + Windows users may typically want to set the &SCONSFLAGS; in the appropriate tab of the <literal>System Properties</literal> window. @@ -1285,7 +1285,7 @@ to control an expected path name. If, for example, you need to define a variable in the preprocessor - that control the location of a + that controls the location of a configuration file: </para> @@ -1557,3 +1557,9 @@ </para> </section> + + <!-- + + AddOption() function for things like - -prefix=, - -force + + --> diff --git a/doc/user/command-line.xml b/doc/user/command-line.xml index d7f3d7b..8675c19 100644 --- a/doc/user/command-line.xml +++ b/doc/user/command-line.xml @@ -53,7 +53,7 @@ <para> If, for example, - and you're using a POSIX shell that's + you're using a POSIX shell that's compatible with the Bourne shell, and you always want &SCons; to use the <literal>-Q</literal> option, @@ -89,7 +89,7 @@ <para> - Windows users may typically want to set this + Windows users may typically want to set the &SCONSFLAGS; in the appropriate tab of the <literal>System Properties</literal> window. @@ -1256,7 +1256,7 @@ to control an expected path name. If, for example, you need to define a variable in the preprocessor - that control the location of a + that controls the location of a configuration file: </para> @@ -1484,3 +1484,9 @@ </para> </section> + + <!-- + + AddOption() function for things like - -prefix=, - -force + + --> diff --git a/doc/user/depends.in b/doc/user/depends.in index e0d5e92..7c15c52 100644 --- a/doc/user/depends.in +++ b/doc/user/depends.in @@ -26,12 +26,12 @@ <para> So far we've seen how &SCons; handles one-time builds. - But the real point of a build tool like &SCons; + But one of the main functions of a build tool like &SCons; is to rebuild only the necessary things when source files change--or, put another way, &SCons; should <emphasis>not</emphasis> waste time rebuilding things that have already been built. - You can see this at work simply be re-invoking &SCons; + You can see this at work simply by re-invoking &SCons; after building our simple &hello; example: </para> @@ -75,33 +75,36 @@ </para> <section> - <title>Deciding When a Source File Has Changed: the &SourceSignatures; Function</title> + <title>Deciding When an Input File Has Changed: the &Decider; Function</title> <para> - The other side of avoiding unnecessary rebuilds + Another aspect of avoiding unnecessary rebuilds is the fundamental build tool behavior of <emphasis>rebuilding</emphasis> - things when a source file changes, + things when an input file changes, so that the built software is up to date. - &SCons; keeps track of this through a - &signature; for each source file, - and allows you to configure - whether you want to use the source - file contents or the modification time (timestamp) - as the signature. + By default, + &SCons; keeps track of this through an + MD5 &signature;, or checksum, of the contents of each file, + although you can easily configure + &SCons; to use the + modification times (or time stamps) + instead. + You can even specify your own Python function + for deciding if an input file has changed. </para> <section> - <title>MD5 Source File Signatures</title> + <title>Using MD5 Signatures to Decide if a File Has Changed</title> <para> By default, - &SCons; keeps track of whether a source file has changed - based on the file's contents, - not the modification time. + &SCons; keeps track of whether a file has changed + based on an MD5 checksum of the file's contents, + not the file's modification time. This means that you may be surprised by the default &SCons; behavior if you are used to the &Make; convention of forcing @@ -143,36 +146,110 @@ Note that you can, if you wish, specify this default behavior (MD5 signatures) explicitly - using the &SourceSignatures; function as follows: + using the &Decider; function as follows: </para> <sconstruct> Program('hello.c') - SourceSignatures('MD5') + Decider('MD5') </sconstruct> + <para> + + You can also use the string <literal>'content'</literal> + as a synonym for <literal>'MD5'</literal> + when calling the &Decider; function. + + </para> + + <section> + <title>Ramifications of Using MD5 Signatures</title> + + <para> + + Using MD5 Signatures to decide if an input file has changed + has one surprising benefit: + if a source file has been changed + in such a way that the contents of the + rebuilt target file(s) + will be exactly the same as the last time + the file was built, + then any "downstream" target files + that depend on the rebuilt-but-not-changed target + file actually need not be rebuilt. + + </para> + + <para> + + So if, for example, + a user were to only change a comment in a &hello_c; file, + then the rebuilt &hello_o; file + would be exactly the same as the one previously built + (assuming the compiler doesn't put any build-specific + information in the object file). + &SCons; would then realize that it would not + need to rebuild the &hello; program as follows: + + </para> + + <scons_output example="ex1" os="posix"> + <scons_output_command>scons -Q hello</scons_output_command> + <scons_output_command output=" [CHANGE A COMMENT IN hello.c]" edit="STRIP CCCOM line">edit hello.c</scons_output_command> + <scons_output_command>scons -Q hello</scons_output_command> + </scons_output> + + <para> + + In essence, &SCons; + "short-circuits" any dependent builds + when it realizes that a target file + has been rebuilt to exactly the same file as the last build. + This does take some extra processing time + to read the contents of the target (&hello_o;) file, + but often saves time when the rebuild that was avoided + would have been time-consuming and expensive. + + </para> + + </section> + </section> <section> - <title>Source File Time Stamps</title> + <title>Using Time Stamps to Decide If a File Has Changed</title> <para> If you prefer, you can configure &SCons; to use the modification time - of source files, - not the file contents, - when deciding if something needs to be rebuilt. - To do this, call the &SourceSignatures; + of a file, not the file contents, + when deciding if a target needs to be rebuilt. + &SCons; gives you two ways to use time stamps + to decide if an input file has changed + since the last time a target has been built. + + </para> + + <para> + + The most familiar way to use time stamps + is the way &Make; does: + that is, have &SCons; decide + and target must be rebuilt if + if a source file's modification time is + <emphasis>newer</emphasis> + than the target file. + To do this, call the &Decider; function as follows: </para> - <scons_example name="ex2"> + <scons_example name="newer"> <file name="SConstruct" printme="1"> Program('hello.c') - SourceSignatures('timestamp') + Decider('timestamp-newer') </file> <file name="hello.c"> int main() { printf("Hello, world!\n"); } @@ -187,117 +264,222 @@ </para> - <scons_output example="ex2" os="posix"> + <scons_output example="newer" os="posix"> <scons_output_command>scons -Q hello</scons_output_command> <scons_output_command>touch hello.c</scons_output_command> <scons_output_command>scons -Q hello</scons_output_command> </scons_output> - </section> + <para> - </section> + And, in fact, because this behavior is the same + as the behavior of &Make;, + you can also use the string <literal>'make'</literal> + as a synonym for <literal>'timestamp-newer'</literal> + when calling the &Decider; function: - <section> - <title>Deciding When a Target File Has Changed: the &TargetSignatures; Function</title> + </para> - <para> + <sconstruct> + Program('hello.c') + Decider('make') + </sconstruct> - As you've just seen, - &SCons; uses signatures to decide whether a - target file is up to date or must be rebuilt. - When a target file depends on another target file, - &SCons; allows you to configure separately - how the signatures of "intermediate" target files - are used when deciding if a dependent target file - must be rebuilt. + <para> - </para> + One drawback to using times stamps exactly like &Make; + is that if an input file's modification time suddenly + becomes <emphasis>older</emphasis> than a target file, + the target file will not be rebuilt. + This can happen if an old copy of a source file is restored + from a backup archive, for example. + The contents of the restored file will likely be different + than they were the last time a dependent target was built, + but the target won't be rebuilt + because the modification time of the source file + is not newer than the target. - <section> - <title>Build Signatures</title> + </para> <para> - Modifying a source file - will cause not only its direct target file to be rebuilt, - but also the target file(s) - that depend on that direct target file. - In our example, - changing the contents of the &hello_c; file causes - the &hello_o; file to be rebuilt, - which in turn causes the - &hello; program to be rebuilt: + Because &SCons; actually stores information + about the source files' time stamps whenever a target is built, + it can handle this situation by checking for + an exact match of the source file time stamp, + instead of just whether or not the source file + is newer than the target file. + To do this, specify the argument + <literal>'timestamp-match'</literal> + when calling the &Decider; function: </para> - <scons_output example="ex1" os="posix"> + <scons_example name="match"> + <file name="SConstruct" printme="1"> + Program('hello.c') + Decider('timestamp-match') + </file> + <file name="hello.c"> + int main() { printf("Hello, world!\n"); } + </file> + </scons_example> + + <para> + + When configured this way, + &SCons; will rebuild a target whenever + a source file's modification time has changed. + So if we use the <literal>touch -t</literal> + option to change the modification time of + &hello_c; to an old date (January 1, 1989), + &SCons; will still rebuild the target file: + + </para> + + <scons_output example="match" os="posix"> <scons_output_command>scons -Q hello</scons_output_command> - <scons_output_command output=" [CHANGE THE CONTENTS OF hello.c]">edit hello.c</scons_output_command> + <scons_output_command>touch -t 198901010000 hello.c</scons_output_command> <scons_output_command>scons -Q hello</scons_output_command> </scons_output> <para> - What's not obvious, though, - is that &SCons; internally handles the signature of - the target file(s) - (&hello_o; in the above example) - differently from the signature of the source file - (&hello_c;). - By default, - &SCons; tracks whether a target file must be rebuilt - by using a &buildsignature; - that consists of the combined - signatures of all the files - that go into making the target file. - This is efficient because - the accumulated signatures - actually give &SCons; all of the - information it needs - to decide if the target file is out of date. + In general, the only reason to prefer + <literal>timestamp-newer</literal> + instead of + <literal>timestamp-match</literal>, + would be if you have some specific reason + to require this &Make;-like behavior of + not rebuilding a target when an otherwise-modified + source file is older. </para> + </section> + + <section> + <title>Deciding If a File Has Changed Using Both MD Signatures and Time Stamps</title> + <para> - If you wish, you can - specify this default behavior - (build signatures) explicitly - using the &TargetSignatures; function: + As a performance enhancement, + &SCons; provides a way to use + MD5 checksums of file contents + but to only read the contents + whenever the file's timestamp has changed. + To do this, call the &Decider; + function with <literal>'MD5-timestamp'</literal> + argument as follows: </para> - <sconstruct> + <scons_example name="MD5-timestamp"> + <file name="SConstruct" printme="1"> Program('hello.c') - TargetSignatures('build') - </sconstruct> + Decider('MD5-timestamp') + </file> + <file name="hello.c"> + int main() { printf("Hello, world!\n"); } + </file> + </scons_example> + + <para> + + So configured, &SCons will still behave like + it does when using <literal>Decider('MD5')</literal>: + + </para> + + <scons_output example="MD5-timestamp" os="posix"> + <scons_output_command>scons -Q hello</scons_output_command> + <scons_output_command>touch hello.c</scons_output_command> + <scons_output_command>scons -Q hello</scons_output_command> + <scons_output_command output=" [CHANGE THE CONTENTS OF hello.c]">edit hello.c</scons_output_command> + <scons_output_command>scons -Q hello</scons_output_command> + </scons_output> + + <para> + + However, the second call to &SCons; in the above output, + when the build is up-to-date, + will have been performed by simply looking at the + modification time of the &hello_c; file, + not by opening it and performing + an MD5 checksum calcuation on its contents. + This can significantly speed up many up-to-date builds. + + </para> + + <para> + + The only drawback to using + <literal>Decider('MD5-timestamp')</literal> + is that &SCons; will <emphasis>not</emphasis> + rebuild a target file if a source file was modified + within one second of the last time &SCons; built the file. + While most developers are programming, + this isn't a problem in practice, + since it's unlikely that someone will have built + and then thought quickly enought to make a substantive + change to a source file within one second. + Certain build scripts or + continuous integration tools may, however, + rely on the ability to applying changes to files + automatically and then rebuild as quickly as possible, + in which case use of + <literal>Decider('MD5-timestamp')</literal> + may not be appropriate. + + </para> </section> <section> - <title>File Contents</title> + <title>Writing Your Own Custom &Decider; Function</title> <para> - Sometimes a source file can be changed - in such a way that the contents of the - rebuilt target file(s) - will be exactly the same as the last time - the file was built. - If so, then any other target files - that depend on such a built-but-not-changed target - file actually need not be rebuilt. - You can make &SCons; - realize that it does not need to rebuild - a dependent target file in this situation - using the &TargetSignatures; function as follows: + The different string values that we've passed to + the &Decider; function are essentially used by &SCons; + to pick one of several specific internal functions + that implement various ways of deciding if a dependency + (usually a source file) + has changed since a target file has been built. + As it turns out, + you can also supply your own function + to decide if a dependency has changed. </para> - <scons_example name="ex3"> + <para> + + For example, suppose we have an input file + that contains a lot of data, + in some specific regular format, + that is used to rebuild a lot of different target files, + but each target file really only depends on + one particular section of the input file. + We'd like to have each target file depend on + only its section of the input file. + However, since the input file may contain a lot of data, + we only want to open the input file if its timestamp has changed. + This could done with a custom + &Decider; function that might look something like this: + + </para> + + <scons_example name="function"> <file name="SConstruct" printme="1"> Program('hello.c') - TargetSignatures('content') + def decide_if_changed(dependency, target, prev_ni): + if self.get_timestamp() != prev_ni.timestamp: + dep = str(dependency) + tgt = str(target) + if specific_part_of_file_has_changed(dep, tgt): + return True + return False + Decider(decide_if_changed) </file> <file name="hello.c"> int main() { printf("Hello, world!\n"); } @@ -306,35 +488,294 @@ <para> - So if, for example, - a user were to only change a comment in a C file, - then the rebuilt &hello_o; file - would be exactly the same as the one previously built - (assuming the compiler doesn't put any build-specific - information in the object file). - &SCons; would then realize that it would not - need to rebuild the &hello; program as follows: + Note that in the function definition, + the <literal>dependency</literal> + (input file) is the first argument, + and then the <literal>target</literal>. + Both of these are passed to the functions as + SCons &Node; objects, + which we convert to strings using the Python + <function>str()</function>. + The third argument, <literal>prev_ni</literal>, + is an object that holds the + signature or timestamp information + that was recorded about the dependency + the last time the target was built. </para> - <scons_output example="ex3" os="posix"> - <scons_output_command>scons -Q hello</scons_output_command> - <scons_output_command output=" [CHANGE A COMMENT IN hello.c]" edit="STRIP CCCOM line">edit hello.c</scons_output_command> - <scons_output_command>scons -Q hello</scons_output_command> + <para> + + Note that ignoring some of the arguments + in your custom &Decider; function + is a perfectly normal thing to do, + if they don't impact the way you want to + decide if the dependency file has changed. + + </para> + + </section> + + <section> + <title>Mixing Different Ways of Deciding If a File Has Changed</title> + + <para> + + The previous examples have all demonstrated calling + the global &Decider; function + to configure all dependency decisions that &SCons; makes. + Sometimes, however, you want to be able to configure + different decision-making for different targets. + When that's necessary, you can use the + <function>env.Decider</function> + method to affect only the configuration + decisions for targets built with a + specific construction environment. + + </para> + + <para> + + For example, if we arbitrarily want to build + one program using MD5 checkums + and another use file modification times + from the same source + we might configure it this way: + + </para> + + <scons_example name="mixing"> + <file name="SConstruct" printme="1"> + env1 = Environment(CPPPATH = ['.']) + env2 = env1.Clone() + env2.Decider('timestamp-match') + env1.Program('prog-MD5', 'program1.c') + env2.Program('prog-timestamp', 'program2.c') + </file> + <file name="program1.c"> + #include "inc.h" + int main() { printf("Hello, world!\n"); } + </file> + <file name="program2.c"> + #include "inc.h" + int main() { printf("Hello, world!\n"); } + </file> + <file name="inc.h"> + #define INC 1 + </file> + </scons_example> + + <para> + + If both of the programs include the same + <filename>inc.h</filename> file, + then updating the modification time of + <filename>inc.h</filename> + (using the &touch; command) + will cause only <filename>prog-timestamp</filename> + to be rebuilt: + + </para> + + <scons_output example="mixing" os="posix"> + <scons_output_command>scons -Q</scons_output_command> + <scons_output_command>touch inc.h</scons_output_command> + <scons_output_command>scons -Q</scons_output_command> </scons_output> + </section> + + </section> + + <section> + <title>Older Functions for Deciding When an Input File Has Changed</title> + + <para> + + &SCons; still supports two functions that used to be the + primary methods for configuring the + decision about whether or not an input file has changed. + Although they're not officially deprecated yet, + their use is discouraged, + mainly because they rely on a somewhat + confusing distinction between how + source files and target files are handled. + These functions are documented here mainly in case you + encounter them in existing &SConscript; files. + + </para> + + <section> + <title>The &SourceSignatures; Function</title> + <para> - In essence, &SCons; has - "short-circuited" any dependent builds - when it realizes that a target file - has been rebuilt to exactly the same file as the last build. - So configured, - &SCons; does take some extra processing time - to scan the contents of the target (&hello_o;) file, - but this may save time - if the rebuild that was avoided - would have been very time-consuming and expensive. + The &SourceSignatures; function is fairly straightforward, + and supports two different argument values + to configure whether source file changes should be decided + using MD5 signatures: + + </para> + + <sconstruct> + Program('hello.c') + SourceSignatures('MD5') + </sconstruct> + + <para> + + Or using time stamps: + + </para> + + <sconstruct> + Program('hello.c') + SourceSignatures('timestamp') + </sconstruct> + + <para> + + These are roughly equivalent to specifying + <function>Decider('MD5')</function> + or + <function>Decider('timestamp-match')</function>, + respectively, + although it only affects how SCons makes + decisions about dependencies on + <emphasis>source</emphasis> files--that is, + files that are not built from any other files. + + </para> + + </section> + + <section> + <title>The &TargetSignatures; Function</title> + + <para> + + The &TargetSignatures; function + specifies how &SCons; decides + when a target file has changed + <emphasis>when it is used as a + dependency of (input to) another target</emphasis>--that is, + the &TargetSignatures; function configures + how the signatures of "intermediate" target files + are used when deciding if a "downstream" target file + must be rebuilt. + <footnote><para> + This easily-overlooked distinction between + how &SCons; decides if the target itself must be rebuilt + and how the target is then used to decide if a different + target must be rebuilt is one of the confusing + things that has led to the &TargetSignatures; + and &SourceSignatures; functions being + replaced by the simpler &Decider; function. + </para></footnote> + + </para> + + <para> + + The &TargetSignatures; function supports the same + <literal>'MD5'</literal> and <literal>'timestamp'</literal> + argument values that are supported by the &SourceSignatures;, + with the same meanings, but applied to target files. + That is, in the example: + + </para> + + <sconstruct> + Program('hello.c') + TargetSignatures('MD5') + </sconstruct> + + <para> + + The MD5 checksum of the &hello_o; target file + will be used to decide if it has changed since the last + time the "downstream" &hello; target file was built. + And in the example: + + </para> + + <sconstruct> + Program('hello.c') + TargetSignatures('timestamp') + </sconstruct> + + <para> + + The modification time of the &hello_o; target file + will be used to decide if it has changed since the last + time the "downstream" &hello; target file was built. + + </para> + + <para> + + The &TargetSignatures; function supports + two additional argument values: + <literal>'source'</literal> and <literal>'build'</literal>. + The <literal>'source'</literal> argument + specifies that decisions involving + whether target files have changed + since a previous build + should use the same behavior + for the decisions configured for source files + (using the &SourceSignatures; function). + So in the example: + + </para> + + <sconstruct> + Program('hello.c') + TargetSignatures('source') + SourceSignatures('timestamp') + </sconstruct> + + <para> + + All files, both targets and sources, + will use modification times + when deciding if an input file + has changed since the last + time a target was built. + + </para> + + <para> + + Lastly, the <literal>'build'</literal> argument + specifies that &SCons; should examine + the build status of a target file + and always rebuild a "downstream" target + if the target file was itself rebuilt, + without re-examining the contents or timestamp + of the newly-built target file. + If the target file was not rebuilt during + this &scons; invocation, + then the target file will be examined + the same way as configured by + the &SourceSignature; call + to decide if it has changed. + + </para> + + <para> + + This mimics the behavior of + <literal>build signatures</literal> + in earlier versions of &SCons;. + A &buildsignature; re-combined + signatures of all the input files + that went into making the target file, + so that the target file itself + did not need to have its contents read + to compute an MD5 signature. + This can improve performance for some configurations, + but is generally not as effective as using + <literal>Decider('MD5-timestamp')</literal>. </para> @@ -348,12 +789,12 @@ <para> Now suppose that our "Hello, World!" program - actually has a <literal>#include</literal> line + actually has an <literal>#include</literal> line to include the &hello_h; file in the compilation: </para> - <scons_example name="ex4"> + <scons_example name="include"> <file name="SConstruct"> Program('hello.c', CPPPATH = '.') </file> @@ -376,7 +817,7 @@ </para> - <scons_example_file example="ex4" name="hello.h"> + <scons_example_file example="include" name="hello.h"> </scons_example_file> <para> @@ -389,7 +830,7 @@ </para> - <scons_example_file example="ex4" name="SConstruct"> + <scons_example_file example="include" name="SConstruct"> </scons_example_file> <para> @@ -403,7 +844,7 @@ </para> - <scons_output example="ex4" os="posix"> + <scons_output example="include" os="posix"> <scons_output_command>scons -Q hello</scons_output_command> <scons_output_command>scons -Q hello</scons_output_command> <scons_output_command output=" [CHANGE THE CONTENTS OF hello.h]">edit hello.h</scons_output_command> @@ -445,7 +886,7 @@ the &cv-CPPPATH; variable may be a list of directories, or a string separated by - the system-specific path separate character + the system-specific path separation character (':' on POSIX/Linux, ';' on Windows). Either way, &SCons; creates the right command-line options @@ -682,7 +1123,44 @@ </section> <section> - <title>Ignoring Dependencies: the &Ignore; Method</title> + <title>Explicit Dependencies: the &Depends; Function</title> + + <para> + + Sometimes a file depends on another file + that is not detected by an &SCons; scanner. + For this situation, + &SCons; allows you to specific explicitly that one file + depends on another file, + and must be rebuilt whenever that file changes. + This is specified using the &Depends; method: + + </para> + + <programlisting> + hello = Program('hello.c') + Depends(hello, 'other_file') + </programlisting> + + <!-- XXX mention that you can use arrays for target and source? --> + + <screen> + % <userinput>scons -Q hello</userinput> + cc -c hello.c -o hello.o + cc -o hello hello.o + % <userinput>scons -Q hello</userinput> + scons: `hello' is up to date. + % <userinput>edit other_file</userinput> + [CHANGE THE CONTENTS OF other_file] + % <userinput>scons -Q hello</userinput> + cc -c hello.c -o hello.o + cc -o hello hello.o + </screen> + + </section> + + <section> + <title>Ignoring Dependencies: the &Ignore; Function</title> <para> @@ -737,7 +1215,7 @@ Now, the above example is a little contrived, because it's hard to imagine a real-world situation - where you wouldn't to rebuild &hello; + where you wouldn't want to rebuild &hello; if the &hello_h; file changed. A more realistic example might be if the &hello; @@ -762,45 +1240,7 @@ </section> <section> - <title>Explicit Dependencies: the &Depends; Method</title> - - <para> - - On the other hand, - sometimes a file depends on another file - that is not detected by an &SCons; scanner. - For this situation, - &SCons; allows you to specific explicitly that one file - depends on another file, - and must be rebuilt whenever that file changes. - This is specified using the &Depends; method: - - </para> - - <programlisting> - hello = Program('hello.c') - Depends(hello, 'other_file') - </programlisting> - - <!-- XXX mention that you can use arrays for target and source? --> - - <screen> - % <userinput>scons -Q hello</userinput> - cc -c hello.c -o hello.o - cc -o hello hello.o - % <userinput>scons -Q hello</userinput> - scons: `hello' is up to date. - % <userinput>edit other_file</userinput> - [CHANGE THE CONTENTS OF other_file] - % <userinput>scons -Q hello</userinput> - cc -c hello.c -o hello.o - cc -o hello hello.o - </screen> - - </section> - - <section> - <title>The &AlwaysBuild; Method</title> + <title>The &AlwaysBuild; Function</title> <para> diff --git a/doc/user/depends.xml b/doc/user/depends.xml index 9e055ee..936e4b2 100644 --- a/doc/user/depends.xml +++ b/doc/user/depends.xml @@ -26,12 +26,12 @@ <para> So far we've seen how &SCons; handles one-time builds. - But the real point of a build tool like &SCons; + But one of the main functions of a build tool like &SCons; is to rebuild only the necessary things when source files change--or, put another way, &SCons; should <emphasis>not</emphasis> waste time rebuilding things that have already been built. - You can see this at work simply be re-invoking &SCons; + You can see this at work simply by re-invoking &SCons; after building our simple &hello; example: </para> @@ -74,33 +74,36 @@ </para> <section> - <title>Deciding When a Source File Has Changed: the &SourceSignatures; Function</title> + <title>Deciding When an Input File Has Changed: the &Decider; Function</title> <para> - The other side of avoiding unnecessary rebuilds + Another aspect of avoiding unnecessary rebuilds is the fundamental build tool behavior of <emphasis>rebuilding</emphasis> - things when a source file changes, + things when an input file changes, so that the built software is up to date. - &SCons; keeps track of this through a - &signature; for each source file, - and allows you to configure - whether you want to use the source - file contents or the modification time (timestamp) - as the signature. + By default, + &SCons; keeps track of this through an + MD5 &signature;, or checksum, of the contents of each file, + although you can easily configure + &SCons; to use the + modification times (or time stamps) + instead. + You can even specify your own Python function + for deciding if an input file has changed. </para> <section> - <title>MD5 Source File Signatures</title> + <title>Using MD5 Signatures to Decide if a File Has Changed</title> <para> By default, - &SCons; keeps track of whether a source file has changed - based on the file's contents, - not the modification time. + &SCons; keeps track of whether a file has changed + based on an MD5 checksum of the file's contents, + not the file's modification time. This means that you may be surprised by the default &SCons; behavior if you are used to the &Make; convention of forcing @@ -150,35 +153,114 @@ Note that you can, if you wish, specify this default behavior (MD5 signatures) explicitly - using the &SourceSignatures; function as follows: + using the &Decider; function as follows: </para> <programlisting> Program('hello.c') - SourceSignatures('MD5') + Decider('MD5') </programlisting> + <para> + + You can also use the string <literal>'content'</literal> + as a synonym for <literal>'MD5'</literal> + when calling the &Decider; function. + + </para> + + <section> + <title>Ramifications of Using MD5 Signatures</title> + + <para> + + Using MD5 Signatures to decide if an input file has changed + has one surprising benefit: + if a source file has been changed + in such a way that the contents of the + rebuilt target file(s) + will be exactly the same as the last time + the file was built, + then any "downstream" target files + that depend on the rebuilt-but-not-changed target + file actually need not be rebuilt. + + </para> + + <para> + + So if, for example, + a user were to only change a comment in a &hello_c; file, + then the rebuilt &hello_o; file + would be exactly the same as the one previously built + (assuming the compiler doesn't put any build-specific + information in the object file). + &SCons; would then realize that it would not + need to rebuild the &hello; program as follows: + + </para> + + <screen> + % <userinput>scons -Q hello</userinput> + cc -o hello.o -c hello.c + cc -o hello hello.o + % <userinput>edit hello.c</userinput> + [CHANGE A COMMENT IN hello.c] + % <userinput>scons -Q hello</userinput> + cc -o hello.o -c hello.c + scons: `hello' is up to date. + </screen> + + <para> + + In essence, &SCons; + "short-circuits" any dependent builds + when it realizes that a target file + has been rebuilt to exactly the same file as the last build. + This does take some extra processing time + to read the contents of the target (&hello_o;) file, + but often saves time when the rebuild that was avoided + would have been time-consuming and expensive. + + </para> + + </section> + </section> <section> - <title>Source File Time Stamps</title> + <title>Using Time Stamps to Decide If a File Has Changed</title> <para> If you prefer, you can configure &SCons; to use the modification time - of source files, - not the file contents, - when deciding if something needs to be rebuilt. - To do this, call the &SourceSignatures; + of a file, not the file contents, + when deciding if a target needs to be rebuilt. + &SCons; gives you two ways to use time stamps + to decide if an input file has changed + since the last time a target has been built. + + </para> + + <para> + + The most familiar way to use time stamps + is the way &Make; does: + that is, have &SCons; decide + and target must be rebuilt if + if a source file's modification time is + <emphasis>newer</emphasis> + than the target file. + To do this, call the &Decider; function as follows: </para> <programlisting> Program('hello.c') - SourceSignatures('timestamp') + Decider('timestamp-newer') </programlisting> <para> @@ -199,40 +281,118 @@ cc -o hello hello.o </screen> - </section> + <para> - </section> + And, in fact, because this behavior is the same + as the behavior of &Make;, + you can also use the string <literal>'make'</literal> + as a synonym for <literal>'timestamp-newer'</literal> + when calling the &Decider; function: - <section> - <title>Deciding When a Target File Has Changed: the &TargetSignatures; Function</title> + </para> - <para> + <programlisting> + Program('hello.c') + Decider('make') + </programlisting> - As you've just seen, - &SCons; uses signatures to decide whether a - target file is up to date or must be rebuilt. - When a target file depends on another target file, - &SCons; allows you to configure separately - how the signatures of "intermediate" target files - are used when deciding if a dependent target file - must be rebuilt. + <para> - </para> + One drawback to using times stamps exactly like &Make; + is that if an input file's modification time suddenly + becomes <emphasis>older</emphasis> than a target file, + the target file will not be rebuilt. + This can happen if an old copy of a source file is restored + from a backup archive, for example. + The contents of the restored file will likely be different + than they were the last time a dependent target was built, + but the target won't be rebuilt + because the modification time of the source file + is not newer than the target. + + </para> + + <para> + + Because &SCons; actually stores information + about the source files' time stamps whenever a target is built, + it can handle this situation by checking for + an exact match of the source file time stamp, + instead of just whether or not the source file + is newer than the target file. + To do this, specify the argument + <literal>'timestamp-match'</literal> + when calling the &Decider; function: + + </para> + + <programlisting> + Program('hello.c') + Decider('timestamp-match') + </programlisting> + + <para> + + When configured this way, + &SCons; will rebuild a target whenever + a source file's modification time has changed. + So if we use the <literal>touch -t</literal> + option to change the modification time of + &hello_c; to an old date (January 1, 1989), + &SCons; will still rebuild the target file: + + </para> + + <screen> + % <userinput>scons -Q hello</userinput> + cc -o hello.o -c hello.c + cc -o hello hello.o + % <userinput>touch -t 198901010000 hello.c</userinput> + % <userinput>scons -Q hello</userinput> + cc -o hello.o -c hello.c + scons: `hello' is up to date. + </screen> + + <para> + + In general, the only reason to prefer + <literal>timestamp-newer</literal> + instead of + <literal>timestamp-match</literal>, + would be if you have some specific reason + to require this &Make;-like behavior of + not rebuilding a target when an otherwise-modified + source file is older. + + </para> + + </section> <section> - <title>Build Signatures</title> + <title>Deciding If a File Has Changed Using Both MD Signatures and Time Stamps</title> + + <para> + + As a performance enhancement, + &SCons; provides a way to use + MD5 checksums of file contents + but to only read the contents + whenever the file's timestamp has changed. + To do this, call the &Decider; + function with <literal>'MD5-timestamp'</literal> + argument as follows: + + </para> + + <programlisting> + Program('hello.c') + Decider('MD5-timestamp') + </programlisting> <para> - Modifying a source file - will cause not only its direct target file to be rebuilt, - but also the target file(s) - that depend on that direct target file. - In our example, - changing the contents of the &hello_c; file causes - the &hello_o; file to be rebuilt, - which in turn causes the - &hello; program to be rebuilt: + So configured, &SCons; will still behave like + it does when using <literal>Decider('MD5')</literal>: </para> @@ -240,112 +400,380 @@ % <userinput>scons -Q hello</userinput> cc -o hello.o -c hello.c cc -o hello hello.o + % <userinput>touch hello.c</userinput> + % <userinput>scons -Q hello</userinput> + scons: `hello' is up to date. % <userinput>edit hello.c</userinput> [CHANGE THE CONTENTS OF hello.c] % <userinput>scons -Q hello</userinput> - cc -o hello.o -c hello.c - cc -o hello hello.o + scons: `hello' is up to date. </screen> <para> - What's not obvious, though, - is that &SCons; internally handles the signature of - the target file(s) - (&hello_o; in the above example) - differently from the signature of the source file - (&hello_c;). - By default, - &SCons; tracks whether a target file must be rebuilt - by using a &buildsignature; - that consists of the combined - signatures of all the files - that go into making the target file. - This is efficient because - the accumulated signatures - actually give &SCons; all of the - information it needs - to decide if the target file is out of date. + However, the second call to &SCons; in the above output, + when the build is up-to-date, + will have been performed by simply looking at the + modification time of the &hello_c; file, + not by opening it and performing + an MD5 checksum calcuation on its contents. + This can significantly speed up many up-to-date builds. </para> <para> - If you wish, you can - specify this default behavior - (build signatures) explicitly - using the &TargetSignatures; function: + The only drawback to using + <literal>Decider('MD5-timestamp')</literal> + is that &SCons; will <emphasis>not</emphasis> + rebuild a target file if a source file was modified + within one second of the last time &SCons; built the file. + While most developers are programming, + this isn't a problem in practice, + since it's unlikely that someone will have built + and then thought quickly enought to make a substantive + change to a source file within one second. + Certain build scripts or + continuous integration tools may, however, + rely on the ability to applying changes to files + automatically and then rebuild as quickly as possible, + in which case use of + <literal>Decider('MD5-timestamp')</literal> + may not be appropriate. + + </para> + + </section> + + <section> + <title>Writing Your Own Custom &Decider; Function</title> + + <para> + + The different string values that we've passed to + the &Decider; function are essentially used by &SCons; + to pick one of several specific internal functions + that implement various ways of deciding if a dependency + (usually a source file) + has changed since a target file has been built. + As it turns out, + you can also supply your own function + to decide if a dependency has changed. + + </para> + + <para> + + For example, suppose we have an input file + that contains a lot of data, + in some specific regular format, + that is used to rebuild a lot of different target files, + but each target file really only depends on + one particular section of the input file. + We'd like to have each target file depend on + only its section of the input file. + However, since the input file may contain a lot of data, + we only want to open the input file if its timestamp has changed. + This could done with a custom + &Decider; function that might look something like this: </para> <programlisting> Program('hello.c') - TargetSignatures('build') + def decide_if_changed(dependency, target, prev_ni): + if self.get_timestamp() != prev_ni.timestamp: + dep = str(dependency) + tgt = str(target) + if specific_part_of_file_has_changed(dep, tgt): + return True + return False + Decider(decide_if_changed) </programlisting> + <para> + + Note that in the function definition, + the <literal>dependency</literal> + (input file) is the first argument, + and then the <literal>target</literal>. + Both of these are passed to the functions as + SCons &Node; objects, + which we convert to strings using the Python + <function>str()</function>. + The third argument, <literal>prev_ni</literal>, + is an object that holds the + signature or timestamp information + that was recorded about the dependency + the last time the target was built. + + </para> + + <para> + + Note that ignoring some of the arguments + in your custom &Decider; function + is a perfectly normal thing to do, + if they don't impact the way you want to + decide if the dependency file has changed. + + </para> + </section> <section> - <title>File Contents</title> + <title>Mixing Different Ways of Deciding If a File Has Changed</title> + + <para> + + The previous examples have all demonstrated calling + the global &Decider; function + to configure all dependency decisions that &SCons; makes. + Sometimes, however, you want to be able to configure + different decision-making for different targets. + When that's necessary, you can use the + <function>env.Decider</function> + method to affect only the configuration + decisions for targets built with a + specific construction environment. + + </para> <para> - Sometimes a source file can be changed - in such a way that the contents of the - rebuilt target file(s) - will be exactly the same as the last time - the file was built. - If so, then any other target files - that depend on such a built-but-not-changed target - file actually need not be rebuilt. - You can make &SCons; - realize that it does not need to rebuild - a dependent target file in this situation - using the &TargetSignatures; function as follows: + For example, if we arbitrarily want to build + one program using MD5 checkums + and another use file modification times + from the same source + we might configure it this way: </para> <programlisting> - Program('hello.c') - TargetSignatures('content') + env1 = Environment(CPPPATH = ['.']) + env2 = env1.Clone() + env2.Decider('timestamp-match') + env1.Program('prog-MD5', 'program1.c') + env2.Program('prog-timestamp', 'program2.c') </programlisting> <para> - So if, for example, - a user were to only change a comment in a C file, - then the rebuilt &hello_o; file - would be exactly the same as the one previously built - (assuming the compiler doesn't put any build-specific - information in the object file). - &SCons; would then realize that it would not - need to rebuild the &hello; program as follows: + If both of the programs include the same + <filename>inc.h</filename> file, + then updating the modification time of + <filename>inc.h</filename> + (using the &touch; command) + will cause only <filename>prog-timestamp</filename> + to be rebuilt: </para> <screen> - % <userinput>scons -Q hello</userinput> - cc -o hello.o -c hello.c - cc -o hello hello.o - % <userinput>edit hello.c</userinput> - [CHANGE A COMMENT IN hello.c] - % <userinput>scons -Q hello</userinput> - cc -o hello.o -c hello.c - scons: `hello' is up to date. + % <userinput>scons -Q</userinput> + cc -o program1.o -c -I. program1.c + cc -o prog-MD5 program1.o + cc -o program2.o -c -I. program2.c + cc -o prog-timestamp program2.o + % <userinput>touch inc.h</userinput> + % <userinput>scons -Q</userinput> + cc -o program2.o -c -I. program2.c + cc -o prog-timestamp program2.o </screen> + </section> + + </section> + + <section> + <title>Older Functions for Deciding When an Input File Has Changed</title> + + <para> + + &SCons; still supports two functions that used to be the + primary methods for configuring the + decision about whether or not an input file has changed. + Although they're not officially deprecated yet, + their use is discouraged, + mainly because they rely on a somewhat + confusing distinction between how + source files and target files are handled. + These functions are documented here mainly in case you + encounter them in existing &SConscript; files. + + </para> + + <section> + <title>The &SourceSignatures; Function</title> + + <para> + + The &SourceSignatures; function is fairly straightforward, + and supports two different argument values + to configure whether source file changes should be decided + using MD5 signatures: + + </para> + + <programlisting> + Program('hello.c') + SourceSignatures('MD5') + </programlisting> + + <para> + + Or using time stamps: + + </para> + + <programlisting> + Program('hello.c') + SourceSignatures('timestamp') + </programlisting> + <para> - In essence, &SCons; has - "short-circuited" any dependent builds - when it realizes that a target file - has been rebuilt to exactly the same file as the last build. - So configured, - &SCons; does take some extra processing time - to scan the contents of the target (&hello_o;) file, - but this may save time - if the rebuild that was avoided - would have been very time-consuming and expensive. + These are roughly equivalent to specifying + <function>Decider('MD5')</function> + or + <function>Decider('timestamp-match')</function>, + respectively, + although it only affects how SCons makes + decisions about dependencies on + <emphasis>source</emphasis> files--that is, + files that are not built from any other files. + + </para> + + </section> + + <section> + <title>The &TargetSignatures; Function</title> + + <para> + + The &TargetSignatures; function + specifies how &SCons; decides + when a target file has changed + <emphasis>when it is used as a + dependency of (input to) another target</emphasis>--that is, + the &TargetSignatures; function configures + how the signatures of "intermediate" target files + are used when deciding if a "downstream" target file + must be rebuilt. + <footnote><para> + This easily-overlooked distinction between + how &SCons; decides if the target itself must be rebuilt + and how the target is then used to decide if a different + target must be rebuilt is one of the confusing + things that has led to the &TargetSignatures; + and &SourceSignatures; functions being + replaced by the simpler &Decider; function. + </para></footnote> + + </para> + + <para> + + The &TargetSignatures; function supports the same + <literal>'MD5'</literal> and <literal>'timestamp'</literal> + argument values that are supported by the &SourceSignatures;, + with the same meanings, but applied to target files. + That is, in the example: + + </para> + + <programlisting> + Program('hello.c') + TargetSignatures('MD5') + </programlisting> + + <para> + + The MD5 checksum of the &hello_o; target file + will be used to decide if it has changed since the last + time the "downstream" &hello; target file was built. + And in the example: + + </para> + + <programlisting> + Program('hello.c') + TargetSignatures('timestamp') + </programlisting> + + <para> + + The modification time of the &hello_o; target file + will be used to decide if it has changed since the last + time the "downstream" &hello; target file was built. + + </para> + + <para> + + The &TargetSignatures; function supports + two additional argument values: + <literal>'source'</literal> and <literal>'build'</literal>. + The <literal>'source'</literal> argument + specifies that decisions involving + whether target files have changed + since a previous build + should use the same behavior + for the decisions configured for source files + (using the &SourceSignatures; function). + So in the example: + + </para> + + <programlisting> + Program('hello.c') + TargetSignatures('source') + SourceSignatures('timestamp') + </programlisting> + + <para> + + All files, both targets and sources, + will use modification times + when deciding if an input file + has changed since the last + time a target was built. + + </para> + + <para> + + Lastly, the <literal>'build'</literal> argument + specifies that &SCons; should examine + the build status of a target file + and always rebuild a "downstream" target + if the target file was itself rebuilt, + without re-examining the contents or timestamp + of the newly-built target file. + If the target file was not rebuilt during + this &scons; invocation, + then the target file will be examined + the same way as configured by + the &SourceSignature; call + to decide if it has changed. + + </para> + + <para> + + This mimics the behavior of + <literal>build signatures</literal> + in earlier versions of &SCons;. + A &buildsignature; re-combined + signatures of all the input files + that went into making the target file, + so that the target file itself + did not need to have its contents read + to compute an MD5 signature. + This can improve performance for some configurations, + but is generally not as effective as using + <literal>Decider('MD5-timestamp')</literal>. </para> @@ -359,7 +787,7 @@ <para> Now suppose that our "Hello, World!" program - actually has a <literal>#include</literal> line + actually has an <literal>#include</literal> line to include the &hello_h; file in the compilation: </para> @@ -458,7 +886,7 @@ the &cv-CPPPATH; variable may be a list of directories, or a string separated by - the system-specific path separate character + the system-specific path separation character (':' on POSIX/Linux, ';' on Windows). Either way, &SCons; creates the right command-line options @@ -703,7 +1131,44 @@ </section> <section> - <title>Ignoring Dependencies: the &Ignore; Method</title> + <title>Explicit Dependencies: the &Depends; Function</title> + + <para> + + Sometimes a file depends on another file + that is not detected by an &SCons; scanner. + For this situation, + &SCons; allows you to specific explicitly that one file + depends on another file, + and must be rebuilt whenever that file changes. + This is specified using the &Depends; method: + + </para> + + <programlisting> + hello = Program('hello.c') + Depends(hello, 'other_file') + </programlisting> + + <!-- XXX mention that you can use arrays for target and source? --> + + <screen> + % <userinput>scons -Q hello</userinput> + cc -c hello.c -o hello.o + cc -o hello hello.o + % <userinput>scons -Q hello</userinput> + scons: `hello' is up to date. + % <userinput>edit other_file</userinput> + [CHANGE THE CONTENTS OF other_file] + % <userinput>scons -Q hello</userinput> + cc -c hello.c -o hello.o + cc -o hello hello.o + </screen> + + </section> + + <section> + <title>Ignoring Dependencies: the &Ignore; Function</title> <para> @@ -749,7 +1214,7 @@ Now, the above example is a little contrived, because it's hard to imagine a real-world situation - where you wouldn't to rebuild &hello; + where you wouldn't want to rebuild &hello; if the &hello_h; file changed. A more realistic example might be if the &hello; @@ -774,45 +1239,7 @@ </section> <section> - <title>Explicit Dependencies: the &Depends; Method</title> - - <para> - - On the other hand, - sometimes a file depends on another file - that is not detected by an &SCons; scanner. - For this situation, - &SCons; allows you to specific explicitly that one file - depends on another file, - and must be rebuilt whenever that file changes. - This is specified using the &Depends; method: - - </para> - - <programlisting> - hello = Program('hello.c') - Depends(hello, 'other_file') - </programlisting> - - <!-- XXX mention that you can use arrays for target and source? --> - - <screen> - % <userinput>scons -Q hello</userinput> - cc -c hello.c -o hello.o - cc -o hello hello.o - % <userinput>scons -Q hello</userinput> - scons: `hello' is up to date. - % <userinput>edit other_file</userinput> - [CHANGE THE CONTENTS OF other_file] - % <userinput>scons -Q hello</userinput> - cc -c hello.c -o hello.o - cc -o hello hello.o - </screen> - - </section> - - <section> - <title>The &AlwaysBuild; Method</title> + <title>The &AlwaysBuild; Function</title> <para> diff --git a/doc/user/environments.in b/doc/user/environments.in index 504ef67..3fcec02 100644 --- a/doc/user/environments.in +++ b/doc/user/environments.in @@ -391,7 +391,7 @@ environment undisturbed. enabled on the command line, or different executable programs need to be linked with different libraries. - &SCons; accomodates these different build + &SCons; accommodates these different build requirements by allowing you to create and configure multiple &consenvs; that control how the software is built. @@ -416,7 +416,7 @@ environment undisturbed. <para> - By default, &SCons; intializes every + By default, &SCons; initializes every new construction environment with a set of &consvars; based on the tools that it finds on your system, @@ -845,7 +845,7 @@ environment undisturbed. (Note that because we're not expanding this in the context of building something there are no target or source files - for &cv-link-TARGET; and &cv-link-SOURCES; to expand. + for &cv-link-TARGET; and &cv-link-SOURCES; to expand.) </para> diff --git a/doc/user/environments.xml b/doc/user/environments.xml index fbcef99..01ae2ed 100644 --- a/doc/user/environments.xml +++ b/doc/user/environments.xml @@ -391,7 +391,7 @@ environment undisturbed. enabled on the command line, or different executable programs need to be linked with different libraries. - &SCons; accomodates these different build + &SCons; accommodates these different build requirements by allowing you to create and configure multiple &consenvs; that control how the software is built. @@ -416,7 +416,7 @@ environment undisturbed. <para> - By default, &SCons; intializes every + By default, &SCons; initializes every new construction environment with a set of &consvars; based on the tools that it finds on your system, @@ -842,7 +842,7 @@ environment undisturbed. (Note that because we're not expanding this in the context of building something there are no target or source files - for &cv-link-TARGET; and &cv-link-SOURCES; to expand. + for &cv-link-TARGET; and &cv-link-SOURCES; to expand.) </para> diff --git a/doc/user/factories.in b/doc/user/factories.in index d0c5313..222f02a 100644 --- a/doc/user/factories.in +++ b/doc/user/factories.in @@ -246,7 +246,7 @@ The &Move; factory allows you to rename a file or directory. For example, if we don't want to copy the temporary file, - we could: + we could use: </para> @@ -334,7 +334,7 @@ a file in a temporary directory in which the processing tool will create other files that we don't care about, - you could: + you could use: </para> diff --git a/doc/user/factories.xml b/doc/user/factories.xml index 7c09e4b..6d0de02 100644 --- a/doc/user/factories.xml +++ b/doc/user/factories.xml @@ -224,7 +224,7 @@ The &Move; factory allows you to rename a file or directory. For example, if we don't want to copy the temporary file, - we could: + we could use: </para> @@ -296,7 +296,7 @@ a file in a temporary directory in which the processing tool will create other files that we don't care about, - you could: + you could use: </para> diff --git a/doc/user/file-removal.in b/doc/user/file-removal.in index adc5b5f..1d1b604 100644 --- a/doc/user/file-removal.in +++ b/doc/user/file-removal.in @@ -101,7 +101,7 @@ By default, &SCons; removes all built targets when invoked with the <literal>-c</literal> option to clean a source tree - of built tragets. + of built targets. Sometimes, however, this is not what you want. For example, you may want to remove only intermediate generated files (such as object files), diff --git a/doc/user/file-removal.xml b/doc/user/file-removal.xml index f64d394..bfec7ac 100644 --- a/doc/user/file-removal.xml +++ b/doc/user/file-removal.xml @@ -94,7 +94,7 @@ By default, &SCons; removes all built targets when invoked with the <literal>-c</literal> option to clean a source tree - of built tragets. + of built targets. Sometimes, however, this is not what you want. For example, you may want to remove only intermediate generated files (such as object files), diff --git a/doc/user/help.in b/doc/user/help.in index 4356d18..5ab7e83 100644 --- a/doc/user/help.in +++ b/doc/user/help.in @@ -105,7 +105,7 @@ <para> - Will display the completely help text on Windows: + Will display the complete help text on Windows: </para> diff --git a/doc/user/help.xml b/doc/user/help.xml index ca44a40..ed2b5de 100644 --- a/doc/user/help.xml +++ b/doc/user/help.xml @@ -108,7 +108,7 @@ <para> - Will display the completely help text on Windows: + Will display the complete help text on Windows: </para> diff --git a/doc/user/hierarchy.in b/doc/user/hierarchy.in index e0d6b00..4286556 100644 --- a/doc/user/hierarchy.in +++ b/doc/user/hierarchy.in @@ -518,7 +518,7 @@ make no difference to the build. so that it may be used by other &SConscript; files. First, you can call the &Export; function with a list of variables, - or a string white-space separated variable names. + or a string of white-space separated variable names. Each call to &Export; adds one or more variables to a global list of variables that are available for import @@ -770,6 +770,10 @@ make no difference to the build. <scons_output_command>scons -Q</scons_output_command> </scons_output> + <!-- + XXX Return(stop=False) + --> + </section> </section> diff --git a/doc/user/hierarchy.xml b/doc/user/hierarchy.xml index 4c84d5b..2e2941c 100644 --- a/doc/user/hierarchy.xml +++ b/doc/user/hierarchy.xml @@ -482,7 +482,7 @@ make no difference to the build. so that it may be used by other &SConscript; files. First, you can call the &Export; function with a list of variables, - or a string white-space separated variable names. + or a string of white-space separated variable names. Each call to &Export; adds one or more variables to a global list of variables that are available for import @@ -722,6 +722,10 @@ make no difference to the build. ranlib libprog.a </screen> + <!-- + XXX Return(stop=False) + --> + </section> </section> diff --git a/doc/user/install.in b/doc/user/install.in index ac9510f..ba179e7 100644 --- a/doc/user/install.in +++ b/doc/user/install.in @@ -209,7 +209,7 @@ you can either call the &InstallAs; function multiple times, or as a shorthand, you can supply same-length lists - for the both the target and source arguments: + for both the target and source arguments: </para> diff --git a/doc/user/install.xml b/doc/user/install.xml index 2a6d1b8..dd79153 100644 --- a/doc/user/install.xml +++ b/doc/user/install.xml @@ -201,7 +201,7 @@ you can either call the &InstallAs; function multiple times, or as a shorthand, you can supply same-length lists - for the both the target and source arguments: + for both the target and source arguments: </para> diff --git a/doc/user/libraries.in b/doc/user/libraries.in index 1ccb1c3..73ba6c9 100644 --- a/doc/user/libraries.in +++ b/doc/user/libraries.in @@ -251,6 +251,10 @@ the library will be found in the &cv-link-LIBPATH; construction variable: + <!-- In the preceding paragraph, the "$" notation for + LIBS, LIBPATH etc. is used for the first time. + Maybe some words of explanation would be nice. --> + </para> <scons_example name="ex2"> @@ -427,6 +431,8 @@ <scons_output example="ex3" os="win32"> <scons_output_command>scons -Q</scons_output_command> </scons_output> + <!-- The link command is too wide in the PDF version. + There are some other examples of this throughout the document. --> <para> diff --git a/doc/user/libraries.xml b/doc/user/libraries.xml index 035ebd3..da7d835 100644 --- a/doc/user/libraries.xml +++ b/doc/user/libraries.xml @@ -235,6 +235,10 @@ the library will be found in the &cv-link-LIBPATH; construction variable: + <!-- In the preceding paragraph, the "$" notation for + LIBS, LIBPATH etc. is used for the first time. + Maybe some words of explanation would be nice. --> + </para> <programlisting> @@ -409,6 +413,8 @@ cl /nologo /c prog.c /Foprog.obj link /nologo /OUT:prog.exe /LIBPATH:\usr\lib /LIBPATH:\usr\local\lib m.lib prog.obj </screen> + <!-- The link command is too wide in the PDF version. + There are some other examples of this throughout the document. --> <para> diff --git a/doc/user/main.in b/doc/user/main.in index c3cf2c2..4095e6b 100644 --- a/doc/user/main.in +++ b/doc/user/main.in @@ -103,6 +103,7 @@ XXX GetBuildPath() XXX GetLaunchDir() + XXX ParseConfig() XXX MergeFlags() XXX ParseFlags() @@ -125,6 +126,20 @@ XXX GetOption('num_jobs') XXX SetOption('num_jobs') + XXX Options.UnknownOption() + + XXX GetBuildFailures() + + XXX Requires() + + XXX CheckTypeSize() + + XXX Glob() + + XXX Progress() + + XXX AddMethod() + XXX - - diskcheck= XXX site_scons diff --git a/doc/user/main.xml b/doc/user/main.xml index c3cf2c2..4095e6b 100644 --- a/doc/user/main.xml +++ b/doc/user/main.xml @@ -103,6 +103,7 @@ XXX GetBuildPath() XXX GetLaunchDir() + XXX ParseConfig() XXX MergeFlags() XXX ParseFlags() @@ -125,6 +126,20 @@ XXX GetOption('num_jobs') XXX SetOption('num_jobs') + XXX Options.UnknownOption() + + XXX GetBuildFailures() + + XXX Requires() + + XXX CheckTypeSize() + + XXX Glob() + + XXX Progress() + + XXX AddMethod() + XXX - - diskcheck= XXX site_scons diff --git a/doc/user/parseconfig.in b/doc/user/parseconfig.in index d3f563c..45b58cc 100644 --- a/doc/user/parseconfig.in +++ b/doc/user/parseconfig.in @@ -50,7 +50,7 @@ <scons_example name="ParseConfig1"> <file name="SConstruct" printme="1"> env = Environment() - env.ParseConfig("pkg-config") + env.ParseConfig("pkg-config x11") </file> <file name="f1.c"> int f1() { } diff --git a/doc/user/parseconfig.xml b/doc/user/parseconfig.xml index 067ef37..25ea12c 100644 --- a/doc/user/parseconfig.xml +++ b/doc/user/parseconfig.xml @@ -49,7 +49,7 @@ <programlisting> env = Environment() - env.ParseConfig("pkg-config") + env.ParseConfig("pkg-config x11") </programlisting> <para> @@ -62,7 +62,6 @@ <screen> % <userinput>scons -Q</userinput> scons: `.' is up to date. - Must specify package names on the command line </screen> <para> diff --git a/doc/user/repositories.in b/doc/user/repositories.in index 9065593..d4ed418 100644 --- a/doc/user/repositories.in +++ b/doc/user/repositories.in @@ -487,8 +487,8 @@ coming into existence.) Usually, this would be done by a build integrator who would run &SCons; in the repository to create all of its derived files and &sconsign; files, - or who would &SCons; in a separate build directory - and copying the resulting tree to the desired repository: + or who would run &SCons; in a separate build directory + and copy the resulting tree to the desired repository: </para> @@ -552,7 +552,7 @@ coming into existence.) <para> Notice that &SCons; realizes that it does not need to - rebuild local copies file1.o and file2.o files, + rebuild local copies <filename>file1.o</filename> and <filename>file2.o</filename> files, but instead uses the already-compiled files from the repository. diff --git a/doc/user/repositories.xml b/doc/user/repositories.xml index f22611b..766d8bc 100644 --- a/doc/user/repositories.xml +++ b/doc/user/repositories.xml @@ -457,8 +457,8 @@ coming into existence.) Usually, this would be done by a build integrator who would run &SCons; in the repository to create all of its derived files and &sconsign; files, - or who would &SCons; in a separate build directory - and copying the resulting tree to the desired repository: + or who would run &SCons; in a separate build directory + and copy the resulting tree to the desired repository: </para> @@ -511,7 +511,7 @@ coming into existence.) <para> Notice that &SCons; realizes that it does not need to - rebuild local copies file1.o and file2.o files, + rebuild local copies <filename>file1.o</filename> and <filename>file2.o</filename> files, but instead uses the already-compiled files from the repository. diff --git a/doc/user/sconf.in b/doc/user/sconf.in index 0165ddd..265b720 100644 --- a/doc/user/sconf.in +++ b/doc/user/sconf.in @@ -124,7 +124,7 @@ Note that you can choose to terminate the build if a given header file doesn't exist, - or you can modify the contstruction environment + or you can modify the construction environment based on the existence of a header file. </para> diff --git a/doc/user/sconf.xml b/doc/user/sconf.xml index df530fe..163db3b 100644 --- a/doc/user/sconf.xml +++ b/doc/user/sconf.xml @@ -124,7 +124,7 @@ Note that you can choose to terminate the build if a given header file doesn't exist, - or you can modify the contstruction environment + or you can modify the construction environment based on the existence of a header file. </para> diff --git a/doc/user/simple.in b/doc/user/simple.in index b468f9a..3382f86 100644 --- a/doc/user/simple.in +++ b/doc/user/simple.in @@ -508,7 +508,7 @@ Because we want this User's Guide to focus on what &SCons; is actually doing, - we're going use the &Q; option + we're going to use the &Q; option to remove these messages from the output of all the remaining examples in this Guide. diff --git a/doc/user/simple.xml b/doc/user/simple.xml index c5ef430..4e4ff0e 100644 --- a/doc/user/simple.xml +++ b/doc/user/simple.xml @@ -542,7 +542,7 @@ Because we want this User's Guide to focus on what &SCons; is actually doing, - we're going use the &Q; option + we're going to use the &Q; option to remove these messages from the output of all the remaining examples in this Guide. diff --git a/doc/user/sourcecode.xml b/doc/user/sourcecode.xml index 6cb4162..1818d8e 100644 --- a/doc/user/sourcecode.xml +++ b/doc/user/sourcecode.xml @@ -50,7 +50,6 @@ <screen> % <userinput>scons -Q</userinput> - bk get - bk get hello.c cc -o hello.o -c hello.c cc -o hello hello.o @@ -75,7 +74,6 @@ <screen> % <userinput>scons -Q</userinput> - cvs -d /usr/local/CVS co - cvs -d /usr/local/CVS co hello.c cc -o hello.o -c hello.c cc -o hello hello.o @@ -100,7 +98,6 @@ <screen> % <userinput>scons -Q</userinput> - co - co hello.c cc -o hello.o -c hello.c cc -o hello hello.o @@ -125,7 +122,6 @@ <screen> % <userinput>scons -Q</userinput> - sccs get - sccs get hello.c cc -o hello.o -c hello.c cc -o hello hello.o diff --git a/doc/user/troubleshoot.in b/doc/user/troubleshoot.in index 11f44dd..3e0bfdb 100644 --- a/doc/user/troubleshoot.in +++ b/doc/user/troubleshoot.in @@ -241,7 +241,7 @@ Although this is usually helpful and what you want, it might be frustrating if &SCons; doesn't set certain variables that you - expect to be sit. + expect to be set. In situations like this, it's sometimes helpful to use the construction environment &Dump; method @@ -292,6 +292,10 @@ In a real-life situation, the construction environments will likely contain a great many more variables. + Also note that we've massaged the example output above + to make the memory address of all objects a constant 0x700000. + In reality, you would see a different hexadecimal + number for each object. </para> diff --git a/doc/user/troubleshoot.xml b/doc/user/troubleshoot.xml index 6ec6185..ca5ace8 100644 --- a/doc/user/troubleshoot.xml +++ b/doc/user/troubleshoot.xml @@ -232,7 +232,7 @@ Although this is usually helpful and what you want, it might be frustrating if &SCons; doesn't set certain variables that you - expect to be sit. + expect to be set. In situations like this, it's sometimes helpful to use the construction environment &Dump; method @@ -261,7 +261,7 @@ <screen> % <userinput>scons</userinput> scons: Reading SConscript files ... - { 'BUILDERS': {'InstallAs': <function InstallAsBuilderWrapper at 0xb23a28>, 'Install': <function InstallBuilderWrapper at 0xb1b7d0>}, + { 'BUILDERS': {'InstallAs': <function InstallAsBuilderWrapper at 0x700000>, 'Install': <function InstallBuilderWrapper at 0x700000>}, 'CONFIGUREDIR': '#/.sconf_temp', 'CONFIGURELOG': '#/config.log', 'CPPSUFFIXES': [ '.c', @@ -284,13 +284,13 @@ '.spp', '.SPP'], 'DSUFFIXES': ['.d'], - 'Dir': <SCons.Defaults.Variable_Method_Caller instance at 0xa1eb48>, - 'Dirs': <SCons.Defaults.Variable_Method_Caller instance at 0xa1eb90>, + 'Dir': <SCons.Defaults.Variable_Method_Caller instance at 0x700000>, + 'Dirs': <SCons.Defaults.Variable_Method_Caller instance at 0x700000>, 'ENV': {'PATH': '/usr/local/bin:/opt/bin:/bin:/usr/bin'}, - 'ESCAPE': <function escape at 0xb1fa28>, - 'File': <SCons.Defaults.Variable_Method_Caller instance at 0xa1ebd8>, + 'ESCAPE': <function escape at 0x700000>, + 'File': <SCons.Defaults.Variable_Method_Caller instance at 0x700000>, 'IDLSUFFIXES': ['.idl', '.IDL'], - 'INSTALL': <function copyFunc at 0xb23aa0>, + 'INSTALL': <function copyFunc at 0x700000>, 'LATEXSUFFIXES': ['.tex', '.ltx', '.latex'], 'LIBPREFIX': 'lib', 'LIBPREFIXES': '$LIBPREFIX', @@ -302,16 +302,16 @@ 'PLATFORM': 'posix', 'PROGPREFIX': '', 'PROGSUFFIX': '', - 'PSPAWN': <function piped_env_spawn at 0xb23230>, - 'RDirs': <SCons.Defaults.Variable_Method_Caller instance at 0xa1ec20>, + 'PSPAWN': <function piped_env_spawn at 0x700000>, + 'RDirs': <SCons.Defaults.Variable_Method_Caller instance at 0x700000>, 'SCANNERS': [], 'SHELL': 'sh', 'SHLIBPREFIX': '$LIBPREFIX', 'SHLIBSUFFIX': '.so', 'SHOBJPREFIX': '$OBJPREFIX', 'SHOBJSUFFIX': '$OBJSUFFIX', - 'SPAWN': <function spawnvpe_spawn at 0xb1f7d0>, - 'TEMPFILE': <class SCons.Platform.TempFileMunge at 0xa4e170>, + 'SPAWN': <function spawnvpe_spawn at 0x700000>, + 'TEMPFILE': <class SCons.Platform.TempFileMunge at 0x700000>, 'TEMPFILEPREFIX': '@', 'TOOLS': ['install', 'install'], '_CPPDEFFLAGS': '${_defines(CPPDEFPREFIX, CPPDEFINES, CPPDEFSUFFIX, __env__)}', @@ -319,9 +319,9 @@ '_LIBDIRFLAGS': '$( ${_concat(LIBDIRPREFIX, LIBPATH, LIBDIRSUFFIX, __env__, RDirs, TARGET, SOURCE)} $)', '_LIBFLAGS': '${_concat(LIBLINKPREFIX, LIBS, LIBLINKSUFFIX, __env__)}', '__RPATH': '$_RPATH', - '_concat': <function _concat at 0xa3d398>, - '_defines': <function _defines at 0xa3d500>, - '_stripixes': <function _stripixes at 0xa3d488>} + '_concat': <function _concat at 0x700000>, + '_defines': <function _defines at 0x700000>, + '_stripixes': <function _stripixes at 0x700000>} scons: done reading SConscript files. scons: Building targets ... scons: `.' is up to date. @@ -338,9 +338,9 @@ <screen> C:\><userinput>scons</userinput> scons: Reading SConscript files ... - { 'BUILDERS': {'RES': <SCons.Builder.BuilderBase instance at 0xb39518>, 'Object': <SCons.Builder.CompositeBuilder instance at 0xb4a710>, 'InstallAs': <function InstallAsBuilderWrapper at 0xb45c08>, 'PCH': <SCons.Builder.BuilderBase instance at 0xb1cef0>, 'Install': <function InstallBuilderWrapper at 0xb1b7d0>, 'SharedObject': <SCons.Builder.CompositeBuilder instance at 0xb4aa28>, 'StaticObject': <SCons.Builder.CompositeBuilder instance at 0xb4a710>}, + { 'BUILDERS': {'RES': <SCons.Builder.BuilderBase instance at 0x700000>, 'Object': <SCons.Builder.CompositeBuilder instance at 0x700000>, 'InstallAs': <function InstallAsBuilderWrapper at 0x700000>, 'PCH': <SCons.Builder.BuilderBase instance at 0x700000>, 'Install': <function InstallBuilderWrapper at 0x700000>, 'SharedObject': <SCons.Builder.CompositeBuilder instance at 0x700000>, 'StaticObject': <SCons.Builder.CompositeBuilder instance at 0x700000>}, 'CC': 'cl', - 'CCCOM': <SCons.Action.FunctionAction instance at 0xb4c290>, + 'CCCOM': <SCons.Action.FunctionAction instance at 0x700000>, 'CCCOMFLAGS': '$CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS /c $SOURCES /Fo$TARGET $CCPCHFLAGS $CCPDBFLAGS', 'CCFLAGS': ['/nologo'], 'CCPCHFLAGS': ['${(PCH and "/Yu%s /Fp%s"%(PCHSTOP or "",File(PCH))) or ""}'], @@ -375,19 +375,19 @@ 'CXXFILESUFFIX': '.cc', 'CXXFLAGS': ['$CCFLAGS', '$(', '/TP', '$)'], 'DSUFFIXES': ['.d'], - 'Dir': <SCons.Defaults.Variable_Method_Caller instance at 0xa1eb48>, - 'Dirs': <SCons.Defaults.Variable_Method_Caller instance at 0xa1eb90>, + 'Dir': <SCons.Defaults.Variable_Method_Caller instance at 0x700000>, + 'Dirs': <SCons.Defaults.Variable_Method_Caller instance at 0x700000>, 'ENV': { 'INCLUDE': 'C:\\Program Files\\Microsoft Visual Studio/VC98\\include', 'LIB': 'C:\\Program Files\\Microsoft Visual Studio/VC98\\lib', 'PATH': 'C:\\Program Files\\Microsoft Visual Studio\\Common\\tools\\WIN95;C:\\Program Files\\Microsoft Visual Studio\\Common\\MSDev98\\bin;C:\\Program Files\\Microsoft Visual Studio\\Common\\tools;C:\\Program Files\\Microsoft Visual Studio/VC98\\bin', 'PATHEXT': '.COM;.EXE;.BAT;.CMD', 'SystemRoot': 'C:/WINDOWS'}, - 'ESCAPE': <function escape at 0xb24848>, - 'File': <SCons.Defaults.Variable_Method_Caller instance at 0xa1ebd8>, + 'ESCAPE': <function escape at 0x700000>, + 'File': <SCons.Defaults.Variable_Method_Caller instance at 0x700000>, 'IDLSUFFIXES': ['.idl', '.IDL'], 'INCPREFIX': '/I', 'INCSUFFIX': '', - 'INSTALL': <function copyFunc at 0xb45c80>, + 'INSTALL': <function copyFunc at 0x700000>, 'LATEXSUFFIXES': ['.tex', '.ltx', '.latex'], 'LIBPREFIX': '', 'LIBPREFIXES': ['$LIBPREFIX'], @@ -403,14 +403,14 @@ 'PLATFORM': 'win32', 'PROGPREFIX': '', 'PROGSUFFIX': '.exe', - 'PSPAWN': <function piped_spawn at 0xb20488>, + 'PSPAWN': <function piped_spawn at 0x700000>, 'RC': 'rc', 'RCCOM': '$RC $_CPPDEFFLAGS $_CPPINCFLAGS $RCFLAGS /fo$TARGET $SOURCES', 'RCFLAGS': [], - 'RDirs': <SCons.Defaults.Variable_Method_Caller instance at 0xa1ec20>, + 'RDirs': <SCons.Defaults.Variable_Method_Caller instance at 0x700000>, 'SCANNERS': [], 'SHCC': '$CC', - 'SHCCCOM': <SCons.Action.FunctionAction instance at 0xb4c320>, + 'SHCCCOM': <SCons.Action.FunctionAction instance at 0x700000>, 'SHCCFLAGS': ['$CCFLAGS'], 'SHCFLAGS': ['$CFLAGS'], 'SHCXX': '$CXX', @@ -421,18 +421,18 @@ 'SHLIBSUFFIX': '.dll', 'SHOBJPREFIX': '$OBJPREFIX', 'SHOBJSUFFIX': '$OBJSUFFIX', - 'SPAWN': <function spawn at 0xb247d0>, + 'SPAWN': <function spawn at 0x700000>, 'STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME': 1, - 'TEMPFILE': <class SCons.Platform.TempFileMunge at 0xa4e170>, + 'TEMPFILE': <class SCons.Platform.TempFileMunge at 0x700000>, 'TEMPFILEPREFIX': '@', 'TOOLS': ['msvc', 'install', 'install'], '_CPPDEFFLAGS': '${_defines(CPPDEFPREFIX, CPPDEFINES, CPPDEFSUFFIX, __env__)}', '_CPPINCFLAGS': '$( ${_concat(INCPREFIX, CPPPATH, INCSUFFIX, __env__, RDirs, TARGET, SOURCE)} $)', '_LIBDIRFLAGS': '$( ${_concat(LIBDIRPREFIX, LIBPATH, LIBDIRSUFFIX, __env__, RDirs, TARGET, SOURCE)} $)', '_LIBFLAGS': '${_concat(LIBLINKPREFIX, LIBS, LIBLINKSUFFIX, __env__)}', - '_concat': <function _concat at 0xa3d398>, - '_defines': <function _defines at 0xa3d500>, - '_stripixes': <function _stripixes at 0xa3d488>} + '_concat': <function _concat at 0x700000>, + '_defines': <function _defines at 0x700000>, + '_stripixes': <function _stripixes at 0x700000>} scons: done reading SConscript files. scons: Building targets ... scons: `.' is up to date. @@ -447,6 +447,10 @@ In a real-life situation, the construction environments will likely contain a great many more variables. + Also note that we've massaged the example output above + to make the memory address of all objects a constant 0x700000. + In reality, you would see a different hexadecimal + number for each object. </para> @@ -552,7 +556,6 @@ cc -o f3.o -c -I. f3.c cc -o prog f1.o f2.o f3.o +-. - +-- +-SConstruct +-f1.c +-f1.o @@ -659,31 +662,30 @@ H = no cache [E b ]+-. - [ ] +-- - [E ] +-SConstruct - [E ] +-f1.c + [E C ] +-SConstruct + [E C ] +-f1.c [E B C ] +-f1.o - [E ] | +-f1.c - [E ] | +-inc.h - [E ] +-f2.c + [E C ] | +-f1.c + [E C ] | +-inc.h + [E C ] +-f2.c [E B C ] +-f2.o - [E ] | +-f2.c - [E ] | +-inc.h - [E ] +-f3.c + [E C ] | +-f2.c + [E C ] | +-inc.h + [E C ] +-f3.c [E B C ] +-f3.o - [E ] | +-f3.c - [E ] | +-inc.h - [E ] +-inc.h + [E C ] | +-f3.c + [E C ] | +-inc.h + [E C ] +-inc.h [E B C ] +-prog [E B C ] +-f1.o - [E ] | +-f1.c - [E ] | +-inc.h + [E C ] | +-f1.c + [E C ] | +-inc.h [E B C ] +-f2.o - [E ] | +-f2.c - [E ] | +-inc.h + [E C ] | +-f2.c + [E C ] | +-inc.h [E B C ] +-f3.o - [E ] +-f3.c - [E ] +-inc.h + [E C ] +-f3.c + [E C ] +-inc.h </screen> <para> @@ -707,6 +709,13 @@ cc -o f3.o -c -I. f3.c cc -o prog f1.o f2.o f3.o +-. + +-f1.o + +-f2.o + +-f3.o + +-prog + +-f1.o + +-f2.o + +-f3.o </screen> <para> @@ -790,7 +799,6 @@ cc -o prog2.o -c -I. prog2.c cc -o prog2 prog2.o -L. -lfoo +-. - +-- +-SConstruct +-f1.c +-f1.o @@ -883,7 +891,6 @@ cc -o prog2.o -c -I. prog2.c cc -o prog2 prog2.o -L. -lfoo +-. - +-- +-SConstruct +-f1.c +-f1.o @@ -1109,9 +1116,10 @@ scons: *** Source `prog.c' not found, needed by target `prog.o'. Stop. scons: internal stack trace: File "bootstrap/src/engine/SCons/Job.py", line 114, in start + File "bootstrap/src/engine/SCons/Script/Main.py", line 157, in prepare File "bootstrap/src/engine/SCons/Taskmaster.py", line 169, in prepare - File "bootstrap/src/engine/SCons/Node/FS.py", line 2220, in prepare - File "bootstrap/src/engine/SCons/Node/__init__.py", line 819, in prepare + File "bootstrap/src/engine/SCons/Node/FS.py", line 2568, in prepare + File "bootstrap/src/engine/SCons/Node/__init__.py", line 349, in prepare </screen> <para> @@ -1168,17 +1176,22 @@ % <userinput>scons -Q --taskmastertrace=- prog</userinput> Taskmaster: 'prog': children: ['prog.o'] - waiting on unstarted children: + waiting on unfinished children: ['prog.o'] Taskmaster: 'prog.o': children: ['inc.h', 'prog.c'] + waiting on unfinished children: + ['inc.h', 'prog.c'] + Taskmaster: 'prog.c': evaluating prog.c + Taskmaster: 'inc.h': evaluating inc.h + Taskmaster: 'prog.o': children: + ['inc.h', 'prog.c'] evaluating prog.o cc -o prog.o -c -I. prog.c Taskmaster: 'prog': children: ['prog.o'] evaluating prog cc -o prog prog.o - Taskmaster: 'prog': already handled (executed) </screen> <para> diff --git a/src/CHANGES.txt b/src/CHANGES.txt index 8886204..c8dc7d3 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -8,7 +8,106 @@ -RELEASE 0.XX - XXX +RELEASE 0.97.0d20071212 - Wed, 12 Dec 2007 09:29:32 -0600 + + From Benoit Belley: + + - Fix occasional spurious rebuilds and inefficiency when using + --implicit-cache and Builders that produce multiple targets. + + - Allow SCons to not have to know about the builders of generated + files when BuildDir(duplicate=0) is used, potentially allowing some + SConscript files to be ignored for smaller builds. + + From David Cournapeau: + + - Add a CheckTypeSize() call to configure contexts. + + From Ken Deeter: + + - Make the "contents" of Alias Nodes a concatenation of the children's + content signatures (MD5 checksums), not a concatenation of the + children's contents, to avoid using large amounts of memory during + signature calculation. + + From Malte Helmert: + + - Fix a lot of typos in the man page and User's Guide. + + From Geoffrey Irving: + + - Speed up conversion of paths in .sconsign files to File or Dir Nodes. + + From Steven Knight: + + - Add an Options.UnknownOptions() method that returns any settings + (from the command line, or whatever dictionary was passed in) + that aren't known to the Options object. + + - Add a Glob() function. + + - When removing targets with the -c option, use the absolute path (to + avoid problems interpreting BuildDir() when the top-level directory + is the source directory). + + - Fix problems with Install() and InstallAs() when called through a + clone (of a clone, ...) of a cloned construction environment. + + - When executing a file containing Options() settings, add the file's + directory to sys.path (so modules can be imported from there) and + explicity set __name__ to the name of the file so the statement's + in the file can deduce the location if they need to. + + - Fix an O(n^2) performance problem when adding sources to a target + through calls to a multi Builder (including Aliases). + + - Redefine the $WINDOWSPROGMANIFESTSUFFIX and + $WINDOWSSHLIBMANIFESTSUFFIX variables so they pick up changes to + the underlying $SHLIBSUFFIX and $PROGSUFFIX variables. + + - Add a GetBuildFailures() function that can be called from functions + registered with the Python atexit module to print summary information + about any failures encountered while building. + + - Return a NodeList object, not a Python list, when a single_source + Builder like Object() is called with more than one file. + + - When searching for implicit dependency files in the directories + in a $*PATH list, don't create Dir Nodes for directories that + don't actually exist on-disk. + + - Add a Requires() function to allow the specification of order-only + prerequisites, which will be updated before specified "downstream" + targets but which don't actually cause the target to be rebuilt. + + - Restore the FS.{Dir,File,Entry}.rel_path() method. + + - Make the default behavior of {Source,Target}Signatures('timestamp') + be equivalent to 'timestamp-match', not 'timestamp-newer'. + + - Fix use of CacheDir with Decider('timestamp-newer') by updating + the modification time when copying files from the cache. + + - Fix random issues with parallel (-j) builds on Windows when Python + holds open file handles (especially for SCons temporary files, + or targets built by Python function actions) across process creation. + + From Maxim Kartashev: + + - Fix test scripts when run on Solaris. + + From Gary Oberbrunner: + + - Fix Glob() when a pattern is in an explicitly-named subdirectory. + + From Philipp Scholl: + + - Fix setting up targets if multiple Package builders are specified + at once. + + + +RELEASE 0.97.0d20070918 - Tue, 18 Sep 2007 10:51:27 -0500 From Steven Knight: diff --git a/src/RELEASE.txt b/src/RELEASE.txt index 23c5638..ca01607 100644 --- a/src/RELEASE.txt +++ b/src/RELEASE.txt @@ -20,11 +20,39 @@ more effectively, please sign up for the scons-users mailing list at: -RELEASE 0.97.0d200709XX - XXX +RELEASE 0.97.0d20071212 - Wed, 12 Dec 2007 09:29:32 -0600 This is the eighth beta release of SCons. Please consult the CHANGES.txt file for a list of specific changes since last release. + Please note the following important changes since release 0.97.0d20070918: + + -- SCons REDEFINES PYTHON open() AND file() ON Windows TO NOT PASS + ON OPEN FILE HANDLES TO CREATED PROCESSES + + On Windows systems, SCons now redefines the Python open() + and file() functions so that, if the Python Win32 extensions + are available, the file handles for any opened files will *not* + be inherited by subprocesses, such as the spawned compilers and + other tools invoked to build the software. + + This prevents certain race conditions where a file handle for + a file opened by Python (either in a Python function action, + or directly in a SConscript file) could be inherited and help + open by a subprocess, interfering with the ability of other + processes to create or modify the file. + + In general, this should not cause problems for the vast majority + of configurations. The only time this would be a problem would be + in the unlikely event that a process spawned by SCons specifically + *expected* to use an inherited file handle opened by SCons. + + If the Python Win32 extensions are not installed or are an + earlier version that does not have the ability to disable file + handle inheritance, SCons will print a warning message when the + -j option is used. The warning message may be suppressed by + specifying --warn=no-parallel-support. + Please note the following important changes since release 0.97.0d20070809: -- "content" SIGNATURES ARE NOW THE DEFAULT BEHAVIOR @@ -433,9 +461,9 @@ RELEASE 0.97.0d200709XX - XXX You can guarantee that a list will be updated in place regardless of which SConscript file defines it and which adds to it by - using the list append() method as follows: + using the list extend() method as follows: - obj.append(env.Object('foo.c')) + obj.extend(env.Object('foo.c')) Please note the following important changes since release 0.96.1: diff --git a/src/engine/SCons/Action.py b/src/engine/SCons/Action.py index bdedc99..c2c1158 100644 --- a/src/engine/SCons/Action.py +++ b/src/engine/SCons/Action.py @@ -330,7 +330,14 @@ class _ActionAction(ActionBase): os.chdir(chdir) try: stat = self.execute(target, source, env) - stat = exitstatfunc(stat) + if isinstance(stat, SCons.Errors.BuildError): + s = exitstatfunc(stat.status) + if s: + stat.status = s + else: + stat = s + else: + stat = exitstatfunc(stat) finally: if save_cwd: os.chdir(save_cwd) @@ -478,7 +485,11 @@ class CommandAction(_ActionAction): cmd_line = escape_list(cmd_line, escape) result = spawn(shell, escape, cmd_line[0], cmd_line, ENV) if not ignore and result: - return result + msg = "Error %s" % result + return SCons.Errors.BuildError(errstr=msg, + status=result, + action=self, + command=cmd_line) return 0 def get_contents(self, target, source, env): @@ -689,9 +700,19 @@ class FunctionAction(_ActionAction): # target file will appear). try: filename = e.filename except AttributeError: filename = None - raise SCons.Errors.BuildError(node=target, - errstr=e.strerror, - filename=filename) + result = SCons.Errors.BuildError(node=target, + errstr=e.strerror, + status=1, + filename=filename, + action=self, + command=self.strfunction(target, source, env)) + else: + if result: + msg = "Error %s" % result + result = SCons.Errors.BuildError(errstr=msg, + status=result, + action=self, + command=self.strfunction(target, source, env)) return result def get_contents(self, target, source, env): @@ -822,8 +843,9 @@ class ActionCaller: # was called by using this hard-coded value as a special return. if s == '$__env__': return env - else: + elif SCons.Util.is_String(s): return env.subst(s, 0, target, source) + return self.parent.convert(s) def subst_args(self, target, source, env): return map(lambda x, self=self, t=target, s=source, e=env: self.subst(x, t, s, e), @@ -853,9 +875,10 @@ class ActionFactory: called with and give them to the ActionCaller object we create, so it can hang onto them until it needs them. """ - def __init__(self, actfunc, strfunc): + def __init__(self, actfunc, strfunc, convert=lambda x: x): self.actfunc = actfunc self.strfunc = strfunc + self.convert = convert def __call__(self, *args, **kw): ac = ActionCaller(self, args, kw) action = Action(ac, strfunction=ac.strfunction) diff --git a/src/engine/SCons/ActionTests.py b/src/engine/SCons/ActionTests.py index 01d0992..06030e3 100644 --- a/src/engine/SCons/ActionTests.py +++ b/src/engine/SCons/ActionTests.py @@ -430,7 +430,7 @@ class _ActionActionTestCase(unittest.TestCase): sio = StringIO.StringIO() sys.stdout = sio result = a("out", "in", env) - assert result == 7, result + assert result.status == 7, result s = sio.getvalue() assert s == 'execfunc(["out"], ["in"])\n', s @@ -440,14 +440,14 @@ class _ActionActionTestCase(unittest.TestCase): sio = StringIO.StringIO() sys.stdout = sio result = a("out", "in", env) - assert result == 7, result + assert result.status == 7, result.status s = sio.getvalue() assert s == expect % (repr('xyz'), repr(test.workpath())), s sio = StringIO.StringIO() sys.stdout = sio result = a("out", "in", env, chdir='sub') - assert result == 7, result + assert result.status == 7, result.status s = sio.getvalue() assert s == expect % (repr('sub'), repr(test.workpath())), s @@ -456,7 +456,7 @@ class _ActionActionTestCase(unittest.TestCase): sio = StringIO.StringIO() sys.stdout = sio result = b("out", "in", env) - assert result == 7, result + assert result.status == 7, result.status s = sio.getvalue() assert s == 'firstfunc(["out"], ["in"])\nexecfunc(["out"], ["in"])\n', s @@ -482,35 +482,35 @@ class _ActionActionTestCase(unittest.TestCase): sio = StringIO.StringIO() sys.stdout = sio result = a("out", "in", env) - assert result == 7, result + assert result.status == 7, result.status s = sio.getvalue() assert s == 'Building out with action:\n execfunc(target, source, env)\nexecfunc(["out"], ["in"])\n', s sio = StringIO.StringIO() sys.stdout = sio result = a("out", "in", env, presub=0) - assert result == 7, result + assert result.status == 7, result.status s = sio.getvalue() assert s == 'execfunc(["out"], ["in"])\n', s sio = StringIO.StringIO() sys.stdout = sio result = a("out", "in", env, presub=1) - assert result == 7, result + assert result.status == 7, result.status s = sio.getvalue() assert s == 'Building out with action:\n execfunc(target, source, env)\nexecfunc(["out"], ["in"])\n', s sio = StringIO.StringIO() sys.stdout = sio result = b(["out"], "in", env, presub=1) - assert result == 7, result + assert result.status == 7, result.status s = sio.getvalue() assert s == 'Building out with action:\n firstfunc(target, source, env)\nfirstfunc(["out"], ["in"])\nBuilding out with action:\n execfunc(target, source, env)\nexecfunc(["out"], ["in"])\n', s sio = StringIO.StringIO() sys.stdout = sio result = b(["out", "list"], "in", env, presub=1) - assert result == 7, result + assert result.status == 7, result.status s = sio.getvalue() assert s == 'Building out and list with action:\n firstfunc(target, source, env)\nfirstfunc(["out", "list"], ["in"])\nBuilding out and list with action:\n execfunc(target, source, env)\nexecfunc(["out", "list"], ["in"])\n', s @@ -519,14 +519,14 @@ class _ActionActionTestCase(unittest.TestCase): sio = StringIO.StringIO() sys.stdout = sio result = a2("out", "in", env) - assert result == 7, result + assert result.status == 7, result.status s = sio.getvalue() assert s == 'Building out with action:\n execfunc(target, source, env)\nexecfunc(["out"], ["in"])\n', s sio = StringIO.StringIO() sys.stdout = sio result = a2("out", "in", env, presub=0) - assert result == 7, result + assert result.status == 7, result.status s = sio.getvalue() assert s == 'execfunc(["out"], ["in"])\n', s @@ -542,7 +542,7 @@ class _ActionActionTestCase(unittest.TestCase): sio = StringIO.StringIO() sys.stdout = sio result = a("out", "in", env, presub=0, execute=1, show=0) - assert result == 7, result + assert result.status == 7, result.status s = sio.getvalue() assert s == '', s @@ -558,7 +558,7 @@ class _ActionActionTestCase(unittest.TestCase): assert exitstatfunc_result == [], exitstatfunc_result result = a("out", "in", env, execute=1, exitstatfunc=exitstatfunc) - assert result == 7, result + assert result.status == 7, result.status assert exitstatfunc_result == [7], exitstatfunc_result SCons.Action.execute_actions = 1 @@ -709,7 +709,7 @@ class CommandActionTestCase(unittest.TestCase): m = 'Invalid command display variable' assert string.find(s, m) != -1, 'Unexpected string: %s' % s else: - raise "did not catch expected UserError" + raise Exception, "did not catch expected UserError" def test___str__(self): """Test fetching the pre-substitution string for command Actions @@ -1014,26 +1014,26 @@ class CommandActionTestCase(unittest.TestCase): # Test that a nonexistent command returns 127 act = SCons.Action.CommandAction(python + "_no_such_command_") r = act([], [], env.Clone(out = outfile)) - assert r == expect_nonexistent, "r == %d" % r + assert r.status == expect_nonexistent, r.status # Test that trying to execute a directory returns 126 dir, tail = os.path.split(python) act = SCons.Action.CommandAction(dir) r = act([], [], env.Clone(out = outfile)) - assert r == expect_nonexecutable, "r == %d" % r + assert r.status == expect_nonexecutable, r.status # Test that trying to execute a non-executable file returns 126 act = SCons.Action.CommandAction(outfile) r = act([], [], env.Clone(out = outfile)) - assert r == expect_nonexecutable, "r == %d" % r + assert r.status == expect_nonexecutable, r.status act = SCons.Action.CommandAction('%s %s 1' % (_python_, exit_py)) r = act([], [], env) - assert r == 1, r + assert r.status == 1, r.status act = SCons.Action.CommandAction('@%s %s 1' % (_python_, exit_py)) r = act([], [], env) - assert r == 1, r + assert r.status == 1, r.status act = SCons.Action.CommandAction('@-%s %s 1' % (_python_, exit_py)) r = act([], [], env) @@ -1045,7 +1045,7 @@ class CommandActionTestCase(unittest.TestCase): act = SCons.Action.CommandAction('@ %s %s 1' % (_python_, exit_py)) r = act([], [], env) - assert r == 1, r + assert r.status == 1, r.status act = SCons.Action.CommandAction('@- %s %s 1' % (_python_, exit_py)) r = act([], [], env) @@ -1441,13 +1441,10 @@ class FunctionActionTestCase(unittest.TestCase): return 1 act = SCons.Action.FunctionAction(function1) - r = None - try: - r = act(target = [outfile, outfile2], source=[], env=Environment()) - except SCons.Errors.BuildError: - pass - assert r == 1 - assert count == 1 + r = act(target = [outfile, outfile2], source=[], env=Environment()) + assert r.status == 1, r.status + + assert count == 1, count c = test.read(outfile, 'r') assert c == "function1\n", c c = test.read(outfile2, 'r') @@ -1459,7 +1456,7 @@ class FunctionActionTestCase(unittest.TestCase): act = SCons.Action.FunctionAction(class1a) r = act([], [], Environment(out = outfile)) - assert r.__class__ == class1a + assert isinstance(r.status, class1a), r.status c = test.read(outfile, 'r') assert c == "class1a\n", c @@ -1470,7 +1467,7 @@ class FunctionActionTestCase(unittest.TestCase): act = SCons.Action.FunctionAction(class1b()) r = act([], [], Environment(out = outfile)) - assert r == 2 + assert r.status == 2, r.status c = test.read(outfile, 'r') assert c == "class1b\n", c @@ -1611,7 +1608,7 @@ class ListActionTestCase(unittest.TestCase): open(env['out'], 'a').write("class2b\n") act = SCons.Action.ListAction([cmd2, function2, class2a(), class2b]) r = act([], [], Environment(out = outfile)) - assert r.__class__ == class2b + assert isinstance(r.status, class2b), r.status c = test.read(outfile, 'r') assert c == "act.py: 'syzygy'\nfunction2\nclass2a\nclass2b\n", c diff --git a/src/engine/SCons/Builder.py b/src/engine/SCons/Builder.py index 6164a55..4021f2b 100644 --- a/src/engine/SCons/Builder.py +++ b/src/engine/SCons/Builder.py @@ -554,7 +554,7 @@ class BuilderBase: if not tgt is None: tgt = [tgt] if not src is None: src = [src] result.extend(self._execute(env, tgt, src, overwarn)) - return result + return SCons.Node.NodeList(result) overwarn.warn() diff --git a/src/engine/SCons/BuilderTests.py b/src/engine/SCons/BuilderTests.py index bc4c52d..cf13025 100644 --- a/src/engine/SCons/BuilderTests.py +++ b/src/engine/SCons/BuilderTests.py @@ -697,14 +697,32 @@ class BuilderTestCase(unittest.TestCase): single_source = 1, suffix='.out') env['CNT'] = [0] tgt = builder(env, target=outfiles[0], source=infiles[0])[0] + s = str(tgt) + assert s == test.workpath('0.out'), s tgt.prepare() tgt.build() assert env['CNT'][0] == 1, env['CNT'][0] tgt = builder(env, outfiles[1], infiles[1])[0] + s = str(tgt) + assert s == test.workpath('1.out'), s tgt.prepare() tgt.build() assert env['CNT'][0] == 2 tgts = builder(env, None, infiles[2:4]) + try: + [].extend(UserList.UserList()) + except TypeError: + # Old Python version (1.5.2) that can't handle extending + # a list with list-like objects. That means the return + # value from the builder call is a real list with Nodes, + # and doesn't have a __str__() method that stringifies + # the individual elements. Since we're gong to drop 1.5.2 + # support anyway, don't bother trying to test for it. + pass + else: + s = str(tgts) + expect = str([test.workpath('2.out'), test.workpath('3.out')]) + assert s == expect, s for t in tgts: t.prepare() tgts[0].build() tgts[1].build() diff --git a/src/engine/SCons/CacheDir.py b/src/engine/SCons/CacheDir.py index e3730a4..9b2b4b4 100644 --- a/src/engine/SCons/CacheDir.py +++ b/src/engine/SCons/CacheDir.py @@ -51,7 +51,7 @@ def CacheRetrieveFunc(target, source, env): if fs.islink(cachefile): fs.symlink(fs.readlink(cachefile), t.path) else: - fs.copy2(cachefile, t.path) + env.copy_from_cache(cachefile, t.path) st = fs.stat(cachefile) fs.chmod(t.path, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE) return 0 diff --git a/src/engine/SCons/Conftest.py b/src/engine/SCons/Conftest.py index bb3be56..fcf8c5a 100644 --- a/src/engine/SCons/Conftest.py +++ b/src/engine/SCons/Conftest.py @@ -318,6 +318,102 @@ int main() { return ret +def CheckTypeSize(context, type_name, header = None, language = None, expect = None): + """This check can be used to get the size of a given type, or to check whether + the type is of expected size. + + Arguments: + - type : str + the type to check + - includes : sequence + list of headers to include in the test code before testing the type + - language : str + 'C' or 'C++' + - expect : int + if given, will test wether the type has the given number of bytes. + If not given, will automatically find the size. + + Returns: + status : int + 0 if the check failed, or the found size of the type if the check succeeded.""" + + # Include "confdefs.h" first, so that the header can use HAVE_HEADER_H. + if context.headerfilename: + includetext = '#include "%s"' % context.headerfilename + else: + includetext = '' + + if not header: + header = "" + + lang, suffix, msg = _lang2suffix(language) + if msg: + context.Display("Cannot check for %s type: %s\n" % (type_name, msg)) + return msg + + src = includetext + header + if not expect is None: + # Only check if the given size is the right one + context.Display('Checking %s is %d bytes... ' % (type_name, expect)) + + # test code taken from autoconf: this is a pretty clever hack to find that + # a type is of a given size using only compilation. This speeds things up + # quite a bit compared to straightforward code using TryRun + src = src + r""" +typedef %s scons_check_type; + +int main() +{ + static int test_array[1 - 2 * !(((long int) (sizeof(scons_check_type))) == %d)]; + test_array[0] = 0; + + return 0; +} +""" + + # XXX: Try* vs CompileProg ? + st = context.TryCompile(src % (type_name, expect), suffix) + if st: + _Have(context, "SIZEOF_" + type_name, str(expect)) + context.Display("yes\n") + return expect + else: + context.Display("no\n") + _LogFailed(context, src, st) + return 0 + else: + # Only check if the given size is the right one + context.Message('Checking size of %s ... ' % type_name) + + # We have to be careful with the program we wish to test here since + # compilation will be attempted using the current environment's flags. + # So make sure that the program will compile without any warning. For + # example using: 'int main(int argc, char** argv)' will fail with the + # '-Wall -Werror' flags since the variables argc and argv would not be + # used in the program... + # + src = src + """ +#include <stdlib.h> +#include <stdio.h> +int main() { + printf("%d", (int)sizeof(""" + type_name + """)); + return 0; +} + """ + ret = context.TryRun(src, suffix) + st = ret[0] + try: + size = int(ret[1]) + _Have(context, "SIZEOF_" + type_name, str(size)) + context.Display("%d\n" % size) + except ValueError: + size = 0 + _LogFailed(context, src, st) + context.Display(" Failed !\n") + if st: + return size + else: + return 0 def CheckLib(context, libs, func_name = None, header = None, extra_libs = None, call = None, language = None, autoadd = 1): diff --git a/src/engine/SCons/Defaults.py b/src/engine/SCons/Defaults.py index 9308051..c3d30cb 100644 --- a/src/engine/SCons/Defaults.py +++ b/src/engine/SCons/Defaults.py @@ -87,11 +87,10 @@ def DefaultEnvironment(*args, **kw): if not _default_env: import SCons.Util _default_env = apply(SCons.Environment.Environment, args, kw) - _default_env.TargetSignatures('source') if SCons.Util.md5: - _default_env.SourceSignatures('MD5') + _default_env.Decider('MD5') else: - _default_env.SourceSignatures('timestamp') + _default_env.Decider('timestamp-match') global DefaultEnvironment DefaultEnvironment = _fetch_DefaultEnvironment _default_env._CacheDir = SCons.CacheDir.Null() @@ -158,7 +157,10 @@ LdModuleLinkAction = SCons.Action.Action("$LDMODULECOM", "$LDMODULECOMSTR") # ways by creating ActionFactory instances. ActionFactory = SCons.Action.ActionFactory -Chmod = ActionFactory(os.chmod, +def chmod_func(path, mode): + return os.chmod(str(path), mode) + +Chmod = ActionFactory(chmod_func, lambda dest, mode: 'Chmod("%s", 0%o)' % (dest, mode)) def copy_func(dest, src): @@ -172,9 +174,11 @@ def copy_func(dest, src): return shutil.copytree(src, dest, 1) Copy = ActionFactory(copy_func, - lambda dest, src: 'Copy("%s", "%s")' % (dest, src)) + lambda dest, src: 'Copy("%s", "%s")' % (dest, src), + convert=str) def delete_func(entry, must_exist=0): + entry = str(entry) if not must_exist and not os.path.exists(entry): return None if not os.path.exists(entry) or os.path.isfile(entry): @@ -188,12 +192,15 @@ def delete_strfunc(entry, must_exist=0): Delete = ActionFactory(delete_func, delete_strfunc) Mkdir = ActionFactory(os.makedirs, - lambda dir: 'Mkdir("%s")' % dir) + lambda dir: 'Mkdir("%s")' % dir, + convert=str) Move = ActionFactory(lambda dest, src: os.rename(src, dest), - lambda dest, src: 'Move("%s", "%s")' % (dest, src)) + lambda dest, src: 'Move("%s", "%s")' % (dest, src), + convert=str) def touch_func(file): + file = str(file) mtime = int(time.time()) if os.path.exists(file): atime = os.path.getatime(file) diff --git a/src/engine/SCons/Environment.py b/src/engine/SCons/Environment.py index 2f4c34e..cf2d0eb 100644 --- a/src/engine/SCons/Environment.py +++ b/src/engine/SCons/Environment.py @@ -216,6 +216,9 @@ class BuilderWrapper(MethodWrapper): def __repr__(self): return '<BuilderWrapper %s>' % repr(self.name) + def __str__(self): + return self.__repr__() + def __getattr__(self, name): if name == 'env': return self.object @@ -259,6 +262,12 @@ class BuilderDict(UserDict): return self.__class__(self.data, self.env) def __setitem__(self, item, val): + try: + method = getattr(self.env, item).method + except AttributeError: + pass + else: + self.env.RemoveMethod(method) UserDict.__setitem__(self, item, val) BuilderWrapper(self.env, val, item) @@ -773,6 +782,10 @@ def default_decide_target(dependency, target, prev_ni): f = SCons.Defaults.DefaultEnvironment().decide_target return f(dependency, target, prev_ni) +def default_copy_from_cache(src, dst): + f = SCons.Defaults.DefaultEnvironment().copy_from_cache + return f(src, dst) + class Base(SubstitutionEnvironment): """Base class for "real" construction Environments. These are the primary objects used to communicate dependency and construction @@ -836,6 +849,8 @@ class Base(SubstitutionEnvironment): self.decide_target = default_decide_target self.decide_source = default_decide_source + self.copy_from_cache = default_copy_from_cache + self._dict['BUILDERS'] = BuilderDict(self._dict['BUILDERS'], self) if platform is None: @@ -1137,7 +1152,7 @@ class Base(SubstitutionEnvironment): clone.added_methods = [] for mw in self.added_methods: - mw.clone(clone) + clone.added_methods.append(mw.clone(clone)) clone._memo = {} @@ -1185,7 +1200,14 @@ class Base(SubstitutionEnvironment): def _changed_timestamp_match(self, dependency, target, prev_ni): return dependency.changed_timestamp_match(target, prev_ni) + def _copy_from_cache(self, src, dst): + return self.fs.copy(src, dst) + + def _copy2_from_cache(self, src, dst): + return self.fs.copy2(src, dst) + def Decider(self, function): + copy_function = self._copy2_from_cache if function in ('MD5', 'content'): if not SCons.Util.md5: raise UserError, "MD5 signatures are not available in this version of Python." @@ -1194,6 +1216,7 @@ class Base(SubstitutionEnvironment): function = self._changed_timestamp_then_content elif function in ('timestamp-newer', 'make'): function = self._changed_timestamp_newer + copy_function = self._copy_from_cache elif function == 'timestamp-match': function = self._changed_timestamp_match elif not callable(function): @@ -1205,6 +1228,8 @@ class Base(SubstitutionEnvironment): self.decide_target = function self.decide_source = function + self.copy_from_cache = copy_function + def Detect(self, progs): """Return the first available program in progs. """ @@ -1706,7 +1731,11 @@ class Base(SubstitutionEnvironment): """Directly execute an action through an Environment """ action = apply(self.Action, (action,) + args, kw) - return action([], [], self) + result = action([], [], self) + if isinstance(result, SCons.Errors.BuildError): + return result.status + else: + return result def File(self, name, *args, **kw): """ @@ -1728,6 +1757,9 @@ class Base(SubstitutionEnvironment): else: return result[0] + def Glob(self, pattern, ondisk=True, source=False, strings=False): + return self.fs.Glob(self.subst(pattern), ondisk, source, strings) + def Ignore(self, target, dependency): """Ignore a dependency.""" tlist = self.arg2nodes(target, self.fs.Entry) @@ -1763,6 +1795,16 @@ class Base(SubstitutionEnvironment): dirs = self.arg2nodes(list(dirs), self.fs.Dir) apply(self.fs.Repository, dirs, kw) + def Requires(self, target, prerequisite): + """Specify that 'prerequisite' must be built before 'target', + (but 'target' does not actually depend on 'prerequisite' + and need not be rebuilt if it changes).""" + tlist = self.arg2nodes(target, self.fs.Entry) + plist = self.arg2nodes(prerequisite, self.fs.Entry) + for t in tlist: + t.add_prerequisite(plist) + return tlist + def Scanner(self, *args, **kw): nargs = [] for arg in args: @@ -1810,7 +1852,7 @@ class Base(SubstitutionEnvironment): raise UserError, "MD5 signatures are not available in this version of Python." self.decide_source = self._changed_content elif type == 'timestamp': - self.decide_source = self._changed_timestamp_newer + self.decide_source = self._changed_timestamp_match else: raise UserError, "Unknown source signature type '%s'" % type @@ -1840,7 +1882,7 @@ class Base(SubstitutionEnvironment): raise UserError, "MD5 signatures are not available in this version of Python." self.decide_target = self._changed_content elif type == 'timestamp': - self.decide_target = self._changed_timestamp_newer + self.decide_target = self._changed_timestamp_match elif type == 'build': self.decide_target = self._changed_build elif type == 'source': diff --git a/src/engine/SCons/EnvironmentTests.py b/src/engine/SCons/EnvironmentTests.py index 630f594..3f64d43 100644 --- a/src/engine/SCons/EnvironmentTests.py +++ b/src/engine/SCons/EnvironmentTests.py @@ -674,6 +674,21 @@ sys.exit(1) r = env.func('-yyy') assert r == 'func2-foo-yyy', r + # Test that clones of clones correctly re-bind added methods. + env1 = Environment(FOO = '1') + env1.AddMethod(func2) + env2 = env1.Clone(FOO = '2') + env3 = env2.Clone(FOO = '3') + env4 = env3.Clone(FOO = '4') + r = env1.func2() + assert r == 'func2-1', r + r = env2.func2() + assert r == 'func2-2', r + r = env3.func2() + assert r == 'func2-3', r + r = env4.func2() + assert r == 'func2-4', r + def test_Override(self): "Test overriding construction variables" env = SubstitutionEnvironment(ONE=1, TWO=2, THREE=3, FOUR=4) diff --git a/src/engine/SCons/Errors.py b/src/engine/SCons/Errors.py index 3ee7ff4..fc55cf4 100644 --- a/src/engine/SCons/Errors.py +++ b/src/engine/SCons/Errors.py @@ -33,10 +33,16 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" class BuildError(Exception): - def __init__(self, node=None, errstr="Unknown error", filename=None, *args): + def __init__(self, node=None, errstr="Unknown error", status=0, + filename=None, executor=None, action=None, command=None, + *args): self.node = node self.errstr = errstr + self.status = status self.filename = filename + self.executor = executor + self.action = action + self.command = command apply(Exception.__init__, (self,) + args) class InternalError(Exception): diff --git a/src/engine/SCons/Executor.py b/src/engine/SCons/Executor.py index 88a46cc..1cb0cf9 100644 --- a/src/engine/SCons/Executor.py +++ b/src/engine/SCons/Executor.py @@ -33,6 +33,7 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import string from SCons.Debug import logInstanceCreation +import SCons.Errors import SCons.Memoize @@ -59,6 +60,7 @@ class Executor: self.overridelist = overridelist self.targets = targets self.sources = sources[:] + self.sources_need_sorting = False self.builder_kw = builder_kw self._memo = {} @@ -110,7 +112,7 @@ class Executor: cwd = self.targets[0].cwd except (IndexError, AttributeError): cwd = None - return scanner.path(env, cwd, self.targets, self.sources) + return scanner.path(env, cwd, self.targets, self.get_sources()) def get_kw(self, kw={}): result = self.builder_kw.copy() @@ -126,9 +128,13 @@ class Executor: kw = self.get_kw(kw) status = 0 for act in self.get_action_list(): - status = apply(act, (self.targets, self.sources, env), kw) - if status: - break + status = apply(act, (self.targets, self.get_sources(), env), kw) + if isinstance(status, SCons.Errors.BuildError): + status.executor = self + raise status + elif status: + msg = "Error %s" % status + raise SCons.Errors.BuildError(errstr=msg, executor=self, action=act) return status # use extra indirection because with new-style objects (Python 2.2 @@ -145,8 +151,14 @@ class Executor: """Add source files to this Executor's list. This is necessary for "multi" Builders that can be called repeatedly to build up a source file list for a given target.""" - slist = filter(lambda x, s=self.sources: x not in s, sources) - self.sources.extend(slist) + self.sources.extend(sources) + self.sources_need_sorting = True + + def get_sources(self): + if self.sources_need_sorting: + self.sources = SCons.Util.uniquer_hashables(self.sources) + self.sources_need_sorting = False + return self.sources def add_pre_action(self, action): self.pre_actions.append(action) @@ -158,7 +170,7 @@ class Executor: def my_str(self): env = self.get_build_env() - get = lambda action, t=self.targets, s=self.sources, e=env: \ + get = lambda action, t=self.targets, s=self.get_sources(), e=env: \ action.genstring(t, s, e) return string.join(map(get, self.get_action_list()), "\n") @@ -183,7 +195,7 @@ class Executor: except KeyError: pass env = self.get_build_env() - get = lambda action, t=self.targets, s=self.sources, e=env: \ + get = lambda action, t=self.targets, s=self.get_sources(), e=env: \ action.get_contents(t, s, e) result = string.join(map(get, self.get_action_list()), "") self._memo['get_contents'] = result @@ -201,7 +213,7 @@ class Executor: def scan_sources(self, scanner): if self.sources: - self.scan(scanner, self.sources) + self.scan(scanner, self.get_sources()) def scan(self, scanner, node_list): """Scan a list of this Executor's files (targets or sources) for @@ -241,7 +253,7 @@ class Executor: def get_missing_sources(self): """ """ - return filter(lambda s: s.missing(), self.sources) + return filter(lambda s: s.missing(), self.get_sources()) def _get_unignored_sources_key(self, ignore=()): return tuple(ignore) @@ -261,9 +273,12 @@ class Executor: except KeyError: pass - sourcelist = self.sources + sourcelist = self.get_sources() if ignore: - sourcelist = filter(lambda s, i=ignore: not s in i, sourcelist) + idict = {} + for i in ignore: + idict[i] = 1 + sourcelist = filter(lambda s, i=idict: not i.has_key(s), sourcelist) memo_dict[ignore] = sourcelist @@ -299,7 +314,7 @@ class Executor: result = [] build_env = self.get_build_env() for act in self.get_action_list(): - result.extend(act.get_implicit_deps(self.targets, self.sources, build_env)) + result.extend(act.get_implicit_deps(self.targets, self.get_sources(), build_env)) return result diff --git a/src/engine/SCons/ExecutorTests.py b/src/engine/SCons/ExecutorTests.py index 59deca5..368e034 100644 --- a/src/engine/SCons/ExecutorTests.py +++ b/src/engine/SCons/ExecutorTests.py @@ -236,7 +236,12 @@ class ExecutorTestCase(unittest.TestCase): x = SCons.Executor.Executor(a, env, [], t, ['s1', 's2']) x.add_pre_action(pre_err) x.add_post_action(post) - x(t) + try: + x(t) + except SCons.Errors.BuildError: + pass + else: + raise Exception, "Did not catch expected BuildError" assert result == ['pre_err'], result del result[:] @@ -265,8 +270,19 @@ class ExecutorTestCase(unittest.TestCase): x = SCons.Executor.Executor('b', 'e', 'o', 't', ['s1', 's2']) assert x.sources == ['s1', 's2'], x.sources x.add_sources(['s1', 's2']) + assert x.sources == ['s1', 's2', 's1', 's2'], x.sources + x.add_sources(['s3', 's1', 's4']) + assert x.sources == ['s1', 's2', 's1', 's2', 's3', 's1', 's4'], x.sources + + def test_get_sources(self): + """Test getting sources from an Executor""" + x = SCons.Executor.Executor('b', 'e', 'o', 't', ['s1', 's2']) + assert x.sources == ['s1', 's2'], x.sources + x.add_sources(['s1', 's2']) + x.get_sources() assert x.sources == ['s1', 's2'], x.sources x.add_sources(['s3', 's1', 's4']) + x.get_sources() assert x.sources == ['s1', 's2', 's3', 's4'], x.sources def test_add_pre_action(self): diff --git a/src/engine/SCons/Node/Alias.py b/src/engine/SCons/Node/Alias.py index 15de664..bb23d3f 100644 --- a/src/engine/SCons/Node/Alias.py +++ b/src/engine/SCons/Node/Alias.py @@ -91,9 +91,9 @@ class Alias(SCons.Node.Node): def get_contents(self): """The contents of an alias is the concatenation - of all the contents of its sources""" - contents = map(lambda n: n.get_contents(), self.children()) - return string.join(contents, '') + of the content signatures of all its sources.""" + childsigs = map(lambda n: n.get_csig(), self.children()) + return string.join(childsigs, '') def sconsign(self): """An Alias is not recorded in .sconsign files""" @@ -133,7 +133,7 @@ class Alias(SCons.Node.Node): return self.ninfo.csig except AttributeError: pass - + contents = self.get_contents() csig = SCons.Util.MD5signature(contents) self.get_ninfo().csig = csig diff --git a/src/engine/SCons/Node/AliasTests.py b/src/engine/SCons/Node/AliasTests.py index 755cf75..02488f0 100644 --- a/src/engine/SCons/Node/AliasTests.py +++ b/src/engine/SCons/Node/AliasTests.py @@ -54,6 +54,8 @@ class AliasTestCase(unittest.TestCase): class DummyNode: def __init__(self, contents): self.contents = contents + def get_csig(self): + return self.contents def get_contents(self): return self.contents diff --git a/src/engine/SCons/Node/FS.py b/src/engine/SCons/Node/FS.py index 964af62..d0843d1 100644 --- a/src/engine/SCons/Node/FS.py +++ b/src/engine/SCons/Node/FS.py @@ -35,8 +35,10 @@ that can be used by scripts or modules looking for the canonical default. __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" +import fnmatch import os import os.path +import re import shutil import stat import string @@ -85,6 +87,42 @@ def save_strings(val): Save_Strings = val # +# Avoid unnecessary function calls by recording a Boolean value that +# tells us whether or not os.path.splitdrive() actually does anything +# on this system, and therefore whether we need to bother calling it +# when looking up path names in various methods below. +# + +do_splitdrive = None + +def initialize_do_splitdrive(): + global do_splitdrive + drive, path = os.path.splitdrive('X:/foo') + do_splitdrive = not not drive + +initialize_do_splitdrive() + +# + +needs_normpath_check = None + +def initialize_normpath_check(): + """ + Initialize the normpath_check regular expression. + + This function is used by the unit tests to re-initialize the pattern + when testing for behavior with different values of os.sep. + """ + global needs_normpath_check + if os.sep == '/': + pattern = r'.*/|\.$|\.\.$' + else: + pattern = r'.*[/%s]|\.$|\.\.$' % re.escape(os.sep) + needs_normpath_check = re.compile(pattern) + +initialize_normpath_check() + +# # SCons.Action objects for interacting with the outside world. # # The Node.FS methods in this module should use these actions to @@ -544,9 +582,29 @@ class Base(SCons.Node.Node): return result def _get_str(self): + global Save_Strings if self.duplicate or self.is_derived(): return self.get_path() - return self.srcnode().get_path() + srcnode = self.srcnode() + if srcnode.stat() is None and not self.stat() is None: + result = self.get_path() + else: + result = srcnode.get_path() + if not Save_Strings: + # We're not at the point where we're saving the string string + # representations of FS Nodes (because we haven't finished + # reading the SConscript files and need to have str() return + # things relative to them). That also means we can't yet + # cache values returned (or not returned) by stat(), since + # Python code in the SConscript files might still create + # or otherwise affect the on-disk file. So get rid of the + # values that the underlying stat() method saved. + try: del self._memo['stat'] + except KeyError: pass + if not self is srcnode: + try: del srcnode._memo['stat'] + except KeyError: pass + return result rstr = __str__ @@ -607,15 +665,11 @@ class Base(SCons.Node.Node): corresponding to its source file. Otherwise, return ourself. """ - dir=self.dir - name=self.name - while dir: - if dir.srcdir: - srcnode = dir.srcdir.Entry(name) - srcnode.must_be_same(self.__class__) - return srcnode - name = dir.name + os.sep + name - dir = dir.up() + srcdir_list = self.dir.srcdir_list() + if srcdir_list: + srcnode = srcdir_list[0].Entry(self.name) + srcnode.must_be_same(self.__class__) + return srcnode return self def get_path(self, dir=None): @@ -673,7 +727,7 @@ class Base(SCons.Node.Node): def target_from_source(self, prefix, suffix, splitext=SCons.Util.splitext): """ - Generates a target entry that corresponds to this entry (usually + Generates a target entry that corresponds to this entry (usually a source file) with the specified prefix and suffix. Note that this method can be overridden dynamically for generated @@ -745,6 +799,9 @@ class Base(SCons.Node.Node): self._memo['rentry'] = result return result + def _glob1(self, pattern, ondisk=True, source=False, strings=False): + return [] + class Entry(Base): """This is the class for generic Node.FS entries--that is, things that could be a File or a Dir, but we're just not sure yet. @@ -845,11 +902,11 @@ class Entry(Base): directory.""" return self.disambiguate().exists() -# def rel_path(self, other): -# d = self.disambiguate() -# if d.__class__ == Entry: -# raise "rel_path() could not disambiguate File/Dir" -# return d.rel_path(other) + def rel_path(self, other): + d = self.disambiguate() + if d.__class__ == Entry: + raise "rel_path() could not disambiguate File/Dir" + return d.rel_path(other) def new_ninfo(self): return self.disambiguate().new_ninfo() @@ -857,6 +914,9 @@ class Entry(Base): def changed_since_last_build(self, target, prev_ni): return self.disambiguate().changed_since_last_build(target, prev_ni) + def _glob1(self, pattern, ondisk=True, source=False, strings=False): + return self.disambiguate()._glob1(pattern, ondisk, source, strings) + # This is for later so we can differentiate between Entry the class and Entry # the method of the FS class. _classEntry = Entry @@ -885,6 +945,8 @@ class LocalFS: # return os.chdir(path) def chmod(self, path, mode): return os.chmod(path, mode) + def copy(self, src, dst): + return shutil.copy(src, dst) def copy2(self, src, dst): return shutil.copy2(src, dst) def exists(self, path): @@ -975,8 +1037,8 @@ class FS(LocalFS): self.Top.tpath = '.' self._cwd = self.Top - DirNodeInfo.top = self.Top - FileNodeInfo.top = self.Top + DirNodeInfo.fs = self + FileNodeInfo.fs = self def set_SConstruct_dir(self, dir): self.SConstruct_dir = dir @@ -1028,11 +1090,16 @@ class FS(LocalFS): This translates arbitrary input into a canonical Node.FS object of the specified fsclass. The general approach for strings is - to turn it into a normalized absolute path and then call the - root directory's lookup_abs() method for the heavy lifting. + to turn it into a fully normalized absolute path and then call + the root directory's lookup_abs() method for the heavy lifting. If the path name begins with '#', it is unconditionally - interpreted relative to the top-level directory of this FS. + interpreted relative to the top-level directory of this FS. '#' + is treated as a synonym for the top-level SConstruct directory, + much like '~' is treated as a synonym for the user's home + directory in a UNIX shell. So both '#foo' and '#/foo' refer + to the 'foo' subdirectory underneath the top-level SConstruct + directory. If the path name is relative, then the path is looked up relative to the specified directory, or the current directory (self._cwd, @@ -1046,33 +1113,53 @@ class FS(LocalFS): return p # str(p) in case it's something like a proxy object p = str(p) - drive, p = os.path.splitdrive(p) + + initial_hash = (p[0:1] == '#') + if initial_hash: + # There was an initial '#', so we strip it and override + # whatever directory they may have specified with the + # top-level SConstruct directory. + p = p[1:] + directory = self.Top + + if directory and not isinstance(directory, Dir): + directory = self.Dir(directory) + + if do_splitdrive: + drive, p = os.path.splitdrive(p) + else: + drive = '' if drive and not p: - # A drive letter without a path... + # This causes a naked drive letter to be treated as a synonym + # for the root directory on that drive. p = os.sep - root = self.get_root(drive) - elif os.path.isabs(p): - # An absolute path... + absolute = os.path.isabs(p) + + needs_normpath = needs_normpath_check.match(p) + + if initial_hash or not absolute: + # This is a relative lookup, either to the top-level + # SConstruct directory (because of the initial '#') or to + # the current directory (the path name is not absolute). + # Add the string to the appropriate directory lookup path, + # after which the whole thing gets normalized. + if not directory: + directory = self._cwd + if p: + p = directory.labspath + '/' + p + else: + p = directory.labspath + + if needs_normpath: p = os.path.normpath(p) + + if drive or absolute: root = self.get_root(drive) else: - if p[0:1] == '#': - # A top-relative path... - directory = self.Top - offset = 1 - if p[1:2] in(os.sep, '/'): - offset = 2 - p = p[offset:] - else: - # A relative path... - if not directory: - # ...to the current (SConscript) directory. - directory = self._cwd - elif not isinstance(directory, Dir): - # ...to the specified directory. - directory = self.Dir(directory) - p = os.path.normpath(directory.labspath + '/' + p) + if not directory: + directory = self._cwd root = directory.root + if os.sep != '/': p = string.replace(p, os.sep, '/') return root._lookup_abs(p, fsclass, create) @@ -1098,7 +1185,7 @@ class FS(LocalFS): """ return self._lookup(name, directory, File, create) - def Dir(self, name, directory = None, create = 1): + def Dir(self, name, directory = None, create = True): """Lookup or create a Dir node with the specified name. If the name is a relative path (begins with ./, ../, or a file name), then it is looked up relative to the supplied directory node, @@ -1160,24 +1247,41 @@ class FS(LocalFS): message = fmt % string.join(map(str, targets)) return targets, message + def Glob(self, pathname, ondisk=True, source=True, strings=False, cwd=None): + """ + Globs + + This is mainly a shim layer + """ + if cwd is None: + cwd = self.getcwd() + return cwd.glob(pathname, ondisk, source, strings) + class DirNodeInfo(SCons.Node.NodeInfoBase): # This should get reset by the FS initialization. current_version_id = 1 - top = None + fs = None def str_to_node(self, s): - top = self.top - if os.path.isabs(s): - n = top.fs._lookup(s, top, Entry) - else: + top = self.fs.Top + root = top.root + if do_splitdrive: + drive, s = os.path.splitdrive(s) + if drive: + root = self.fs.get_root(drive) + if not os.path.isabs(s): s = top.labspath + '/' + s - n = top.root._lookup_abs(s, Entry) - return n + return root._lookup_abs(s, Entry) class DirBuildInfo(SCons.Node.BuildInfoBase): current_version_id = 1 +glob_magic_check = re.compile('[*?[]') + +def has_glob_magic(s): + return glob_magic_check.search(s) is not None + class Dir(Base): """A class for directories in a file system. """ @@ -1252,12 +1356,12 @@ class Dir(Base): """ return self.fs.Entry(name, self) - def Dir(self, name): + def Dir(self, name, create=True): """ Looks up or creates a directory node named 'name' relative to this directory. """ - dir = self.fs.Dir(name, self) + dir = self.fs.Dir(name, self, create) return dir def File(self, name): @@ -1313,7 +1417,10 @@ class Dir(Base): while dir: for rep in dir.getRepositories(): result.append(rep.Dir(fname)) - fname = dir.name + os.sep + fname + if fname == '.': + fname = dir.name + else: + fname = dir.name + os.sep + fname dir = dir.up() self._memo['get_all_rdirs'] = result @@ -1329,66 +1436,68 @@ class Dir(Base): def up(self): return self.entries['..'] -# This complicated method, which constructs relative paths between -# arbitrary Node.FS objects, is no longer used. It was introduced to -# store dependency paths in .sconsign files relative to the target, but -# that ended up being significantly inefficient. We're leaving the code -# here, commented out, because it would be too easy for someone to decide -# to re-invent this wheel in the future (if it becomes necessary) because -# they didn't know this code was buried in some source-code change from -# the distant past... -# -# def _rel_path_key(self, other): -# return str(other) -# -# memoizer_counters.append(SCons.Memoize.CountDict('rel_path', _rel_path_key)) -# -# def rel_path(self, other): -# """Return a path to "other" relative to this directory. -# """ -# try: -# memo_dict = self._memo['rel_path'] -# except KeyError: -# memo_dict = {} -# self._memo['rel_path'] = memo_dict -# else: -# try: -# return memo_dict[other] -# except KeyError: -# pass -# -# if self is other: -# -# result = '.' -# -# elif not other in self.path_elements: -# -# try: -# other_dir = other.get_dir() -# except AttributeError: -# result = str(other) -# else: -# if other_dir is None: -# result = other.name -# else: -# dir_rel_path = self.rel_path(other_dir) -# if dir_rel_path == '.': -# result = other.name -# else: -# result = dir_rel_path + os.sep + other.name -# -# else: -# -# i = self.path_elements.index(other) + 1 -# -# path_elems = ['..'] * (len(self.path_elements) - i) \ -# + map(lambda n: n.name, other.path_elements[i:]) -# -# result = string.join(path_elems, os.sep) -# -# memo_dict[other] = result -# -# return result + def _rel_path_key(self, other): + return str(other) + + memoizer_counters.append(SCons.Memoize.CountDict('rel_path', _rel_path_key)) + + def rel_path(self, other): + """Return a path to "other" relative to this directory. + """ + + # This complicated and expensive method, which constructs relative + # paths between arbitrary Node.FS objects, is no longer used + # by SCons itself. It was introduced to store dependency paths + # in .sconsign files relative to the target, but that ended up + # being significantly inefficient. + # + # We're continuing to support the method because some SConstruct + # files out there started using it when it was available, and + # we're all about backwards compatibility.. + + try: + memo_dict = self._memo['rel_path'] + except KeyError: + memo_dict = {} + self._memo['rel_path'] = memo_dict + else: + try: + return memo_dict[other] + except KeyError: + pass + + if self is other: + + result = '.' + + elif not other in self.path_elements: + + try: + other_dir = other.get_dir() + except AttributeError: + result = str(other) + else: + if other_dir is None: + result = other.name + else: + dir_rel_path = self.rel_path(other_dir) + if dir_rel_path == '.': + result = other.name + else: + result = dir_rel_path + os.sep + other.name + + else: + + i = self.path_elements.index(other) + 1 + + path_elems = ['..'] * (len(self.path_elements) - i) \ + + map(lambda n: n.name, other.path_elements[i:]) + + result = string.join(path_elems, os.sep) + + memo_dict[other] = result + + return result def get_env_scanner(self, env, kw={}): import SCons.Defaults @@ -1575,13 +1684,7 @@ class Dir(Base): dir = self while dir: if dir.srcdir: - d = dir.srcdir.Dir(dirname) - if d.is_under(dir): - # Shouldn't source from something in the build path: - # build_dir is probably under src_dir, in which case - # we are reflecting. - break - result.append(d) + result.append(dir.srcdir.Dir(dirname)) dirname = dir.name + os.sep + dirname dir = dir.up() @@ -1591,6 +1694,11 @@ class Dir(Base): def srcdir_duplicate(self, name): for dir in self.srcdir_list(): + if self.is_under(dir): + # We shouldn't source from something in the build path; + # build_dir is probably under src_dir, in which case + # we are reflecting. + break if dir.entry_exists_on_disk(name): srcnode = dir.Entry(name).disambiguate() if self.duplicate: @@ -1693,6 +1801,118 @@ class Dir(Base): for dirname in filter(select_dirs, names): entries[dirname].walk(func, arg) + def glob(self, pathname, ondisk=True, source=False, strings=False): + """ + Returns a list of Nodes (or strings) matching a specified + pathname pattern. + + Pathname patterns follow UNIX shell semantics: * matches + any-length strings of any characters, ? matches any character, + and [] can enclose lists or ranges of characters. Matches do + not span directory separators. + + The matches take into account Repositories, returning local + Nodes if a corresponding entry exists in a Repository (either + an in-memory Node or something on disk). + + By defafult, the glob() function matches entries that exist + on-disk, in addition to in-memory Nodes. Setting the "ondisk" + argument to False (or some other non-true value) causes the glob() + function to only match in-memory Nodes. The default behavior is + to return both the on-disk and in-memory Nodes. + + The "source" argument, when true, specifies that corresponding + source Nodes must be returned if you're globbing in a build + directory (initialized with BuildDir()). The default behavior + is to return Nodes local to the BuildDir(). + + The "strings" argument, when true, returns the matches as strings, + not Nodes. The strings are path names relative to this directory. + + The underlying algorithm is adapted from the glob.glob() function + in the Python library (but heavily modified), and uses fnmatch() + under the covers. + """ + dirname, basename = os.path.split(pathname) + if not dirname: + return self._glob1(basename, ondisk, source, strings) + if has_glob_magic(dirname): + list = self.glob(dirname, ondisk, source, strings=False) + else: + list = [self.Dir(dirname, create=True)] + result = [] + for dir in list: + r = dir._glob1(basename, ondisk, source, strings) + if strings: + r = map(lambda x, d=str(dir): os.path.join(d, x), r) + result.extend(r) + return result + + def _glob1(self, pattern, ondisk=True, source=False, strings=False): + """ + Globs for and returns a list of entry names matching a single + pattern in this directory. + + This searches any repositories and source directories for + corresponding entries and returns a Node (or string) relative + to the current directory if an entry is found anywhere. + + TODO: handle pattern with no wildcard + """ + search_dir_list = self.get_all_rdirs() + for srcdir in self.srcdir_list(): + search_dir_list.extend(srcdir.get_all_rdirs()) + + names = [] + for dir in search_dir_list: + # We use the .name attribute from the Node because the keys of + # the dir.entries dictionary are normalized (that is, all upper + # case) on case-insensitive systems like Windows. + #node_names = [ v.name for k, v in dir.entries.items() if k not in ('.', '..') ] + entry_names = filter(lambda n: n not in ('.', '..'), dir.entries.keys()) + node_names = map(lambda n, e=dir.entries: e[n].name, entry_names) + names.extend(node_names) + if ondisk: + try: + disk_names = os.listdir(dir.abspath) + except os.error: + pass + else: + names.extend(disk_names) + if not strings: + # We're going to return corresponding Nodes in + # the local directory, so we need to make sure + # those Nodes exist. We only want to create + # Nodes for the entries that will match the + # specified pattern, though, which means we + # need to filter the list here, even though + # the overall list will also be filtered later, + # after we exit this loop. + if pattern[0] != '.': + #disk_names = [ d for d in disk_names if d[0] != '.' ] + disk_names = filter(lambda x: x[0] != '.', disk_names) + disk_names = fnmatch.filter(disk_names, pattern) + rep_nodes = map(dir.Entry, disk_names) + #rep_nodes = [ n.disambiguate() for n in rep_nodes ] + rep_nodes = map(lambda n: n.disambiguate(), rep_nodes) + for node, name in zip(rep_nodes, disk_names): + n = self.Entry(name) + if n.__class__ != node.__class__: + n.__class__ = node.__class__ + n._morph() + + names = set(names) + if pattern[0] != '.': + #names = [ n for n in names if n[0] != '.' ] + names = filter(lambda x: x[0] != '.', names) + names = fnmatch.filter(names, pattern) + + if strings: + return names + + #return [ self.entries[_my_normcase(n)] for n in names ] + return map(lambda n, e=self.entries: e[_my_normcase(n)], names) + class RootDir(Dir): """A class for the root directory of a file system. @@ -1817,16 +2037,18 @@ class FileNodeInfo(SCons.Node.NodeInfoBase): field_list = ['csig', 'timestamp', 'size'] # This should get reset by the FS initialization. - top = None + fs = None def str_to_node(self, s): - top = self.top - if os.path.isabs(s): - n = top.fs._lookup(s, top, Entry) - else: + top = self.fs.Top + root = top.root + if do_splitdrive: + drive, s = os.path.splitdrive(s) + if drive: + root = self.fs.get_root(drive) + if not os.path.isabs(s): s = top.labspath + '/' + s - n = top.root._lookup_abs(s, Entry) - return n + return root._lookup_abs(s, Entry) class FileBuildInfo(SCons.Node.BuildInfoBase): current_version_id = 1 @@ -1925,10 +2147,10 @@ class File(Base): the SConscript directory of this file.""" return self.cwd.Entry(name) - def Dir(self, name): + def Dir(self, name, create=True): """Create a directory node named 'name' relative to the SConscript directory of this file.""" - return self.cwd.Dir(name) + return self.cwd.Dir(name, create) def Dirs(self, pathlist): """Create a list of directories relative to the SConscript @@ -2171,8 +2393,8 @@ class File(Base): try: return binfo.bimplicit except AttributeError: return None -# def rel_path(self, other): -# return self.dir.rel_path(other) + def rel_path(self, other): + return self.dir.rel_path(other) def _get_found_includes_key(self, env, scanner, path): return (id(env), id(scanner), path) @@ -2329,7 +2551,9 @@ class File(Base): def _rmv_existing(self): self.clear_memoized_values() - Unlink(self, [], None) + e = Unlink(self, [], None) + if isinstance(e, SCons.Errors.BuildError): + raise e # # Taskmaster interface subsystem @@ -2367,13 +2591,9 @@ class File(Base): def do_duplicate(self, src): self._createDir() - try: - Unlink(self, None, None) - except SCons.Errors.BuildError: - pass - try: - Link(self, src, None) - except SCons.Errors.BuildError, e: + Unlink(self, None, None) + e = Link(self, src, None) + if isinstance(e, SCons.Errors.BuildError): desc = "Cannot duplicate `%s' in `%s': %s." % (src.path, self.dir.path, e.errstr) raise SCons.Errors.StopError, desc self.linked = 1 @@ -2440,7 +2660,7 @@ class File(Base): # which can be the case if they've disabled disk checks, # or if an action with a File target actually happens to # create a same-named directory by mistake. - csig = None + csig = '' else: csig = SCons.Util.MD5signature(contents) @@ -2511,7 +2731,9 @@ class File(Base): # ...and it's even up-to-date... if self._local: # ...and they'd like a local copy. - LocalCopy(self, r, None) + e = LocalCopy(self, r, None) + if isinstance(e, SCons.Errors.BuildError): + raise self.store_info() if T: Trace(' 1\n') return 1 @@ -2610,6 +2832,39 @@ class FileFinder: def __init__(self): self._memo = {} + def filedir_lookup(self, p, fd=None): + """ + A helper method for find_file() that looks up a directory for + a file we're trying to find. This only creates the Dir Node if + it exists on-disk, since if the directory doesn't exist we know + we won't find any files in it... :-) + + It would be more compact to just use this as a nested function + with a default keyword argument (see the commented-out version + below), but that doesn't work unless you have nested scopes, + so we define it here just this works work under Python 1.5.2. + """ + if fd is None: + fd = self.default_filedir + dir, name = os.path.split(fd) + drive, d = os.path.splitdrive(dir) + if d in ('/', os.sep): + return p + if dir: + p = self.filedir_lookup(p, dir) + if not p: + return None + norm_name = _my_normcase(name) + try: + node = p.entries[norm_name] + except KeyError: + return p.dir_on_disk(name) + # Once we move to Python 2.2 we can do: + #if isinstance(node, (Dir, Entry)): + if isinstance(node, Dir) or isinstance(node, Entry): + return node + return None + def _find_file_key(self, filename, paths, verbose=None): return (filename, paths) @@ -2655,14 +2910,35 @@ class FileFinder: filedir, filename = os.path.split(filename) if filedir: - def filedir_lookup(p, fd=filedir): - try: - return p.Dir(fd) - except TypeError: - # We tried to look up a Dir, but it seems there's - # already a File (or something else) there. No big. - return None - paths = filter(None, map(filedir_lookup, paths)) + # More compact code that we can't use until we drop + # support for Python 1.5.2: + # + #def filedir_lookup(p, fd=filedir): + # """ + # A helper function that looks up a directory for a file + # we're trying to find. This only creates the Dir Node + # if it exists on-disk, since if the directory doesn't + # exist we know we won't find any files in it... :-) + # """ + # dir, name = os.path.split(fd) + # if dir: + # p = filedir_lookup(p, dir) + # if not p: + # return None + # norm_name = _my_normcase(name) + # try: + # node = p.entries[norm_name] + # except KeyError: + # return p.dir_on_disk(name) + # # Once we move to Python 2.2 we can do: + # #if isinstance(node, (Dir, Entry)): + # if isinstance(node, Dir) or isinstance(node, Entry): + # return node + # return None + #paths = filter(None, map(filedir_lookup, paths)) + + self.default_filedir = filedir + paths = filter(None, map(self.filedir_lookup, paths)) result = None for dir in paths: diff --git a/src/engine/SCons/Node/FSTests.py b/src/engine/SCons/Node/FSTests.py index 225226d..b698e87 100644 --- a/src/engine/SCons/Node/FSTests.py +++ b/src/engine/SCons/Node/FSTests.py @@ -288,8 +288,9 @@ class BuildDirTestCase(unittest.TestCase): assert not f7.exists() assert f7.rexists() - assert f7.rfile().path == os.path.normpath(test.workpath('rep1/build/var1/test2.out')),\ - f7.rfile().path + r = f7.rfile().path + expect = os.path.normpath(test.workpath('rep1/build/var1/test2.out')) + assert r == expect, (repr(r), repr(expect)) assert not f8.exists() assert f8.rexists() @@ -534,6 +535,8 @@ class BuildDirTestCase(unittest.TestCase): 'work/src/b1/b2', 'work/src/b1/b2/b1', 'work/src/b1/b2/b1/b2', + 'work/src/b1/b2/b1/b2/b1', + 'work/src/b1/b2/b1/b2/b1/b2', ] srcnode_map = { @@ -543,6 +546,10 @@ class BuildDirTestCase(unittest.TestCase): 'work/src/b1/b2/b1/f' : 'work/src/b1/f', 'work/src/b1/b2/b1/b2' : 'work/src/b1/b2', 'work/src/b1/b2/b1/b2/f' : 'work/src/b1/b2/f', + 'work/src/b1/b2/b1/b2/b1' : 'work/src/b1/b2/b1', + 'work/src/b1/b2/b1/b2/b1/f' : 'work/src/b1/b2/b1/f', + 'work/src/b1/b2/b1/b2/b1/b2' : 'work/src/b1/b2/b1/b2', + 'work/src/b1/b2/b1/b2/b1/b2/f' : 'work/src/b1/b2/b1/b2/f', } alter_map = { @@ -910,43 +917,47 @@ class FSTestCase(_tempdirTestCase): drive, path = os.path.splitdrive(os.getcwd()) + def _do_Dir_test(lpath, path_, abspath_, up_path_, sep, fileSys=fs, drive=drive): + dir = fileSys.Dir(string.replace(lpath, '/', sep)) + + if os.sep != '/': + path_ = string.replace(path_, '/', os.sep) + abspath_ = string.replace(abspath_, '/', os.sep) + up_path_ = string.replace(up_path_, '/', os.sep) + + def strip_slash(p, drive=drive): + if p[-1] == os.sep and len(p) > 1: + p = p[:-1] + if p[0] == os.sep: + p = drive + p + return p + path = strip_slash(path_) + abspath = strip_slash(abspath_) + up_path = strip_slash(up_path_) + name = string.split(abspath, os.sep)[-1] + + assert dir.name == name, \ + "dir.name %s != expected name %s" % \ + (dir.name, name) + assert dir.path == path, \ + "dir.path %s != expected path %s" % \ + (dir.path, path) + assert str(dir) == path, \ + "str(dir) %s != expected path %s" % \ + (str(dir), path) + assert dir.get_abspath() == abspath, \ + "dir.abspath %s != expected absolute path %s" % \ + (dir.get_abspath(), abspath) + assert dir.up().path == up_path, \ + "dir.up().path %s != expected parent path %s" % \ + (dir.up().path, up_path) + for sep in seps: - def Dir_test(lpath, path_, abspath_, up_path_, fileSys=fs, s=sep, drive=drive): - dir = fileSys.Dir(string.replace(lpath, '/', s)) - - if os.sep != '/': - path_ = string.replace(path_, '/', os.sep) - abspath_ = string.replace(abspath_, '/', os.sep) - up_path_ = string.replace(up_path_, '/', os.sep) - - def strip_slash(p, drive=drive): - if p[-1] == os.sep and len(p) > 1: - p = p[:-1] - if p[0] == os.sep: - p = drive + p - return p - path = strip_slash(path_) - abspath = strip_slash(abspath_) - up_path = strip_slash(up_path_) - name = string.split(abspath, os.sep)[-1] - - assert dir.name == name, \ - "dir.name %s != expected name %s" % \ - (dir.name, name) - assert dir.path == path, \ - "dir.path %s != expected path %s" % \ - (dir.path, path) - assert str(dir) == path, \ - "str(dir) %s != expected path %s" % \ - (str(dir), path) - assert dir.get_abspath() == abspath, \ - "dir.abspath %s != expected absolute path %s" % \ - (dir.get_abspath(), abspath) - assert dir.up().path == up_path, \ - "dir.up().path %s != expected parent path %s" % \ - (dir.up().path, up_path) + def Dir_test(lpath, path_, abspath_, up_path_, sep=sep, func=_do_Dir_test): + return func(lpath, path_, abspath_, up_path_, sep) + Dir_test('', './', sub_dir, sub) Dir_test('foo', 'foo/', sub_dir_foo, './') Dir_test('foo/bar', 'foo/bar/', sub_dir_foo_bar, 'foo/') Dir_test('/foo', '/foo/', '/foo/', '/') @@ -1374,6 +1385,109 @@ class FSTestCase(_tempdirTestCase): f.get_string(0) assert f.get_string(1) == 'baz', f.get_string(1) + def test_drive_letters(self): + """Test drive-letter look-ups""" + + test = self.test + + test.subdir('sub', ['sub', 'dir']) + + def drive_workpath(drive, dirs, test=test): + x = apply(test.workpath, dirs) + drive, path = os.path.splitdrive(x) + return 'X:' + path + + wp = drive_workpath('X:', ['']) + + if wp[-1] in (os.sep, '/'): + tmp = os.path.split(wp[:-1])[0] + else: + tmp = os.path.split(wp)[0] + + parent_tmp = os.path.split(tmp)[0] + if parent_tmp == 'X:': + parent_tmp = 'X:' + os.sep + + tmp_foo = os.path.join(tmp, 'foo') + + foo = drive_workpath('X:', ['foo']) + foo_bar = drive_workpath('X:', ['foo', 'bar']) + sub = drive_workpath('X:', ['sub', '']) + sub_dir = drive_workpath('X:', ['sub', 'dir', '']) + sub_dir_foo = drive_workpath('X:', ['sub', 'dir', 'foo', '']) + sub_dir_foo_bar = drive_workpath('X:', ['sub', 'dir', 'foo', 'bar', '']) + sub_foo = drive_workpath('X:', ['sub', 'foo', '']) + + fs = SCons.Node.FS.FS() + + seps = [os.sep] + if os.sep != '/': + seps = seps + ['/'] + + def _do_Dir_test(lpath, path_, up_path_, sep, fileSys=fs): + dir = fileSys.Dir(string.replace(lpath, '/', sep)) + + if os.sep != '/': + path_ = string.replace(path_, '/', os.sep) + up_path_ = string.replace(up_path_, '/', os.sep) + + def strip_slash(p): + if p[-1] == os.sep and len(p) > 3: + p = p[:-1] + return p + path = strip_slash(path_) + up_path = strip_slash(up_path_) + name = string.split(path, os.sep)[-1] + + assert dir.name == name, \ + "dir.name %s != expected name %s" % \ + (dir.name, name) + assert dir.path == path, \ + "dir.path %s != expected path %s" % \ + (dir.path, path) + assert str(dir) == path, \ + "str(dir) %s != expected path %s" % \ + (str(dir), path) + assert dir.up().path == up_path, \ + "dir.up().path %s != expected parent path %s" % \ + (dir.up().path, up_path) + + save_os_path = os.path + save_os_sep = os.sep + try: + import ntpath + os.path = ntpath + os.sep = '\\' + SCons.Node.FS.initialize_do_splitdrive() + SCons.Node.FS.initialize_normpath_check() + + for sep in seps: + + def Dir_test(lpath, path_, up_path_, sep=sep, func=_do_Dir_test): + return func(lpath, path_, up_path_, sep) + + Dir_test('#X:', wp, tmp) + Dir_test('X:foo', foo, wp) + Dir_test('X:foo/bar', foo_bar, foo) + Dir_test('X:/foo', 'X:/foo', 'X:/') + Dir_test('X:/foo/bar', 'X:/foo/bar/', 'X:/foo/') + Dir_test('X:..', tmp, parent_tmp) + Dir_test('X:foo/..', wp, tmp) + Dir_test('X:../foo', tmp_foo, tmp) + Dir_test('X:.', wp, tmp) + Dir_test('X:./.', wp, tmp) + Dir_test('X:foo/./bar', foo_bar, foo) + Dir_test('#X:../foo', tmp_foo, tmp) + Dir_test('#X:/../foo', tmp_foo, tmp) + Dir_test('#X:foo/bar', foo_bar, foo) + Dir_test('#X:/foo/bar', foo_bar, foo) + Dir_test('#X:/', wp, tmp) + finally: + os.path = save_os_path + os.sep = save_os_sep + SCons.Node.FS.initialize_do_splitdrive() + SCons.Node.FS.initialize_normpath_check() + def test_target_from_source(self): """Test the method for generating target nodes from sources""" fs = self.fs @@ -1426,13 +1540,7 @@ class FSTestCase(_tempdirTestCase): above_path = apply(os.path.join, ['..']*len(dirs) + ['above']) above = d2.Dir(above_path) - # Note that the rel_path() method is not used right now, but we're - # leaving it commented out and disabling the unit here because - # it would be a shame to have to recreate the logic (or remember - # that it's buried in a long-past code checkin) if we ever need to - # resurrect it. - - def DO_NOT_test_rel_path(self): + def test_rel_path(self): """Test the rel_path() method""" test = self.test fs = self.fs @@ -1669,10 +1777,10 @@ class DirTestCase(_tempdirTestCase): check(s, ['src/b1']) s = b1_b2_b1_b2.srcdir_list() - check(s, []) + check(s, ['src/b1/b2']) s = b1_b2_b1_b2_sub.srcdir_list() - check(s, []) + check(s, ['src/b1/b2/sub']) def test_srcdir_duplicate(self): """Test the Dir.srcdir_duplicate() method @@ -1978,6 +2086,291 @@ class FileTestCase(_tempdirTestCase): +class GlobTestCase(_tempdirTestCase): + def setUp(self): + _tempdirTestCase.setUp(self) + + fs = SCons.Node.FS.FS() + self.fs = fs + + # Make entries on disk that will not have Nodes, so we can verify + # the behavior of looking for things on disk. + self.test.write('disk-aaa', "disk-aaa\n") + self.test.write('disk-bbb', "disk-bbb\n") + self.test.write('disk-ccc', "disk-ccc\n") + self.test.subdir('disk-sub') + self.test.write(['disk-sub', 'disk-ddd'], "disk-sub/disk-ddd\n") + self.test.write(['disk-sub', 'disk-eee'], "disk-sub/disk-eee\n") + self.test.write(['disk-sub', 'disk-fff'], "disk-sub/disk-fff\n") + + # Make some entries that have both Nodes and on-disk entries, + # so we can verify what we do with + self.test.write('both-aaa', "both-aaa\n") + self.test.write('both-bbb', "both-bbb\n") + self.test.write('both-ccc', "both-ccc\n") + self.test.subdir('both-sub1') + self.test.write(['both-sub1', 'both-ddd'], "both-sub1/both-ddd\n") + self.test.write(['both-sub1', 'both-eee'], "both-sub1/both-eee\n") + self.test.write(['both-sub1', 'both-fff'], "both-sub1/both-fff\n") + self.test.subdir('both-sub2') + self.test.write(['both-sub2', 'both-ddd'], "both-sub2/both-ddd\n") + self.test.write(['both-sub2', 'both-eee'], "both-sub2/both-eee\n") + self.test.write(['both-sub2', 'both-fff'], "both-sub2/both-fff\n") + + self.both_aaa = fs.File('both-aaa') + self.both_bbb = fs.File('both-bbb') + self.both_ccc = fs.File('both-ccc') + self.both_sub1 = fs.Dir('both-sub1') + self.both_sub1_both_ddd = self.both_sub1.File('both-ddd') + self.both_sub1_both_eee = self.both_sub1.File('both-eee') + self.both_sub1_both_fff = self.both_sub1.File('both-fff') + self.both_sub2 = fs.Dir('both-sub2') + self.both_sub2_both_ddd = self.both_sub2.File('both-ddd') + self.both_sub2_both_eee = self.both_sub2.File('both-eee') + self.both_sub2_both_fff = self.both_sub2.File('both-fff') + + # Make various Nodes (that don't have on-disk entries) so we + # can verify how we match them. + self.ggg = fs.File('ggg') + self.hhh = fs.File('hhh') + self.iii = fs.File('iii') + self.subdir1 = fs.Dir('subdir1') + self.subdir1_jjj = self.subdir1.File('jjj') + self.subdir1_kkk = self.subdir1.File('kkk') + self.subdir1_lll = self.subdir1.File('lll') + self.subdir2 = fs.Dir('subdir2') + self.subdir2_jjj = self.subdir2.File('jjj') + self.subdir2_kkk = self.subdir2.File('kkk') + self.subdir2_lll = self.subdir2.File('lll') + self.sub = fs.Dir('sub') + self.sub_dir3 = self.sub.Dir('dir3') + self.sub_dir3_jjj = self.sub_dir3.File('jjj') + self.sub_dir3_kkk = self.sub_dir3.File('kkk') + self.sub_dir3_lll = self.sub_dir3.File('lll') + + + def do_cases(self, cases, **kwargs): + + # First, execute all of the cases with string=True and verify + # that we get the expected strings returned. We do this first + # so the Glob() calls don't add Nodes to the self.fs file system + # hierarchy. + + import copy + strings_kwargs = copy.copy(kwargs) + strings_kwargs['strings'] = True + for input, string_expect, node_expect in cases: + r = apply(self.fs.Glob, (input,), strings_kwargs) + r.sort() + assert r == string_expect, "Glob(%s, strings=True) expected %s, got %s" % (input, string_expect, r) + + # Now execute all of the cases without string=True and look for + # the expected Nodes to be returned. If we don't have a list of + # actual expected Nodes, that means we're expecting a search for + # on-disk-only files to have returned some newly-created nodes. + # Verify those by running the list through str() before comparing + # them with the expected list of strings. + for input, string_expect, node_expect in cases: + r = apply(self.fs.Glob, (input,), kwargs) + if node_expect: + r.sort(lambda a,b: cmp(a.path, b.path)) + result = node_expect + else: + r = map(str, r) + r.sort() + result = string_expect + assert r == result, "Glob(%s) expected %s, got %s" % (input, map(str, result), map(str, r)) + + def test_exact_match(self): + """Test globbing for exact Node matches""" + join = os.path.join + + cases = ( + ('ggg', ['ggg'], [self.ggg]), + + ('subdir1', ['subdir1'], [self.subdir1]), + + ('subdir1/jjj', [join('subdir1', 'jjj')], [self.subdir1_jjj]), + + ('disk-aaa', ['disk-aaa'], None), + + ('disk-sub', ['disk-sub'], None), + + ('both-aaa', ['both-aaa'], []), + ) + + self.do_cases(cases) + + def test_subdir_matches(self): + """Test globbing for exact Node matches in subdirectories""" + join = os.path.join + + cases = ( + ('*/jjj', + [join('subdir1', 'jjj'), join('subdir2', 'jjj')], + [self.subdir1_jjj, self.subdir2_jjj]), + + ('*/disk-ddd', + [join('disk-sub', 'disk-ddd')], + None), + ) + + self.do_cases(cases) + + def test_asterisk(self): + """Test globbing for simple asterisk Node matches""" + cases = ( + ('h*', + ['hhh'], + [self.hhh]), + + ('*', + ['both-aaa', 'both-bbb', 'both-ccc', + 'both-sub1', 'both-sub2', + 'ggg', 'hhh', 'iii', + 'sub', 'subdir1', 'subdir2'], + [self.both_aaa, self.both_bbb, self.both_ccc, + self.both_sub1, self.both_sub2, + self.ggg, self.hhh, self.iii, + self.sub, self.subdir1, self.subdir2]), + ) + + self.do_cases(cases, ondisk=False) + + cases = ( + ('disk-b*', + ['disk-bbb'], + None), + + ('*', + ['both-aaa', 'both-bbb', 'both-ccc', 'both-sub1', 'both-sub2', + 'disk-aaa', 'disk-bbb', 'disk-ccc', 'disk-sub', + 'ggg', 'hhh', 'iii', + 'sub', 'subdir1', 'subdir2'], + None), + ) + + self.do_cases(cases) + + def test_question_mark(self): + """Test globbing for simple question-mark Node matches""" + join = os.path.join + + cases = ( + ('ii?', + ['iii'], + [self.iii]), + + ('both-sub?/both-eee', + [join('both-sub1', 'both-eee'), join('both-sub2', 'both-eee')], + [self.both_sub1_both_eee, self.both_sub2_both_eee]), + + ('subdir?/jjj', + [join('subdir1', 'jjj'), join('subdir2', 'jjj')], + [self.subdir1_jjj, self.subdir2_jjj]), + + ('disk-cc?', + ['disk-ccc'], + None), + ) + + self.do_cases(cases) + + def test_does_not_exist(self): + """Test globbing for things that don't exist""" + + cases = ( + ('does_not_exist', [], []), + ('no_subdir/*', [], []), + ('subdir?/no_file', [], []), + ) + + self.do_cases(cases) + + def test_subdir_asterisk(self): + """Test globbing for asterisk Node matches in subdirectories""" + join = os.path.join + + cases = ( + ('*/k*', + [join('subdir1', 'kkk'), join('subdir2', 'kkk')], + [self.subdir1_kkk, self.subdir2_kkk]), + + ('both-sub?/*', + [join('both-sub1', 'both-ddd'), + join('both-sub1', 'both-eee'), + join('both-sub1', 'both-fff'), + join('both-sub2', 'both-ddd'), + join('both-sub2', 'both-eee'), + join('both-sub2', 'both-fff')], + [self.both_sub1_both_ddd, self.both_sub1_both_eee, self.both_sub1_both_fff, + self.both_sub2_both_ddd, self.both_sub2_both_eee, self.both_sub2_both_fff], + ), + + ('subdir?/*', + [join('subdir1', 'jjj'), + join('subdir1', 'kkk'), + join('subdir1', 'lll'), + join('subdir2', 'jjj'), + join('subdir2', 'kkk'), + join('subdir2', 'lll')], + [self.subdir1_jjj, self.subdir1_kkk, self.subdir1_lll, + self.subdir2_jjj, self.subdir2_kkk, self.subdir2_lll]), + + ('sub/*/*', + [join('sub', 'dir3', 'jjj'), + join('sub', 'dir3', 'kkk'), + join('sub', 'dir3', 'lll')], + [self.sub_dir3_jjj, self.sub_dir3_kkk, self.sub_dir3_lll]), + + ('*/k*', + [join('subdir1', 'kkk'), join('subdir2', 'kkk')], + None), + + ('subdir?/*', + [join('subdir1', 'jjj'), + join('subdir1', 'kkk'), + join('subdir1', 'lll'), + join('subdir2', 'jjj'), + join('subdir2', 'kkk'), + join('subdir2', 'lll')], + None), + + ('sub/*/*', + [join('sub', 'dir3', 'jjj'), + join('sub', 'dir3', 'kkk'), + join('sub', 'dir3', 'lll')], + None), + ) + + self.do_cases(cases) + + def test_subdir_question(self): + """Test globbing for question-mark Node matches in subdirectories""" + join = os.path.join + + cases = ( + ('*/?kk', + [join('subdir1', 'kkk'), join('subdir2', 'kkk')], + [self.subdir1_kkk, self.subdir2_kkk]), + + ('subdir?/l?l', + [join('subdir1', 'lll'), join('subdir2', 'lll')], + [self.subdir1_lll, self.subdir2_lll]), + + ('*/disk-?ff', + [join('disk-sub', 'disk-fff')], + None), + + ('subdir?/l?l', + [join('subdir1', 'lll'), join('subdir2', 'lll')], + None), + ) + + self.do_cases(cases) + + + class RepositoryTestCase(_tempdirTestCase): def setUp(self): @@ -2379,7 +2772,7 @@ class StringDirTestCase(unittest.TestCase): fs = SCons.Node.FS.FS(test.workpath('')) d = fs.Dir('sub', '.') - assert str(d) == 'sub' + assert str(d) == 'sub', str(d) assert d.exists() f = fs.File('file', 'sub') assert str(f) == os.path.join('sub', 'file') @@ -2913,6 +3306,7 @@ if __name__ == "__main__": FileBuildInfoTestCase, FileNodeInfoTestCase, FSTestCase, + GlobTestCase, RepositoryTestCase, ] for tclass in tclasses: diff --git a/src/engine/SCons/Node/__init__.py b/src/engine/SCons/Node/__init__.py index 7ddca37..f252151 100644 --- a/src/engine/SCons/Node/__init__.py +++ b/src/engine/SCons/Node/__init__.py @@ -207,6 +207,7 @@ class Node: self.depends_dict = {} self.ignore = [] # dependencies to ignore self.ignore_dict = {} + self.prerequisites = SCons.Util.UniqueList() self.implicit = None # implicit (scanned) dependencies (None means not scanned yet) self.waiting_parents = {} self.waiting_s_e = {} @@ -361,11 +362,11 @@ class Node: in built(). """ - executor = self.get_executor() - stat = apply(executor, (self,), kw) - if stat: - msg = "Error %d" % stat - raise SCons.Errors.BuildError(node=self, errstr=msg) + try: + apply(self.get_executor(), (self,), kw) + except SCons.Errors.BuildError, e: + e.node = self + raise def built(self): """Called just after this node is successfully built.""" @@ -614,6 +615,7 @@ class Node: return build_env = self.get_build_env() + executor = self.get_executor() # Here's where we implement --implicit-cache. if implicit_cache and not implicit_deps_changed: @@ -623,7 +625,14 @@ class Node: # stored .sconsign entry to have already been converted # to Nodes for us. (We used to run them through a # source_factory function here.) - self._add_child(self.implicit, self.implicit_dict, implicit) + + # Update all of the targets with them. This + # essentially short-circuits an N*M scan of the + # sources for each individual target, which is a hell + # of a lot more efficient. + for tgt in executor.targets: + tgt.add_to_implicit(implicit) + if implicit_deps_unchanged or self.is_up_to_date(): return # one of this node's sources has changed, @@ -633,8 +642,6 @@ class Node: self._children_reset() self.del_binfo() - executor = self.get_executor() - # Have the executor scan the sources. executor.scan_sources(self.builder.source_scanner) @@ -825,6 +832,11 @@ class Node: s = str(e) raise SCons.Errors.UserError("attempted to add a non-Node dependency to %s:\n\t%s is a %s, not a Node" % (str(self), s, type(e))) + def add_prerequisite(self, prerequisite): + """Adds prerequisites""" + self.prerequisites.extend(prerequisite) + self._children_reset() + def add_ignore(self, depend): """Adds dependencies to ignore.""" try: @@ -1200,19 +1212,18 @@ class Node: lines = ["%s:\n" % preamble] + lines return string.join(lines, ' '*11) -l = [1] -ul = UserList.UserList([2]) try: - l.extend(ul) + [].extend(UserList.UserList([])) except TypeError: + # Python 1.5.2 doesn't allow a list to be extended by list-like + # objects (such as UserList instances), so just punt and use + # real lists. def NodeList(l): return l else: class NodeList(UserList.UserList): def __str__(self): return str(map(str, self.data)) -del l -del ul def get_children(node, parent): return node.children() def ignore_cycle(node, stack): pass diff --git a/src/engine/SCons/Options/OptionsTests.py b/src/engine/SCons/Options/OptionsTests.py index 95bd1cd..5ec9d7a 100644 --- a/src/engine/SCons/Options/OptionsTests.py +++ b/src/engine/SCons/Options/OptionsTests.py @@ -516,9 +516,37 @@ B 42 54 b - alpha test ['B'] assert text == expectAlpha, text - + +class UnknownOptionsTestCase(unittest.TestCase): + + def test_unknown(self): + """Test the UnknownOptions() method""" + opts = SCons.Options.Options() + opts.Add('ANSWER', + 'THE answer to THE question', + "42") + + args = { + 'ANSWER' : 'answer', + 'UNKNOWN' : 'unknown', + } + + env = Environment() + opts.Update(env, args) + + r = opts.UnknownOptions() + assert r == {'UNKNOWN' : 'unknown'}, r + assert env['ANSWER'] == 'answer', env['ANSWER'] + + + if __name__ == "__main__": - suite = unittest.makeSuite(OptionsTestCase, 'test_') + suite = unittest.TestSuite() + tclasses = [ OptionsTestCase, + UnknownOptionsTestCase ] + for tclass in tclasses: + names = unittest.getTestCaseNames(tclass, 'test_') + suite.addTests(map(tclass, names)) if not unittest.TextTestRunner().run(suite).wasSuccessful(): sys.exit(1) diff --git a/src/engine/SCons/Options/__init__.py b/src/engine/SCons/Options/__init__.py index e2ad80f..3dc7772 100644 --- a/src/engine/SCons/Options/__init__.py +++ b/src/engine/SCons/Options/__init__.py @@ -29,8 +29,11 @@ customizable variables to an SCons build. __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" +import SCons.compat + import os.path import string +import sys import SCons.Errors import SCons.Util @@ -64,6 +67,7 @@ class Options: else: files = [] self.files = files + self.unknown = {} # create the singleton instance if is_global: @@ -158,16 +162,29 @@ class Options: # next set the value specified in the options file for filename in self.files: if os.path.exists(filename): - execfile(filename, values) - - # finally set the values specified on the command line + dir = os.path.split(os.path.abspath(filename))[0] + if dir: + sys.path.insert(0, dir) + try: + values['__name__'] = filename + execfile(filename, {}, values) + finally: + if dir: + del sys.path[0] + del values['__name__'] + + # set the values specified on the command line if args is None: args = self.args for arg, value in args.items(): - for option in self.options: - if arg in option.aliases + [ option.key ]: - values[option.key]=value + added = False + for option in self.options: + if arg in option.aliases + [ option.key ]: + values[option.key] = value + added = True + if not added: + self.unknown[arg] = value # put the variables in the environment: # (don't copy over variables that are not declared as options) @@ -195,6 +212,13 @@ class Options: if option.validator and values.has_key(option.key): option.validator(option.key, env.subst('${%s}'%option.key), env) + def UnknownOptions(self): + """ + Returns any options in the specified arguments lists that + were not known, declared options in this object. + """ + return self.unknown + def Save(self, filename, env): """ Saves all the options in the given file. This file can diff --git a/src/engine/SCons/Platform/win32.py b/src/engine/SCons/Platform/win32.py index 8d35a8d..2fa30cc 100644 --- a/src/engine/SCons/Platform/win32.py +++ b/src/engine/SCons/Platform/win32.py @@ -40,13 +40,53 @@ import tempfile from SCons.Platform.posix import exitvalmap from SCons.Platform import TempFileMunge - -# XXX See note below about why importing SCons.Action should be -# eventually refactored. -import SCons.Action import SCons.Util + +try: + import msvcrt + import win32api + import win32con + + msvcrt.get_osfhandle + win32api.SetHandleInformation + win32con.HANDLE_FLAG_INHERIT +except ImportError: + parallel_msg = \ + "you do not seem to have the pywin32 extensions installed;\n" + \ + "\tparallel (-j) builds may not work reliably with open Python files." +except AttributeError: + parallel_msg = \ + "your pywin32 extensions do not support file handle operations;\n" + \ + "\tparallel (-j) builds may not work reliably with open Python files." +else: + parallel_msg = None + + import __builtin__ + + _builtin_file = __builtin__.file + _builtin_open = __builtin__.open + + def _scons_file(*args, **kw): + fp = apply(_builtin_file, args, kw) + win32api.SetHandleInformation(msvcrt.get_osfhandle(fp.fileno()), + win32con.HANDLE_FLAG_INHERIT, + 0) + return fp + + def _scons_open(*args, **kw): + fp = apply(_builtin_open, args, kw) + win32api.SetHandleInformation(msvcrt.get_osfhandle(fp.fileno()), + win32con.HANDLE_FLAG_INHERIT, + 0) + return fp + + __builtin__.file = _scons_file + __builtin__.open = _scons_open + + + # The upshot of all this is that, if you are using Python 1.5.2, # you had better have cmd or command.com in your PATH when you run # scons. diff --git a/src/engine/SCons/SConf.py b/src/engine/SCons/SConf.py index 47a552a..ae3a77e 100644 --- a/src/engine/SCons/SConf.py +++ b/src/engine/SCons/SConf.py @@ -54,6 +54,14 @@ from SCons.Debug import Trace SCons.Conftest.LogInputFiles = 0 SCons.Conftest.LogErrorMessages = 0 +# Set +build_type = None +build_types = ['clean', 'help'] + +def SetBuildType(type): + global build_type + build_type = type + # to be set, if we are in dry-run mode dryrun = 0 @@ -354,7 +362,7 @@ class SConfBuildTask(SCons.Taskmaster.Task): sconsign.set_entry(t.name, sconsign_entry) sconsign.merge() -class SConf: +class SConfBase: """This is simply a class to represent a configure context. After creating a SConf object, you can call any tests. After finished with your tests, be sure to call the Finish() method, which returns the modified @@ -395,6 +403,7 @@ class SConf: default_tests = { 'CheckFunc' : CheckFunc, 'CheckType' : CheckType, + 'CheckTypeSize' : CheckTypeSize, 'CheckHeader' : CheckHeader, 'CheckCHeader' : CheckCHeader, 'CheckCXXHeader' : CheckCXXHeader, @@ -603,7 +612,7 @@ class SConf: def AddTest(self, test_name, test_instance): """Adds test_class to this SConf instance. It can be called with self.test_name(...)""" - setattr(self, test_name, SConf.TestWrapper(test_instance, self)) + setattr(self, test_name, SConfBase.TestWrapper(test_instance, self)) def AddTests(self, tests): """Adds all the tests given in the tests dictionary to this SConf @@ -815,6 +824,19 @@ class CheckContext: #### End of stuff used by Conftest.py. +def SConf(*args, **kw): + if kw.get(build_type, True): + kw['_depth'] = kw.get('_depth', 0) + 1 + for bt in build_types: + try: + del kw[bt] + except KeyError: + pass + return apply(SConfBase, args, kw) + else: + return SCons.Util.Null() + + def CheckFunc(context, function_name, header = None, language = None): res = SCons.Conftest.CheckFunc(context, function_name, header = header, language = language) context.did_show_result = 1 @@ -826,6 +848,13 @@ def CheckType(context, type_name, includes = "", language = None): context.did_show_result = 1 return not res +def CheckTypeSize(context, type_name, includes = "", language = None, expect = None): + res = SCons.Conftest.CheckTypeSize(context, type_name, + header = includes, language = language, + expect = expect) + context.did_show_result = 1 + return res + def createIncludesFromHeaders(headers, leaveLast, include_quotes = '""'): # used by CheckHeader and CheckLibWithHeader to produce C - #include # statements from the specified header (list) diff --git a/src/engine/SCons/SConfTests.py b/src/engine/SCons/SConfTests.py index 2c35730..601c5eb 100644 --- a/src/engine/SCons/SConfTests.py +++ b/src/engine/SCons/SConfTests.py @@ -95,7 +95,7 @@ class SConfTestCase(unittest.TestCase): # 'TryLink'), so we are aware of reloading modules. def checks(self, sconf, TryFuncString): - TryFunc = self.SConf.SConf.__dict__[TryFuncString] + TryFunc = self.SConf.SConfBase.__dict__[TryFuncString] res1 = TryFunc( sconf, "int main() { return 0; }\n", ".c" ) res2 = TryFunc( sconf, '#include "no_std_header.h"\nint main() {return 0; }\n', @@ -497,6 +497,40 @@ int main() { finally: sconf.Finish() + def test_CheckTypeSize(self): + """Test SConf.CheckTypeSize() + """ + self._resetSConfState() + sconf = self.SConf.SConf(self.scons_env, + conf_dir=self.test.workpath('config.tests'), + log_file=self.test.workpath('config.log')) + try: + # CheckTypeSize() + + # In ANSI C, sizeof(char) == 1. + r = sconf.CheckTypeSize('char', expect = 1) + assert r == 1, "sizeof(char) != 1 ??" + r = sconf.CheckTypeSize('char', expect = 0) + assert r == 0, "sizeof(char) == 0 ??" + r = sconf.CheckTypeSize('char', expect = 2) + assert r == 0, "sizeof(char) == 2 ??" + r = sconf.CheckTypeSize('char') + assert r == 1, "sizeof(char) != 1 ??" + r = sconf.CheckTypeSize('const unsigned char') + assert r == 1, "sizeof(const unsigned char) != 1 ??" + + # Checking C++ + r = sconf.CheckTypeSize('const unsigned char', language = 'C++') + assert r == 1, "sizeof(const unsigned char) != 1 ??" + + # Checking Non-existing type + r = sconf.CheckTypeSize('thistypedefhasnotchancetosexist_scons') + assert r == 0, \ + "Checking size of thistypedefhasnotchancetosexist_scons succeeded ?" + + finally: + sconf.Finish() + def test_(self): """Test SConf.CheckType() """ diff --git a/src/engine/SCons/Script/Main.py b/src/engine/SCons/Script/Main.py index 36dc21e..97e0b19 100644 --- a/src/engine/SCons/Script/Main.py +++ b/src/engine/SCons/Script/Main.py @@ -139,6 +139,12 @@ def Progress(*args, **kw): # Task control. # + +_BuildFailures = [] + +def GetBuildFailures(): + return _BuildFailures + class BuildTask(SCons.Taskmaster.Task): """An SCons build task.""" progress = ProgressObject @@ -174,6 +180,7 @@ class BuildTask(SCons.Taskmaster.Task): display("scons: `%s' is up to date." % str(self.node)) def do_failed(self, status=2): + _BuildFailures.append(self.exception[1]) global exit_status if self.options.ignore_errors: SCons.Taskmaster.Task.executed(self) @@ -276,34 +283,31 @@ class BuildTask(SCons.Taskmaster.Task): class CleanTask(SCons.Taskmaster.Task): """An SCons clean task.""" - def dir_index(self, directory): - dirname = lambda f, d=directory: os.path.join(d, f) - files = map(dirname, os.listdir(directory)) - - # os.listdir() isn't guaranteed to return files in any specific order, - # but some of the test code expects sorted output. - files.sort() - return files - - def fs_delete(self, path, remove=1): + def fs_delete(self, path, pathstr, remove=1): try: if os.path.exists(path): if os.path.isfile(path): if remove: os.unlink(path) - display("Removed " + path) + display("Removed " + pathstr) elif os.path.isdir(path) and not os.path.islink(path): # delete everything in the dir - for p in self.dir_index(path): + entries = os.listdir(path) + # Sort for deterministic output (os.listdir() Can + # return entries in a random order). + entries.sort() + for e in entries: + p = os.path.join(path, e) + s = os.path.join(pathstr, e) if os.path.isfile(p): if remove: os.unlink(p) - display("Removed " + p) + display("Removed " + s) else: - self.fs_delete(p, remove) + self.fs_delete(p, s, remove) # then delete dir itself if remove: os.rmdir(path) - display("Removed directory " + path) + display("Removed directory " + pathstr) except (IOError, OSError), e: - print "scons: Could not remove '%s':" % str(path), e.strerror + print "scons: Could not remove '%s':" % pathstr, e.strerror def show(self): target = self.targets[0] @@ -314,7 +318,7 @@ class CleanTask(SCons.Taskmaster.Task): if SCons.Environment.CleanTargets.has_key(target): files = SCons.Environment.CleanTargets[target] for f in files: - self.fs_delete(str(f), 0) + self.fs_delete(f.abspath, str(f), 0) def remove(self): target = self.targets[0] @@ -335,7 +339,7 @@ class CleanTask(SCons.Taskmaster.Task): if SCons.Environment.CleanTargets.has_key(target): files = SCons.Environment.CleanTargets[target] for f in files: - self.fs_delete(str(f)) + self.fs_delete(f.abspath, str(f)) execute = remove @@ -726,6 +730,7 @@ def version_string(label, module): module.__buildsys__) def _main(parser): + import SCons global exit_status options = parser.values @@ -836,12 +841,10 @@ def _main(parser): CleanTask.execute = CleanTask.show if options.question: SCons.SConf.dryrun = 1 - if options.clean or options.help: - # If they're cleaning targets or have asked for help, replace - # the whole SCons.SConf module with a Null object so that the - # Configure() calls when reading the SConscript files don't - # actually do anything. - SCons.SConf.SConf = SCons.Util.Null + if options.clean: + SCons.SConf.SetBuildType('clean') + if options.help: + SCons.SConf.SetBuildType('help') SCons.SConf.SetCacheMode(options.config) SCons.SConf.SetProgressDisplay(progress_display) @@ -1067,7 +1070,6 @@ def _main(parser): """Leave the order of dependencies alone.""" return dependencies - progress_display("scons: " + opening_message) if options.taskmastertrace_file == '-': tmtrace = sys.stdout elif options.taskmastertrace_file: @@ -1083,15 +1085,22 @@ def _main(parser): global num_jobs num_jobs = options.num_jobs jobs = SCons.Job.Jobs(num_jobs, taskmaster) - if num_jobs > 1 and jobs.num_jobs == 1: - msg = "parallel builds are unsupported by this version of Python;\n" + \ - "\tignoring -j or num_jobs option.\n" - SCons.Warnings.warn(SCons.Warnings.NoParallelSupportWarning, msg) + if num_jobs > 1: + msg = None + if jobs.num_jobs == 1: + msg = "parallel builds are unsupported by this version of Python;\n" + \ + "\tignoring -j or num_jobs option.\n" + elif sys.platform == 'win32': + import SCons.Platform.win32 + msg = SCons.Platform.win32.parallel_msg + if msg: + SCons.Warnings.warn(SCons.Warnings.NoParallelSupportWarning, msg) memory_stats.append('before building targets:') count_stats.append(('pre-', 'build')) try: + progress_display("scons: " + opening_message) jobs.run() finally: jobs.cleanup() diff --git a/src/engine/SCons/Script/__init__.py b/src/engine/SCons/Script/__init__.py index d1a115a..44f01b8 100644 --- a/src/engine/SCons/Script/__init__.py +++ b/src/engine/SCons/Script/__init__.py @@ -111,6 +111,7 @@ AddOption = Main.AddOption GetOption = Main.GetOption SetOption = Main.SetOption Progress = Main.Progress +GetBuildFailures = Main.GetBuildFailures #keep_going_on_error = Main.keep_going_on_error #print_dtree = Main.print_dtree @@ -302,6 +303,7 @@ GlobalDefaultEnvironmentFunctions = [ 'FindSourceFiles', 'Flatten', 'GetBuildPath', + 'Glob', 'Ignore', 'Install', 'InstallAs', @@ -310,6 +312,7 @@ GlobalDefaultEnvironmentFunctions = [ 'ParseDepends', 'Precious', 'Repository', + 'Requires', 'SConsignFile', 'SideEffect', 'SourceCode', diff --git a/src/engine/SCons/Taskmaster.py b/src/engine/SCons/Taskmaster.py index 598566e..3bb4225 100644 --- a/src/engine/SCons/Taskmaster.py +++ b/src/engine/SCons/Taskmaster.py @@ -535,7 +535,7 @@ class Taskmaster: node.set_state(SCons.Node.pending) try: - children = node.children() + children = node.children() + node.prerequisites except SystemExit: exc_value = sys.exc_info()[1] e = SCons.Errors.ExplicitExit(node, exc_value.code) diff --git a/src/engine/SCons/TaskmasterTests.py b/src/engine/SCons/TaskmasterTests.py index c79fb93..9a7969b 100644 --- a/src/engine/SCons/TaskmasterTests.py +++ b/src/engine/SCons/TaskmasterTests.py @@ -46,6 +46,7 @@ class Node: self.scanned = 0 self.scanner = None self.targets = [self] + self.prerequisites = [] class Builder: def targets(self, node): return node.targets diff --git a/src/engine/SCons/Tool/mslink.py b/src/engine/SCons/Tool/mslink.py index c071aa7..25f3564 100644 --- a/src/engine/SCons/Tool/mslink.py +++ b/src/engine/SCons/Tool/mslink.py @@ -186,9 +186,9 @@ def generate(env): env['WINDOWSEXPSUFFIX'] = '${WIN32EXPSUFFIX}' env['WINDOWSSHLIBMANIFESTPREFIX'] = '' - env['WINDOWSSHLIBMANIFESTSUFFIX'] = env['SHLIBSUFFIX'] + '.manifest' + env['WINDOWSSHLIBMANIFESTSUFFIX'] = '${SHLIBSUFFIX}.manifest' env['WINDOWSPROGMANIFESTPREFIX'] = '' - env['WINDOWSPROGMANIFESTSUFFIX'] = env['PROGSUFFIX'] + '.manifest' + env['WINDOWSPROGMANIFESTSUFFIX'] = '${PROGSUFFIX}.manifest' env['REGSVRACTION'] = regServerCheck env['REGSVR'] = os.path.join(SCons.Platform.win32.get_system_root(),'System32','regsvr32') diff --git a/src/engine/SCons/Tool/packaging/__init__.py b/src/engine/SCons/Tool/packaging/__init__.py index 72bbff0..79cd4ab 100644 --- a/src/engine/SCons/Tool/packaging/__init__.py +++ b/src/engine/SCons/Tool/packaging/__init__.py @@ -86,14 +86,17 @@ def Tag(env, target, source, *more_tags, **kw_tags): def Package(env, target=None, source=None, **kw): """ Entry point for the package tool. """ - # first check some arguments + # check if we need to find the source files ourself if not source: source = env.FindInstalledFiles() if len(source)==0: raise UserError, "No source for Package() given" - # has the option for this Tool been set? + # decide which types of packages shall be built. Can be defined through + # four mechanisms: command line argument, keyword argument, + # environment argument and default selection( zip or tar.gz ) in that + # order. try: kw['PACKAGETYPE']=env['PACKAGETYPE'] except KeyError: pass @@ -108,12 +111,12 @@ def Package(env, target=None, source=None, **kw): kw['PACKAGETYPE']='zip' else: raise UserError, "No type for Package() given" + PACKAGETYPE=kw['PACKAGETYPE'] if not is_List(PACKAGETYPE): - #PACKAGETYPE=PACKAGETYPE.split(',') PACKAGETYPE=string.split(PACKAGETYPE, ',') - # now load the needed packagers. + # load the needed packagers. def load_packager(type): try: file,path,desc=imp.find_module(type, __path__) @@ -123,23 +126,24 @@ def Package(env, target=None, source=None, **kw): packagers=map(load_packager, PACKAGETYPE) - # now try to setup the default_target and the default PACKAGEROOT - # arguments. + # set up targets and the PACKAGEROOT try: # fill up the target list with a default target name until the PACKAGETYPE # list is of the same size as the target list. - if target==None or target==[]: - target=["%(NAME)s-%(VERSION)s"%kw] + if not target: target = [] + + size_diff = len(PACKAGETYPE)-len(target) + default_name = "%(NAME)s-%(VERSION)s" - size_diff=len(PACKAGETYPE)-len(target) if size_diff>0: - target.extend([target]*size_diff) + default_target = default_name%kw + target.extend( [default_target]*size_diff ) if not kw.has_key('PACKAGEROOT'): - kw['PACKAGEROOT']="%(NAME)s-%(VERSION)s"%kw + kw['PACKAGEROOT'] = default_name%kw except KeyError, e: - raise SCons.Errors.UserError( "Missing PackageTag '%s'"%e.args[0] ) + raise SCons.Errors.UserError( "Missing Packagetag '%s'"%e.args[0] ) # setup the source files source=env.arg2nodes(source, env.fs.Entry) @@ -148,11 +152,14 @@ def Package(env, target=None, source=None, **kw): targets=[] try: for packager in packagers: - t=apply(packager.package, [env,target,source], kw) + t=[target.pop(0)] + t=apply(packager.package, [env,t,source], kw) targets.extend(t) + assert( len(target) == 0 ) + except KeyError, e: - raise SCons.Errors.UserError( "Missing PackageTag '%s' for %s packager"\ + raise SCons.Errors.UserError( "Missing Packagetag '%s' for %s packager"\ % (e.args[0],packager.__name__) ) except TypeError, e: # this exception means that a needed argument for the packager is @@ -170,10 +177,10 @@ def Package(env, target=None, source=None, **kw): if len(args)==0: raise # must be a different error, so reraise elif len(args)==1: - raise SCons.Errors.UserError( "Missing PackageTag '%s' for %s packager"\ + raise SCons.Errors.UserError( "Missing Packagetag '%s' for %s packager"\ % (args[0],packager.__name__) ) else: - raise SCons.Errors.UserError( "Missing PackageTags '%s' for %s packager"\ + raise SCons.Errors.UserError( "Missing Packagetags '%s' for %s packager"\ % (", ".join(args),packager.__name__) ) target=env.arg2nodes(target, env.fs.Entry) diff --git a/src/engine/SCons/Util.py b/src/engine/SCons/Util.py index 04d263b..258de0f 100644 --- a/src/engine/SCons/Util.py +++ b/src/engine/SCons/Util.py @@ -1051,6 +1051,99 @@ class LogicalLines: +class UniqueList(UserList): + def __init__(self, seq = []): + UserList.__init__(self, seq) + self.unique = True + def __make_unique(self): + if not self.unique: + self.data = uniquer_hashables(self.data) + self.unique = True + def __lt__(self, other): + self.__make_unique() + return UserList.__lt__(self, other) + def __le__(self, other): + self.__make_unique() + return UserList.__le__(self, other) + def __eq__(self, other): + self.__make_unique() + return UserList.__eq__(self, other) + def __ne__(self, other): + self.__make_unique() + return UserList.__ne__(self, other) + def __gt__(self, other): + self.__make_unique() + return UserList.__gt__(self, other) + def __ge__(self, other): + self.__make_unique() + return UserList.__ge__(self, other) + def __cmp__(self, other): + self.__make_unique() + return UserList.__cmp__(self, other) + def __len__(self): + self.__make_unique() + return UserList.__len__(self) + def __getitem__(self, i): + self.__make_unique() + return UserList.__getitem__(self, i) + def __setitem__(self, i, item): + UserList.__setitem__(self, i, item) + self.unique = False + def __getslice__(self, i, j): + self.__make_unique() + return UserList.__getslice__(self, i, j) + def __setslice__(self, i, j, other): + UserList.__setslice__(self, i, j, other) + self.unique = False + def __add__(self, other): + result = UserList.__add__(self, other) + result.unique = False + return result + def __radd__(self, other): + result = UserList.__radd__(self, other) + result.unique = False + return result + def __iadd__(self, other): + result = UserList.__iadd__(self, other) + result.unique = False + return result + def __mul__(self, other): + result = UserList.__mul__(self, other) + result.unique = False + return result + def __rmul__(self, other): + result = UserList.__rmul__(self, other) + result.unique = False + return result + def __imul__(self, other): + result = UserList.__imul__(self, other) + result.unique = False + return result + def append(self, item): + UserList.append(self, item) + self.unique = False + def insert(self, i): + UserList.insert(self, i) + self.unique = False + def count(self, item): + self.__make_unique() + return UserList.count(self, item) + def index(self, item): + self.__make_unique() + return UserList.index(self, item) + def reverse(self): + self.__make_unique() + UserList.reverse(self) + def sort(self, *args, **kwds): + self.__make_unique() + #return UserList.sort(self, *args, **kwds) + return apply(UserList.sort, (self,)+args, kwds) + def extend(self, other): + UserList.extend(self, other) + self.unique = False + + + class Unbuffered: """ A proxy class that wraps a file object, flushing after every write, diff --git a/src/engine/SCons/compat/__init__.py b/src/engine/SCons/compat/__init__.py index 5e095d1..47ae3be 100644 --- a/src/engine/SCons/compat/__init__.py +++ b/src/engine/SCons/compat/__init__.py @@ -111,6 +111,35 @@ except NameError: import sets __builtin__.set = sets.Set +import fnmatch +try: + fnmatch.filter +except AttributeError: + # Pre-2.2 Python has no fnmatch.filter() function. + def filter(names, pat): + """Return the subset of the list NAMES that match PAT""" + import os,posixpath + result=[] + pat = os.path.normcase(pat) + if not fnmatch._cache.has_key(pat): + import re + res = fnmatch.translate(pat) + fnmatch._cache[pat] = re.compile(res) + match = fnmatch._cache[pat].match + if os.path is posixpath: + # normcase on posix is NOP. Optimize it away from the loop. + for name in names: + if match(name): + result.append(name) + else: + for name in names: + if match(os.path.normcase(name)): + result.append(name) + return result + fnmatch.filter = filter + del filter + + # If we need the compatibility version of textwrap, it must be imported # before optparse, which uses it. try: diff --git a/test/AR/AR.py b/test/AR/AR.py index cb11175..4048340 100644 --- a/test/AR/AR.py +++ b/test/AR/AR.py @@ -79,6 +79,9 @@ test.write('main.c', r""" #include <stdio.h> #include <stdlib.h> +extern void +library_function(void); + int main(int argc, char *argv[]) { diff --git a/test/AR/ARFLAGS.py b/test/AR/ARFLAGS.py index d4d122e..fbca0ff 100644 --- a/test/AR/ARFLAGS.py +++ b/test/AR/ARFLAGS.py @@ -78,6 +78,9 @@ library_function(void) test.write('main.c', r""" #include <stdlib.h> +extern void +library_function(); + int main(int argc, char *argv[]) { diff --git a/test/BuildDir/Clean.py b/test/BuildDir/Clean.py new file mode 100644 index 0000000..f4a8c48 --- /dev/null +++ b/test/BuildDir/Clean.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python +# +# __COPYRIGHT__ +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +""" +Verify that we can Clean() files in a BuildDir() that's underneath us. +(At one point this didn't work because we were using str() instead of +abspath to remove the files, which would interfere with the removal by +returning a path relative to the BuildDir(), not the top-level SConstruct +directory, if the source directory was the top-level directory.) +""" + +import TestSCons + +test = TestSCons.TestSCons() + +test.write('SConstruct', """\ +BuildDir('build0', '.', duplicate=0) +BuildDir('build1', '.', duplicate=1) + +def build_sample(target, source, env): + targetdir = str(target[0].dir) + target = str(target[0]) + open(target, 'wb').write(open(str(source[0]), 'rb').read()) + open(targetdir+'/sample.junk', 'wb').write('Side effect!\\n') + +t0 = Command("build0/sample.out", "sample.in", build_sample) +t1 = Command("build1/sample.out", "sample.in", build_sample) + +Clean(t0, 'build0/sample.junk') +Clean(t1, 'build1/sample.junk') +""") + +test.write('sample.in', "sample.in\n") + +test.run(arguments = '.') + +test.must_match(['build0', 'sample.out'], "sample.in\n") +test.must_exist(['build0', 'sample.junk']) + +test.must_match(['build1', 'sample.out'], "sample.in\n") +test.must_exist(['build1', 'sample.junk']) + +test.run(arguments = '-c .') + +test.must_not_exist(['build', 'sample.out']) +test.must_not_exist(['build', 'sample.junk']) + +test.pass_test() diff --git a/test/BuildDir/File-create.py b/test/BuildDir/File-create.py new file mode 100644 index 0000000..0a838be --- /dev/null +++ b/test/BuildDir/File-create.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__" + +""" +Verify that explicit use of File() Nodes in a BuildDir, followed by +*direct* creation of the file by Python in the SConscript file itself, +works correctly, with both duplicate=0 and duplicate=1. + +Right now it only works if you explicitly str() the Node before the file +is created on disk, but we at least want to make sure that continues +to work. The non-str() case, which doesn't currently work, is captured +here but commented out. +""" + +import TestSCons + +test = TestSCons.TestSCons() + +test.subdir('src') + +test.write('SConstruct', """\ +SConscript('src/SConscript', build_dir='build0', chdir=1, duplicate=0) +SConscript('src/SConscript', build_dir='build1', chdir=1, duplicate=1) +""") + +test.write(['src', 'SConscript'], """\ +#f1_in = File('f1.in') +#Command('f1.out', f1_in, Copy('$TARGET', '$SOURCE')) +#open('f1.in', 'wb').write("f1.in\\n") + +f2_in = File('f2.in') +str(f2_in) +Command('f2.out', f2_in, Copy('$TARGET', '$SOURCE')) +open('f2.in', 'wb').write("f2.in\\n") +""") + +test.run(arguments = '--tree=all .') + +#test.must_match(['build0', 'f1.out'], "f1.in\n") +test.must_match(['build0', 'f2.out'], "f2.in\n") + +#test.must_match(['build1', 'f1.out'], "f1.in\n") +test.must_match(['build1', 'f2.out'], "f2.in\n") + +test.up_to_date(arguments = '.') + +test.pass_test() diff --git a/test/BuildDir/Sconscript-build_dir.py b/test/BuildDir/Sconscript-build_dir.py index 685011d..50e2c4f 100644 --- a/test/BuildDir/Sconscript-build_dir.py +++ b/test/BuildDir/Sconscript-build_dir.py @@ -225,6 +225,9 @@ test.write(['test2', 'foo.c'], r""" #include <stdio.h> #include <stdlib.h> +extern void +bar(void); + int main(int argc, char *argv[]) { bar(); diff --git a/test/CC/SHCCCOMSTR.py b/test/CC/SHCCCOMSTR.py index 4d240ae..562961a 100644 --- a/test/CC/SHCCCOMSTR.py +++ b/test/CC/SHCCCOMSTR.py @@ -58,6 +58,7 @@ else: test.write('SConstruct', """ env = Environment(SHCCCOM = r'%(_python_)s mycc.py $TARGET $SOURCE', SHCCCOMSTR = 'Building $TARGET from $SOURCE', + SHOBJPREFIX='', SHOBJSUFFIX='.obj') env.SharedObject(target = 'test1', source = 'test1.c') env.SharedObject(target = 'test2', source = 'test2%(alt_c_suffix)s') diff --git a/test/CXX/CXX.py b/test/CXX/CXX.py index a1b4c2a..f537ee8 100644 --- a/test/CXX/CXX.py +++ b/test/CXX/CXX.py @@ -199,7 +199,7 @@ test.write('foo.cxx', r""" int main(int argc, char *argv[]) { - argv[argc++] = "--"; + argv[argc++] = (char *)"--"; printf("foo.cxx\n"); exit (0); } @@ -211,7 +211,7 @@ test.write('bar.cxx', r""" int main(int argc, char *argv[]) { - argv[argc++] = "--"; + argv[argc++] = (char *)"--"; printf("foo.cxx\n"); exit (0); } diff --git a/test/CXX/SHCXX.py b/test/CXX/SHCXX.py index c02086b..abfc19d 100644 --- a/test/CXX/SHCXX.py +++ b/test/CXX/SHCXX.py @@ -55,7 +55,7 @@ test.write('foo.cpp', r""" int main(int argc, char *argv[]) { - argv[argc++] = "--"; + argv[argc++] = (char *)"--"; printf("foo.c\n"); exit (0); } @@ -67,7 +67,7 @@ test.write('bar.cpp', r""" int main(int argc, char *argv[]) { - argv[argc++] = "--"; + argv[argc++] = (char *)"--"; printf("foo.c\n"); exit (0); } diff --git a/test/CXX/SHCXXCOMSTR.py b/test/CXX/SHCXXCOMSTR.py index 33beae7..8e55441 100644 --- a/test/CXX/SHCXXCOMSTR.py +++ b/test/CXX/SHCXXCOMSTR.py @@ -58,7 +58,7 @@ else: test.write('SConstruct', """ env = Environment(SHCXXCOM = r'%(_python_)s mycc.py $TARGET $SOURCE', SHCXXCOMSTR = 'Building shared object $TARGET from $SOURCE', - SHOBJSUFFIX='.obj') + SHOBJPREFIX='', SHOBJSUFFIX='.obj') env.SharedObject(target = 'test1', source = 'test1.cpp') env.SharedObject(target = 'test2', source = 'test2.cc') env.SharedObject(target = 'test3', source = 'test3.cxx') diff --git a/test/CacheDir/timestamp.py b/test/CacheDir/timestamp-content.py index b0d45b6..6434d0c 100644 --- a/test/CacheDir/timestamp.py +++ b/test/CacheDir/timestamp-content.py @@ -25,7 +25,8 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" """ -Verify that CacheDir() works even when using timestamp signatures. +Verify that CacheDir() works when using SourceSignatures('timestamp') +and TargetSignatures 'content'. """ import TestSCons @@ -41,8 +42,20 @@ Command('file.out', 'file.in', Copy('$TARGET', '$SOURCE')) test.write('file.in', "file.in\n") -test.run() +test.run(arguments = '.') test.must_match('file.out', "file.in\n") +test.up_to_date(options = '--cache-show --debug=explain', arguments = '.') + +test.sleep() + +test.touch('file.in') + +test.not_up_to_date(options = '--cache-show --debug=explain', arguments = '.') + +test.up_to_date(options = '--cache-show --debug=explain', arguments = '.') + +test.up_to_date(options = '--cache-show --debug=explain', arguments = '.') + test.pass_test() diff --git a/test/CacheDir/timestamp-match.py b/test/CacheDir/timestamp-match.py new file mode 100644 index 0000000..0e0074d --- /dev/null +++ b/test/CacheDir/timestamp-match.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 that CAcheDir() works when using 'timestamp-match' decisions. +""" + +import TestSCons + +test = TestSCons.TestSCons() + +test.write(['SConstruct'], """\ +Decider('timestamp-match') +CacheDir('cache') +Command('file.out', 'file.in', Copy('$TARGET', '$SOURCE')) +""") + +test.write('file.in', "file.in\n") + +test.run(arguments = '--cache-show --debug=explain .') + +test.must_match('file.out', "file.in\n") + +test.up_to_date(options = '--cache-show --debug=explain', arguments = '.') + +test.sleep() + +test.touch('file.in') + +test.not_up_to_date(options = '--cache-show --debug=explain', arguments = '.') + +test.up_to_date(options = '--cache-show --debug=explain', arguments = '.') + +test.up_to_date(options = '--cache-show --debug=explain', arguments = '.') + +test.pass_test() diff --git a/test/CacheDir/timestamp-newer.py b/test/CacheDir/timestamp-newer.py new file mode 100644 index 0000000..36267f8 --- /dev/null +++ b/test/CacheDir/timestamp-newer.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 that CAcheDir() works when using 'timestamp-newer' decisions. +""" + +import TestSCons + +test = TestSCons.TestSCons() + +test.write(['SConstruct'], """\ +Decider('timestamp-newer') +CacheDir('cache') +Command('file.out', 'file.in', Copy('$TARGET', '$SOURCE')) +""") + +test.write('file.in', "file.in\n") + +test.run(arguments = '--cache-show --debug=explain .') + +test.must_match('file.out', "file.in\n") + +test.up_to_date(options = '--cache-show --debug=explain', arguments = '.') + +test.sleep() + +test.touch('file.in') + +test.not_up_to_date(options = '--cache-show --debug=explain', arguments = '.') + +test.up_to_date(options = '--cache-show --debug=explain', arguments = '.') + +test.up_to_date(options = '--cache-show --debug=explain', arguments = '.') + +test.pass_test() diff --git a/test/CacheDir/timestamp-timestamp.py b/test/CacheDir/timestamp-timestamp.py new file mode 100644 index 0000000..2bef1cd --- /dev/null +++ b/test/CacheDir/timestamp-timestamp.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 that CacheDir() works when using both SourceSignatures() +and TargetSignatures values of 'timestamp'. +""" + +import TestSCons + +test = TestSCons.TestSCons() + +test.write(['SConstruct'], """\ +SourceSignatures('timestamp') +TargetSignatures('timestamp') +CacheDir('cache') +Command('file.out', 'file.in', Copy('$TARGET', '$SOURCE')) +""") + +test.write('file.in', "file.in\n") + +test.run(arguments = '--cache-show --debug=explain .') + +test.must_match('file.out', "file.in\n") + +test.up_to_date(options = '--cache-show --debug=explain', arguments = '.') + +test.sleep() + +test.touch('file.in') + +test.not_up_to_date(options = '--cache-show --debug=explain', arguments = '.') + +test.up_to_date(options = '--cache-show --debug=explain', arguments = '.') + +test.up_to_date(options = '--cache-show --debug=explain', arguments = '.') + +test.pass_test() diff --git a/test/Chmod.py b/test/Chmod.py index eb0b6c1..92fa639 100644 --- a/test/Chmod.py +++ b/test/Chmod.py @@ -41,7 +41,9 @@ test = TestSCons.TestSCons() # oscillate between those values. test.write('SConstruct', """ Execute(Chmod('f1', 0666)) +Execute(Chmod(('f1-File'), 0666)) Execute(Chmod('d2', 0777)) +Execute(Chmod(Dir('d2-Dir'), 0777)) def cat(env, source, target): target = str(target[0]) source = map(str, source) @@ -62,8 +64,11 @@ env.Command('f7.out', 'f7.in', [Cat, """) test.write('f1', "f1\n") +test.write('f1-File', "f1-File\n") test.subdir('d2') test.write(['d2', 'file'], "d2/file\n") +test.subdir('d2-Dir') +test.write(['d2-Dir', 'file'], "d2-Dir/file\n") test.write('bar.in', "bar.in\n") test.write('f3', "f3\n") test.subdir('d4') @@ -75,14 +80,21 @@ test.write('Chmod-f7.in', "Chmod-f7.in\n") test.write('f7.out-Chmod', "f7.out-Chmod\n") os.chmod(test.workpath('f1'), 0444) +os.chmod(test.workpath('f1-File'), 0444) os.chmod(test.workpath('d2'), 0555) +os.chmod(test.workpath('d2-Dir'), 0555) os.chmod(test.workpath('f3'), 0444) os.chmod(test.workpath('d4'), 0555) os.chmod(test.workpath('f5'), 0444) os.chmod(test.workpath('Chmod-f7.in'), 0444) os.chmod(test.workpath('f7.out-Chmod'), 0444) -expect = test.wrap_stdout(read_str = 'Chmod("f1", 0666)\nChmod("d2", 0777)\n', +expect = test.wrap_stdout(read_str = """\ +Chmod("f1", 0666) +Chmod("f1-File", 0666) +Chmod("d2", 0777) +Chmod("d2-Dir", 0777) +""", build_str = """\ cat(["bar.out"], ["bar.in"]) Chmod("f3", 0666) @@ -97,8 +109,12 @@ test.run(options = '-n', arguments = '.', stdout = expect) s = stat.S_IMODE(os.stat(test.workpath('f1'))[stat.ST_MODE]) test.fail_test(s != 0444) +s = stat.S_IMODE(os.stat(test.workpath('f1-File'))[stat.ST_MODE]) +test.fail_test(s != 0444) s = stat.S_IMODE(os.stat(test.workpath('d2'))[stat.ST_MODE]) test.fail_test(s != 0555) +s = stat.S_IMODE(os.stat(test.workpath('d2-Dir'))[stat.ST_MODE]) +test.fail_test(s != 0555) test.must_not_exist('bar.out') s = stat.S_IMODE(os.stat(test.workpath('f3'))[stat.ST_MODE]) test.fail_test(s != 0444) @@ -117,8 +133,12 @@ test.run() s = stat.S_IMODE(os.stat(test.workpath('f1'))[stat.ST_MODE]) test.fail_test(s != 0666) +s = stat.S_IMODE(os.stat(test.workpath('f1-File'))[stat.ST_MODE]) +test.fail_test(s != 0666) s = stat.S_IMODE(os.stat(test.workpath('d2'))[stat.ST_MODE]) test.fail_test(s != 0777) +s = stat.S_IMODE(os.stat(test.workpath('d2-Dir'))[stat.ST_MODE]) +test.fail_test(s != 0777) test.must_match('bar.out', "bar.in\n") s = stat.S_IMODE(os.stat(test.workpath('f3'))[stat.ST_MODE]) test.fail_test(s != 0666) diff --git a/test/Configure/clean.py b/test/Configure/clean.py index 5b8e43d..a6ba7c1 100644 --- a/test/Configure/clean.py +++ b/test/Configure/clean.py @@ -39,7 +39,7 @@ test.write('SConstruct', """\ env = Environment() import os env.AppendENVPath('PATH', os.environ['PATH']) -conf = Configure(env) +conf = Configure(env, clean=int(ARGUMENTS['clean'])) r1 = conf.CheckCHeader( 'math.h' ) r2 = conf.CheckCHeader( 'no_std_c_header.h' ) # leads to compile error env = conf.Finish() @@ -65,32 +65,16 @@ lines = [ "Checking for C header file no_std_c_header.h... " ] -unexpected = [] +test.run(arguments = '-c clean=0') +test.must_not_contain_lines(lines, test.stdout()) -test.run(arguments = '-c') +test.run(arguments = '-c clean=1') +test.must_contain_lines(lines, test.stdout()) -for line in lines: - if string.find(test.stdout(), line) != -1: - unexpected.append(line) +test.run(arguments = '--clean clean=0') +test.must_not_contain_lines(lines, test.stdout()) -if unexpected: - print "Unexpected lines in standard output:" - print string.join(unexpected, '\n') - print "STDOUT ============================================================" - print test.stdout() - test.fail_test() - -test.run(arguments = '--clean') - -for line in lines: - if string.find(test.stdout(), line) != -1: - unexpected.append(line) - -if unexpected: - print "Unexpected lines in standard output:" - print string.join(unexpected, '\n') - print "STDOUT ============================================================" - print test.stdout() - test.fail_test() +test.run(arguments = '--clean clean=1') +test.must_contain_lines(lines, test.stdout()) test.pass_test() diff --git a/test/Configure/help.py b/test/Configure/help.py index f79831c..32c68eb 100644 --- a/test/Configure/help.py +++ b/test/Configure/help.py @@ -39,7 +39,7 @@ test.write('SConstruct', """\ env = Environment() import os env.AppendENVPath('PATH', os.environ['PATH']) -conf = Configure(env) +conf = Configure(env, help=int(ARGUMENTS['help'])) r1 = conf.CheckCHeader( 'math.h' ) r2 = conf.CheckCHeader( 'no_std_c_header.h' ) # leads to compile error env = conf.Finish() @@ -65,45 +65,26 @@ lines = [ "Checking for C header file no_std_c_header.h... " ] -unexpected = [] +# The help setting should have no effect on -H, so the -H output +# should never contain the lines. +test.run(arguments = '-H help=0') +test.must_not_contain_lines(lines, test.stdout()) -test.run(arguments = '-H') +test.run(arguments = '-H help=1') +test.must_not_contain_lines(lines, test.stdout()) -for line in lines: - if string.find(test.stdout(), line) != -1: - unexpected.append(line) +# For -h and --help, the lines appear or not depending on how Configure() +# is initialized. +test.run(arguments = '-h help=0') +test.must_not_contain_lines(lines, test.stdout()) -if unexpected: - print "Unexpected lines in standard output:" - print string.join(unexpected, '\n') - print "STDOUT ============================================================" - print test.stdout() - test.fail_test() +test.run(arguments = '-h help=1') +test.must_contain_lines(lines, test.stdout()) -test.run(arguments = '-h') +test.run(arguments = '--help help=0') +test.must_not_contain_lines(lines, test.stdout()) -for line in lines: - if string.find(test.stdout(), line) != -1: - unexpected.append(line) - -if unexpected: - print "Unexpected lines in standard output:" - print string.join(unexpected, '\n') - print "STDOUT ============================================================" - print test.stdout() - test.fail_test() - -test.run(arguments = '--help') - -for line in lines: - if string.find(test.stdout(), line) != -1: - unexpected.append(line) - -if unexpected: - print "Unexpected lines in standard output:" - print string.join(unexpected, '\n') - print "STDOUT ============================================================" - print test.stdout() - test.fail_test() +test.run(arguments = '--help help=1') +test.must_contain_lines(lines, test.stdout()) test.pass_test() diff --git a/test/Copy.py b/test/Copy.py index 6659d93..827b912 100644 --- a/test/Copy.py +++ b/test/Copy.py @@ -36,8 +36,8 @@ test = TestSCons.TestSCons() test.write('SConstruct', """ Execute(Copy('f1.out', 'f1.in')) -Execute(Copy('d2.out', 'd2.in')) -Execute(Copy('d3.out', 'f3.in')) +Execute(Copy(File('d2.out'), 'd2.in')) +Execute(Copy('d3.out', File('f3.in'))) def cat(env, source, target): target = str(target[0]) source = map(str, source) diff --git a/test/Errors/preparation.py b/test/Errors/preparation.py index 9857f99..597216a 100644 --- a/test/Errors/preparation.py +++ b/test/Errors/preparation.py @@ -25,8 +25,15 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" """ -Verify that we print a useful message (and exit non-zero) if an external -error occurs while deciding if a Node is current or not. +A currently disabled test that used to verify that we print a useful +message (and exit non-zero) if an external error occurs while deciding +if a Node is current or not. + +This behavior changed when the Big Signature Refactoring changed when +signature calculation happens to *after* a Node has been visited (and +therefore visiting source Nodes in turn). Creating an analogous situation +in the new code isn't obvious, and It's not clear whether we need it +anyway, so we're going to leave this checked in but disabled for now. """ import sys @@ -35,6 +42,8 @@ import TestSCons test = TestSCons.TestSCons() +test.skip_test('Test not useful with current code; skipping.\n') + work_file_out = test.workpath('work', 'file.out') test.subdir('install', 'work') diff --git a/test/Fortran/SHF77COMSTR.py b/test/Fortran/SHF77COMSTR.py index 9085570..2bedb48 100644 --- a/test/Fortran/SHF77COMSTR.py +++ b/test/Fortran/SHF77COMSTR.py @@ -56,7 +56,7 @@ env = Environment(SHF77COM = r'%(_python_)s myfc.py f77 $TARGET $SOURCES', SHF77COMSTR = 'Building f77 $TARGET from $SOURCES', SHF77PPCOM = r'%(_python_)s myfc.py f77pp $TARGET $SOURCES', SHF77PPCOMSTR = 'Building f77pp $TARGET from $SOURCES', - SHOBJSUFFIX='.shobj') + SHOBJPREFIX='', SHOBJSUFFIX='.shobj') env.SharedObject(source = 'test01.f') env.SharedObject(source = 'test02.F') env.SharedObject(source = 'test03.for') diff --git a/test/Fortran/SHF90COMSTR.py b/test/Fortran/SHF90COMSTR.py index 9633d45..08208fa 100644 --- a/test/Fortran/SHF90COMSTR.py +++ b/test/Fortran/SHF90COMSTR.py @@ -56,7 +56,7 @@ env = Environment(SHF90COM = r'%(_python_)s myfc.py f90 $TARGET $SOURCES', SHF90COMSTR = 'Building f90 $TARGET from $SOURCES', SHF90PPCOM = r'%(_python_)s myfc.py f90pp $TARGET $SOURCES', SHF90PPCOMSTR = 'Building f90pp $TARGET from $SOURCES', - SHOBJSUFFIX='.shobj') + SHOBJPREFIX='', SHOBJSUFFIX='.shobj') env.SharedObject(source = 'test01.f90') env.SharedObject(source = 'test02.F90') """ % locals()) diff --git a/test/Fortran/SHF95COMSTR.py b/test/Fortran/SHF95COMSTR.py index eaa24ae..71a5627 100644 --- a/test/Fortran/SHF95COMSTR.py +++ b/test/Fortran/SHF95COMSTR.py @@ -56,7 +56,7 @@ env = Environment(SHF95COM = r'%(_python_)s myfc.py f95 $TARGET $SOURCES', SHF95COMSTR = 'Building f95 $TARGET from $SOURCES', SHF95PPCOM = r'%(_python_)s myfc.py f95pp $TARGET $SOURCES', SHF95PPCOMSTR = 'Building f95pp $TARGET from $SOURCES', - SHOBJSUFFIX='.shobj') + SHOBJPREFIX='', SHOBJSUFFIX='.shobj') env.SharedObject(source = 'test01.f95') env.SharedObject(source = 'test02.F95') """ % locals()) diff --git a/test/Fortran/SHFORTRANCOMSTR.py b/test/Fortran/SHFORTRANCOMSTR.py index 69a1eba..52b20c2 100644 --- a/test/Fortran/SHFORTRANCOMSTR.py +++ b/test/Fortran/SHFORTRANCOMSTR.py @@ -56,7 +56,7 @@ env = Environment(SHFORTRANCOM = r'%(_python_)s myfc.py fortran $TARGET $SOURCES SHFORTRANCOMSTR = 'Building fortran $TARGET from $SOURCES', SHFORTRANPPCOM = r'%(_python_)s myfc.py fortranpp $TARGET $SOURCES', SHFORTRANPPCOMSTR = 'Building fortranpp $TARGET from $SOURCES', - SHOBJSUFFIX='.shobj') + SHOBJPREFIX='', SHOBJSUFFIX='.shobj') env.SharedObject(source = 'test01.f') env.SharedObject(source = 'test02.F') env.SharedObject(source = 'test03.for') diff --git a/test/GetBuildFailures/option-k.py b/test/GetBuildFailures/option-k.py new file mode 100644 index 0000000..5246f31 --- /dev/null +++ b/test/GetBuildFailures/option-k.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python +# +# __COPYRIGHT__ +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +""" +Verify that a failed build action with -j works as expected. +""" + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +import TestSCons + +_python_ = TestSCons._python_ + +try: + import threading +except ImportError: + # if threads are not supported, then + # there is nothing to test + TestCmd.no_result() + sys.exit() + + +test = TestSCons.TestSCons() + +contents = r"""\ +import sys +if sys.argv[0] == 'mypass.py': + open(sys.argv[3], 'wb').write(open(sys.argv[4], 'rb').read()) + exit_value = 0 +elif sys.argv[0] == 'myfail.py': + exit_value = 1 +sys.exit(exit_value) +""" + +test.write('mypass.py', contents) +test.write('myfail.py', contents) + +test.write('SConstruct', """\ +Command('f3', 'f3.in', r'@%(_python_)s mypass.py - f3 $TARGET $SOURCE') +Command('f4', 'f4.in', r'@%(_python_)s myfail.py f3 f4 $TARGET $SOURCE') +Command('f5', 'f5.in', r'@%(_python_)s myfail.py f4 f5 $TARGET $SOURCE') +Command('f6', 'f6.in', r'@%(_python_)s mypass.py f5 - $TARGET $SOURCE') + +def print_build_failures(): + from SCons.Script import GetBuildFailures + bf_list = GetBuildFailures() + bf_list.sort(lambda a,b: cmp(a.filename, b.filename)) + for bf in bf_list: + print "%%s failed: %%s" %% (bf.node, bf.errstr) + +try: + import atexit +except ImportError: + import sys + sys.exitfunc = print_build_failures +else: + atexit.register(print_build_failures) +""" % locals()) + +test.write('f3.in', "f3.in\n") +test.write('f4.in', "f4.in\n") +test.write('f5.in', "f5.in\n") +test.write('f6.in', "f6.in\n") + +expect_stdout = """\ +scons: Reading SConscript files ... +scons: done reading SConscript files. +scons: Building targets ... +scons: done building targets (errors occurred during build). +f4 failed: Error 1 +f5 failed: Error 1 +""" % locals() + +expect_stderr = """\ +scons: *** [f4] Error 1 +scons: *** [f5] Error 1 +""" + +test.run(arguments = '-k .', + status = 2, + stdout = expect_stdout, + stderr = expect_stderr) + +test.must_match(test.workpath('f3'), 'f3.in\n') +test.must_not_exist(test.workpath('f4')) +test.must_not_exist(test.workpath('f5')) +test.must_match(test.workpath('f6'), 'f6.in\n') + + + +test.pass_test() diff --git a/test/GetBuildFailures/parallel.py b/test/GetBuildFailures/parallel.py new file mode 100644 index 0000000..cfadd0d --- /dev/null +++ b/test/GetBuildFailures/parallel.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python +# +# __COPYRIGHT__ +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +""" +Verify that a failed build action with -j works as expected. +""" + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +import TestSCons + +_python_ = TestSCons._python_ + +try: + import threading +except ImportError: + # if threads are not supported, then + # there is nothing to test + TestCmd.no_result() + sys.exit() + + +test = TestSCons.TestSCons() + +# We want to verify that -j 4 starts all four jobs, the first and last of +# which fail and the second and third of which succeed, and then stops +# processing due to the build failures. To try to control the timing, +# the created build scripts use marker directories to avoid doing their +# processing until the previous script has finished. + +contents = r"""\ +import os.path +import sys +import time +wait_marker = sys.argv[1] + '.marker' +write_marker = sys.argv[2] + '.marker' +if wait_marker != '-.marker': + while not os.path.exists(wait_marker): + time.sleep(1) +if sys.argv[0] == 'mypass.py': + open(sys.argv[3], 'wb').write(open(sys.argv[4], 'rb').read()) + exit_value = 0 +elif sys.argv[0] == 'myfail.py': + exit_value = 1 +if write_marker != '-.marker': + os.mkdir(write_marker) +sys.exit(exit_value) +""" + +test.write('mypass.py', contents) +test.write('myfail.py', contents) + +test.write('SConstruct', """\ +Command('f3', 'f3.in', r'@%(_python_)s mypass.py - f3 $TARGET $SOURCE') +Command('f4', 'f4.in', r'@%(_python_)s myfail.py f3 f4 $TARGET $SOURCE') +Command('f5', 'f5.in', r'@%(_python_)s myfail.py f4 f5 $TARGET $SOURCE') +Command('f6', 'f6.in', r'@%(_python_)s mypass.py f5 - $TARGET $SOURCE') + +def print_build_failures(): + from SCons.Script import GetBuildFailures + bf_list = GetBuildFailures() + bf_list.sort(lambda a,b: cmp(a.filename, b.filename)) + for bf in bf_list: + print "%%s failed: %%s" %% (bf.node, bf.errstr) + +try: + import atexit +except ImportError: + import sys + sys.exitfunc = print_build_failures +else: + atexit.register(print_build_failures) +""" % locals()) + +test.write('f3.in', "f3.in\n") +test.write('f4.in', "f4.in\n") +test.write('f5.in', "f5.in\n") +test.write('f6.in', "f6.in\n") + +expect_stdout = """\ +scons: Reading SConscript files ... +scons: done reading SConscript files. +scons: Building targets ... +scons: building terminated because of errors. +f4 failed: Error 1 +f5 failed: Error 1 +""" % locals() + +expect_stderr = """\ +scons: *** [f4] Error 1 +scons: *** [f5] Error 1 +""" + +test.run(arguments = '-j 4 .', + status = 2, + stdout = expect_stdout, + stderr = expect_stderr) + +test.must_match(test.workpath('f3'), 'f3.in\n') +test.must_not_exist(test.workpath('f4')) +test.must_not_exist(test.workpath('f5')) +test.must_match(test.workpath('f6'), 'f6.in\n') + + + +test.pass_test() diff --git a/test/GetBuildFailures/serial.py b/test/GetBuildFailures/serial.py new file mode 100644 index 0000000..c8205ed --- /dev/null +++ b/test/GetBuildFailures/serial.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. +# + +""" +Verify that the GetBuildFailures() function returns a list of +BuildError exceptions. Also verify printing the BuildError +attributes we expect to be most commonly used. +""" + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +import TestSCons + +_python_ = TestSCons._python_ + +try: + import threading +except ImportError: + # if threads are not supported, then + # there is nothing to test + TestCmd.no_result() + sys.exit() + + +test = TestSCons.TestSCons() + +contents = r"""\ +import sys +if sys.argv[0] == 'mypass.py': + open(sys.argv[3], 'wb').write(open(sys.argv[4], 'rb').read()) + exit_value = 0 +elif sys.argv[0] == 'myfail.py': + exit_value = 1 +sys.exit(exit_value) +""" + +test.write('mypass.py', contents) +test.write('myfail.py', contents) + +test.write('SConstruct', """\ +Command('f3', 'f3.in', r'@%(_python_)s mypass.py - f3 $TARGET $SOURCE') +Command('f4', 'f4.in', r'@%(_python_)s myfail.py f3 f4 $TARGET $SOURCE') +Command('f5', 'f5.in', r'@%(_python_)s myfail.py f4 f5 $TARGET $SOURCE') +Command('f6', 'f6.in', r'@%(_python_)s mypass.py f5 - $TARGET $SOURCE') + +def print_build_failures(): + from SCons.Script import GetBuildFailures + import string + bf_list = GetBuildFailures() + bf_list.sort(lambda a,b: cmp(a.filename, b.filename)) + for bf in bf_list: + print "%%s failed (%%s): %%s" %% (bf.node, bf.status, bf.errstr) + print " %%s" %% string.join(bf.command) + +try: + import atexit +except ImportError: + import sys + sys.exitfunc = print_build_failures +else: + atexit.register(print_build_failures) +""" % locals()) + +test.write('f3.in', "f3.in\n") +test.write('f4.in', "f4.in\n") +test.write('f5.in', "f5.in\n") +test.write('f6.in', "f6.in\n") + +expect_stdout = """\ +scons: Reading SConscript files ... +scons: done reading SConscript files. +scons: Building targets ... +scons: building terminated because of errors. +f4 failed (1): Error 1 + %(_python_)s myfail.py f3 f4 "f4" "f4.in" +""" % locals() + +expect_stderr = """\ +scons: *** [f4] Error 1 +""" + +test.run(arguments = '.', + status = 2, + stdout = expect_stdout, + stderr = expect_stderr) + +test.must_match(test.workpath('f3'), 'f3.in\n') +test.must_not_exist(test.workpath('f4')) +test.must_not_exist(test.workpath('f5')) +test.must_not_exist(test.workpath('f6')) + + + +test.pass_test() diff --git a/test/Glob/BuildDir.py b/test/Glob/BuildDir.py new file mode 100644 index 0000000..274ca49 --- /dev/null +++ b/test/Glob/BuildDir.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python +# +# __COPYRIGHT__ +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +""" +Verify that default use of the Glob() function within a BuildDir() +finds the local file Nodes. +""" + +import TestSCons + +test = TestSCons.TestSCons() + +test.subdir('src') + +test.write('SConstruct', """\ +BuildDir('var1', 'src') +BuildDir('var2', 'src') + +SConscript('var1/SConscript') +SConscript('var2/SConscript') +""") + +test.write(['src', 'SConscript'], """\ +env = Environment() + +def concatenate(target, source, env): + fp = open(str(target[0]), 'wb') + for s in source: + fp.write(open(str(s), 'rb').read()) + fp.close() + +env['BUILDERS']['Concatenate'] = Builder(action=concatenate) + +f_in = Glob('f*.in') +f_in.sort(lambda a,b: cmp(a.name, b.name)) +env.Concatenate('f.out', f_in) +""") + +test.write(['src', 'f1.in'], "src/f1.in\n") +test.write(['src', 'f2.in'], "src/f2.in\n") +test.write(['src', 'f3.in'], "src/f3.in\n") + +test.run(arguments = '.') + +test.must_match(['var1', 'f.out'], "src/f1.in\nsrc/f2.in\nsrc/f3.in\n") +test.must_match(['var2', 'f.out'], "src/f1.in\nsrc/f2.in\nsrc/f3.in\n") + +test.pass_test() diff --git a/test/Glob/Repository.py b/test/Glob/Repository.py new file mode 100644 index 0000000..6cef443 --- /dev/null +++ b/test/Glob/Repository.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python +# +# __COPYRIGHT__ +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +""" +Verify that the Glob() function finds files in repositories. +""" + +import TestSCons + +test = TestSCons.TestSCons() + +test.subdir('work', + 'repository', + ['repository', 'src'], + ['repository', 'src', 'sub1'], + ['repository', 'src', 'sub2']) + +work_aaa = test.workpath('work', 'aaa') +work_bbb = test.workpath('work', 'bbb') +work_ccc = test.workpath('work', 'ccc') +work_src_xxx = test.workpath('work', 'src', 'xxx') +work_src_yyy = test.workpath('work', 'src', 'yyy') + +opts = "-Y " + test.workpath('repository') + +test.write(['repository', 'SConstruct'], """\ +def cat(env, source, target): + target = str(target[0]) + source = map(str, source) + f = open(target, "wb") + for src in source: + f.write(open(src, "rb").read()) + f.close() + +env = Environment(BUILDERS={'Build':Builder(action=cat)}) +env.Build('aaa.out', Glob('a*.in')) +env.Build('bbb.out', Glob('b*.in')) +env.Build('ccc.out', Glob('c*.in')) +SConscript('src/SConscript', "env") +""") + +test.write(['repository', 'aaa.in'], "repository/aaa.in\n") +test.write(['repository', 'bbb.in'], "repository/bbb.in\n") +test.write(['repository', 'ccc.in'], "repository/ccc.in\n") + +test.write(['repository', 'src', 'SConscript'], """ +Import("env") +env.Build('xxx.out', Glob('x*.in')) +env.Build('yyy.out', Glob('yy?.in')) +zzz_in = Glob('*/zzz.in') +zzz_in.sort(lambda a,b: cmp(a.abspath, b.abspath)) +env.Build('zzz.out', zzz_in) +""") + +test.write(['repository', 'src', 'xxx.in'], "repository/src/xxx.in\n") +test.write(['repository', 'src', 'yyy.in'], "repository/src/yyy.in\n") +test.write(['repository', 'src', 'sub1', 'zzz.in'], "repository/src/sub1/zzz.in\n") +test.write(['repository', 'src', 'sub2', 'zzz.in'], "repository/src/sub2/zzz.in\n") + +# +# Make the repository non-writable, +# so we'll detect if we try to write into it accidentally. +test.writable('repository', 0) + +# +test.run(chdir = 'work', options = opts, arguments = 'aaa.out') + +test.must_match(['work', 'aaa.out'], "repository/aaa.in\n") +test.must_not_exist(test.workpath('work', 'bbb.out')) +test.must_not_exist(test.workpath('work', 'ccc.out')) +test.must_not_exist(test.workpath('work', 'src', 'xxx.out')) +test.must_not_exist(test.workpath('work', 'src', 'yyy.out')) + +test.run(chdir = 'work', options = opts, arguments = 'bbb.out src') + +test.must_match(['work', 'bbb.out'], "repository/bbb.in\n") +test.must_not_exist(test.workpath('work', 'ccc.out')) +test.must_match(['work', 'src', 'xxx.out'], "repository/src/xxx.in\n") +test.must_match(['work', 'src', 'yyy.out'], "repository/src/yyy.in\n") + +expect = """\ +repository/src/sub1/zzz.in +repository/src/sub2/zzz.in +""" + +test.must_match(['work', 'src', 'zzz.out'], expect) + +# +test.run(chdir = 'work', options = opts, arguments = '.') + +test.must_match(['work', 'ccc.out'], "repository/ccc.in\n") + +# +test.pass_test() diff --git a/test/Glob/basic.py b/test/Glob/basic.py new file mode 100644 index 0000000..2e7427a --- /dev/null +++ b/test/Glob/basic.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 basic operation of the Glob() function. +""" + +import TestSCons + +test = TestSCons.TestSCons() + +test.write('SConstruct', """\ +env = Environment() + +def concatenate(target, source, env): + fp = open(str(target[0]), 'wb') + for s in source: + fp.write(open(str(s), 'rb').read()) + fp.close() + +env['BUILDERS']['Concatenate'] = Builder(action=concatenate) + +f_in = Glob('f*.in') +f_in.sort(lambda a,b: cmp(a.name, b.name)) +env.Concatenate('f.out', f_in) +""") + +test.write('f1.in', "f1.in\n") +test.write('f2.in', "f2.in\n") +test.write('f3.in', "f3.in\n") + +test.run(arguments = '.') + +test.must_match('f.out', "f1.in\nf2.in\nf3.in\n") + +test.pass_test() diff --git a/test/Glob/source.py b/test/Glob/source.py new file mode 100644 index 0000000..b82e1d9 --- /dev/null +++ b/test/Glob/source.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__" + +""" +Verify that use of the Glob() function within a BuildDir() returns the +file Nodes in the source directory when the source= keyword argument is +specified (and duplicate=0 is specified for the BuildDir()). +""" + +import TestSCons + +test = TestSCons.TestSCons() + +test.subdir('src', 'var1', 'var2') + +test.write('SConstruct', """\ +env = Environment() + +def concatenate(target, source, env): + fp = open(str(target[0]), 'wb') + for s in source: + fp.write(open(str(s), 'rb').read()) + fp.close() + +env['BUILDERS']['Concatenate'] = Builder(action=concatenate) + +Export("env") + +BuildDir('var1', 'src', duplicate=0) +BuildDir('var2', 'src', duplicate=0) + +SConscript('var1/SConscript') +SConscript('var2/SConscript') +""") + +test.write(['var1', 'SConscript'], """\ +Import("env") + +f_in = Glob('f[45].in', source=True) +f_in.sort(lambda a,b: cmp(a.name, b.name)) +env.Concatenate('f.out', f_in) +""") + +test.write(['var2', 'SConscript'], """\ +Import("env") + +f_in = Glob('f[67].in') +f_in.sort(lambda a,b: cmp(a.name, b.name)) +env.Concatenate('f.out', f_in) +""") + +test.write(['src', 'f1.in'], "src/f1.in\n") +test.write(['src', 'f2.in'], "src/f2.in\n") + +test.write(['src', 'f4.in'], "src/f4.in\n") +test.write(['src', 'f5.in'], "src/f5.in\n") +test.write(['src', 'f6.in'], "src/f6.in\n") +test.write(['src', 'f7.in'], "src/f7.in\n") + +test.run(arguments = '.') + +test.must_match(['var1', 'f.out'], "src/f4.in\nsrc/f5.in\n") +test.must_match(['var2', 'f.out'], "src/f6.in\nsrc/f7.in\n") + +test.pass_test() diff --git a/test/Glob/strings.py b/test/Glob/strings.py new file mode 100644 index 0000000..d9f8ff1 --- /dev/null +++ b/test/Glob/strings.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python +# +# __COPYRIGHT__ +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +""" +Verify that use of the Glob() function with the strings= option works +when they're used in the same SConscript file (and therefore the same +directory) as input to a Builder call. +""" + +import TestSCons + +test = TestSCons.TestSCons() + +test.subdir('src') + +test.write('SConstruct', """\ +BuildDir('var1', 'src') +BuildDir('var2', 'src') + +SConscript('var1/SConscript') +SConscript('var2/SConscript') +""") + +test.write(['src', 'SConscript'], """\ +env = Environment() + +def concatenate(target, source, env): + fp = open(str(target[0]), 'wb') + for s in source: + fp.write(open(str(s), 'rb').read()) + fp.close() + +env['BUILDERS']['Concatenate'] = Builder(action=concatenate) + +f_in = Glob('f*.in', strings=True) +f_in.sort() +env.Concatenate('f.out', f_in) +""") + +test.write(['src', 'f1.in'], "src/f1.in\n") +test.write(['src', 'f2.in'], "src/f2.in\n") +test.write(['src', 'f3.in'], "src/f3.in\n") + +test.run(arguments = '.') + +test.must_match(['var1', 'f.out'], "src/f1.in\nsrc/f2.in\nsrc/f3.in\n") +test.must_match(['var2', 'f.out'], "src/f1.in\nsrc/f2.in\nsrc/f3.in\n") + +test.pass_test() diff --git a/test/Glob/subdir.py b/test/Glob/subdir.py new file mode 100644 index 0000000..b4e89e8 --- /dev/null +++ b/test/Glob/subdir.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__" + +""" +Verify that Glob() works to find Nodes underneath an explicitly- +named subdirectory. +""" + +import TestSCons + +test = TestSCons.TestSCons() + +test.subdir('subdir') + +test.write('SConstruct', """\ +env = Environment() + +def concatenate(target, source, env): + fp = open(str(target[0]), 'wb') + for s in source: + fp.write(open(str(s), 'rb').read()) + fp.close() + +env['BUILDERS']['Concatenate'] = Builder(action=concatenate) + +f_in = Glob('subdir/*.in') +f_in.sort(lambda a,b: cmp(a.name, b.name)) +env.Concatenate('f.out', f_in) +""") + +test.write(['subdir', 'file.in'], "subdir/file.in\n") + +test.run(arguments = '.') + +test.must_match('f.out', "subdir/file.in\n") + +test.pass_test() diff --git a/test/Glob/subst.py b/test/Glob/subst.py new file mode 100644 index 0000000..e6aa3ca --- /dev/null +++ b/test/Glob/subst.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__" + +""" +Verify the ability to Glob() using a pattern from a construction variable +expansion. +""" + +import TestSCons + +test = TestSCons.TestSCons() + +test.write('SConstruct', """\ +env = Environment(PATTERN = 'f*.in') + +def copy(target, source, env): + fp = open(str(target[0]), 'wb') + for s in source: + fp.write(open(str(s), 'rb').read()) + fp.close() + +env['BUILDERS']['Copy'] = Builder(action=copy) + +f_in = env.Glob('$PATTERN') +f_in.sort(lambda a,b: cmp(a.name, b.name)) +env.Copy('f.out', f_in) +""") + +test.write('f1.in', "f1.in\n") +test.write('f2.in', "f2.in\n") +test.write('f3.in', "f3.in\n") + +test.run(arguments = '.') + +test.must_match('f.out', "f1.in\nf2.in\nf3.in\n") + +test.pass_test() diff --git a/test/Install/Clone.py b/test/Install/Clone.py new file mode 100644 index 0000000..65770d2 --- /dev/null +++ b/test/Install/Clone.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python +# +# __COPYRIGHT__ +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +""" +Verify that we can Install() and InstallAs() from a construction +environment cloned from a clone. +""" + +import TestSCons + +test = TestSCons.TestSCons() + +test.write('SConstruct', """ +env1 = Environment(DESTDIR='sub1', tools=[]) + +# Call env1.Install() but not env1.InstallAs() *before* we clone it. +# This is to verify that re-initializing the Install() attribute on the +# construction environment doesn't mess up the environment settings in +# a way that leaves the InstallAs() intializer in place, which leads to +# infinite recursion. +env1.Install('$DESTDIR', 'foo.in') + +env2 = env1.Clone(DESTDIR='sub2') +env3 = env2.Clone(DESTDIR='sub3') + +env2.Install('$DESTDIR', 'foo.in') +env3.Install('$DESTDIR', 'foo.in') + +env1.InstallAs('$DESTDIR/foo.out', 'foo.in') +env2.InstallAs('$DESTDIR/foo.out', 'foo.in') +env3.InstallAs('$DESTDIR/foo.out', 'foo.in') +""") + +test.write('foo.in', "foo.in\n") + +test.run(arguments = '.') + +test.must_match(['sub1', 'foo.in'], "foo.in\n") +test.must_match(['sub2', 'foo.in'], "foo.in\n") +test.must_match(['sub3', 'foo.in'], "foo.in\n") + +test.must_match(['sub1', 'foo.out'], "foo.in\n") +test.must_match(['sub2', 'foo.out'], "foo.in\n") +test.must_match(['sub3', 'foo.out'], "foo.in\n") + +test.pass_test() diff --git a/test/LINK/SHLINKCOMSTR.py b/test/LINK/SHLINKCOMSTR.py index cf31813..94740b8 100644 --- a/test/LINK/SHLINKCOMSTR.py +++ b/test/LINK/SHLINKCOMSTR.py @@ -64,6 +64,7 @@ test.write('SConstruct', """ env = Environment(SHCCCOM = r'%(_python_)s mycc.py $TARGET $SOURCES', SHLINKCOM = r'%(_python_)s mylink.py $TARGET $SOURCES', SHLINKCOMSTR = 'Linking shared $TARGET from $SOURCES', + SHOBJPREFIX = '', SHOBJSUFFIX = '.obj', SHLIBPREFIX = '', SHLIBSUFFIX = '.dll') diff --git a/test/Library.py b/test/Library.py index 3a04b9c..4bcb2c7 100644 --- a/test/Library.py +++ b/test/Library.py @@ -114,7 +114,7 @@ void f3c(void); int main(int argc, char *argv[]) { - argv[argc++] = "--"; + argv[argc++] = (char *)"--"; f1(); f2a(); f2b(); diff --git a/test/MSVC/multiple-pdb.py b/test/MSVC/multiple-pdb.py index f359fb9..feb3fd9 100644 --- a/test/MSVC/multiple-pdb.py +++ b/test/MSVC/multiple-pdb.py @@ -46,7 +46,7 @@ if sys.platform != 'win32': test.skip_test(msg)
test.write('SConstruct', """\
-env = Environment(PDB = '${TARGET}.pdb')
+env = Environment(PDB = '${TARGET.base}.pdb')
env.Program('test1.cpp')
env.Program('test2.cpp')
""")
@@ -75,9 +75,9 @@ main(int argc, char *argv) test.run(arguments = '.')
-test.must_exist('test1%s' % _exe)
-test.must_exist('test1%s.pdb' % _exe)
-test.must_exist('test2%s' % _exe)
-test.must_exist('test2%s.pdb' % _exe)
+test.must_exist('test1%s' % _exe)
+test.must_exist('test1.pdb')
+test.must_exist('test2%s' % _exe)
+test.must_exist('test2.pdb')
test.pass_test()
diff --git a/test/Mkdir.py b/test/Mkdir.py index cbf465e..00b222b 100644 --- a/test/Mkdir.py +++ b/test/Mkdir.py @@ -38,6 +38,7 @@ test.subdir('work1', 'work2') test.write(['work1', 'SConstruct'], """ Execute(Mkdir('d1')) +Execute(Mkdir(Dir('#d1-Dir'))) def cat(env, source, target): target = str(target[0]) source = map(str, source) @@ -64,7 +65,7 @@ test.write(['work1', 'f2.in'], "f2.in\n") test.write(['work1', 'f5.in'], "f5.in\n") test.write(['work1', 'f6.in'], "f6.in\n") -expect = test.wrap_stdout(read_str = 'Mkdir("d1")\n', +expect = test.wrap_stdout(read_str = 'Mkdir("d1")\nMkdir("d1-Dir")\n', build_str = """\ cat(["f2.out"], ["f2.in"]) Mkdir("d3") @@ -79,6 +80,7 @@ Touch("%s") test.run(chdir = 'work1', options = '-n', arguments = '.', stdout = expect) test.must_not_exist(['work1', 'd1']) +test.must_not_exist(['work1', 'd1-Dir']) test.must_not_exist(['work1', 'f2.out']) test.must_not_exist(['work1', 'd3']) test.must_not_exist(['work1', 'd4']) @@ -90,6 +92,7 @@ test.must_not_exist(['work1', 'f6.out-Mkdir']) test.run(chdir = 'work1') test.must_exist(['work1', 'd1']) +test.must_exist(['work1', 'd1-Dir']) test.must_match(['work1', 'f2.out'], "f2.in\n") test.must_exist(['work1', 'd3']) test.must_exist(['work1', 'd4']) diff --git a/test/Move.py b/test/Move.py index c1cdcfd..ba55581 100644 --- a/test/Move.py +++ b/test/Move.py @@ -34,6 +34,7 @@ test = TestSCons.TestSCons() test.write('SConstruct', """ Execute(Move('f1.out', 'f1.in')) +Execute(Move('File-f1.out', File('f1.in-File'))) def cat(env, source, target): target = str(target[0]) source = map(str, source) @@ -50,6 +51,7 @@ env.Command('f6.out', 'f6.in', [Cat, Move("Move-$TARGET", "$SOURCE-Move")]) """) test.write('f1.in', "f1.in\n") +test.write('f1.in-File', "f1.in-File\n") test.write('f2.in', "f2.in\n") test.write('f3.in', "f3.in\n") test.write('f4.in', "f4.in\n") @@ -57,7 +59,10 @@ test.write('f5.in', "f5.in\n") test.write('f6.in', "f6.in\n") test.write('f6.in-Move', "f6.in-Move\n") -expect = test.wrap_stdout(read_str = 'Move("f1.out", "f1.in")\n', +expect = test.wrap_stdout(read_str = """\ +Move("f1.out", "f1.in") +Move("File-f1.out", "f1.in-File") +""", build_str = """\ cat(["f2.out"], ["f2.in"]) Move("f3.out", "f3.in") @@ -69,6 +74,7 @@ Move("Move-f6.out", "f6.in-Move") test.run(options = '-n', arguments = '.', stdout = expect) test.must_not_exist('f1.out') +test.must_not_exist('File-f1.out') test.must_not_exist('f2.out') test.must_not_exist('f3.out') test.must_not_exist('f4.out') @@ -79,6 +85,7 @@ test.must_not_exist('Move-f6.out') test.run() test.must_match('f1.out', "f1.in\n") +test.must_match('File-f1.out', "f1.in-File\n") test.must_match('f2.out', "f2.in\n") test.must_not_exist('f3.in') test.must_match('f3.out', "f3.in\n") diff --git a/test/NodeOps.py b/test/NodeOps.py index 7e656f7..b23a8d4 100644 --- a/test/NodeOps.py +++ b/test/NodeOps.py @@ -59,8 +59,8 @@ barflags = e['SHCXXFLAGS'] + ' -DBAR' test.subdir('bld', 'src', ['src', 'subsrcdir']) sconstruct = r""" -foo = Environment(SHCXXFLAGS = '%(fooflags)s', WINDOWS_INSERT_DEF=1) -bar = Environment(SHCXXFLAGS = '%(barflags)s', WINDOWS_INSERT_DEF=1) +foo = Environment(SHOBJPREFIX='', SHCXXFLAGS = '%(fooflags)s', WINDOWS_INSERT_DEF=1) +bar = Environment(SHOBJPREFIX='', SHCXXFLAGS = '%(barflags)s', WINDOWS_INSERT_DEF=1) src = Dir('src') BuildDir('bld', src, duplicate=1) Nodes=[] diff --git a/test/Object.py b/test/Object.py index da945e3..108960d 100644 --- a/test/Object.py +++ b/test/Object.py @@ -100,7 +100,7 @@ extern "C" void f3(void); int main(int argc, char *argv[]) { - argv[argc++] = "--"; + argv[argc++] = (char *)"--"; f1(); f2(); f3(); diff --git a/test/Options/chdir.py b/test/Options/chdir.py new file mode 100644 index 0000000..7ba85ea --- /dev/null +++ b/test/Options/chdir.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python +# +# __COPYRIGHT__ +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +""" +Verify that we can chdir() to the directory in which an Options +file lives by using the __name__ value. +""" + +import TestSCons + +test = TestSCons.TestSCons() + +test.subdir('bin', 'subdir') + +test.write('SConstruct', """\ +opts = Options('../bin/opts.cfg', ARGUMENTS) +opts.Add('VARIABLE') +Export("opts") +SConscript('subdir/SConscript') +""") + +SConscript_contents = """\ +Import("opts") +env = Environment() +opts.Update(env) +print "VARIABLE =", repr(env['VARIABLE']) +""" + +test.write(['bin', 'opts.cfg'], """\ +import os +import os.path +os.chdir(os.path.split(__name__)[0]) +execfile('opts2.cfg') +""") + +test.write(['bin', 'opts2.cfg'], """\ +VARIABLE = 'opts2.cfg value' +""") + +test.write(['subdir', 'SConscript'], SConscript_contents) + +expect = """\ +VARIABLE = 'opts2.cfg value' +""" + +test.run(arguments = '-q -Q .', stdout=expect) + +test.pass_test() diff --git a/test/Options/import.py b/test/Options/import.py new file mode 100644 index 0000000..0a3d367 --- /dev/null +++ b/test/Options/import.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python +# +# __COPYRIGHT__ +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +""" +Verify that an Options file in a different directory can import +a module in that directory. +""" + +import TestSCons + +test = TestSCons.TestSCons() + +workpath = test.workpath('') + +test.subdir('bin', 'subdir') + +test.write('SConstruct', """\ +opts = Options('../bin/opts.cfg', ARGUMENTS) +opts.Add('VARIABLE') +Export("opts") +SConscript('subdir/SConscript') +""") + +SConscript_contents = """\ +Import("opts") +env = Environment() +opts.Update(env) +print "VARIABLE =", env.get('VARIABLE') +""" + +test.write(['bin', 'opts.cfg'], """\ +import sys +from local_options import VARIABLE +""" % locals()) + +test.write(['bin', 'local_options.py'], """\ +VARIABLE = 'bin/local_options.py' +""") + +test.write(['subdir', 'SConscript'], SConscript_contents) + +expect = "VARIABLE = bin/local_options.py\n" + +test.run(arguments = '-q -Q .', stdout = expect) + +test.pass_test() diff --git a/test/Parallel/duplicate-target.py b/test/Parallel/duplicate-target.py index 5d4f5e1..43015fe 100644 --- a/test/Parallel/duplicate-target.py +++ b/test/Parallel/duplicate-target.py @@ -41,34 +41,58 @@ _python_ = TestSCons._python_ test = TestSCons.TestSCons() -test.subdir('work') +test.subdir('work', ['work', 'sub']) tar_output = test.workpath('work.tar') -test.write(['work', 'copy.py'], """\ +test.write(['work', 'mycopy.py'], """\ import sys import time time.sleep(int(sys.argv[1])) open(sys.argv[2], 'wb').write(open(sys.argv[3], 'rb').read()) """) +test.write(['work', 'mytar.py'], """\ +import sys +import os.path + +def visit(arg, dirname, fnames): + fnames.sort() + for fn in fnames: + p = os.path.join(dirname, fn) + if os.path.isfile(p): + arg.write(open(p, 'rb').read()) + +fp = open(sys.argv[1], 'wb') +for s in sys.argv[2:]: + os.path.walk(s, visit, fp) +""") + test.write(['work', 'SConstruct'], """\ env = Environment() -out1 = File('f1.out') -out2 = File('f2.out') -env.Command([out1, out1], 'f1.in', r'%(_python_)s copy.py 3 $TARGET $SOURCE') -env.Command([out2, out2], 'f2.in', r'%(_python_)s copy.py 3 $TARGET $SOURCE') +out1 = File('sub/f1.out') +out2 = File('sub/f2.out') +env.Command([out1, out1], 'sub/f1.in', + r'%(_python_)s mycopy.py 3 $TARGET $SOURCE') +env.Command([out2, out2], 'sub/f2.in', + r'%(_python_)s mycopy.py 3 $TARGET $SOURCE') -env.Tar(r'%(tar_output)s', Dir('.')) +env.Command(r'%(tar_output)s', Dir('sub'), + r'%(_python_)s mytar.py $TARGET $SOURCE') """ % locals()) -test.write(['work', 'f1.in'], "work/f1.in\n") -test.write(['work', 'f2.in'], "work/f2.in\n") +test.write(['work', 'sub', 'f1.in'], "work/sub/f1.in\n") +test.write(['work', 'sub', 'f2.in'], "work/sub/f2.in\n") test.run(chdir = 'work', arguments = tar_output + ' -j2') -test.must_match(['work', 'f1.out'], "work/f1.in\n") -test.must_match(['work', 'f2.out'], "work/f2.in\n") -test.must_exist(tar_output) +test.must_match(['work', 'sub', 'f1.out'], "work/sub/f1.in\n") +test.must_match(['work', 'sub', 'f2.out'], "work/sub/f2.in\n") +test.must_match(tar_output, """\ +work/sub/f1.in +work/sub/f1.in +work/sub/f2.in +work/sub/f2.in +""") test.pass_test() diff --git a/test/Parallel/failed-build.py b/test/Parallel/failed-build.py new file mode 100644 index 0000000..64e90c9 --- /dev/null +++ b/test/Parallel/failed-build.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python +# +# __COPYRIGHT__ +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +""" +Verify that a failed build action with -j works as expected. +""" + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +import TestSCons + +_python_ = TestSCons._python_ + +try: + import threading +except ImportError: + # if threads are not supported, then + # there is nothing to test + TestCmd.no_result() + sys.exit() + + +test = TestSCons.TestSCons() + +# We want to verify that -j 2 starts precisely two jobs, the first of +# which fails and the second of which succeeds, and then stops processing +# due to the first build failure. To try to control the timing, the two +# created build scripts use a pair of marker directories. +# +# The failure script waits until it sees the 'mycopy.started' directory +# that indicates the successful script has, in fact, gotten started. +# If we don't wait, then SCons could detect our script failure early +# (typically if a high system load happens to delay SCons' ability to +# start the next script) and then not start the successful script at all. +# +# The successful script waits until it sees the 'myfail.exiting' directory +# that indicates the failure script has finished (with everything except +# the final sys.exit(), that is). If we don't wait for that, then SCons +# could detect our successful exit first (typically if a high system +# load happens to delay the failure script) and start another job before +# it sees the failure from the first script. + +test.write('myfail.py', r"""\ +import os.path +import sys +import time +while not os.path.exists('mycopy.started'): + time.sleep(1) +os.mkdir('myfail.exiting') +sys.exit(1) +""") + +test.write('mycopy.py', r"""\ +import os +import sys +import time +os.mkdir('mycopy.started') +open(sys.argv[1], 'wb').write(open(sys.argv[2], 'rb').read()) +while not os.path.exists('myfail.exiting'): + time.sleep(1) +sys.exit(0) +""") + +test.write('SConstruct', """ +MyCopy = Builder(action = r'%(_python_)s mycopy.py $TARGET $SOURCE') +Fail = Builder(action = r'%(_python_)s myfail.py $TARGETS $SOURCE') +env = Environment(BUILDERS = { 'MyCopy' : MyCopy, 'Fail' : Fail }) +env.Fail(target = 'f3', source = 'f3.in') +env.MyCopy(target = 'f4', source = 'f4.in') +env.MyCopy(target = 'f5', source = 'f5.in') +env.MyCopy(target = 'f6', source = 'f6.in') +""" % locals()) + +test.write('f3.in', "f3.in\n") +test.write('f4.in', "f4.in\n") +test.write('f5.in', "f5.in\n") +test.write('f6.in', "f6.in\n") + +test.run(arguments = '-j 2 .', + status = 2, + stderr = "scons: *** [f3] Error 1\n") + +test.must_not_exist(test.workpath('f3')) +test.must_match(test.workpath('f4'), 'f4.in\n') +test.must_not_exist(test.workpath('f5')) +test.must_not_exist(test.workpath('f6')) + + + +test.pass_test() diff --git a/test/Repository/variants.py b/test/Repository/variants.py index cd4c24a..d124431 100644 --- a/test/Repository/variants.py +++ b/test/Repository/variants.py @@ -204,7 +204,8 @@ test.write(['repository', 'src2', 'xxx', 'main.c'], r""" #ifdef BAR #define MAIN_OS "BAR" #endif -main() +int +main(int argc, char *argv[]) { printf(INCLUDE_STRING, INCLUDE_OS); printf(XXX_STRING, XXX_OS); diff --git a/test/Requires/basic.py b/test/Requires/basic.py new file mode 100644 index 0000000..59cdd37 --- /dev/null +++ b/test/Requires/basic.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__" + +""" +Verify basic operation of the env.Requires() method for specifying +order-only prerequisites. +""" + +import TestSCons + +test = TestSCons.TestSCons() + +test.write('SConstruct', """ +def append_prereq_func(target, source, env): + fp = open(str(target[0]), 'wb') + for s in map(str, source): + fp.write(open(s, 'rb').read()) + fp.write(open('prereq.out', 'rb').read()) + fp.close() + return None +append_prereq = Action(append_prereq_func) +env = Environment() +env.Requires('file.out', 'prereq.out') +env.Command('file.out', 'file.in', append_prereq) +env.Command('prereq.out', 'prereq.in', Copy('$TARGET', '$SOURCES')) +""") + +test.write('file.in', "file.in 1\n") +test.write('prereq.in', "prereq.in 1\n") + +# First: build file.out. prereq.out should be built first, and if +# not, we'll get an error when the build action tries to use it to +# build file.out. + +test.run(arguments = 'file.out') + +test.must_match('prereq.out', "prereq.in 1\n") +test.must_match('file.out', "file.in 1\nprereq.in 1\n") + +# Update the prereq.out file. file.out should still be up to date because +# prereq.out is not actually a dependency, so we don't detect the +# underlying change. + +test.write('prereq.out', "prereq.out 2\n") + +test.up_to_date(arguments = 'file.out') + +# Now update the prereq.in file. Trying to rebuild file.out should +# cause prereq.out to be updated because of the change, but file.out +# should *not* be rebuilt because, again, prereq.out isn't actually +# a dependency that causes rebuilds. + +test.write('prereq.in', "prereq.in 3\n") + +test.run(arguments = 'file.out') + +test.must_match('prereq.out', "prereq.in 3\n") +test.must_match('file.out', "file.in 1\nprereq.in 1\n") + +# Now update file.in, which will cause file.out to be rebuilt, picking +# up the change(s) to prereq.out of which we were previously oblivious. + +test.write('file.in', 'file.in 4\n') + +test.run(arguments = 'file.out') + +test.must_match('prereq.out', "prereq.in 3\n") +test.must_match('file.out', "file.in 4\nprereq.in 3\n") + + + +test.pass_test() diff --git a/test/Scanner/no-Dir-node.py b/test/Scanner/no-Dir-node.py new file mode 100644 index 0000000..b5706e3 --- /dev/null +++ b/test/Scanner/no-Dir-node.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python +# +# __COPYRIGHT__ +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +""" +Verify that use of a Scanner that searches a *PATH list doesn't create +nodes for directories that don't exist, so they don't get picked up +by DirScanner. + +Under the covers, this tests the behavior of the SCons.Node.FS.find_file() +utility function that is used by the Scanner.Classic class to search +directories in variables such as $CPPPATH. +""" + +import os.path + +import TestSCons + +_python_ = TestSCons._python_ + +test = TestSCons.TestSCons() + +subdir_SConscript = os.path.join('subdir', 'SConscript') +subdir_foo = os.path.join('subdir', 'foo') +subdir_foo_k = os.path.join('subdir', 'foo.k') + +test.subdir('subdir', 'inc1', 'inc2') + +inc2_include_h = test.workpath('inc2', 'include.h') + +test.write('build.py', r""" +import os.path +import string +import sys +path = string.split(sys.argv[1]) +input = open(sys.argv[2], 'rb') +output = open(sys.argv[3], 'wb') + +def find_file(f): + if os.path.isabs(f): + return open(f, 'rb') + for dir in path: + p = dir + os.sep + f + if os.path.exists(p): + return open(p, 'rb') + return None + +def process(infp, outfp): + for line in infp.readlines(): + if line[:8] == 'include ': + file = line[8:-1] + process(find_file(file), outfp) + else: + outfp.write(line) + +process(input, output) + +sys.exit(0) +""") + +test.write('SConstruct', """\ +def foo(target, source, env): + children = source[0].children() + children.sort(lambda a,b: cmp(a.name, b.name)) + fp = open(str(target[0]), 'wb') + for c in children: + fp.write('%s\\n' % c) + fp.close() +Command('list.out', 'subdir', foo, source_scanner = DirScanner) +SConscript('subdir/SConscript') +""") + +test.write(['subdir', 'SConscript'], """\ +import SCons.Scanner +kscan = SCons.Scanner.Classic(name = 'kfile', + suffixes = ['.k'], + path_variable = 'KPATH', + regex = r'^include\s+(\S+)$') + +env = Environment(KPATH=['.', '..']) +env.Append(SCANNERS = kscan) + +env.Command('foo', 'foo.k', r'%(_python_)s build.py "$KPATH" $SOURCES $TARGET') +""" % locals()) + +test.write(['subdir', 'foo.k'], """\ +subdir/foo.k +include inc1/include.h +include %(inc2_include_h)s +""" % locals()) + +test.write(['inc1', 'include.h'], """\ +inc1/include.h +""") + +test.write(['inc2', 'include.h'], """\ +inc2/include.h +""") + +test.run(arguments = '.') + +test.must_match('subdir/foo', """\ +subdir/foo.k +inc1/include.h +inc2/include.h +""") + +test.must_match('list.out', """\ +%(subdir_SConscript)s +%(subdir_foo)s +%(subdir_foo_k)s +""" % locals()) + +test.pass_test() diff --git a/test/Touch.py b/test/Touch.py index b41db25..6ecc3ff 100644 --- a/test/Touch.py +++ b/test/Touch.py @@ -36,6 +36,7 @@ test = TestSCons.TestSCons() test.write('SConstruct', """ Execute(Touch('f1')) +Execute(Touch(File('f1-File'))) def cat(env, source, target): target = str(target[0]) source = map(str, source) @@ -54,13 +55,18 @@ env.Command('f6.out', 'f6.in', [Cat, """) test.write('f1', "f1\n") +test.write('f1-File', "f1-File\n") test.write('f2.in', "f2.in\n") test.write('f5.in', "f5.in\n") test.write('f6.in', "f6.in\n") -oldtime = os.path.getmtime(test.workpath('f1')) +old_f1_time = os.path.getmtime(test.workpath('f1')) +old_f1_File_time = os.path.getmtime(test.workpath('f1-File')) -expect = test.wrap_stdout(read_str = 'Touch("f1")\n', +expect = test.wrap_stdout(read_str = """\ +Touch("f1") +Touch("f1-File") +""", build_str = """\ cat(["f2.out"], ["f2.in"]) Touch("f3") @@ -74,8 +80,10 @@ test.run(options = '-n', arguments = '.', stdout = expect) test.sleep(2) -newtime = os.path.getmtime(test.workpath('f1')) -test.fail_test(oldtime != newtime) +new_f1_time = os.path.getmtime(test.workpath('f1')) +test.fail_test(old_f1_time != new_f1_time) +new_f1_File_time = os.path.getmtime(test.workpath('f1-File')) +test.fail_test(old_f1_File_time != new_f1_File_time) test.must_not_exist(test.workpath('f2.out')) test.must_not_exist(test.workpath('f3')) @@ -87,8 +95,10 @@ test.must_not_exist(test.workpath('f6.out-Touch')) test.run() -newtime = os.path.getmtime(test.workpath('f1')) -test.fail_test(oldtime == newtime) +new_f1_time = os.path.getmtime(test.workpath('f1')) +test.fail_test(old_f1_time == new_f1_time) +new_f1_File_time = os.path.getmtime(test.workpath('f1-File')) +test.fail_test(old_f1_File_time == new_f1_File_time) test.must_match('f2.out', "f2.in\n") test.must_exist(test.workpath('f3')) diff --git a/test/implicit-cache/DualTargets.py b/test/implicit-cache/DualTargets.py new file mode 100644 index 0000000..cf41640 --- /dev/null +++ b/test/implicit-cache/DualTargets.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python +# +# __COPYRIGHT__ +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +""" +Test that --implicit-cache works correctly in conjonction with a +builder that produces multiple targets. +""" + +import string + +import TestSCons + +test = TestSCons.TestSCons() + +test.write('SConstruct', """\ +import os.path + +def emitter(target, source, env): + tgt0 = target[0].abspath + base,ext = os.path.splitext(tgt0) + target.append(base + '.b') + return(target, source) + + +def source_scan(node, env, path): + path = node.abspath + base,ext = os.path.splitext(path) + return [base + '.lib'] + + +env = Environment() +env['BUILDERS']['DualTarget'] = Builder( + action = Action( + [ + Copy( '$TARGET', '$SOURCE' ), + Copy( '${TARGET.base}.b', '$SOURCE' ), + ], + ), + suffix = '.a', + src_suffix = '.cpp', + single_source = True, + emitter=emitter, + source_scanner=Scanner(source_scan), + ) + +env.Command( 'x.cpp', '', Touch('$TARGET') ) +env.Command( 'x.lib', '', Touch('$TARGET') ) + +env.DualTarget('x.cpp') +""" % locals()) + +test.must_not_exist('x.cpp') +test.must_not_exist('x.lib') +test.must_not_exist('x.a') +test.must_not_exist('x.b') + +# Build everything first. +test.run(arguments = '.') +test.must_exist('x.cpp') +test.must_exist('x.lib') +test.must_exist('x.a') +test.must_exist('x.b') + +test.fail_test(string.find(test.stdout(), 'Copy') == -1) + +# Double check that targets are not rebuilt. +test.run(arguments = '.') +test.must_exist('x.cpp') +test.must_exist('x.lib') +test.must_exist('x.a') +test.must_exist('x.b') + +test.fail_test(string.find(test.stdout(), 'Copy') != -1) + +# Double check that targets are not rebuilt even with --implicit-cache +test.run(arguments = '--implicit-cache x.a') +test.must_exist('x.cpp') +test.must_exist('x.lib') +test.must_exist('x.a') +test.must_exist('x.b') + +test.fail_test(string.find(test.stdout(), 'Copy') != -1) + +# Double check that targets are not rebuilt even with --implicit-cache +# a second time. +test.run(arguments = '--implicit-cache x.a') +test.must_exist('x.cpp') +test.must_exist('x.lib') +test.must_exist('x.a') +test.must_exist('x.b') + +test.fail_test(string.find(test.stdout(), 'Copy') != -1) + +# Double check that targets are not rebuilt if we reran without +# --implicit-cache +test.run(arguments = '.') +test.must_exist('x.cpp') +test.must_exist('x.lib') +test.must_exist('x.a') +test.must_exist('x.b') + +test.fail_test(string.find(test.stdout(), 'Copy') != -1) + +# Double check again +test.run(arguments = '.') +test.must_exist('x.cpp') +test.must_exist('x.lib') +test.must_exist('x.a') +test.must_exist('x.b') + +test.fail_test(string.find(test.stdout(), 'Copy') != -1) + +# Then only of the targets using --implicit-cache +test.pass_test() diff --git a/test/no-global-dependencies.py b/test/no-global-dependencies.py new file mode 100644 index 0000000..3cdea1b --- /dev/null +++ b/test/no-global-dependencies.py @@ -0,0 +1,170 @@ +#!/usr/bin/env python +# +# __COPYRIGHT__ +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +""" +Test that files are correctly located in the build directory even when +Scons does not have a global view of all targets. + +Sometimes, it might be interesting to not tell scons about every +targets. For example, one might not read in all the SConscipts of a +hierarchical build for a particular invocation of scons. This would be +done to speed-up a partial rebuild when the developer knows that only +a subset of the targets need to be rebuilt. +""" + +import string + +import TestSCons + +test = TestSCons.TestSCons() + +test.subdir('dir1') +test.subdir('dir2') + +test.write('SConstruct', """\ +opts = Options() +opts.AddOptions( + BoolOption('view_all_dependencies', 'View all dependencies', True), + BoolOption('duplicate', 'Duplicate sources to build dir', True) +) + +env = Environment(options=opts) +Export('env') + +SConscript(dirs='.', build_dir='build', duplicate=env['duplicate']) +""" % locals()) + + +test.write('SConscript', """\ +Import('env') + +if env['view_all_dependencies']: + SConscript(dirs='dir1') + +SConscript(dirs='dir2') +""" % locals()) + +test.write('dir1/SConscript', """\ +Import('env') + +env.Command('x.cpp', '', Touch('$TARGET')) + +env.Object(env.File('x.cpp')) +""" % locals()) + +test.write('dir2/SConscript', """\ +Import('env') + +env.Object(env.File('#/build/dir1/x.cpp')) +""" % locals()) + +test.must_not_exist('build/dir1/x.cpp') + + +############################################################ +# +# Test without duplication +# + +# Build everything first. +test.run(arguments = 'duplicate=False view_all_dependencies=True .') +test.must_exist('build/dir1/x.cpp') +test.fail_test(string.find(test.stdout(), "`.' is up to date.") != -1) + +# Double check that targets are not rebuilt. +test.run(arguments = 'duplicate=False view_all_dependencies=True .') +test.must_exist('build/dir1/x.cpp') +test.fail_test(string.find(test.stdout(), "`.' is up to date.") == -1) + +# Clean-up only the object file +test.run(arguments = 'duplicate=False view_all_dependencies=False -c .') +test.must_exist('build/dir1/x.cpp') + +# Rebuild the only object file without seeing all the dependencies. +test.run(arguments = 'duplicate=False view_all_dependencies=False .') +test.must_exist('build/dir1/x.cpp') +test.fail_test(string.find(test.stdout(), "`.' is up to date.") != -1) + +# Double check that targets are not rebuilt without and with all the +# dependencies. +test.run(arguments = 'duplicate=False view_all_dependencies=False .') +test.must_exist('build/dir1/x.cpp') +test.fail_test(string.find(test.stdout(), "`.' is up to date.") == -1) + +test.run(arguments = 'duplicate=False view_all_dependencies=True .') +test.must_exist('build/dir1/x.cpp') +test.fail_test(string.find(test.stdout(), "`.' is up to date.") == -1) + +# Clean-up everything. +test.run(arguments = 'duplicate=False view_all_dependencies=True -c .') +test.must_not_exist('build/dir1/x.cpp') + + +############################################################ +# +# Test with duplication +# +# FIXME: This can not work for now because there is no way that SCons +# can differentiate between a source that no longer exists and a file +# that has a builder that scons does not know about because scons has +# not parsed all the SConscript of a given project. +# + +# # Build everything first. +# test.run(arguments = 'duplicate=True view_all_dependencies=True .') +# test.must_exist('build/dir1/x.cpp') +# test.fail_test(string.find(test.stdout(), "`.' is up to date.") != -1) + +# # Double check that targets are not rebuilt. +# test.run(arguments = 'duplicate=True view_all_dependencies=True .') +# test.must_exist('build/dir1/x.cpp') +# test.fail_test(string.find(test.stdout(), "`.' is up to date.") == -1) + +# # Clean-up only the object file +# test.run(arguments = 'duplicate=True view_all_dependencies=False -c .') +# test.must_exist('build/dir1/x.cpp') + +# # Rebuild the only object file without seeing all the dependencies. +# test.run(arguments = 'duplicate=True view_all_dependencies=False .') +# test.must_exist('build/dir1/x.cpp') +# test.fail_test(string.find(test.stdout(), "`.' is up to date.") != -1) + +# # Double check that targets are not rebuilt without and with all the +# # dependencies. +# test.run(arguments = 'duplicate=True view_all_dependencies=False .') +# test.must_exist('build/dir1/x.cpp') +# test.fail_test(string.find(test.stdout(), "`.' is up to date.") == -1) + +# test.run(arguments = 'duplicate=True view_all_dependencies=True .') +# test.must_exist('build/dir1/x.cpp') +# test.fail_test(string.find(test.stdout(), "`.' is up to date.") == -1) + +# # Clean-up everything. +# test.run(arguments = 'duplicate=True view_all_dependencies=True -c .') +# test.must_not_exist('build/dir1/x.cpp') + + +test.pass_test() diff --git a/test/option-j.py b/test/option-j.py index bc36f08..ffb290c 100644 --- a/test/option-j.py +++ b/test/option-j.py @@ -218,42 +218,5 @@ start2, finish1 = RunTest('-j 1 f1 f2', "fourth") test.fail_test(start2 < finish1) -# Test that a failed build with -j works properly. - -test.write('mycopy.py', r"""\ -import sys -import time -time.sleep(1) -open(sys.argv[1], 'wb').write(open(sys.argv[2], 'rb').read()) -""") - -test.write('myfail.py', r"""\ -import sys -sys.exit(1) -""") - -test.write('SConstruct', """ -MyCopy = Builder(action = r'%(_python_)s mycopy.py $TARGET $SOURCE') -Fail = Builder(action = r'%(_python_)s myfail.py $TARGETS $SOURCE') -env = Environment(BUILDERS = { 'MyCopy' : MyCopy, 'Fail' : Fail }) -env.Fail(target = 'f3', source = 'f3.in') -env.MyCopy(target = 'f4', source = 'f4.in') -env.MyCopy(target = 'f5', source = 'f5.in') -env.MyCopy(target = 'f6', source = 'f6.in') -""" % locals()) - -test.write('f3.in', "f3.in\n") -test.write('f4.in', "f4.in\n") -test.write('f5.in', "f5.in\n") -test.write('f6.in', "f6.in\n") - -test.run(arguments = '-j 2 .', - status = 2, - stderr = "scons: *** [f3] Error 1\n") - -test.fail_test(os.path.exists(test.workpath('f3'))) -test.fail_test(test.read(test.workpath('f4')) != 'f4.in\n') -test.fail_test(os.path.exists(test.workpath('f5'))) -test.fail_test(os.path.exists(test.workpath('f6'))) test.pass_test() diff --git a/test/option/debug-dtree.py b/test/option/debug-dtree.py index 06296b9..3ef396e 100644 --- a/test/option/debug-dtree.py +++ b/test/option/debug-dtree.py @@ -56,6 +56,7 @@ int main(int argc, char *argv[]) test.write('bar.c', """ #include "bar.h" +int local = 1; """) test.write('foo.h', """ diff --git a/test/option/debug-includes.py b/test/option/debug-includes.py index 52d64a9..172cbb0 100644 --- a/test/option/debug-includes.py +++ b/test/option/debug-includes.py @@ -56,6 +56,7 @@ int main(int argc, char *argv[]) test.write('bar.c', """ #include "bar.h" +int local = 1; """) test.write('foo.h', """ diff --git a/test/option/debug-stree.py b/test/option/debug-stree.py index d25b7fa..bf65dbb 100644 --- a/test/option/debug-stree.py +++ b/test/option/debug-stree.py @@ -60,6 +60,7 @@ int main(int argc, char *argv[]) test.write('bar.c', """ #include "bar.h" +int local = 1; """) test.write('foo.h', """ diff --git a/test/option/debug-tree.py b/test/option/debug-tree.py index 09cdffb..f581bc4 100644 --- a/test/option/debug-tree.py +++ b/test/option/debug-tree.py @@ -64,6 +64,7 @@ int main(int argc, char *argv[]) test.write('Bar.c', """ #include "Bar.h" +int local = 1; """) test.write('Foo.h', """ diff --git a/test/option/tree-all.py b/test/option/tree-all.py index 7940d47..163d286 100644 --- a/test/option/tree-all.py +++ b/test/option/tree-all.py @@ -64,6 +64,7 @@ int main(int argc, char *argv[]) test.write('Bar.c', """ #include "Bar.h" +int local = 1; """) test.write('Foo.h', """ diff --git a/test/option/tree-derived.py b/test/option/tree-derived.py index 3ccada8..43735f8 100644 --- a/test/option/tree-derived.py +++ b/test/option/tree-derived.py @@ -56,6 +56,7 @@ int main(int argc, char *argv[]) test.write('bar.c', """ #include "bar.h" +int local = 1; """) test.write('foo.h', """ diff --git a/test/packaging/multiple-packages-at-once.py b/test/packaging/multiple-packages-at-once.py index 3151c05..bbf273f 100644 --- a/test/packaging/multiple-packages-at-once.py +++ b/test/packaging/multiple-packages-at-once.py @@ -79,4 +79,21 @@ test.run(arguments='', stderr = None) test.must_exist( 'src-1.0.zip' ) test.must_exist( 'src-1.0.tar.gz' ) +test.write('SConstruct', """ +Program( 'src/main.c' ) +env=Environment(tools=['default', 'packaging']) +env.Package( PACKAGETYPE = ['src_zip', 'src_targz'], + NAME = "src", VERSION = "1.0", + PACKAGEROOT = 'test', + source = [ 'src/main.c', 'SConstruct' ], + target = 'src.zip' ) +""") + +test.run(arguments='', stderr = None) + +test.must_exist( 'src.zip' ) +test.must_exist( 'src-1.0.tar.gz' ) + + + test.pass_test() diff --git a/test/packaging/rpm/explicit-target.py b/test/packaging/rpm/explicit-target.py index 12a6c6c..b9fcc0f 100644 --- a/test/packaging/rpm/explicit-target.py +++ b/test/packaging/rpm/explicit-target.py @@ -31,7 +31,6 @@ Test the ability to create a rpm package from a explicit target name. import os import TestSCons -machine = TestSCons.machine _python_ = TestSCons._python_ test = TestSCons.TestSCons() @@ -81,15 +80,10 @@ env.Package( NAME = 'foo', ) """ % locals()) -test.run(arguments='', stderr = None) +expect = """ +scons: *** Setting target is not supported for rpm. +""" + test.python_file_line(test.workpath('SConstruct'), 24) -src_rpm = 'foo-1.2.3-0.src.rpm' -machine_rpm = 'foo-1.2.3-0.%s.rpm' % machine - -test.must_exist( machine_rpm ) -test.must_exist( src_rpm ) -test.must_not_exist( 'bin/main' ) -test.fail_test( not os.popen('rpm -qpl %s' % machine_rpm).read()=='/bin/main\n') -test.fail_test( not os.popen('rpm -qpl %s' % src_rpm).read()=='foo-1.2.3.spec\nfoo-1.2.3.tar.gz\n') +test.run(arguments='', status=2, stderr=expect) test.pass_test() diff --git a/test/sconsign/ghost-entries.py b/test/sconsign/ghost-entries.py new file mode 100644 index 0000000..45426ad --- /dev/null +++ b/test/sconsign/ghost-entries.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python +# +# __COPYRIGHT__ +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +""" +Verify that "ghost" entries in the .sconsign file don't have Nodes +created for them on subsequent runs (which would cause errors +when scanning directories). +""" + +import os.path + +d_current_txt = os.path.join('d', 'current.txt') + +import TestSCons + +test = TestSCons.TestSCons() + +# This test case is from Morten Elo Petersen. It's harder because +# blib-case1only actually exists in the build dir after the -c, so the +# Dir scanner finds it and adds it to the dir's entries. +# Unfortunately FS.py's File.exists() method checks it later and finds +# it looks like a stale build copy of a missing source, so it deletes +# it. And then it's later discovered to be missing since it's still +# in the dir's entries list. The fix for this is to test for missing +# source files in the dir scanner (Scanner/Dir.py), and delete them +# (just like File.exists() does if they're found in the build dir +# rather than making entries for them. + +test.write('SConstruct', """\ +def cat(target, source, env): + fp = open(str(target[0]), 'wb') + for s in source: + fp.write(open(str(s), 'rb').read()) + fp.close() +env=Environment() +Export('env') +env['BUILDERS']['Cat']=Builder(action=cat, multi=1) +SConscript('src/SConscript',build_dir='build') +""") + +test.subdir('src') +test.write(['src', 'SConscript'], """\ +Import('env') +if ARGUMENTS['case']=='1': + A=env.Cat('#build/lib/blib-case1only','src.txt') + B=env.Cat('#build/lib/blibB','#build/lib/blib-case1only') +if ARGUMENTS['case']=='2': + A=env.Cat('case2only','src.txt') + B=env.Cat('#build/lib/blibB.txt','case2only') +env.Alias('go','#build/lib') +""") + +test.write(['src', 'src.txt'], "anything") + +test.run(arguments="-Q go case=1") +test.must_exist('build/lib/blib-case1only') +test.run(arguments="-Q go case=2") +test.run(arguments="-Q go case=2 -c") +test.run(arguments="-Q go case=2") + + +############################################################################# +# This test case is from Jason Orendorff. +# Checking for existence before making nodes for things found in the +# .sconsign fixes this one. + +test.write('SConstruct', """\ +Command("d/current.txt", [], [Touch("$TARGET")]) +if ARGUMENTS.has_key('pass') and ARGUMENTS['pass'] == '1': + Command("d/obsolete.txt", [], [Touch("$TARGET")]) +Command("installer.exe", ['d'], [Touch("$TARGET")]) +""") + +test.run(arguments='-Q pass=1') + +# Now delete the created files +test.unlink(['d', 'obsolete.txt']) +test.unlink(['d', 'current.txt']) +test.rmdir(['d']) + +# Run again, as pass 2 +expect = """Touch("%(d_current_txt)s") +Touch("installer.exe") +""" % locals() + +test.run(arguments='-Q pass=2', stdout=expect) + +test.pass_test() diff --git a/test/sconsign/script/Configure.py b/test/sconsign/script/Configure.py index 8e17e95..19c8a1c 100644 --- a/test/sconsign/script/Configure.py +++ b/test/sconsign/script/Configure.py @@ -30,6 +30,7 @@ info in them (which have different BuildInfo entries). """ import os.path +import re import TestSCons import TestSConsign @@ -40,6 +41,8 @@ test = TestSConsign.TestSConsign(match = TestSConsign.match_re) CC = test.detect('CC', norm=1) CC_dir, CC_file = os.path.split(CC) +CC_dir = re.escape(os.path.normcase(CC_dir)) +CC_file = re.escape(CC_file) # Note: We don't use os.path.join() representations of the file names # in the expected output because paths in the .sconsign files are diff --git a/test/sconsign/script/SConsignFile.py b/test/sconsign/script/SConsignFile.py index 99845e3..54ecaaa 100644 --- a/test/sconsign/script/SConsignFile.py +++ b/test/sconsign/script/SConsignFile.py @@ -29,19 +29,61 @@ Verify that the sconsign script works with files generated when using the signatures in an SConsignFile(). """ -import os.path - +import TestSCons import TestSConsign -test = TestSConsign.TestSConsign(match = TestSConsign.match_re) +_python_ = TestSCons._python_ -CC = test.detect('CC', norm=1) -CC_dir, CC_file = os.path.split(CC) -LINK = test.detect('LINK', norm=1) -if LINK is None: LINK = CC +test = TestSConsign.TestSConsign(match = TestSConsign.match_re) test.subdir('sub1', 'sub2') +test.write('fake_cc.py', r""" +import os.path +import re +import string +import sys + +path = string.split(sys.argv[1]) +output = open(sys.argv[2], 'wb') +input = open(sys.argv[3], 'rb') + +output.write('fake_cc.py: %s\n' % sys.argv) + +def find_file(f): + for dir in path: + p = dir + os.sep + f + if os.path.exists(p): + return open(p, 'rb') + return None + +def process(infp, outfp): + for line in infp.readlines(): + m = re.match('#include <(.*)>', line) + if m: + file = m.group(1) + process(find_file(file), outfp) + else: + outfp.write(line) + +process(input, output) + +sys.exit(0) +""") + +test.write('fake_link.py', r""" +import sys + +output = open(sys.argv[1], 'wb') +input = open(sys.argv[2], 'rb') + +output.write('fake_link.py: %s\n' % sys.argv) + +output.write(input.read()) + +sys.exit(0) +""") + # Note: We don't use os.path.join() representations of the file names # in the expected output because paths in the .sconsign files are # canonicalized to use / as the separator. @@ -55,27 +97,20 @@ sub2_inc2_h = 'sub2/inc2.h' test.write(['SConstruct'], """\ SConsignFile() -env1 = Environment(PROGSUFFIX = '.exe', OBJSUFFIX = '.obj') +env1 = Environment(PROGSUFFIX = '.exe', + OBJSUFFIX = '.obj', + CCCOM = r'%(_python_)s fake_cc.py sub2 $TARGET $SOURCE', + LINKCOM = r'%(_python_)s fake_link.py $TARGET $SOURCE') env1.Program('sub1/hello.c') env2 = env1.Clone(CPPPATH = ['sub2']) env2.Program('sub2/hello.c') -""") +""" % locals()) -test.write(['sub1', 'hello.c'], r"""\ -#include <stdio.h> -#include <stdlib.h> -int -main(int argc, char *argv[]) -{ - argv[argc++] = "--"; - printf("sub1/hello.c\n"); - exit (0); -} +test.write(['sub1', 'hello.c'], r""" +sub1/hello.c """) -test.write(['sub2', 'hello.c'], r"""\ -#include <stdio.h> -#include <stdlib.h> +test.write(['sub2', 'hello.c'], r""" #include <inc1.h> #include <inc2.h> int @@ -102,29 +137,23 @@ sig_re = r'[0-9a-fA-F]{32}' test.run_sconsign(arguments = ".sconsign", stdout = r"""=== .: SConstruct: None \d+ \d+ -=== %(CC_dir)s: -%(CC_file)s: %(sig_re)s \d+ \d+ === sub1: hello.c: %(sig_re)s \d+ \d+ hello.exe: %(sig_re)s \d+ \d+ %(sub1_hello_obj)s: %(sig_re)s \d+ \d+ - %(LINK)s: %(sig_re)s \d+ \d+ %(sig_re)s \[.*\] hello.obj: %(sig_re)s \d+ \d+ %(sub1_hello_c)s: %(sig_re)s \d+ \d+ - %(CC)s: %(sig_re)s \d+ \d+ %(sig_re)s \[.*\] === sub2: hello.c: %(sig_re)s \d+ \d+ hello.exe: %(sig_re)s \d+ \d+ %(sub2_hello_obj)s: %(sig_re)s \d+ \d+ - %(LINK)s: %(sig_re)s \d+ \d+ %(sig_re)s \[.*\] hello.obj: %(sig_re)s \d+ \d+ %(sub2_hello_c)s: %(sig_re)s \d+ \d+ %(sub2_inc1_h)s: %(sig_re)s \d+ \d+ %(sub2_inc2_h)s: %(sig_re)s \d+ \d+ - %(CC)s: %(sig_re)s \d+ \d+ %(sig_re)s \[.*\] inc1.h: %(sig_re)s \d+ \d+ inc2.h: %(sig_re)s \d+ \d+ @@ -133,29 +162,23 @@ inc2.h: %(sig_re)s \d+ \d+ test.run_sconsign(arguments = "--raw .sconsign", stdout = r"""=== .: SConstruct: {'csig': None, 'timestamp': \d+, 'size': \d+L?, '_version_id': 1} -=== %(CC_dir)s: -%(CC_file)s: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1} === sub1: hello.c: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1} hello.exe: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1} %(sub1_hello_obj)s: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1} - %(LINK)s: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1} %(sig_re)s \[.*\] hello.obj: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1} %(sub1_hello_c)s: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1} - %(CC)s: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1} %(sig_re)s \[.*\] === sub2: hello.c: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1} hello.exe: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1} %(sub2_hello_obj)s: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1} - %(LINK)s: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1} %(sig_re)s \[.*\] hello.obj: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1} %(sub2_hello_c)s: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1} %(sub2_inc1_h)s: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1} %(sub2_inc2_h)s: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1} - %(CC)s: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1} %(sig_re)s \[.*\] inc1.h: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1} inc2.h: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1} @@ -166,11 +189,6 @@ SConstruct: csig: None timestamp: \d+ size: \d+ -=== %(CC_dir)s: -%(CC_file)s: - csig: %(sig_re)s - timestamp: \d+ - size: \d+ === sub1: hello.c: csig: %(sig_re)s @@ -185,10 +203,6 @@ hello.exe: csig: %(sig_re)s timestamp: \d+ size: \d+ - %(LINK)s: - csig: %(sig_re)s - timestamp: \d+ - size: \d+ action: %(sig_re)s \[.*\] hello.obj: csig: %(sig_re)s @@ -199,10 +213,6 @@ hello.obj: csig: %(sig_re)s timestamp: \d+ size: \d+ - %(CC)s: - csig: %(sig_re)s - timestamp: \d+ - size: \d+ action: %(sig_re)s \[.*\] === sub2: hello.c: @@ -218,10 +228,6 @@ hello.exe: csig: %(sig_re)s timestamp: \d+ size: \d+ - %(LINK)s: - csig: %(sig_re)s - timestamp: \d+ - size: \d+ action: %(sig_re)s \[.*\] hello.obj: csig: %(sig_re)s @@ -240,10 +246,6 @@ hello.obj: csig: %(sig_re)s timestamp: \d+ size: \d+ - %(CC)s: - csig: %(sig_re)s - timestamp: \d+ - size: \d+ action: %(sig_re)s \[.*\] inc1.h: csig: %(sig_re)s @@ -261,9 +263,6 @@ test.run_sconsign(arguments = "-c -v .sconsign", stdout = r"""=== .: SConstruct: csig: None -=== %(CC_dir)s: -%(CC_file)s: - csig: %(sig_re)s === sub1: hello.c: csig: %(sig_re)s @@ -288,9 +287,6 @@ test.run_sconsign(arguments = "-s -v .sconsign", stdout = r"""=== .: SConstruct: size: \d+ -=== %(CC_dir)s: -%(CC_file)s: - size: \d+ === sub1: hello.c: size: \d+ @@ -315,9 +311,6 @@ test.run_sconsign(arguments = "-t -v .sconsign", stdout = r"""=== .: SConstruct: timestamp: \d+ -=== %(CC_dir)s: -%(CC_file)s: - timestamp: \d+ === sub1: hello.c: timestamp: \d+ @@ -340,64 +333,50 @@ inc2.h: test.run_sconsign(arguments = "-e hello.obj .sconsign", stdout = r"""=== .: -=== %(CC_dir)s: === sub1: hello.obj: %(sig_re)s \d+ \d+ %(sub1_hello_c)s: %(sig_re)s \d+ \d+ - %(CC)s: %(sig_re)s \d+ \d+ %(sig_re)s \[.*\] === sub2: hello.obj: %(sig_re)s \d+ \d+ %(sub2_hello_c)s: %(sig_re)s \d+ \d+ %(sub2_inc1_h)s: %(sig_re)s \d+ \d+ %(sub2_inc2_h)s: %(sig_re)s \d+ \d+ - %(CC)s: %(sig_re)s \d+ \d+ %(sig_re)s \[.*\] """ % locals(), - stderr = r"""sconsign: no entry `hello.obj' in `\.' -sconsign: no entry `hello.obj' in `%(CC_dir)s' -""" % locals()) + stderr = r"""sconsign: no entry `hello.obj' in `\.' +""") test.run_sconsign(arguments = "-e hello.obj -e hello.exe -e hello.obj .sconsign", stdout = r"""=== .: -=== %(CC_dir)s: === sub1: hello.obj: %(sig_re)s \d+ \d+ %(sub1_hello_c)s: %(sig_re)s \d+ \d+ - %(CC)s: %(sig_re)s \d+ \d+ %(sig_re)s \[.*\] hello.exe: %(sig_re)s \d+ \d+ %(sub1_hello_obj)s: %(sig_re)s \d+ \d+ - %(LINK)s: %(sig_re)s \d+ \d+ %(sig_re)s \[.*\] hello.obj: %(sig_re)s \d+ \d+ %(sub1_hello_c)s: %(sig_re)s \d+ \d+ - %(CC)s: %(sig_re)s \d+ \d+ %(sig_re)s \[.*\] === sub2: hello.obj: %(sig_re)s \d+ \d+ %(sub2_hello_c)s: %(sig_re)s \d+ \d+ %(sub2_inc1_h)s: %(sig_re)s \d+ \d+ %(sub2_inc2_h)s: %(sig_re)s \d+ \d+ - %(CC)s: %(sig_re)s \d+ \d+ %(sig_re)s \[.*\] hello.exe: %(sig_re)s \d+ \d+ %(sub2_hello_obj)s: %(sig_re)s \d+ \d+ - %(LINK)s: %(sig_re)s \d+ \d+ %(sig_re)s \[.*\] hello.obj: %(sig_re)s \d+ \d+ %(sub2_hello_c)s: %(sig_re)s \d+ \d+ %(sub2_inc1_h)s: %(sig_re)s \d+ \d+ %(sub2_inc2_h)s: %(sig_re)s \d+ \d+ - %(CC)s: %(sig_re)s \d+ \d+ %(sig_re)s \[.*\] """ % locals(), stderr = r"""sconsign: no entry `hello.obj' in `\.' sconsign: no entry `hello.exe' in `\.' sconsign: no entry `hello.obj' in `\.' -sconsign: no entry `hello.obj' in `%(CC_dir)s' -sconsign: no entry `hello.exe' in `%(CC_dir)s' -sconsign: no entry `hello.obj' in `%(CC_dir)s' """ % locals()) #test.run_sconsign(arguments = "-i -v .sconsign", diff --git a/test/sconsign/script/Signatures.py b/test/sconsign/script/Signatures.py index fc85133..27e4867 100644 --- a/test/sconsign/script/Signatures.py +++ b/test/sconsign/script/Signatures.py @@ -31,13 +31,12 @@ SourceSignatures() and TargetSignatures() values (timestamp and content, respectively). """ +import TestSCons import TestSConsign -test = TestSConsign.TestSConsign(match = TestSConsign.match_re) +_python_ = TestSCons._python_ -CC = test.detect('CC', norm=1) -LINK = test.detect('LINK', norm=1) -if LINK is None: LINK = CC +test = TestSConsign.TestSConsign(match = TestSConsign.match_re) # Note: We don't use os.path.join() representations of the file names # in the expected output because paths in the .sconsign files are @@ -46,47 +45,75 @@ if LINK is None: LINK = CC sub1_hello_c = 'sub1/hello.c' sub1_hello_obj = 'sub1/hello.obj' -def re_sep(*args): - import os.path - import re - return re.escape(apply(os.path.join, args)) - test.subdir('sub1', 'sub2') +test.write('fake_cc.py', r""" +import os.path +import re +import string +import sys + +path = string.split(sys.argv[1]) +output = open(sys.argv[2], 'wb') +input = open(sys.argv[3], 'rb') + +output.write('fake_cc.py: %s\n' % sys.argv) + +def find_file(f): + for dir in path: + p = dir + os.sep + f + if os.path.exists(p): + return open(p, 'rb') + return None + +def process(infp, outfp): + for line in infp.readlines(): + m = re.match('#include <(.*)>', line) + if m: + file = m.group(1) + process(find_file(file), outfp) + else: + outfp.write(line) + +process(input, output) + +sys.exit(0) +""") + +test.write('fake_link.py', r""" +import sys + +output = open(sys.argv[1], 'wb') +input = open(sys.argv[2], 'rb') + +output.write('fake_link.py: %s\n' % sys.argv) + +output.write(input.read()) + +sys.exit(0) +""") + test.write('SConstruct', """ SConsignFile(None) SourceSignatures('timestamp') TargetSignatures('content') -env1 = Environment(PROGSUFFIX = '.exe', OBJSUFFIX = '.obj') +env1 = Environment(PROGSUFFIX = '.exe', + OBJSUFFIX = '.obj', + CCCOM = r'%(_python_)s fake_cc.py sub2 $TARGET $SOURCE', + LINKCOM = r'%(_python_)s fake_link.py $TARGET $SOURCE') env1.Program('sub1/hello.c') env2 = env1.Clone(CPPPATH = ['sub2']) env2.Program('sub2/hello.c') -""") +""" % locals()) test.write(['sub1', 'hello.c'], r"""\ -#include <stdio.h> -#include <stdlib.h> -int -main(int argc, char *argv[]) -{ - argv[argc++] = "--"; - printf("sub1/hello.c\n"); - exit (0); -} +sub1/hello.c """) test.write(['sub2', 'hello.c'], r"""\ -#include <stdio.h> -#include <stdlib.h> #include <inc1.h> #include <inc2.h> -int -main(int argc, char *argv[]) -{ - argv[argc++] = "--"; - printf("sub2/goodbye.c\n"); - exit (0); -} +sub2/hello.c """) test.write(['sub2', 'inc1.h'], r"""\ @@ -107,22 +134,18 @@ date_re = r'\S+ \S+ [ \d]\d \d\d:\d\d:\d\d \d\d\d\d' test.run_sconsign(arguments = "-e hello.exe -e hello.obj sub1/.sconsign", stdout = r"""hello.exe: %(sig_re)s \d+ \d+ %(sub1_hello_obj)s: %(sig_re)s \d+ \d+ - %(LINK)s: None \d+ \d+ %(sig_re)s \[.*\] hello.obj: %(sig_re)s \d+ \d+ %(sub1_hello_c)s: None \d+ \d+ - %(CC)s: None \d+ \d+ %(sig_re)s \[.*\] """ % locals()) test.run_sconsign(arguments = "-e hello.exe -e hello.obj -r sub1/.sconsign", stdout = r"""hello.exe: %(sig_re)s '%(date_re)s' \d+ %(sub1_hello_obj)s: %(sig_re)s '%(date_re)s' \d+ - %(LINK)s: None '%(date_re)s' \d+ %(sig_re)s \[.*\] hello.obj: %(sig_re)s '%(date_re)s' \d+ %(sub1_hello_c)s: None '%(date_re)s' \d+ - %(CC)s: None '%(date_re)s' \d+ %(sig_re)s \[.*\] """ % locals()) diff --git a/test/sconsign/script/no-SConsignFile.py b/test/sconsign/script/no-SConsignFile.py index 1fcfbfd..6aede19 100644 --- a/test/sconsign/script/no-SConsignFile.py +++ b/test/sconsign/script/no-SConsignFile.py @@ -29,16 +29,61 @@ Verify that the sconsign script works when using an individual .sconsign file in each directory (SConsignFile(None)). """ +import TestSCons import TestSConsign -test = TestSConsign.TestSConsign(match = TestSConsign.match_re) +_python_ = TestSCons._python_ -CC = test.detect('CC', norm=1) -LINK = test.detect('LINK', norm=1) -if LINK is None: LINK = CC +test = TestSConsign.TestSConsign(match = TestSConsign.match_re) test.subdir('sub1', 'sub2') +test.write('fake_cc.py', r""" +import os.path +import re +import string +import sys + +path = string.split(sys.argv[1]) +output = open(sys.argv[2], 'wb') +input = open(sys.argv[3], 'rb') + +output.write('fake_cc.py: %s\n' % sys.argv) + +def find_file(f): + for dir in path: + p = dir + os.sep + f + if os.path.exists(p): + return open(p, 'rb') + return None + +def process(infp, outfp): + for line in infp.readlines(): + m = re.match('#include <(.*)>', line) + if m: + file = m.group(1) + process(find_file(file), outfp) + else: + outfp.write(line) + +process(input, output) + +sys.exit(0) +""") + +test.write('fake_link.py', r""" +import sys + +output = open(sys.argv[1], 'wb') +input = open(sys.argv[2], 'rb') + +output.write('fake_link.py: %s\n' % sys.argv) + +output.write(input.read()) + +sys.exit(0) +""") + # Note: We don't use os.path.join() representations of the file names # in the expected output because paths in the .sconsign files are # canonicalized to use / as the separator. @@ -52,36 +97,23 @@ sub2_inc2_h = 'sub2/inc2.h' test.write(['SConstruct'], """ SConsignFile(None) -env1 = Environment(PROGSUFFIX = '.exe', OBJSUFFIX = '.obj') +env1 = Environment(PROGSUFFIX = '.exe', + OBJSUFFIX = '.obj', + CCCOM = r'%(_python_)s fake_cc.py sub2 $TARGET $SOURCE', + LINKCOM = r'%(_python_)s fake_link.py $TARGET $SOURCE') env1.Program('sub1/hello.c') env2 = env1.Clone(CPPPATH = ['sub2']) env2.Program('sub2/hello.c') -""") +""" % locals()) test.write(['sub1', 'hello.c'], r"""\ -#include <stdio.h> -#include <stdlib.h> -int -main(int argc, char *argv[]) -{ - argv[argc++] = "--"; - printf("sub1/hello.c\n"); - exit (0); -} +sub1/hello.c """) test.write(['sub2', 'hello.c'], r"""\ -#include <stdio.h> -#include <stdlib.h> #include <inc1.h> #include <inc2.h> -int -main(int argc, char *argv[]) -{ - argv[argc++] = "--"; - printf("sub2/goodbye.c\n"); - exit (0); -} +sub2/hello.c """) test.write(['sub2', 'inc1.h'], r"""\ @@ -99,11 +131,9 @@ sig_re = r'[0-9a-fA-F]{32}' expect = r"""hello.c: %(sig_re)s \d+ \d+ hello.exe: %(sig_re)s \d+ \d+ %(sub1_hello_obj)s: %(sig_re)s \d+ \d+ - %(LINK)s: %(sig_re)s \d+ \d+ %(sig_re)s \[.*\] hello.obj: %(sig_re)s \d+ \d+ %(sub1_hello_c)s: %(sig_re)s \d+ \d+ - %(CC)s: %(sig_re)s \d+ \d+ %(sig_re)s \[.*\] """ % locals() @@ -115,11 +145,9 @@ test.run_sconsign(arguments = "--raw sub1/.sconsign", stdout = r"""hello.c: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1} hello.exe: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1} %(sub1_hello_obj)s: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1} - %(LINK)s: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1} %(sig_re)s \[.*\] hello.obj: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1} %(sub1_hello_c)s: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1} - %(CC)s: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1} %(sig_re)s \[.*\] """ % locals()) @@ -137,10 +165,6 @@ hello.exe: csig: %(sig_re)s timestamp: \d+ size: \d+ - %(LINK)s: - csig: %(sig_re)s - timestamp: \d+ - size: \d+ action: %(sig_re)s \[.*\] hello.obj: csig: %(sig_re)s @@ -151,10 +175,6 @@ hello.obj: csig: %(sig_re)s timestamp: \d+ size: \d+ - %(CC)s: - csig: %(sig_re)s - timestamp: \d+ - size: \d+ action: %(sig_re)s \[.*\] """ % locals()) @@ -188,22 +208,18 @@ hello.obj: test.run_sconsign(arguments = "-e hello.obj sub1/.sconsign", stdout = r"""hello.obj: %(sig_re)s \d+ \d+ %(sub1_hello_c)s: %(sig_re)s \d+ \d+ - %(CC)s: %(sig_re)s \d+ \d+ %(sig_re)s \[.*\] """ % locals()) test.run_sconsign(arguments = "-e hello.obj -e hello.exe -e hello.obj sub1/.sconsign", stdout = r"""hello.obj: %(sig_re)s \d+ \d+ %(sub1_hello_c)s: %(sig_re)s \d+ \d+ - %(CC)s: %(sig_re)s \d+ \d+ %(sig_re)s \[.*\] hello.exe: %(sig_re)s \d+ \d+ %(sub1_hello_obj)s: %(sig_re)s \d+ \d+ - %(LINK)s: %(sig_re)s \d+ \d+ %(sig_re)s \[.*\] hello.obj: %(sig_re)s \d+ \d+ %(sub1_hello_c)s: %(sig_re)s \d+ \d+ - %(CC)s: %(sig_re)s \d+ \d+ %(sig_re)s \[.*\] """ % locals()) @@ -211,13 +227,11 @@ test.run_sconsign(arguments = "sub2/.sconsign", stdout = r"""hello.c: %(sig_re)s \d+ \d+ hello.exe: %(sig_re)s \d+ \d+ %(sub2_hello_obj)s: %(sig_re)s \d+ \d+ - %(LINK)s: %(sig_re)s \d+ \d+ %(sig_re)s \[.*\] hello.obj: %(sig_re)s \d+ \d+ %(sub2_hello_c)s: %(sig_re)s \d+ \d+ %(sub2_inc1_h)s: %(sig_re)s \d+ \d+ %(sub2_inc2_h)s: %(sig_re)s \d+ \d+ - %(CC)s: %(sig_re)s \d+ \d+ %(sig_re)s \[.*\] inc1.h: %(sig_re)s \d+ \d+ inc2.h: %(sig_re)s \d+ \d+ @@ -240,11 +254,9 @@ test.run_sconsign(arguments = "-e hello.obj sub2/.sconsign sub1/.sconsign", %(sub2_hello_c)s: %(sig_re)s \d+ \d+ %(sub2_inc1_h)s: %(sig_re)s \d+ \d+ %(sub2_inc2_h)s: %(sig_re)s \d+ \d+ - %(CC)s: %(sig_re)s \d+ \d+ %(sig_re)s \[.*\] hello.obj: %(sig_re)s \d+ \d+ %(sub1_hello_c)s: %(sig_re)s \d+ \d+ - %(CC)s: %(sig_re)s \d+ \d+ %(sig_re)s \[.*\] """ % locals()) diff --git a/test/subdivide.py b/test/subdivide.py index a816957..72c7766 100644 --- a/test/subdivide.py +++ b/test/subdivide.py @@ -34,6 +34,8 @@ import os.path import TestSCons +_python_ = TestSCons._python_ + test = TestSCons.TestSCons() #if os.path.exists('sconsign.py'): @@ -46,58 +48,79 @@ test = TestSCons.TestSCons() test.subdir('src', ['src', 'sub']) +test.write('fake_cc.py', """\ +import sys +ofp = open(sys.argv[1], 'wb') +ofp.write('fake_cc.py: %s\\n' % sys.argv) +for s in sys.argv[2:]: + ofp.write(open(s, 'rb').read()) +""") + +test.write('fake_link.py', """\ +import sys +ofp = open(sys.argv[1], 'wb') +ofp.write('fake_link.py: %s\\n' % sys.argv) +for s in sys.argv[2:]: + ofp.write(open(s, 'rb').read()) +""") + test.write('SConstruct', """\ SConsignFile(None) TargetSignatures('content') -env = Environment() +env = Environment(PROGSUFFIX = '.exe', + OBJSUFFIX = '.obj', + CCCOM = r'%(_python_)s fake_cc.py $TARGET $SOURCES', + LINKCOM = r'%(_python_)s fake_link.py $TARGET $SOURCES') env.SConscript('src/SConstruct', exports=['env']) env.Object('foo.c') -""") +""" % locals()) test.write(['src', 'SConstruct'], """\ SConsignFile(None) TargetSignatures('content') -env = Environment() -p = env.Program('prog', ['main.c', '../foo%s', 'sub/bar.c']) +env = Environment(PROGSUFFIX = '.exe', + OBJSUFFIX = '.obj', + CCCOM = r'%(_python_)s fake_cc.py $TARGET $SOURCES', + LINKCOM = r'%(_python_)s fake_link.py $TARGET $SOURCES') +p = env.Program('prog', ['main.c', '../foo$OBJSUFFIX', 'sub/bar.c']) env.Default(p) -""" % TestSCons._obj) +""" % locals()) test.write('foo.c', """\ -#include <stdio.h> -#include <stdlib.h> -void -foo(void) { - printf("foo.c\\n"); -} +foo.c """) test.write(['src', 'main.c'], """\ -#include <stdio.h> -#include <stdlib.h> -extern void foo(void); -extern void bar(void); -int -main(int argc, char *argv[]) { - foo(); - bar(); - printf("src/main.c\\n"); - exit (0); -} +src/main.c """) test.write(['src', 'sub', 'bar.c'], """\ -#include <stdio.h> -#include <stdlib.h> -void -bar(void) { - printf("bar.c\\n"); -} +src/sub/bar.c """) test.run() -test.run(program=test.workpath('src', 'prog'), - stdout="foo.c\nbar.c\nsrc/main.c\n") +src_prog_exe = os.path.join('src', 'prog.exe') +src_main_c = os.path.join('src', 'main.c') +src_main_obj = os.path.join('src', 'main.obj') +src_sub_bar_c = os.path.join('src', 'sub', 'bar.c') +src_sub_bar_obj = os.path.join('src', 'sub', 'bar.obj') + +expect = """\ +fake_link.py: ['fake_link.py', '%(src_prog_exe)s', '%(src_main_obj)s', 'foo.obj', '%(src_sub_bar_obj)s'] +fake_cc.py: ['fake_cc.py', '%(src_main_obj)s', '%(src_main_c)s'] +src/main.c +fake_cc.py: ['fake_cc.py', 'foo.obj', 'foo.c'] +foo.c +fake_cc.py: ['fake_cc.py', '%(src_sub_bar_obj)s', '%(src_sub_bar_c)s'] +src/sub/bar.c +""" % locals() + +if os.sep == '\\': + import string + expect = string.replace(expect, '\\', '\\\\') + +test.must_match(['src', 'prog.exe'], expect) test.up_to_date(chdir='src', arguments = test.workpath()) |