summaryrefslogtreecommitdiffstats
path: root/src/engine/SCons/Node
diff options
context:
space:
mode:
authorSteven Knight <knight@baldmt.com>2007-12-13 04:25:43 (GMT)
committerSteven Knight <knight@baldmt.com>2007-12-13 04:25:43 (GMT)
commit8dfbc4aafc99cd41c9e89b245e0772c862e073a6 (patch)
tree86299c32c336859f1bf05c6e3800d8c54282ccd8 /src/engine/SCons/Node
parent70591272485606edf2293e7d9fa33113ac9cb00c (diff)
downloadSCons-8dfbc4aafc99cd41c9e89b245e0772c862e073a6.zip
SCons-8dfbc4aafc99cd41c9e89b245e0772c862e073a6.tar.gz
SCons-8dfbc4aafc99cd41c9e89b245e0772c862e073a6.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. ........
Diffstat (limited to 'src/engine/SCons/Node')
-rw-r--r--src/engine/SCons/Node/Alias.py8
-rw-r--r--src/engine/SCons/Node/AliasTests.py2
-rw-r--r--src/engine/SCons/Node/FS.py574
-rw-r--r--src/engine/SCons/Node/FSTests.py486
-rw-r--r--src/engine/SCons/Node/__init__.py37
5 files changed, 895 insertions, 212 deletions
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