summaryrefslogtreecommitdiffstats
path: root/src/engine/SCons/Node
diff options
context:
space:
mode:
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