diff options
| author | William Deegan <bill@baddogconsulting.com> | 2016-05-08 23:27:44 (GMT) |
|---|---|---|
| committer | William Deegan <bill@baddogconsulting.com> | 2016-05-08 23:27:44 (GMT) |
| commit | d86a8cf1f8e5aadca9bcd6b7481ab15eccc0530d (patch) | |
| tree | 521ddb1071569fa90100e11d7f03af13d5689aaf /src/engine/SCons/Node | |
| parent | 98bb69c7c9e13ea57ae7e6e8db4740a4dd43ed16 (diff) | |
| parent | 6a37189174372c9c98c63ada58ab4352adf650e8 (diff) | |
| download | SCons-d86a8cf1f8e5aadca9bcd6b7481ab15eccc0530d.zip SCons-d86a8cf1f8e5aadca9bcd6b7481ab15eccc0530d.tar.gz SCons-d86a8cf1f8e5aadca9bcd6b7481ab15eccc0530d.tar.bz2 | |
merged
Diffstat (limited to 'src/engine/SCons/Node')
| -rw-r--r-- | src/engine/SCons/Node/Alias.py | 49 | ||||
| -rw-r--r-- | src/engine/SCons/Node/AliasTests.py | 4 | ||||
| -rw-r--r-- | src/engine/SCons/Node/FS.py | 797 | ||||
| -rw-r--r-- | src/engine/SCons/Node/FSTests.py | 308 | ||||
| -rw-r--r-- | src/engine/SCons/Node/NodeTests.py | 152 | ||||
| -rw-r--r-- | src/engine/SCons/Node/Python.py | 40 | ||||
| -rw-r--r-- | src/engine/SCons/Node/PythonTests.py | 4 | ||||
| -rw-r--r-- | src/engine/SCons/Node/__init__.py | 542 |
8 files changed, 1272 insertions, 624 deletions
diff --git a/src/engine/SCons/Node/Alias.py b/src/engine/SCons/Node/Alias.py index f817356..a035816 100644 --- a/src/engine/SCons/Node/Alias.py +++ b/src/engine/SCons/Node/Alias.py @@ -56,13 +56,47 @@ class AliasNameSpace(collections.UserDict): return None class AliasNodeInfo(SCons.Node.NodeInfoBase): - current_version_id = 1 + __slots__ = ('csig',) + current_version_id = 2 field_list = ['csig'] def str_to_node(self, s): return default_ans.Alias(s) + def __getstate__(self): + """ + Return all fields that shall be pickled. Walk the slots in the class + hierarchy and add those to the state dictionary. If a '__dict__' slot is + available, copy all entries to the dictionary. Also include the version + id, which is fixed for all instances of a class. + """ + state = getattr(self, '__dict__', {}).copy() + for obj in type(self).mro(): + for name in getattr(obj,'__slots__',()): + if hasattr(self, name): + state[name] = getattr(self, name) + + state['_version_id'] = self.current_version_id + try: + del state['__weakref__'] + except KeyError: + pass + + return state + + def __setstate__(self, state): + """ + Restore the attributes from a pickled state. + """ + # TODO check or discard version + del state['_version_id'] + for key, value in state.items(): + if key not in ('__weakref__',): + setattr(self, key, value) + + class AliasBuildInfo(SCons.Node.BuildInfoBase): - current_version_id = 1 + __slots__ = () + current_version_id = 2 class Alias(SCons.Node.Node): @@ -72,7 +106,9 @@ class Alias(SCons.Node.Node): def __init__(self, name): SCons.Node.Node.__init__(self) self.name = name - + self.changed_since_last_build = 1 + self.store_info = 0 + def str_for_display(self): return '"' + self.__str__() + '"' @@ -105,13 +141,6 @@ class Alias(SCons.Node.Node): # # - def changed_since_last_build(self, target, prev_ni): - cur_csig = self.get_csig() - try: - return cur_csig != prev_ni.csig - except AttributeError: - return 1 - def build(self): """A "builder" for aliases.""" pass diff --git a/src/engine/SCons/Node/AliasTests.py b/src/engine/SCons/Node/AliasTests.py index 2d11bdf..8e31875 100644 --- a/src/engine/SCons/Node/AliasTests.py +++ b/src/engine/SCons/Node/AliasTests.py @@ -103,14 +103,14 @@ class AliasNodeInfoTestCase(unittest.TestCase): """Test AliasNodeInfo initialization""" ans = SCons.Node.Alias.AliasNameSpace() aaa = ans.Alias('aaa') - ni = SCons.Node.Alias.AliasNodeInfo(aaa) + ni = SCons.Node.Alias.AliasNodeInfo() class AliasBuildInfoTestCase(unittest.TestCase): def test___init__(self): """Test AliasBuildInfo initialization""" ans = SCons.Node.Alias.AliasNameSpace() aaa = ans.Alias('aaa') - bi = SCons.Node.Alias.AliasBuildInfo(aaa) + bi = SCons.Node.Alias.AliasBuildInfo() if __name__ == "__main__": suite = unittest.TestSuite() diff --git a/src/engine/SCons/Node/FS.py b/src/engine/SCons/Node/FS.py index efca0c7..a4dd5d9 100644 --- a/src/engine/SCons/Node/FS.py +++ b/src/engine/SCons/Node/FS.py @@ -57,10 +57,23 @@ import SCons.Warnings from SCons.Debug import Trace -do_store_info = True print_duplicate = 0 +def sconsign_none(node): + raise NotImplementedError + +def sconsign_dir(node): + """Return the .sconsign file info for this directory, + creating it first if necessary.""" + if not node._sconsign: + import SCons.SConsign + node._sconsign = SCons.SConsign.ForDirectory(node) + return node._sconsign + +_sconsign_map = {0 : sconsign_none, + 1 : sconsign_dir} + class EntryProxyAttributeError(AttributeError): """ An AttributeError subclass for recording and displaying the name @@ -110,7 +123,7 @@ def save_strings(val): # 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 _my_splitdrive =None @@ -145,7 +158,7 @@ def initialize_do_splitdrive(): global OS_SEP global UNC_PREFIX global os_sep_is_slash - + OS_SEP = os.sep UNC_PREFIX = OS_SEP + OS_SEP os_sep_is_slash = OS_SEP == '/' @@ -166,7 +179,7 @@ needs_normpath_check = re.compile( # b) The path starts with '..'. E.g. '../' or '../moredirs' # but we not match '..abc/'. # c) The path ends with '..'. E.g. '/..' or 'dirs/..' - # d) The path contains a '..' in the middle. + # d) The path contains a '..' in the middle. # E.g. dirs/../moredirs (.*/)?\.\.(?:/|$) | @@ -174,7 +187,7 @@ needs_normpath_check = re.compile( # We need to renormalize the path if it contains a '.' # directory, but NOT if it is a single '.' '/' characters. We # do not want to match a single '.' because this case is checked - # for explicitely since this is common enough case. + # for explicitly since this is common enough case. # # Note that we check for all the following cases: # @@ -188,7 +201,7 @@ needs_normpath_check = re.compile( \./|.*/\.(?:/|$) - ''', + ''', re.VERBOSE ) needs_normpath_match = needs_normpath_check.match @@ -269,8 +282,8 @@ def LinkFunc(target, source, env): # who want to move their soft-linked src-trees around. Those # people should use the 'hard-copy' mode, softlinks cannot be # used for that; at least I have no idea how ... - src = source[0].abspath - dest = target[0].abspath + src = source[0].get_abspath() + dest = target[0].get_abspath() dir, file = os.path.split(dest) if dir and not target[0].fs.isdir(dir): os.makedirs(dir) @@ -303,7 +316,7 @@ LocalCopy = SCons.Action.Action(LinkFunc, LocalString) def UnlinkFunc(target, source, env): t = target[0] - t.fs.unlink(t.abspath) + t.fs.unlink(t.get_abspath()) return 0 Unlink = SCons.Action.Action(UnlinkFunc, None) @@ -311,7 +324,7 @@ Unlink = SCons.Action.Action(UnlinkFunc, None) def MkdirFunc(target, source, env): t = target[0] if not t.exists(): - t.fs.mkdir(t.abspath) + t.fs.mkdir(t.get_abspath()) return 0 Mkdir = SCons.Action.Action(MkdirFunc, None, presub=None) @@ -404,7 +417,7 @@ def do_diskcheck_match(node, predicate, errorfmt): except (AttributeError, KeyError): pass if result: - raise TypeError(errorfmt % node.abspath) + raise TypeError(errorfmt % node.get_abspath()) def ignore_diskcheck_match(node, predicate, errorfmt): pass @@ -574,7 +587,20 @@ class Base(SCons.Node.Node): object identity comparisons. """ - memoizer_counters = [] + __slots__ = ['name', + 'fs', + '_abspath', + '_labspath', + '_path', + '_tpath', + '_path_elements', + 'dir', + 'cwd', + 'duplicate', + '_local', + 'sbuilder', + '_proxy', + '_func_sconsign'] def __init__(self, name, directory, fs): """Initialize a generic Node.FS.Base object. @@ -592,27 +618,26 @@ class Base(SCons.Node.Node): #: Filename with extension as it was specified when the object was #: created; to obtain filesystem path, use Python str() function self.name = SCons.Util.silent_intern(name) - #: Cached filename extension - self.suffix = SCons.Util.silent_intern(SCons.Util.splitext(name)[1]) self.fs = fs #: Reference to parent Node.FS object assert directory, "A directory must be provided" - self.abspath = SCons.Util.silent_intern(directory.entry_abspath(name)) - self.labspath = SCons.Util.silent_intern(directory.entry_labspath(name)) - if directory.path == '.': - self.path = SCons.Util.silent_intern(name) - else: - self.path = SCons.Util.silent_intern(directory.entry_path(name)) - if directory.tpath == '.': - self.tpath = SCons.Util.silent_intern(name) - else: - self.tpath = SCons.Util.silent_intern(directory.entry_tpath(name)) - self.path_elements = directory.path_elements + [self] + self._abspath = None + self._labspath = None + self._path = None + self._tpath = None + self._path_elements = None self.dir = directory self.cwd = None # will hold the SConscript directory for target nodes self.duplicate = directory.duplicate + self.changed_since_last_build = 2 + self._func_sconsign = 0 + self._func_exists = 2 + self._func_rexists = 2 + self._func_get_contents = 0 + self._func_target_from_source = 1 + self.store_info = 1 def str_for_display(self): return '"' + self.__str__() + '"' @@ -625,17 +650,38 @@ class Base(SCons.Node.Node): if isinstance(self, klass) or klass is Entry: return raise TypeError("Tried to lookup %s '%s' as a %s." %\ - (self.__class__.__name__, self.path, klass.__name__)) + (self.__class__.__name__, self.get_internal_path(), klass.__name__)) def get_dir(self): return self.dir def get_suffix(self): - return self.suffix + return SCons.Util.splitext(self.name)[1] def rfile(self): return self + def __getattr__(self, attr): + """ Together with the node_bwcomp dict defined below, + this method provides a simple backward compatibility + layer for the Node attributes 'abspath', 'labspath', + 'path', 'tpath', 'suffix' and 'path_elements'. These Node + attributes used to be directly available in v2.3 and earlier, but + have been replaced by getter methods that initialize the + single variables lazily when required, in order to save memory. + The redirection to the getters lets older Tools and + SConstruct continue to work without any additional changes, + fully transparent to the user. + Note, that __getattr__ is only called as fallback when the + requested attribute can't be found, so there should be no + speed performance penalty involved for standard builds. + """ + if attr in node_bwcomp: + return node_bwcomp[attr](self) + + raise AttributeError("%r object has no attribute %r" % + (self.__class__, attr)) + def __str__(self): """A Node.FS.Base object's string representation is its path name.""" @@ -644,8 +690,7 @@ class Base(SCons.Node.Node): return self._save_str() return self._get_str() - memoizer_counters.append(SCons.Memoize.CountValue('_save_str')) - + @SCons.Memoize.CountMethodCall def _save_str(self): try: return self._memo['_save_str'] @@ -682,21 +727,20 @@ class Base(SCons.Node.Node): rstr = __str__ - memoizer_counters.append(SCons.Memoize.CountValue('stat')) - + @SCons.Memoize.CountMethodCall def stat(self): try: return self._memo['stat'] except KeyError: pass - try: result = self.fs.stat(self.abspath) + try: result = self.fs.stat(self.get_abspath()) except os.error: result = None self._memo['stat'] = result return result def exists(self): - return self.stat() is not None + return SCons.Node._exists_map[self._func_exists](self) def rexists(self): - return self.rfile().exists() + return SCons.Node._rexists_map[self._func_rexists](self) def getmtime(self): st = self.stat() @@ -718,7 +762,7 @@ class Base(SCons.Node.Node): if hasattr(os, 'symlink'): def islink(self): - try: st = self.fs.lstat(self.abspath) + try: st = self.fs.lstat(self.get_abspath()) except os.error: return 0 return stat.S_ISLNK(st[stat.ST_MODE]) else: @@ -753,10 +797,10 @@ class Base(SCons.Node.Node): dir = self.fs.getcwd() if self == dir: return '.' - path_elems = self.path_elements + path_elems = self.get_path_elements() pathname = '' try: i = path_elems.index(dir) - except ValueError: + except ValueError: for p in path_elems[:-1]: pathname += p.dirname else: @@ -786,7 +830,26 @@ class Base(SCons.Node.Node): def get_abspath(self): """Get the absolute path of the file.""" - return self.abspath + return self.dir.entry_abspath(self.name) + + def get_labspath(self): + """Get the absolute path of the file.""" + return self.dir.entry_labspath(self.name) + + def get_internal_path(self): + if self.dir._path == '.': + return self.name + else: + return self.dir.entry_path(self.name) + + def get_tpath(self): + if self.dir._tpath == '.': + return self.name + else: + return self.dir.entry_tpath(self.name) + + def get_path_elements(self): + return self.dir._path_elements + [self] def for_signature(self): # Return just our name. Even an absolute path would not work, @@ -812,13 +875,12 @@ class Base(SCons.Node.Node): files that need different behavior. See Tool/swig.py for an example. """ - return self.dir.Entry(prefix + splitext(self.name)[0] + suffix) + return SCons.Node._target_from_source_map[self._func_target_from_source](self, prefix, suffix, splitext) def _Rfindalldirs_key(self, pathlist): return pathlist - memoizer_counters.append(SCons.Memoize.CountDict('Rfindalldirs', _Rfindalldirs_key)) - + @SCons.Memoize.CountDictCall(_Rfindalldirs_key) def Rfindalldirs(self, pathlist): """ Return all of the directories for a given path list, including @@ -857,8 +919,7 @@ class Base(SCons.Node.Node): cwd = self.cwd or self.fs._cwd return cwd.Rfindalldirs(pathlist) - memoizer_counters.append(SCons.Memoize.CountValue('rentry')) - + @SCons.Memoize.CountMethodCall def rentry(self): try: return self._memo['rentry'] @@ -880,6 +941,17 @@ class Base(SCons.Node.Node): def _glob1(self, pattern, ondisk=True, source=False, strings=False): return [] +# Dict that provides a simple backward compatibility +# layer for the Node attributes 'abspath', 'labspath', +# 'path', 'tpath' and 'path_elements'. +# @see Base.__getattr__ above +node_bwcomp = {'abspath' : Base.get_abspath, + 'labspath' : Base.get_labspath, + 'path' : Base.get_internal_path, + 'tpath' : Base.get_tpath, + 'path_elements' : Base.get_path_elements, + 'suffix' : Base.get_suffix} + 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. @@ -888,6 +960,28 @@ class Entry(Base): time comes, and then call the same-named method in the transformed class.""" + __slots__ = ['scanner_paths', + 'cachedir_csig', + 'cachesig', + 'repositories', + 'srcdir', + 'entries', + 'searched', + '_sconsign', + 'variant_dirs', + 'root', + 'dirname', + 'on_disk_entries', + 'sccs_dir', + 'rcs_dir', + 'released_target_info', + 'contentsig'] + + def __init__(self, name, directory, fs): + Base.__init__(self, name, directory, fs) + self._func_exists = 3 + self._func_get_contents = 1 + def diskcheck_match(self): pass @@ -918,7 +1012,7 @@ class Entry(Base): self.__class__ = Dir self._morph() elif must_exist: - msg = "No such file or directory: '%s'" % self.abspath + msg = "No such file or directory: '%s'" % self.get_abspath() raise SCons.Errors.UserError(msg) else: self.__class__ = File @@ -940,17 +1034,7 @@ class Entry(Base): def get_contents(self): """Fetch the contents of the entry. Returns the exact binary contents of the file.""" - try: - self = self.disambiguate(must_exist=1) - except SCons.Errors.UserError: - # There was nothing on disk with which to disambiguate - # this entry. Leave it as an Entry, but return a null - # string so calls to get_contents() in emitters and the - # like (e.g. in qt.py) don't have to disambiguate by hand - # or catch the exception. - return '' - else: - return self.get_contents() + return SCons.Node._get_contents_map[self._func_get_contents](self) def get_text_contents(self): """Fetch the decoded text contents of a Unicode encoded Entry. @@ -990,10 +1074,7 @@ class Entry(Base): # to make various tests pass. def exists(self): - """Return if the Entry exists. Check the file system to see - what we should turn into first. Assume a file if there's no - directory.""" - return self.disambiguate().exists() + return SCons.Node._exists_map[self._func_exists](self) def rel_path(self, other): d = self.disambiguate() @@ -1004,9 +1085,6 @@ class Entry(Base): def new_ninfo(self): return self.disambiguate().new_ninfo() - def changed_since_last_build(self, target, prev_ni): - return self.disambiguate().changed_since_last_build(target, prev_ni) - def _glob1(self, pattern, ondisk=True, source=False, strings=False): return self.disambiguate()._glob1(pattern, ondisk, source, strings) @@ -1020,9 +1098,6 @@ _classEntry = Entry class LocalFS(object): - if SCons.Memoize.use_memoizer: - __metaclass__ = SCons.Memoize.Memoized_Metaclass - # This class implements an abstraction layer for operations involving # a local file system. Essentially, this wraps any function in # the os, os.path or shutil modules that we use to actually go do @@ -1091,19 +1166,8 @@ class LocalFS(object): return '' -#class RemoteFS: -# # Skeleton for the obvious methods we might need from the -# # abstraction layer for a remote filesystem. -# def upload(self, local_src, remote_dst): -# pass -# def download(self, remote_src, local_dst): -# pass - - class FS(LocalFS): - memoizer_counters = [] - def __init__(self, path = None): """Initialize the Node.FS subsystem. @@ -1129,13 +1193,13 @@ class FS(LocalFS): self.defaultDrive = _my_normcase(_my_splitdrive(self.pathTop)[0]) self.Top = self.Dir(self.pathTop) - self.Top.path = '.' - self.Top.tpath = '.' + self.Top._path = '.' + self.Top._tpath = '.' self._cwd = self.Top DirNodeInfo.fs = self FileNodeInfo.fs = self - + def set_SConstruct_dir(self, dir): self.SConstruct_dir = dir @@ -1161,7 +1225,7 @@ class FS(LocalFS): if dir is not None: self._cwd = dir if change_os_dir: - os.chdir(dir.abspath) + os.chdir(dir.get_abspath()) except OSError: self._cwd = curr raise @@ -1247,12 +1311,12 @@ class FS(LocalFS): p = p.strip('/') needs_normpath = needs_normpath_match(p) - + # The path is relative to the top-level SCons directory. if p in ('', '.'): - p = directory.labspath + p = directory.get_labspath() else: - p = directory.labspath + '/' + p + p = directory.get_labspath() + '/' + p else: if do_splitdrive: drive, p = _my_splitdrive(p) @@ -1286,9 +1350,9 @@ class FS(LocalFS): directory = self._cwd if p in ('', '.'): - p = directory.labspath + p = directory.get_labspath() else: - p = directory.labspath + '/' + p + p = directory.get_labspath() + '/' + p if drive: root = self.get_root(drive) @@ -1394,7 +1458,7 @@ class FS(LocalFS): if start_dir.is_under(bd): # If already in the build-dir location, don't reflect return [orig], fmt % str(orig) - p = os.path.join(bd.path, *tail) + p = os.path.join(bd._path, *tail) targets.append(self.Entry(p)) tail = [dir.name] + tail dir = dir.up() @@ -1402,19 +1466,20 @@ class FS(LocalFS): message = fmt % ' '.join(map(str, targets)) return targets, message - def Glob(self, pathname, ondisk=True, source=True, strings=False, cwd=None): + def Glob(self, pathname, ondisk=True, source=True, strings=False, exclude=None, cwd=None): """ Globs - This is mainly a shim layer + This is mainly a shim layer """ if cwd is None: cwd = self.getcwd() - return cwd.glob(pathname, ondisk, source, strings) + return cwd.glob(pathname, ondisk, source, strings, exclude) class DirNodeInfo(SCons.Node.NodeInfoBase): + __slots__ = () # This should get reset by the FS initialization. - current_version_id = 1 + current_version_id = 2 fs = None @@ -1426,11 +1491,12 @@ class DirNodeInfo(SCons.Node.NodeInfoBase): if drive: root = self.fs.get_root(drive) if not os.path.isabs(s): - s = top.labspath + '/' + s + s = top.get_labspath() + '/' + s return root._lookup_abs(s, Entry) class DirBuildInfo(SCons.Node.BuildInfoBase): - current_version_id = 1 + __slots__ = () + current_version_id = 2 glob_magic_check = re.compile('[*?[]') @@ -1441,7 +1507,22 @@ class Dir(Base): """A class for directories in a file system. """ - memoizer_counters = [] + __slots__ = ['scanner_paths', + 'cachedir_csig', + 'cachesig', + 'repositories', + 'srcdir', + 'entries', + 'searched', + '_sconsign', + 'variant_dirs', + 'root', + 'dirname', + 'on_disk_entries', + 'sccs_dir', + 'rcs_dir', + 'released_target_info', + 'contentsig'] NodeInfo = DirNodeInfo BuildInfo = DirBuildInfo @@ -1471,6 +1552,22 @@ class Dir(Base): self._sconsign = None self.variant_dirs = [] self.root = self.dir.root + self.changed_since_last_build = 3 + self._func_sconsign = 1 + self._func_exists = 2 + self._func_get_contents = 2 + + self._abspath = SCons.Util.silent_intern(self.dir.entry_abspath(self.name)) + self._labspath = SCons.Util.silent_intern(self.dir.entry_labspath(self.name)) + if self.dir._path == '.': + self._path = SCons.Util.silent_intern(self.name) + else: + self._path = SCons.Util.silent_intern(self.dir.entry_path(self.name)) + if self.dir._tpath == '.': + self._tpath = SCons.Util.silent_intern(self.name) + else: + self._tpath = SCons.Util.silent_intern(self.dir.entry_tpath(self.name)) + self._path_elements = self.dir._path_elements + [self] # For directories, we make a difference between the directory # 'name' and the directory 'dirname'. The 'name' attribute is @@ -1498,7 +1595,7 @@ class Dir(Base): # Prepend MkdirBuilder action to existing action list l = self.get_executor().action_list a = get_MkdirBuilder().action - l.insert(0, a) + l.insert(0, a) self.get_executor().set_action_list(l) def diskcheck_match(self): @@ -1563,8 +1660,7 @@ class Dir(Base): return self.srcdir.get_all_rdirs() + self.repositories return self.repositories - memoizer_counters.append(SCons.Memoize.CountValue('get_all_rdirs')) - + @SCons.Memoize.CountMethodCall def get_all_rdirs(self): try: return list(self._memo['get_all_rdirs']) @@ -1590,7 +1686,7 @@ class Dir(Base): def addRepository(self, dir): if dir != self and not dir in self.repositories: self.repositories.append(dir) - dir.tpath = '.' + dir._tpath = '.' self.__clearRepositoryCache() def up(self): @@ -1599,8 +1695,7 @@ class Dir(Base): def _rel_path_key(self, other): return str(other) - memoizer_counters.append(SCons.Memoize.CountDict('rel_path', _rel_path_key)) - + @SCons.Memoize.CountDictCall(_rel_path_key) def rel_path(self, other): """Return a path to "other" relative to this directory. """ @@ -1629,7 +1724,7 @@ class Dir(Base): if self is other: result = '.' - elif not other in self.path_elements: + elif not other in self._path_elements: try: other_dir = other.get_dir() except AttributeError: @@ -1644,11 +1739,11 @@ class Dir(Base): else: result = dir_rel_path + OS_SEP + other.name else: - i = self.path_elements.index(other) + 1 + i = self._path_elements.index(other) + 1 + + path_elems = ['..'] * (len(self._path_elements) - i) \ + + [n.name for n in other._path_elements[i:]] - path_elems = ['..'] * (len(self.path_elements) - i) \ - + [n.name for n in other.path_elements[i:]] - result = OS_SEP.join(path_elems) memo_dict[other] = result @@ -1714,7 +1809,7 @@ class Dir(Base): if p is None: # Don't use while: - else: for this condition because # if so, then parent is None and has no .path attribute. - raise SCons.Errors.StopError(parent.path) + raise SCons.Errors.StopError(parent._path) parent = p listDirs.reverse() for dirnode in listDirs: @@ -1754,10 +1849,7 @@ class Dir(Base): def get_contents(self): """Return content signatures and names of all our children separated by new-lines. Ensure that the nodes are sorted.""" - contents = [] - for node in sorted(self.children(), key=lambda t: t.name): - contents.append('%s %s\n' % (node.get_csig(), node.name)) - return ''.join(contents) + return SCons.Node._get_contents_map[self._func_get_contents](self) def get_csig(self): """Compute the content signature for Directory nodes. In @@ -1771,8 +1863,6 @@ class Dir(Base): def do_duplicate(self, src): pass - changed_since_last_build = SCons.Node.Node.state_has_changed - def is_up_to_date(self): """If any child is not up-to-date, then this directory isn't, either.""" @@ -1796,12 +1886,8 @@ class Dir(Base): return self def sconsign(self): - """Return the .sconsign file info for this directory, - creating it first if necessary.""" - if not self._sconsign: - import SCons.SConsign - self._sconsign = SCons.SConsign.ForDirectory(self) - return self._sconsign + """Return the .sconsign file info for this directory. """ + return _sconsign_map[self._func_sconsign](self) def srcnode(self): """Dir has a special need for srcnode()...if we @@ -1818,25 +1904,48 @@ class Dir(Base): stamp = kid.get_timestamp() return stamp + def get_abspath(self): + """Get the absolute path of the file.""" + return self._abspath + + def get_labspath(self): + """Get the absolute path of the file.""" + return self._labspath + + def get_internal_path(self): + return self._path + + def get_tpath(self): + return self._tpath + + def get_path_elements(self): + return self._path_elements + def entry_abspath(self, name): - return self.abspath + OS_SEP + name + return self._abspath + OS_SEP + name def entry_labspath(self, name): - return self.labspath + '/' + name + return self._labspath + '/' + name def entry_path(self, name): - return self.path + OS_SEP + name + return self._path + OS_SEP + name def entry_tpath(self, name): - return self.tpath + OS_SEP + name + return self._tpath + OS_SEP + name def entry_exists_on_disk(self, name): + """ Searches through the file/dir entries of the current + directory, and returns True if a physical entry with the given + name could be found. + + @see rentry_exists_on_disk + """ try: d = self.on_disk_entries except AttributeError: d = {} try: - entries = os.listdir(self.abspath) + entries = os.listdir(self._abspath) except OSError: pass else: @@ -1849,14 +1958,40 @@ class Dir(Base): if result is None: # Belt-and-suspenders for Windows: check directly for # 8.3 file names that don't show up in os.listdir(). - result = os.path.exists(self.abspath + OS_SEP + name) + result = os.path.exists(self._abspath + OS_SEP + name) d[name] = result return result else: return name in d - memoizer_counters.append(SCons.Memoize.CountValue('srcdir_list')) + def rentry_exists_on_disk(self, name): + """ Searches through the file/dir entries of the current + *and* all its remote directories (repos), and returns + True if a physical entry with the given name could be found. + The local directory (self) gets searched first, so + repositories take a lower precedence regarding the + searching order. + + @see entry_exists_on_disk + """ + rentry_exists = self.entry_exists_on_disk(name) + if not rentry_exists: + # Search through the repository folders + norm_name = _my_normcase(name) + for rdir in self.get_all_rdirs(): + try: + node = rdir.entries[norm_name] + if node: + rentry_exists = True + break + except KeyError: + if rdir.entry_exists_on_disk(name): + rentry_exists = True + break + return rentry_exists + + @SCons.Memoize.CountMethodCall def srcdir_list(self): try: return self._memo['srcdir_list'] @@ -1897,8 +2032,7 @@ class Dir(Base): def _srcdir_find_file_key(self, filename): return filename - memoizer_counters.append(SCons.Memoize.CountDict('srcdir_find_file', _srcdir_find_file_key)) - + @SCons.Memoize.CountDictCall(_srcdir_find_file_key) def srcdir_find_file(self, filename): try: memo_dict = self._memo['srcdir_find_file'] @@ -1988,7 +2122,7 @@ class Dir(Base): for dirname in [n for n in names if isinstance(entries[n], Dir)]: entries[dirname].walk(func, arg) - def glob(self, pathname, ondisk=True, source=False, strings=False): + def glob(self, pathname, ondisk=True, source=False, strings=False, exclude=None): """ Returns a list of Nodes (or strings) matching a specified pathname pattern. @@ -2016,24 +2150,36 @@ class Dir(Base): The "strings" argument, when true, returns the matches as strings, not Nodes. The strings are path names relative to this directory. + The "exclude" argument, if not None, must be a pattern or a list + of patterns following the same UNIX shell semantics. + Elements matching a least one pattern of this list will be excluded + from the result. + 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 sorted(self._glob1(basename, ondisk, source, strings), - key=lambda t: str(t)) - if has_glob_magic(dirname): - list = self.glob(dirname, ondisk, source, strings=False) + result = self._glob1(basename, ondisk, source, strings) else: - list = [self.Dir(dirname, create=True)] - result = [] - for dir in list: - r = dir._glob1(basename, ondisk, source, strings) - if strings: - r = [os.path.join(str(dir), x) for x in r] - result.extend(r) + if has_glob_magic(dirname): + list = self.glob(dirname, ondisk, source, False, exclude) + else: + list = [self.Dir(dirname, create=True)] + result = [] + for dir in list: + r = dir._glob1(basename, ondisk, source, strings) + if strings: + r = [os.path.join(str(dir), x) for x in r] + result.extend(r) + if exclude: + excludes = [] + excludeList = SCons.Util.flatten(exclude) + for x in excludeList: + r = self.glob(x, ondisk, source, strings) + excludes.extend(r) + result = filter(lambda x: not any(fnmatch.fnmatch(str(x), str(e)) for e in SCons.Util.flatten(excludes)), result) return sorted(result, key=lambda a: str(a)) def _glob1(self, pattern, ondisk=True, source=False, strings=False): @@ -2066,7 +2212,7 @@ class Dir(Base): for name in node_names: selfEntry(name) if ondisk: try: - disk_names = os.listdir(dir.abspath) + disk_names = os.listdir(dir._abspath) except os.error: continue names.extend(disk_names) @@ -2080,7 +2226,6 @@ class Dir(Base): # 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 = [x for x in disk_names if x[0] != '.'] disk_names = fnmatch.filter(disk_names, pattern) dirEntry = dir.Entry @@ -2096,14 +2241,12 @@ class Dir(Base): names = set(names) if pattern[0] != '.': - #names = [ n for n in names if n[0] != '.' ] names = [x for x in names if x[0] != '.'] names = fnmatch.filter(names, pattern) if strings: return names - #return [ self.entries[_my_normcase(n)] for n in names ] return [self.entries[_my_normcase(n)] for n in names] class RootDir(Dir): @@ -2114,23 +2257,17 @@ class RootDir(Dir): add a separator when creating the path names of entries within this directory. """ + + __slots__ = ['_lookupDict'] + def __init__(self, drive, fs): if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.FS.RootDir') - # We're going to be our own parent directory (".." entry and .dir - # attribute) so we have to set up some values so Base.__init__() - # won't gag won't it calls some of our methods. - self.abspath = '' - self.labspath = '' - self.path = '' - self.tpath = '' - self.path_elements = [] - self.duplicate = 0 - self.root = self + SCons.Node.Node.__init__(self) # Handle all the types of drives: if drive == '': # No drive, regular UNIX root or Windows default drive. - name = OS_SEP + name = OS_SEP dirname = OS_SEP elif drive == '//': # UNC path @@ -2141,33 +2278,85 @@ class RootDir(Dir): name = drive dirname = drive + OS_SEP - Base.__init__(self, name, self, fs) + #: Filename with extension as it was specified when the object was + #: created; to obtain filesystem path, use Python str() function + self.name = SCons.Util.silent_intern(name) + self.fs = fs #: Reference to parent Node.FS object + + self._path_elements = [self] + self.dir = self + self._func_rexists = 2 + self._func_target_from_source = 1 + self.store_info = 1 # Now set our paths to what we really want them to be. The # name should already contain any necessary separators, such # as the initial drive letter (the name) plus the directory # separator, except for the "lookup abspath," which does not # have the drive letter. - self.abspath = dirname - self.labspath = '' - self.path = dirname - self.tpath = dirname - self._morph() - - # Must be reset after Dir._morph() is invoked... + self._abspath = dirname + self._labspath = '' + self._path = dirname + self._tpath = dirname self.dirname = dirname + self._morph() + + self.duplicate = 0 self._lookupDict = {} self._lookupDict[''] = self self._lookupDict['/'] = self - + self.root = self # The // entry is necessary because os.path.normpath() # preserves double slashes at the beginning of a path on Posix # platforms. if not has_unc: self._lookupDict['//'] = self + def _morph(self): + """Turn a file system Node (either a freshly initialized directory + object or a separate Entry object) into a proper directory object. + + Set up this directory's entries and hook it into the file + system tree. Specify that directories (this Node) don't use + signatures for calculating whether they're current. + """ + + self.repositories = [] + self.srcdir = None + + self.entries = {} + self.entries['.'] = self + self.entries['..'] = self.dir + self.cwd = self + self.searched = 0 + self._sconsign = None + self.variant_dirs = [] + self.changed_since_last_build = 3 + self._func_sconsign = 1 + self._func_exists = 2 + self._func_get_contents = 2 + + # Don't just reset the executor, replace its action list, + # because it might have some pre-or post-actions that need to + # be preserved. + # + # But don't reset the executor if there is a non-null executor + # attached already. The existing executor might have other + # targets, in which case replacing the action list with a + # Mkdir action is a big mistake. + if not hasattr(self, 'executor'): + self.builder = get_MkdirBuilder() + self.get_executor().set_action_list(self.builder.action) + else: + # Prepend MkdirBuilder action to existing action list + l = self.get_executor().action_list + a = get_MkdirBuilder().action + l.insert(0, a) + self.get_executor().set_action_list(l) + + def must_be_same(self, klass): if klass is Dir: return @@ -2198,17 +2387,7 @@ class RootDir(Dir): raise SCons.Errors.UserError(msg) # There is no Node for this path name, and we're allowed # to create it. - # (note: would like to use p.rsplit('/',1) here but - # that's not in python 2.3) - # e.g.: dir_name, file_name = p.rsplit('/',1) - last_slash = p.rindex('/') - if (last_slash >= 0): - dir_name = p[:last_slash] - file_name = p[last_slash+1:] - else: - dir_name = p # shouldn't happen, just in case - file_name = '' - + dir_name, file_name = p.rsplit('/',1) dir_node = self._lookup_abs(dir_name, Dir) result = klass(file_name, dir_node, self.fs) @@ -2226,19 +2405,19 @@ class RootDir(Dir): return result def __str__(self): - return self.abspath + return self._abspath def entry_abspath(self, name): - return self.abspath + name + return self._abspath + name def entry_labspath(self, name): return '/' + name def entry_path(self, name): - return self.path + name + return self._path + name def entry_tpath(self, name): - return self.tpath + name + return self._tpath + name def is_under(self, dir): if self is dir: @@ -2256,7 +2435,8 @@ class RootDir(Dir): return _null class FileNodeInfo(SCons.Node.NodeInfoBase): - current_version_id = 1 + __slots__ = ('csig', 'timestamp', 'size') + current_version_id = 2 field_list = ['csig', 'timestamp', 'size'] @@ -2271,11 +2451,43 @@ class FileNodeInfo(SCons.Node.NodeInfoBase): if drive: root = self.fs.get_root(drive) if not os.path.isabs(s): - s = top.labspath + '/' + s + s = top.get_labspath() + '/' + s return root._lookup_abs(s, Entry) + def __getstate__(self): + """ + Return all fields that shall be pickled. Walk the slots in the class + hierarchy and add those to the state dictionary. If a '__dict__' slot is + available, copy all entries to the dictionary. Also include the version + id, which is fixed for all instances of a class. + """ + state = getattr(self, '__dict__', {}).copy() + for obj in type(self).mro(): + for name in getattr(obj,'__slots__',()): + if hasattr(self, name): + state[name] = getattr(self, name) + + state['_version_id'] = self.current_version_id + try: + del state['__weakref__'] + except KeyError: + pass + + return state + + def __setstate__(self, state): + """ + Restore the attributes from a pickled state. + """ + # TODO check or discard version + del state['_version_id'] + for key, value in state.items(): + if key not in ('__weakref__',): + setattr(self, key, value) + class FileBuildInfo(SCons.Node.BuildInfoBase): - current_version_id = 1 + __slots__ = () + current_version_id = 2 def convert_to_sconsign(self): """ @@ -2290,7 +2502,7 @@ class FileBuildInfo(SCons.Node.BuildInfoBase): else: def node_to_str(n): try: - s = n.path + s = n.get_internal_path() except AttributeError: s = str(n) else: @@ -2331,6 +2543,8 @@ class FileBuildInfo(SCons.Node.BuildInfoBase): nodeinfos = getattr(self, sattr) except AttributeError: continue + if strings is None or nodeinfos is None: + continue nodes = [] for s, ni in zip(strings, nodeinfos): if not isinstance(s, SCons.Node.Node): @@ -2344,6 +2558,8 @@ class FileBuildInfo(SCons.Node.BuildInfoBase): for bkid, bkidsig in zip(bkids, bkidsigs): result.append(str(bkid) + ': ' + ' '.join(bkidsig.format(names=names))) + if not hasattr(self,'bact'): + self.bact = "none" result.append('%s [%s]' % (self.bactsig, self.bact)) return '\n'.join(result) @@ -2351,7 +2567,22 @@ class File(Base): """A class for files in a file system. """ - memoizer_counters = [] + __slots__ = ['scanner_paths', + 'cachedir_csig', + 'cachesig', + 'repositories', + 'srcdir', + 'entries', + 'searched', + '_sconsign', + 'variant_dirs', + 'root', + 'dirname', + 'on_disk_entries', + 'sccs_dir', + 'rcs_dir', + 'released_target_info', + 'contentsig'] NodeInfo = FileNodeInfo BuildInfo = FileBuildInfo @@ -2387,13 +2618,6 @@ class File(Base): the directory of this file.""" return self.dir.File(name) - #def generate_build_dict(self): - # """Return an appropriate dictionary of values for building - # this File.""" - # return {'Dir' : self.Dir, - # 'File' : self.File, - # 'RDirs' : self.RDirs} - def _morph(self): """Turn a file system node into a File object.""" self.scanner_paths = {} @@ -2402,6 +2626,14 @@ class File(Base): if not hasattr(self, 'released_target_info'): self.released_target_info = False + self.store_info = 1 + self._func_exists = 4 + self._func_get_contents = 3 + + # Initialize this Node's decider function to decide_source() because + # every file is a source file until it has a Builder attached... + self.changed_since_last_build = 4 + # If there was already a Builder set on this entry, then # we need to make sure we call the target-decider function, # not the source-decider. Reaching in and doing this by hand @@ -2413,22 +2645,13 @@ class File(Base): # not clear right now how to fix that, stick with what works # until it becomes clear... if self.has_builder(): - self.changed_since_last_build = self.decide_target + self.changed_since_last_build = 5 def scanner_key(self): return self.get_suffix() def get_contents(self): - if not self.rexists(): - return b'' # Should always be bytes - fname = self.rfile().abspath - try: - contents = open(fname, "rb").read() - except EnvironmentError as e: - if not e.filename: - e.filename = fname - raise - return contents + return SCons.Node._get_contents_map[self._func_get_contents](self) # This attempts to figure out what the encoding of the text is # based upon the BOM bytes, and then decodes the contents so that @@ -2455,7 +2678,7 @@ class File(Base): """ if not self.rexists(): return SCons.Util.MD5signature('') - fname = self.rfile().abspath + fname = self.rfile().get_abspath() try: cs = SCons.Util.MD5filesignature(fname, chunksize=SCons.Node.FS.File.md5_chunksize*1024) @@ -2464,10 +2687,8 @@ class File(Base): e.filename = fname raise return cs - - - memoizer_counters.append(SCons.Memoize.CountValue('get_size')) + @SCons.Memoize.CountMethodCall def get_size(self): try: return self._memo['get_size'] @@ -2483,8 +2704,7 @@ class File(Base): return size - memoizer_counters.append(SCons.Memoize.CountValue('get_timestamp')) - + @SCons.Memoize.CountMethodCall def get_timestamp(self): try: return self._memo['get_timestamp'] @@ -2500,14 +2720,6 @@ class File(Base): return timestamp - def store_info(self): - # Merge our build information into the already-stored entry. - # This accomodates "chained builds" where a file that's a target - # in one build (SConstruct file) is a source in a different build. - # See test/chained-build.py for the use case. - if do_store_info: - self.dir.sconsign().store_info(self.name, self) - convert_copy_attrs = [ 'bsources', 'bimplicit', @@ -2620,8 +2832,7 @@ class File(Base): delattr(old_entry, attr) return new_entry - memoizer_counters.append(SCons.Memoize.CountValue('get_stored_info')) - + @SCons.Memoize.CountMethodCall def get_stored_info(self): try: return self._memo['get_stored_info'] @@ -2661,8 +2872,7 @@ class File(Base): def _get_found_includes_key(self, env, scanner, path): return (id(env), id(scanner), path) - memoizer_counters.append(SCons.Memoize.CountDict('get_found_includes', _get_found_includes_key)) - + @SCons.Memoize.CountDictCall(_get_found_includes_key) def get_found_includes(self, env, scanner, path): """Return the included implicit dependencies in this file. Cache results so we only scan the file once per path @@ -2681,9 +2891,7 @@ class File(Base): pass if scanner: - # result = [n.disambiguate() for n in scanner(self, env, path)] - result = scanner(self, env, path) - result = [N.disambiguate() for N in result] + result = [n.disambiguate() for n in scanner(self, env, path)] else: result = [] @@ -2746,18 +2954,18 @@ class File(Base): # any build information that's stored in the .sconsign file # into our binfo object so it doesn't get lost. old = self.get_stored_info() - self.get_binfo().__dict__.update(old.binfo.__dict__) + self.get_binfo().merge(old.binfo) - self.store_info() + SCons.Node.store_info_map[self.store_info](self) def release_target_info(self): """Called just after this node has been marked up-to-date or was built completely. - + This is where we try to release as many target node infos as possible for clean builds and update runs, in order to minimize the overall memory consumption. - + We'd like to remove a lot more attributes like self.sources and self.sources_set, but they might get used in a next build step. For example, during configuration @@ -2765,18 +2973,18 @@ class File(Base): which linker to use for the resulting Program (gcc vs. g++)! That's why we check for the 'keep_targetinfo' attribute, config Nodes and the Interactive mode just don't allow - an early release of most variables. + an early release of most variables. In the same manner, we can't simply remove the self.attributes here. The smart linking relies on the shared flag, and some parts of the java Tool use it to transport information about nodes... - + @see: built() and Node.release_target_info() """ if (self.released_target_info or SCons.Node.interactive): return - + if not hasattr(self.attributes, 'keep_targetinfo'): # Cache some required values, before releasing # stuff like env, executor and builder... @@ -2848,8 +3056,8 @@ class File(Base): def _rmv_existing(self): self.clear_memoized_values() - if print_duplicate: - print("dup: removing existing target %s"%self) + if SCons.Node.print_duplicate: + print("dup: removing existing target {}".format(self)) e = Unlink(self, [], None) if isinstance(e, SCons.Errors.BuildError): raise e @@ -2874,8 +3082,7 @@ class File(Base): try: self._createDir() except SCons.Errors.StopError as drive: - desc = "No drive `%s' for target `%s'." % (drive, self) - raise SCons.Errors.StopError(desc) + raise SCons.Errors.StopError("No drive `%s' for target `{}'.".format(drive, self)) # # @@ -2884,19 +3091,18 @@ class File(Base): def remove(self): """Remove this file.""" if self.exists() or self.islink(): - self.fs.unlink(self.path) + self.fs.unlink(self.get_internal_path()) return 1 return None def do_duplicate(self, src): self._createDir() - if print_duplicate: - print("dup: relinking variant '%s' from '%s'"%(self, src)) + if SCons.Node.print_duplicate: + print("dup: relinking variant '{}' from '{}'".format(self, src)) 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) + raise SCons.Errors.StopError("Cannot duplicate `{}' in `{}': {}.".format(src.get_internal_path(), self.dir._path, e.errstr)) self.linked = 1 # The Link() action may or may not have actually # created the file, depending on whether the -n @@ -2904,36 +3110,13 @@ class File(Base): # _rexists attributes so they can be reevaluated. self.clear() - memoizer_counters.append(SCons.Memoize.CountValue('exists')) - + @SCons.Memoize.CountMethodCall def exists(self): try: return self._memo['exists'] except KeyError: pass - # Duplicate from source path if we are set up to do this. - if self.duplicate and not self.is_derived() and not self.linked: - src = self.srcnode() - if src is not self: - # At this point, src is meant to be copied in a variant directory. - src = src.rfile() - if src.abspath != self.abspath: - if src.exists(): - self.do_duplicate(src) - # Can't return 1 here because the duplication might - # not actually occur if the -n option is being used. - else: - # The source file does not exist. Make sure no old - # copy remains in the variant directory. - if print_duplicate: - print("dup: no src for %s, unlinking old variant copy"%self) - if Base.exists(self) or self.islink(): - self.fs.unlink(self.path) - # Return None explicitly because the Base.exists() call - # above will have cached its value if the file existed. - self._memo['exists'] = None - return None - result = Base.exists(self) + result = SCons.Node._exists_map[self._func_exists](self) self._memo['exists'] = result return result @@ -3010,41 +3193,41 @@ class File(Base): def builder_set(self, builder): SCons.Node.Node.builder_set(self, builder) - self.changed_since_last_build = self.decide_target + self.changed_since_last_build = 5 def built(self): """Called just after this File node is successfully built. - + Just like for 'release_target_info' we try to release some more target node attributes in order to minimize the overall memory consumption. - + @see: release_target_info """ SCons.Node.Node.built(self) - if (not SCons.Node.interactive and + if (not SCons.Node.interactive and not hasattr(self.attributes, 'keep_targetinfo')): - # Ensure that the build infos get computed and cached... - self.store_info() + # Ensure that the build infos get computed and cached... + SCons.Node.store_info_map[self.store_info](self) # ... then release some more variables. self._specific_sources = False - self.labspath = None + self._labspath = None self._save_str() self.cwd = None - + self.scanner_paths = None def changed(self, node=None, allowcache=False): """ Returns if the node is up-to-date with respect to the BuildInfo - stored last time it was built. - + stored last time it was built. + For File nodes this is basically a wrapper around Node.changed(), but we allow the return value to get cached after the reference to the Executor got released in release_target_info(). - + @see: Node.changed() """ if node is None: @@ -3052,7 +3235,7 @@ class File(Base): return self._memo['changed'] except KeyError: pass - + has_changed = SCons.Node.Node.changed(self, node) if allowcache: self._memo['changed'] = has_changed @@ -3089,16 +3272,6 @@ class File(Base): except AttributeError: return 1 - def decide_source(self, target, prev_ni): - return target.get_build_env().decide_source(self, target, prev_ni) - - def decide_target(self, target, prev_ni): - return target.get_build_env().decide_target(self, target, prev_ni) - - # Initialize this Node's decider function to decide_source() because - # every file is a source file until it has a Builder attached... - changed_since_last_build = decide_source - def is_up_to_date(self): T = 0 if T: Trace('is_up_to_date(%s):' % self) @@ -3115,8 +3288,8 @@ class File(Base): # ...and they'd like a local copy. e = LocalCopy(self, r, None) if isinstance(e, SCons.Errors.BuildError): - raise - self.store_info() + raise + SCons.Node.store_info_map[self.store_info](self) if T: Trace(' 1\n') return 1 self.changed() @@ -3127,8 +3300,7 @@ class File(Base): if T: Trace(' self.exists(): %s\n' % r) return not r - memoizer_counters.append(SCons.Memoize.CountValue('rfile')) - + @SCons.Memoize.CountMethodCall def rfile(self): try: return self._memo['rfile'] @@ -3198,12 +3370,12 @@ class File(Base): It computes and returns the signature for this node's contents. """ - + try: return self.contentsig except AttributeError: pass - + executor = self.get_executor() result = self.contentsig = SCons.Util.MD5signature(executor.get_contents()) @@ -3223,14 +3395,14 @@ class File(Base): return self.cachesig except AttributeError: pass - + # Collect signatures for all children children = self.children() sigs = [n.get_cachedir_csig() for n in children] # Append this node's signature... sigs.append(self.get_contents_sig()) # ...and it's path - sigs.append(self.path) + sigs.append(self.get_internal_path()) # Merge this all into a single signature result = self.cachesig = SCons.Util.MD5collect(sigs) return result @@ -3246,10 +3418,6 @@ def get_default_fs(): class FileFinder(object): """ """ - if SCons.Memoize.use_memoizer: - __metaclass__ = SCons.Memoize.Memoized_Metaclass - - memoizer_counters = [] def __init__(self): self._memo = {} @@ -3291,9 +3459,8 @@ class FileFinder(object): def _find_file_key(self, filename, paths, verbose=None): return (filename, paths) - - memoizer_counters.append(SCons.Memoize.CountDict('find_file', _find_file_key)) + @SCons.Memoize.CountDictCall(_find_file_key) def find_file(self, filename, paths, verbose=None): """ find_file(str, [Dir()]) -> [nodes] @@ -3331,36 +3498,6 @@ class FileFinder(object): filedir, filename = os.path.split(filename) if filedir: - # 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) - # if isinstance(node, Dir): - # return node - # if isinstance(node, Entry): - # node.must_be_same(Dir) - # return node - # if isinstance(node, Dir) or isinstance(node, Entry): - # return node - # return None - #paths = [_f for _f in map(filedir_lookup, paths) if _f] - self.default_filedir = filedir paths = [_f for _f in map(self.filedir_lookup, paths) if _f] @@ -3408,7 +3545,7 @@ def invalidate_node_memos(targets): if not SCons.Util.is_List(targets): targets = [targets] - + for entry in targets: # If the target is a Node object, clear the cache. If it is a # filename, look up potentially existing Node object first. @@ -3420,7 +3557,7 @@ def invalidate_node_memos(targets): # do not correspond to an existing Node object. node = get_default_fs().Entry(entry) if node: - node.clear_memoized_values() + node.clear_memoized_values() # Local Variables: # tab-width:4 diff --git a/src/engine/SCons/Node/FSTests.py b/src/engine/SCons/Node/FSTests.py index 9a8763a..b9c19bc 100644 --- a/src/engine/SCons/Node/FSTests.py +++ b/src/engine/SCons/Node/FSTests.py @@ -130,34 +130,34 @@ class VariantDirTestCase(unittest.TestCase): fs.VariantDir('build', 'src') f2 = fs.File('build/test2') d1 = fs.Dir('build') - assert f1.srcnode().path == os.path.normpath('src/test1'), f1.srcnode().path - assert f2.srcnode().path == os.path.normpath('src/test2'), f2.srcnode().path - assert d1.srcnode().path == 'src', d1.srcnode().path + assert f1.srcnode().get_internal_path() == os.path.normpath('src/test1'), f1.srcnode().get_internal_path() + assert f2.srcnode().get_internal_path() == os.path.normpath('src/test2'), f2.srcnode().get_internal_path() + assert d1.srcnode().get_internal_path() == 'src', d1.srcnode().get_internal_path() fs = SCons.Node.FS.FS() f1 = fs.File('build/test1') fs.VariantDir('build', '.') f2 = fs.File('build/test2') d1 = fs.Dir('build') - assert f1.srcnode().path == 'test1', f1.srcnode().path - assert f2.srcnode().path == 'test2', f2.srcnode().path - assert d1.srcnode().path == '.', d1.srcnode().path + assert f1.srcnode().get_internal_path() == 'test1', f1.srcnode().get_internal_path() + assert f2.srcnode().get_internal_path() == 'test2', f2.srcnode().get_internal_path() + assert d1.srcnode().get_internal_path() == '.', d1.srcnode().get_internal_path() fs = SCons.Node.FS.FS() fs.VariantDir('build/var1', 'src') fs.VariantDir('build/var2', 'src') f1 = fs.File('build/var1/test1') f2 = fs.File('build/var2/test1') - assert f1.srcnode().path == os.path.normpath('src/test1'), f1.srcnode().path - assert f2.srcnode().path == os.path.normpath('src/test1'), f2.srcnode().path + assert f1.srcnode().get_internal_path() == os.path.normpath('src/test1'), f1.srcnode().get_internal_path() + assert f2.srcnode().get_internal_path() == os.path.normpath('src/test1'), f2.srcnode().get_internal_path() fs = SCons.Node.FS.FS() fs.VariantDir('../var1', 'src') fs.VariantDir('../var2', 'src') f1 = fs.File('../var1/test1') f2 = fs.File('../var2/test1') - assert f1.srcnode().path == os.path.normpath('src/test1'), f1.srcnode().path - assert f2.srcnode().path == os.path.normpath('src/test1'), f2.srcnode().path + assert f1.srcnode().get_internal_path() == os.path.normpath('src/test1'), f1.srcnode().get_internal_path() + assert f2.srcnode().get_internal_path() == os.path.normpath('src/test1'), f2.srcnode().get_internal_path() # Set up some files test.subdir('work', ['work', 'src']) @@ -210,8 +210,8 @@ class VariantDirTestCase(unittest.TestCase): f2out_2.builder = 1 fs.Repository(test.workpath('rep1')) - assert f1.srcnode().path == os.path.normpath('src/test.in'),\ - f1.srcnode().path + assert f1.srcnode().get_internal_path() == os.path.normpath('src/test.in'),\ + f1.srcnode().get_internal_path() # str(node) returns source path for duplicate = 0 assert str(f1) == os.path.normpath('src/test.in'), str(f1) # Build path does not exist @@ -221,11 +221,11 @@ class VariantDirTestCase(unittest.TestCase): # And duplicate=0 should also work just like a Repository assert f1.rexists() # rfile() should point to the source path - assert f1.rfile().path == os.path.normpath('src/test.in'),\ - f1.rfile().path + assert f1.rfile().get_internal_path() == os.path.normpath('src/test.in'),\ + f1.rfile().get_internal_path() - assert f2.srcnode().path == os.path.normpath('src/test.in'),\ - f2.srcnode().path + assert f2.srcnode().get_internal_path() == os.path.normpath('src/test.in'),\ + f2.srcnode().get_internal_path() # str(node) returns build path for duplicate = 1 assert str(f2) == os.path.normpath('build/var2/test.in'), str(f2) # Build path exists @@ -239,8 +239,8 @@ class VariantDirTestCase(unittest.TestCase): f3 = fs.File('build/var1/test2.in') f4 = fs.File('build/var2/test2.in') - assert f3.srcnode().path == os.path.normpath('src/test2.in'),\ - f3.srcnode().path + assert f3.srcnode().get_internal_path() == os.path.normpath('src/test2.in'),\ + f3.srcnode().get_internal_path() # str(node) returns source path for duplicate = 0 assert str(f3) == os.path.normpath('src/test2.in'), str(f3) # Build path does not exist @@ -250,11 +250,11 @@ class VariantDirTestCase(unittest.TestCase): # But we do have a file in the Repository assert f3.rexists() # rfile() should point to the source path - assert f3.rfile().path == os.path.normpath(test.workpath('rep1/src/test2.in')),\ - f3.rfile().path + assert f3.rfile().get_internal_path() == os.path.normpath(test.workpath('rep1/src/test2.in')),\ + f3.rfile().get_internal_path() - assert f4.srcnode().path == os.path.normpath('src/test2.in'),\ - f4.srcnode().path + assert f4.srcnode().get_internal_path() == os.path.normpath('src/test2.in'),\ + f4.srcnode().get_internal_path() # str(node) returns build path for duplicate = 1 assert str(f4) == os.path.normpath('build/var2/test2.in'), str(f4) # Build path should exist @@ -264,8 +264,8 @@ class VariantDirTestCase(unittest.TestCase): # should exist in repository, since exists() is true assert f4.rexists() # rfile() should point to ourselves - assert f4.rfile().path == os.path.normpath('build/var2/test2.in'),\ - f4.rfile().path + assert f4.rfile().get_internal_path() == os.path.normpath('build/var2/test2.in'),\ + f4.rfile().get_internal_path() f5 = fs.File('build/var1/test.out') f6 = fs.File('build/var2/test.out') @@ -285,14 +285,14 @@ class VariantDirTestCase(unittest.TestCase): assert not f7.exists() assert f7.rexists() - r = f7.rfile().path + r = f7.rfile().get_internal_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() - assert f8.rfile().path == os.path.normpath(test.workpath('rep1/build/var2/test2.out')),\ - f8.rfile().path + assert f8.rfile().get_internal_path() == os.path.normpath(test.workpath('rep1/build/var2/test2.out')),\ + f8.rfile().get_internal_path() # Verify the Mkdir and Link actions are called d9 = fs.Dir('build/var2/new_dir') @@ -319,9 +319,9 @@ class VariantDirTestCase(unittest.TestCase): d9.reset_executor() f9.exists() expect = os.path.join('build', 'var2', 'new_dir') - assert dir_made[0].path == expect, dir_made[0].path + assert dir_made[0].get_internal_path() == expect, dir_made[0].get_internal_path() expect = os.path.join('build', 'var2', 'new_dir', 'test9.out') - assert link_made[0].path == expect, link_made[0].path + assert link_made[0].get_internal_path() == expect, link_made[0].get_internal_path() assert f9.linked finally: SCons.Node.FS.Link = save_Link @@ -338,7 +338,7 @@ class VariantDirTestCase(unittest.TestCase): f11 = fs.File('src/file11') t, m = f11.alter_targets() - bdt = [n.path for n in t] + bdt = [n.get_internal_path() for n in t] var1_file11 = os.path.normpath('build/var1/file11') var2_file11 = os.path.normpath('build/var2/file11') assert bdt == [var1_file11, var2_file11], bdt @@ -346,11 +346,11 @@ class VariantDirTestCase(unittest.TestCase): f12 = fs.File('src/file12') f12.builder = 1 bdt, m = f12.alter_targets() - assert bdt == [], [n.path for n in bdt] + assert bdt == [], [n.get_internal_path() for n in bdt] d13 = fs.Dir('src/new_dir') t, m = d13.alter_targets() - bdt = [n.path for n in t] + bdt = [n.get_internal_path() for n in t] var1_new_dir = os.path.normpath('build/var1/new_dir') var2_new_dir = os.path.normpath('build/var2/new_dir') assert bdt == [var1_new_dir, var2_new_dir], bdt @@ -566,13 +566,13 @@ class VariantDirTestCase(unittest.TestCase): f = dir + '/f' fnode = fs.File(dir + '/f') - dp = dnode.srcnode().path + dp = dnode.srcnode().get_internal_path() expect = os.path.normpath(srcnode_map.get(dir, dir)) if dp != expect: print("Dir `%s' srcnode() `%s' != expected `%s'" % (dir, dp, expect)) errors = errors + 1 - fp = fnode.srcnode().path + fp = fnode.srcnode().get_internal_path() expect = os.path.normpath(srcnode_map.get(f, f)) if fp != expect: print("File `%s' srcnode() `%s' != expected `%s'" % (f, fp, expect)) @@ -584,14 +584,14 @@ class VariantDirTestCase(unittest.TestCase): fnode = fs.File(dir + '/f') t, m = dnode.alter_targets() - tp = t[0].path + tp = t[0].get_internal_path() expect = os.path.normpath(alter_map.get(dir, dir)) if tp != expect: print("Dir `%s' alter_targets() `%s' != expected `%s'" % (dir, tp, expect)) errors = errors + 1 t, m = fnode.alter_targets() - tp = t[0].path + tp = t[0].get_internal_path() expect = os.path.normpath(alter_map.get(f, f)) if tp != expect: print("File `%s' alter_targets() `%s' != expected `%s'" % (f, tp, expect)) @@ -698,19 +698,19 @@ class DirNodeInfoTestCase(_tempdirTestCase): def test___init__(self): """Test DirNodeInfo initialization""" ddd = self.fs.Dir('ddd') - ni = SCons.Node.FS.DirNodeInfo(ddd) + ni = SCons.Node.FS.DirNodeInfo() class DirBuildInfoTestCase(_tempdirTestCase): def test___init__(self): """Test DirBuildInfo initialization""" ddd = self.fs.Dir('ddd') - bi = SCons.Node.FS.DirBuildInfo(ddd) + bi = SCons.Node.FS.DirBuildInfo() class FileNodeInfoTestCase(_tempdirTestCase): def test___init__(self): """Test FileNodeInfo initialization""" fff = self.fs.File('fff') - ni = SCons.Node.FS.FileNodeInfo(fff) + ni = SCons.Node.FS.FileNodeInfo() assert isinstance(ni, SCons.Node.FS.FileNodeInfo) def test_update(self): @@ -718,7 +718,7 @@ class FileNodeInfoTestCase(_tempdirTestCase): test = self.test fff = self.fs.File('fff') - ni = SCons.Node.FS.FileNodeInfo(fff) + ni = SCons.Node.FS.FileNodeInfo() test.write('fff', "fff\n") @@ -751,51 +751,45 @@ class FileNodeInfoTestCase(_tempdirTestCase): size = st[stat.ST_SIZE] assert ni.size != size, (ni.size, size) - #fff.clear() - #ni.update(fff) - - #st = os.stat('fff') - - #mtime = st[stat.ST_MTIME] - #assert ni.timestamp == mtime, (ni.timestamp, mtime) - #size = st[stat.ST_SIZE] - #assert ni.size == size, (ni.size, size) - class FileBuildInfoTestCase(_tempdirTestCase): def test___init__(self): """Test File.BuildInfo initialization""" fff = self.fs.File('fff') - bi = SCons.Node.FS.FileBuildInfo(fff) + bi = SCons.Node.FS.FileBuildInfo() assert bi, bi def test_convert_to_sconsign(self): """Test converting to .sconsign file format""" fff = self.fs.File('fff') - bi = SCons.Node.FS.FileBuildInfo(fff) + bi = SCons.Node.FS.FileBuildInfo() assert hasattr(bi, 'convert_to_sconsign') def test_convert_from_sconsign(self): """Test converting from .sconsign file format""" fff = self.fs.File('fff') - bi = SCons.Node.FS.FileBuildInfo(fff) + bi = SCons.Node.FS.FileBuildInfo() assert hasattr(bi, 'convert_from_sconsign') def test_prepare_dependencies(self): """Test that we have a prepare_dependencies() method""" fff = self.fs.File('fff') - bi = SCons.Node.FS.FileBuildInfo(fff) + bi = SCons.Node.FS.FileBuildInfo() bi.prepare_dependencies() def test_format(self): """Test the format() method""" f1 = self.fs.File('f1') - bi1 = SCons.Node.FS.FileBuildInfo(f1) + bi1 = SCons.Node.FS.FileBuildInfo() + + self.fs.File('n1') + self.fs.File('n2') + self.fs.File('n3') - s1sig = SCons.Node.FS.FileNodeInfo(self.fs.File('n1')) + s1sig = SCons.Node.FS.FileNodeInfo() s1sig.csig = 1 - d1sig = SCons.Node.FS.FileNodeInfo(self.fs.File('n2')) + d1sig = SCons.Node.FS.FileNodeInfo() d1sig.timestamp = 2 - i1sig = SCons.Node.FS.FileNodeInfo(self.fs.File('n3')) + i1sig = SCons.Node.FS.FileNodeInfo() i1sig.size = 3 bi1.bsources = [self.fs.File('s1')] @@ -953,7 +947,7 @@ class FSTestCase(_tempdirTestCase): assert isinstance(f1, SCons.Node.FS.File) d1_f1 = os.path.join('d1', 'f1') - assert f1.path == d1_f1, "f1.path %s != %s" % (f1.path, d1_f1) + assert f1.get_internal_path() == d1_f1, "f1.path %s != %s" % (f1.get_internal_path(), d1_f1) assert str(f1) == d1_f1, "str(f1) %s != %s" % (str(f1), d1_f1) x1 = d1.File('x1') @@ -1045,16 +1039,16 @@ class FSTestCase(_tempdirTestCase): name = os.sep if dir.up() is None: - dir_up_path = dir.path + dir_up_path = dir.get_internal_path() else: - dir_up_path = dir.up().path + dir_up_path = dir.up().get_internal_path() assert dir.name == name, \ "dir.name %s != expected name %s" % \ (dir.name, name) - assert dir.path == path, \ + assert dir.get_internal_path() == path, \ "dir.path %s != expected path %s" % \ - (dir.path, path) + (dir.get_internal_path(), path) assert str(dir) == path, \ "str(dir) %s != expected path %s" % \ (str(dir), path) @@ -1136,9 +1130,9 @@ class FSTestCase(_tempdirTestCase): # Test for a bug in 0.04 that did not like looking up # dirs with a trailing slash on Windows. d=fs.Dir('./') - assert d.path == '.', d.abspath + assert d.get_internal_path() == '.', d.get_abspath() d=fs.Dir('foo/') - assert d.path == 'foo', d.abspath + assert d.get_internal_path() == 'foo', d.get_abspath() # Test for sub-classing of node building. global built_it @@ -1167,50 +1161,50 @@ class FSTestCase(_tempdirTestCase): e1 = fs.Entry("d1") assert e1.__class__.__name__ == 'Dir' - match(e1.path, "d1") - match(e1.dir.path, ".") + match(e1.get_internal_path(), "d1") + match(e1.dir.get_internal_path(), ".") e2 = fs.Entry("d1/f1") assert e2.__class__.__name__ == 'File' - match(e2.path, "d1/f1") - match(e2.dir.path, "d1") + match(e2.get_internal_path(), "d1/f1") + match(e2.dir.get_internal_path(), "d1") e3 = fs.Entry("e3") assert e3.__class__.__name__ == 'Entry' - match(e3.path, "e3") - match(e3.dir.path, ".") + match(e3.get_internal_path(), "e3") + match(e3.dir.get_internal_path(), ".") e4 = fs.Entry("d1/e4") assert e4.__class__.__name__ == 'Entry' - match(e4.path, "d1/e4") - match(e4.dir.path, "d1") + match(e4.get_internal_path(), "d1/e4") + match(e4.dir.get_internal_path(), "d1") e5 = fs.Entry("e3/e5") assert e3.__class__.__name__ == 'Dir' - match(e3.path, "e3") - match(e3.dir.path, ".") + match(e3.get_internal_path(), "e3") + match(e3.dir.get_internal_path(), ".") assert e5.__class__.__name__ == 'Entry' - match(e5.path, "e3/e5") - match(e5.dir.path, "e3") + match(e5.get_internal_path(), "e3/e5") + match(e5.dir.get_internal_path(), "e3") e6 = fs.Dir("d1/e4") assert e6 is e4 assert e4.__class__.__name__ == 'Dir' - match(e4.path, "d1/e4") - match(e4.dir.path, "d1") + match(e4.get_internal_path(), "d1/e4") + match(e4.dir.get_internal_path(), "d1") e7 = fs.File("e3/e5") assert e7 is e5 assert e5.__class__.__name__ == 'File' - match(e5.path, "e3/e5") - match(e5.dir.path, "e3") + match(e5.get_internal_path(), "e3/e5") + match(e5.dir.get_internal_path(), "e3") fs.chdir(fs.Dir('subdir')) f11 = fs.File("f11") - match(f11.path, "subdir/f11") + match(f11.get_internal_path(), "subdir/f11") d12 = fs.Dir("d12") e13 = fs.Entry("subdir/e13") - match(e13.path, "subdir/subdir/e13") + match(e13.get_internal_path(), "subdir/subdir/e13") fs.chdir(fs.Dir('..')) # Test scanning @@ -1220,13 +1214,13 @@ class FSTestCase(_tempdirTestCase): f1.builder.target_scanner = Scanner(xyz) f1.scan() - assert f1.implicit[0].path == "xyz" + assert f1.implicit[0].get_internal_path() == "xyz" f1.implicit = [] f1.scan() assert f1.implicit == [] f1.implicit = None f1.scan() - assert f1.implicit[0].path == "xyz" + assert f1.implicit[0].get_internal_path() == "xyz" # Test underlying scanning functionality in get_found_includes() env = Environment() @@ -1284,9 +1278,9 @@ class FSTestCase(_tempdirTestCase): fs.chdir(fs.Dir('subdir')) # The cwd's path is always "." assert str(fs.getcwd()) == ".", str(fs.getcwd()) - assert fs.getcwd().path == 'subdir', fs.getcwd().path + assert fs.getcwd().get_internal_path() == 'subdir', fs.getcwd().get_internal_path() fs.chdir(fs.Dir('../..')) - assert fs.getcwd().path == test.workdir, fs.getcwd().path + assert fs.getcwd().get_internal_path() == test.workdir, fs.getcwd().get_internal_path() f1 = fs.File(test.workpath("do_i_exist")) assert not f1.exists() @@ -1589,15 +1583,15 @@ class FSTestCase(_tempdirTestCase): assert dir.name == name, \ "dir.name %s != expected name %s" % \ (dir.name, name) - assert dir.path == path, \ + assert dir.get_internal_path() == path, \ "dir.path %s != expected path %s" % \ - (dir.path, path) + (dir.get_internal_path(), path) assert str(dir) == path, \ "str(dir) %s != expected path %s" % \ (str(dir), path) - assert dir.up().path == up_path, \ + assert dir.up().get_internal_path() == up_path, \ "dir.up().path %s != expected parent path %s" % \ - (dir.up().path, up_path) + (dir.up().get_internal_path(), up_path) save_os_path = os.path save_os_sep = os.sep @@ -1691,22 +1685,22 @@ class FSTestCase(_tempdirTestCase): name = path.split(os.sep)[-1] if dir.up() is None: - dir_up_path = dir.path + dir_up_path = dir.get_internal_path() else: - dir_up_path = dir.up().path + dir_up_path = dir.up().get_internal_path() assert dir.name == name, \ "dir.name %s != expected name %s" % \ (dir.name, name) - assert dir.path == path, \ + assert dir.get_internal_path() == path, \ "dir.path %s != expected path %s" % \ - (dir.path, path) + (dir.get_internal_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) + (dir.up().get_internal_path(), up_path) save_os_path = os.path save_os_sep = os.sep @@ -1792,7 +1786,7 @@ class FSTestCase(_tempdirTestCase): d1 = fs.Dir('d1') d2 = d1.Dir('d2') - dirs = os.path.normpath(d2.abspath).split(os.sep) + dirs = os.path.normpath(d2.get_abspath()).split(os.sep) above_path = os.path.join(*['..']*len(dirs) + ['above']) above = d2.Dir(above_path) @@ -1831,9 +1825,9 @@ class FSTestCase(_tempdirTestCase): fs = self.fs if sys.platform not in ('win32',): return - p = fs.Dir(r"\\computername\sharename").abspath + p = fs.Dir(r"\\computername\sharename").get_abspath() assert p == r"\\computername\sharename", p - p = fs.Dir(r"\\\computername\sharename").abspath + p = fs.Dir(r"\\\computername\sharename").get_abspath() assert p == r"\\computername\sharename", p def test_rel_path(self): @@ -1980,7 +1974,7 @@ class DirTestCase(_tempdirTestCase): fs.Dir(os.path.join('ddd', 'd1', 'f4')) fs.Dir(os.path.join('ddd', 'd1', 'f5')) dir.scan() - kids = sorted([x.path for x in dir.children(None)]) + kids = sorted([x.get_internal_path() for x in dir.children(None)]) assert kids == [os.path.join('ddd', 'd1'), os.path.join('ddd', 'f1'), os.path.join('ddd', 'f2'), @@ -2024,12 +2018,12 @@ class DirTestCase(_tempdirTestCase): fs.File(os.path.join('ddd', 'f1')) dir.scan() - kids = sorted([x.path for x in dir.children()]) + kids = sorted([x.get_internal_path() for x in dir.children()]) assert kids == [os.path.join('ddd', 'f1')], kids fs.File(os.path.join('ddd', 'f2')) dir.scan() - kids = sorted([x.path for x in dir.children()]) + kids = sorted([x.get_internal_path() for x in dir.children()]) assert kids == [os.path.join('ddd', 'f1'), os.path.join('ddd', 'f2')], kids @@ -2052,6 +2046,32 @@ class DirTestCase(_tempdirTestCase): if os.path.normcase("TeSt") != os.path.normpath("TeSt") or sys.platform == "cygwin": assert d.entry_exists_on_disk('case-insensitive') + def test_rentry_exists_on_disk(self): + """Test the Dir.rentry_exists_on_disk() method + """ + test = self.test + + does_not_exist = self.fs.Dir('does_not_exist') + assert not does_not_exist.rentry_exists_on_disk('foo') + + test.subdir('d') + test.write(['d', 'exists'], "d/exists\n") + test.write(['d', 'Case-Insensitive'], "d/Case-Insensitive\n") + + test.subdir('r') + test.write(['r', 'rexists'], "r/rexists\n") + + d = self.fs.Dir('d') + r = self.fs.Dir('r') + d.addRepository(r) + + assert d.rentry_exists_on_disk('exists') + assert d.rentry_exists_on_disk('rexists') + assert not d.rentry_exists_on_disk('does_not_exist') + + if os.path.normcase("TeSt") != os.path.normpath("TeSt") or sys.platform == "cygwin": + assert d.rentry_exists_on_disk('case-insensitive') + def test_srcdir_list(self): """Test the Dir.srcdir_list() method """ @@ -2145,8 +2165,12 @@ class DirTestCase(_tempdirTestCase): """ test = self.test - return_true = lambda: 1 + def return_true(node): + return 1 + SCons.Node._is_derived_map[2] = return_true + SCons.Node._exists_map[5] = return_true + test.subdir('src0') test.write(['src0', 'on-disk-f1'], "src0/on-disk-f1\n") test.write(['src0', 'on-disk-f2'], "src0/on-disk-f2\n") @@ -2158,14 +2182,14 @@ class DirTestCase(_tempdirTestCase): self.fs.VariantDir(bld0, src0, duplicate=0) derived_f = src0.File('derived-f') - derived_f.is_derived = return_true + derived_f._func_is_derived = 2 exists_f = src0.File('exists-f') - exists_f.exists = return_true + exists_f._func_exists = 5 derived_e = src0.Entry('derived-e') - derived_e.is_derived = return_true + derived_e._func_is_derived = 2 exists_e = src0.Entry('exists-e') - exists_e.exists = return_true + exists_e._func_exists = 5 def check(result, expect): result = list(map(str, result)) @@ -2219,14 +2243,14 @@ class DirTestCase(_tempdirTestCase): self.fs.VariantDir(bld1, src1, duplicate=1) derived_f = src1.File('derived-f') - derived_f.is_derived = return_true + derived_f._func_is_derived = 2 exists_f = src1.File('exists-f') - exists_f.exists = return_true + exists_f._func_exists = 5 derived_e = src1.Entry('derived-e') - derived_e.is_derived = return_true + derived_e._func_is_derived = 2 exists_e = src1.Entry('exists-e') - exists_e.exists = return_true + exists_e._func_exists = 5 # First check from the source directory. n = src1.srcdir_find_file('does_not_exist') @@ -2406,7 +2430,7 @@ class FileTestCase(_tempdirTestCase): build_f1 = fs.File('build/f1') assert build_f1.exists(), "%s did not realize that %s exists" % (build_f1, src_f1) - assert os.path.exists(build_f1.abspath), "%s did not get duplicated on disk" % build_f1.abspath + assert os.path.exists(build_f1.get_abspath()), "%s did not get duplicated on disk" % build_f1.get_abspath() test.unlink(['src', 'f1']) src_f1.clear() # so the next exists() call will look on disk again @@ -2415,7 +2439,7 @@ class FileTestCase(_tempdirTestCase): build_f1.clear() build_f1.linked = None assert not build_f1.exists(), "%s did not realize that %s disappeared" % (build_f1, src_f1) - assert not os.path.exists(build_f1.abspath), "%s did not get removed after %s was removed" % (build_f1, src_f1) + assert not os.path.exists(build_f1.get_abspath()), "%s did not get removed after %s was removed" % (build_f1, src_f1) @@ -2509,7 +2533,7 @@ class GlobTestCase(_tempdirTestCase): for input, string_expect, node_expect in cases: r = self.fs.Glob(input, **kwargs) if node_expect: - r = sorted(r, key=lambda a: a.path) + r = sorted(r, key=lambda a: a.get_internal_path()) result = [] for n in node_expect: if isinstance(n, str): @@ -2880,8 +2904,14 @@ class RepositoryTestCase(_tempdirTestCase): def test_rdir(self): """Test the Dir.rdir() method""" - return_true = lambda: 1 - return_false = lambda: 0 + def return_true(obj): + return 1 + def return_false(obj): + return 0 + SCons.Node._exists_map[5] = return_true + SCons.Node._exists_map[6] = return_false + SCons.Node._is_derived_map[2] = return_true + SCons.Node._is_derived_map[3] = return_false d1 = self.fs.Dir('d1') d2 = self.fs.Dir('d2') @@ -2905,19 +2935,19 @@ class RepositoryTestCase(_tempdirTestCase): assert r == os.path.join(self.rep3, 'd3'), r e1 = self.fs.Dir('e1') - e1.exists = return_false + e1._func_exists = 6 e2 = self.fs.Dir('e2') - e2.exists = return_false + e2._func_exists = 6 # Make sure we match entries in repositories, # regardless of whether they're derived or not. re1 = self.fs.Entry(os.path.join(self.rep1, 'e1')) - re1.exists = return_true - re1.is_derived = return_true + re1._func_exists = 5 + re1._func_is_derived = 2 re2 = self.fs.Entry(os.path.join(self.rep2, 'e2')) - re2.exists = return_true - re2.is_derived = return_false + re2._func_exists = 5 + re2._func_is_derived = 3 r = e1.rdir() assert r is re1, r @@ -2927,8 +2957,14 @@ class RepositoryTestCase(_tempdirTestCase): def test_rfile(self): """Test the File.rfile() method""" - return_true = lambda: 1 - return_false = lambda: 0 + def return_true(obj): + return 1 + def return_false(obj): + return 0 + SCons.Node._exists_map[5] = return_true + SCons.Node._exists_map[6] = return_false + SCons.Node._is_derived_map[2] = return_true + SCons.Node._is_derived_map[3] = return_false f1 = self.fs.File('f1') f2 = self.fs.File('f2') @@ -2952,19 +2988,19 @@ class RepositoryTestCase(_tempdirTestCase): assert r == os.path.join(self.rep3, 'f3'), r e1 = self.fs.File('e1') - e1.exists = return_false + e1._func_exists = 6 e2 = self.fs.File('e2') - e2.exists = return_false + e2._func_exists = 6 # Make sure we match entries in repositories, # regardless of whether they're derived or not. re1 = self.fs.Entry(os.path.join(self.rep1, 'e1')) - re1.exists = return_true - re1.is_derived = return_true + re1._func_exists = 5 + re1._func_is_derived = 2 re2 = self.fs.Entry(os.path.join(self.rep2, 'e2')) - re2.exists = return_true - re2.is_derived = return_false + re2._func_exists = 5 + re2._func_is_derived = 3 r = e1.rfile() assert r is re1, r @@ -3218,9 +3254,13 @@ class stored_infoTestCase(unittest.TestCase): self.xyzzy = 7 def get_entry(self, name): return self.Null() + + def test_sconsign(node): + return MySConsign() f = fs.File('file2', d) - f.dir.sconsign = MySConsign + SCons.Node.FS._sconsign_map[2] = test_sconsign + f.dir._func_sconsign = 2 bi = f.get_stored_info() assert bi.xyzzy == 7, bi @@ -3334,7 +3374,7 @@ class prepareTestCase(unittest.TestCase): xyz.set_state(0) xyz.prepare() - assert dir_made[0].path == "new_dir", dir_made[0] + assert dir_made[0].get_internal_path() == "new_dir", dir_made[0] dir = fs.Dir("dir") dir.prepare() @@ -3347,7 +3387,7 @@ class SConstruct_dirTestCase(unittest.TestCase): fs = SCons.Node.FS.FS() fs.set_SConstruct_dir(fs.Dir('xxx')) - assert fs.SConstruct_dir.path == 'xxx' + assert fs.SConstruct_dir.get_internal_path() == 'xxx' @@ -3526,7 +3566,7 @@ class SpecialAttrTestCase(unittest.TestCase): for_sig = f.suffix.for_signature() assert for_sig == 'baz.blat_suffix', for_sig - s = str(f.abspath) + s = str(f.get_abspath()) assert s == test.workpath('work', 'foo', 'bar', 'baz.blat'), s assert f.abspath.is_literal(), f.abspath for_sig = f.abspath.for_signature() diff --git a/src/engine/SCons/Node/NodeTests.py b/src/engine/SCons/Node/NodeTests.py index da502b0..3802f8c 100644 --- a/src/engine/SCons/Node/NodeTests.py +++ b/src/engine/SCons/Node/NodeTests.py @@ -121,6 +121,8 @@ class Environment(object): self._dict.update(kw) def __getitem__(self, key): return self._dict[key] + def get(self, key, default = None): + return self._dict.get(key, default) def Dictionary(self, *args): return {} def Override(self, overrides): @@ -132,7 +134,12 @@ class Environment(object): def get_factory(self, factory): return factory or MyNode def get_scanner(self, scanner_key): - return self._dict['SCANNERS'][0] + try: + return self._dict['SCANNERS'][0] + except: + pass + + return [] class Builder(object): def __init__(self, env=None, is_explicit=1): @@ -184,8 +191,8 @@ class Scanner(object): called = None def __call__(self, node): self.called = 1 - return node.found_includes - def path(self, env, dir, target=None, source=None): + return node.GetTag('found_includes') + def path(self, env, dir=None, target=None, source=None, kw={}): return () def select(self, node): return self @@ -200,7 +207,7 @@ class MyNode(SCons.Node.Node): def __init__(self, name): SCons.Node.Node.__init__(self) self.name = name - self.found_includes = [] + self.Tag('found_includes', []) def __str__(self): return self.name def get_found_includes(self, env, scanner, target): @@ -224,11 +231,18 @@ class Calculator(object): class NodeInfoBaseTestCase(unittest.TestCase): + # The abstract class NodeInfoBase has not enough default slots to perform + # the merge and format test (arbitrary attributes do not work). Do it with a + # derived class that does provide the slots. def test_merge(self): """Test merging NodeInfoBase attributes""" - ni1 = SCons.Node.NodeInfoBase(SCons.Node.Node()) - ni2 = SCons.Node.NodeInfoBase(SCons.Node.Node()) + + class TestNodeInfo(SCons.Node.NodeInfoBase): + __slots__ = ('a1', 'a2', 'a3') + + ni1 = TestNodeInfo() + ni2 = TestNodeInfo() ni1.a1 = 1 ni1.a2 = 2 @@ -237,27 +251,32 @@ class NodeInfoBaseTestCase(unittest.TestCase): ni2.a3 = 333 ni1.merge(ni2) - expect = {'a1':1, 'a2':222, 'a3':333, '_version_id':1} - assert ni1.__dict__ == expect, ni1.__dict__ + assert ni1.a1 == 1, ni1.a1 + assert ni1.a2 == 222, ni1.a2 + assert ni1.a3 == 333, ni1.a3 def test_update(self): """Test the update() method""" - ni = SCons.Node.NodeInfoBase(SCons.Node.Node()) + ni = SCons.Node.NodeInfoBase() ni.update(SCons.Node.Node()) def test_format(self): """Test the NodeInfoBase.format() method""" - ni1 = SCons.Node.NodeInfoBase(SCons.Node.Node()) + + class TestNodeInfo(SCons.Node.NodeInfoBase): + __slots__ = ('xxx', 'yyy', 'zzz') + + ni1 = TestNodeInfo() ni1.xxx = 'x' ni1.yyy = 'y' ni1.zzz = 'z' f = ni1.format() - assert f == ['1', 'x', 'y', 'z'], f + assert f == ['x', 'y', 'z'], f + + field_list = ['xxx', 'zzz', 'aaa'] - ni1.field_list = ['xxx', 'zzz', 'aaa'] - - f = ni1.format() + f = ni1.format(field_list) assert f == ['x', 'z', 'None'], f @@ -267,26 +286,26 @@ class BuildInfoBaseTestCase(unittest.TestCase): def test___init__(self): """Test BuildInfoBase initialization""" n = SCons.Node.Node() - bi = SCons.Node.BuildInfoBase(n) + bi = SCons.Node.BuildInfoBase() assert bi def test_merge(self): """Test merging BuildInfoBase attributes""" n1 = SCons.Node.Node() - bi1 = SCons.Node.BuildInfoBase(n1) + bi1 = SCons.Node.BuildInfoBase() n2 = SCons.Node.Node() - bi2 = SCons.Node.BuildInfoBase(n2) + bi2 = SCons.Node.BuildInfoBase() - bi1.a1 = 1 - bi1.a2 = 2 + bi1.bsources = 1 + bi1.bdepends = 2 - bi2.a2 = 222 - bi2.a3 = 333 + bi2.bdepends = 222 + bi2.bact = 333 bi1.merge(bi2) - assert bi1.a1 == 1, bi1.a1 - assert bi1.a2 == 222, bi1.a2 - assert bi1.a3 == 333, bi1.a3 + assert bi1.bsources == 1, bi1.bsources + assert bi1.bdepends == 222, bi1.bdepends + assert bi1.bact == 333, bi1.bact class NodeTestCase(unittest.TestCase): @@ -339,8 +358,6 @@ class NodeTestCase(unittest.TestCase): ggg.path = "ggg" fff.sources = ["hhh", "iii"] ggg.sources = ["hhh", "iii"] - # [Charles C. 1/7/2002] Uhhh, why are there no asserts here? - # [SK, 15 May 2003] I dunno, let's add some... built_it = None fff.build() assert built_it @@ -427,6 +444,7 @@ class NodeTestCase(unittest.TestCase): def test_built(self): """Test the built() method""" class SubNodeInfo(SCons.Node.NodeInfoBase): + __slots__ = ('updated',) def update(self, node): self.updated = 1 class SubNode(SCons.Node.Node): @@ -434,7 +452,7 @@ class NodeTestCase(unittest.TestCase): self.cleared = 1 n = SubNode() - n.ninfo = SubNodeInfo(n) + n.ninfo = SubNodeInfo() n.built() assert n.cleared, n.cleared assert n.ninfo.updated, n.ninfo.cleared @@ -568,32 +586,56 @@ class NodeTestCase(unittest.TestCase): def test_get_csig(self): """Test generic content signature calculation """ - node = SCons.Node.Node() - node.get_contents = lambda: 444 - result = node.get_csig() - assert result == '550a141f12de6341fba65b0ad0433500', result + + class TestNodeInfo(SCons.Node.NodeInfoBase): + __slots__ = ('csig',) + try: + SCons.Node.Node.NodeInfo = TestNodeInfo + def my_contents(obj): + return 444 + SCons.Node._get_contents_map[4] = my_contents + node = SCons.Node.Node() + node._func_get_contents = 4 + result = node.get_csig() + assert result == '550a141f12de6341fba65b0ad0433500', result + finally: + SCons.Node.Node.NodeInfo = SCons.Node.NodeInfoBase def test_get_cachedir_csig(self): """Test content signature calculation for CacheDir """ - node = SCons.Node.Node() - node.get_contents = lambda: 555 - result = node.get_cachedir_csig() - assert result == '15de21c670ae7c3f6f3f1f37029303c9', result + class TestNodeInfo(SCons.Node.NodeInfoBase): + __slots__ = ('csig',) + try: + SCons.Node.Node.NodeInfo = TestNodeInfo + def my_contents(obj): + return 555 + SCons.Node._get_contents_map[4] = my_contents + node = SCons.Node.Node() + node._func_get_contents = 4 + result = node.get_cachedir_csig() + assert result == '15de21c670ae7c3f6f3f1f37029303c9', result + finally: + SCons.Node.Node.NodeInfo = SCons.Node.NodeInfoBase def test_get_binfo(self): """Test fetching/creating a build information structure """ + class TestNodeInfo(SCons.Node.NodeInfoBase): + __slots__ = ('csig',) + SCons.Node.Node.NodeInfo = TestNodeInfo node = SCons.Node.Node() - + binfo = node.get_binfo() assert isinstance(binfo, SCons.Node.BuildInfoBase), binfo node = SCons.Node.Node() d = SCons.Node.Node() - d.get_ninfo().csig = 777 + ninfo = d.get_ninfo() + assert isinstance(ninfo, SCons.Node.NodeInfoBase), ninfo i = SCons.Node.Node() - i.get_ninfo().csig = 888 + ninfo = i.get_ninfo() + assert isinstance(ninfo, SCons.Node.NodeInfoBase), ninfo node.depends = [d] node.implicit = [i] @@ -655,7 +697,7 @@ class NodeTestCase(unittest.TestCase): """Test calling the method to store build information """ node = SCons.Node.Node() - node.store_info() + SCons.Node.store_info_map[node.store_info](node) def test_get_stored_info(self): """Test calling the method to fetch stored build information @@ -888,37 +930,37 @@ class NodeTestCase(unittest.TestCase): s = Scanner() d1 = MyNode("d1") d2 = MyNode("d2") - node.found_includes = [d1, d2] + node.Tag('found_includes', [d1, d2]) # Simple return of the found includes - deps = node.get_implicit_deps(env, s, target) + deps = node.get_implicit_deps(env, s, s.path) assert deps == [d1, d2], deps # By default, our fake scanner recurses e = MyNode("eee") f = MyNode("fff") g = MyNode("ggg") - d1.found_includes = [e, f] - d2.found_includes = [e, f] - f.found_includes = [g] - deps = node.get_implicit_deps(env, s, target) + d1.Tag('found_includes', [e, f]) + d2.Tag('found_includes', [e, f]) + f.Tag('found_includes', [g]) + deps = node.get_implicit_deps(env, s, s.path) assert deps == [d1, d2, e, f, g], list(map(str, deps)) # Recursive scanning eliminates duplicates - e.found_includes = [f] - deps = node.get_implicit_deps(env, s, target) + e.Tag('found_includes', [f]) + deps = node.get_implicit_deps(env, s, s.path) assert deps == [d1, d2, e, f, g], list(map(str, deps)) # Scanner method can select specific nodes to recurse def no_fff(nodes): return [n for n in nodes if str(n)[0] != 'f'] s.recurse_nodes = no_fff - deps = node.get_implicit_deps(env, s, target) + deps = node.get_implicit_deps(env, s, s.path) assert deps == [d1, d2, e, f], list(map(str, deps)) # Scanner method can short-circuit recursing entirely s.recurse_nodes = lambda nodes: [] - deps = node.get_implicit_deps(env, s, target) + deps = node.get_implicit_deps(env, s, s.path) assert deps == [d1, d2], list(map(str, deps)) def test_get_env_scanner(self): @@ -994,7 +1036,7 @@ class NodeTestCase(unittest.TestCase): s = Scanner() d = MyNode("ddd") - node.found_includes = [d] + node.Tag('found_includes', [d]) node.builder.target_scanner = s assert node.implicit is None @@ -1207,7 +1249,7 @@ class NodeTestCase(unittest.TestCase): def test_Annotate(self): """Test using an interface-specific Annotate function.""" def my_annotate(node, self=self): - node.annotation = self.node_string + node.Tag('annotation', self.node_string) save_Annotate = SCons.Node.Annotate SCons.Node.Annotate = my_annotate @@ -1215,11 +1257,13 @@ class NodeTestCase(unittest.TestCase): try: self.node_string = '#1' n = SCons.Node.Node() - assert n.annotation == '#1', n.annotation + a = n.GetTag('annotation') + assert a == '#1', a self.node_string = '#2' n = SCons.Node.Node() - assert n.annotation == '#2', n.annotation + a = n.GetTag('annotation') + assert a == '#2', a finally: SCons.Node.Annotate = save_Annotate @@ -1230,7 +1274,7 @@ class NodeTestCase(unittest.TestCase): n.set_state(3) n.binfo = 'xyz' n.includes = 'testincludes' - n.found_include = {'testkey':'testvalue'} + n.Tag('found_includes', {'testkey':'testvalue'}) n.implicit = 'testimplicit' x = MyExecutor() diff --git a/src/engine/SCons/Node/Python.py b/src/engine/SCons/Node/Python.py index 3d8bdaa..f151fc5 100644 --- a/src/engine/SCons/Node/Python.py +++ b/src/engine/SCons/Node/Python.py @@ -32,15 +32,49 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import SCons.Node class ValueNodeInfo(SCons.Node.NodeInfoBase): - current_version_id = 1 + __slots__ = ('csig',) + current_version_id = 2 field_list = ['csig'] def str_to_node(self, s): return Value(s) + def __getstate__(self): + """ + Return all fields that shall be pickled. Walk the slots in the class + hierarchy and add those to the state dictionary. If a '__dict__' slot is + available, copy all entries to the dictionary. Also include the version + id, which is fixed for all instances of a class. + """ + state = getattr(self, '__dict__', {}).copy() + for obj in type(self).mro(): + for name in getattr(obj,'__slots__',()): + if hasattr(self, name): + state[name] = getattr(self, name) + + state['_version_id'] = self.current_version_id + try: + del state['__weakref__'] + except KeyError: + pass + + return state + + def __setstate__(self, state): + """ + Restore the attributes from a pickled state. + """ + # TODO check or discard version + del state['_version_id'] + for key, value in state.items(): + if key not in ('__weakref__',): + setattr(self, key, value) + + class ValueBuildInfo(SCons.Node.BuildInfoBase): - current_version_id = 1 + __slots__ = () + current_version_id = 2 class Value(SCons.Node.Node): """A class for Python variables, typically passed on the command line @@ -53,6 +87,8 @@ class Value(SCons.Node.Node): def __init__(self, value, built_value=None): SCons.Node.Node.__init__(self) self.value = value + self.changed_since_last_build = 6 + self.store_info = 0 if built_value is not None: self.built_value = built_value diff --git a/src/engine/SCons/Node/PythonTests.py b/src/engine/SCons/Node/PythonTests.py index fcdfe77..e2e36bf 100644 --- a/src/engine/SCons/Node/PythonTests.py +++ b/src/engine/SCons/Node/PythonTests.py @@ -104,13 +104,13 @@ class ValueNodeInfoTestCase(unittest.TestCase): def test___init__(self): """Test ValueNodeInfo initialization""" vvv = SCons.Node.Python.Value('vvv') - ni = SCons.Node.Python.ValueNodeInfo(vvv) + ni = SCons.Node.Python.ValueNodeInfo() class ValueBuildInfoTestCase(unittest.TestCase): def test___init__(self): """Test ValueBuildInfo initialization""" vvv = SCons.Node.Python.Value('vvv') - bi = SCons.Node.Python.ValueBuildInfo(vvv) + bi = SCons.Node.Python.ValueBuildInfo() if __name__ == "__main__": suite = unittest.TestSuite() diff --git a/src/engine/SCons/Node/__init__.py b/src/engine/SCons/Node/__init__.py index f13fd03..1a76b60 100644 --- a/src/engine/SCons/Node/__init__.py +++ b/src/engine/SCons/Node/__init__.py @@ -19,6 +19,8 @@ be able to depend on any other type of "thing." """ +from __future__ import print_function + # # __COPYRIGHT__ # @@ -55,6 +57,8 @@ import SCons.Util from SCons.Debug import Trace +print_duplicate = 0 + def classname(obj): return str(obj.__class__).split('.')[-1] @@ -105,6 +109,233 @@ Annotate = do_nothing # clean builds and update runs (see release_target_info). interactive = False +def is_derived_none(node): + raise NotImplementedError + +def is_derived_node(node): + """ + Returns true if this node is derived (i.e. built). + """ + return node.has_builder() or node.side_effect + +_is_derived_map = {0 : is_derived_none, + 1 : is_derived_node} + +def exists_none(node): + raise NotImplementedError + +def exists_always(node): + return 1 + +def exists_base(node): + return node.stat() is not None + +def exists_entry(node): + """Return if the Entry exists. Check the file system to see + what we should turn into first. Assume a file if there's no + directory.""" + node.disambiguate() + return _exists_map[node._func_exists](node) + +def exists_file(node): + # Duplicate from source path if we are set up to do this. + if node.duplicate and not node.is_derived() and not node.linked: + src = node.srcnode() + if src is not node: + # At this point, src is meant to be copied in a variant directory. + src = src.rfile() + if src.get_abspath() != node.get_abspath(): + if src.exists(): + node.do_duplicate(src) + # Can't return 1 here because the duplication might + # not actually occur if the -n option is being used. + else: + # The source file does not exist. Make sure no old + # copy remains in the variant directory. + if print_duplicate: + print("dup: no src for %s, unlinking old variant copy"%self) + if exists_base(node) or node.islink(): + node.fs.unlink(node.get_internal_path()) + # Return None explicitly because the Base.exists() call + # above will have cached its value if the file existed. + return None + return exists_base(node) + +_exists_map = {0 : exists_none, + 1 : exists_always, + 2 : exists_base, + 3 : exists_entry, + 4 : exists_file} + + +def rexists_none(node): + raise NotImplementedError + +def rexists_node(node): + return node.exists() + +def rexists_base(node): + return node.rfile().exists() + +_rexists_map = {0 : rexists_none, + 1 : rexists_node, + 2 : rexists_base} + +def get_contents_none(node): + raise NotImplementedError + +def get_contents_entry(node): + """Fetch the contents of the entry. Returns the exact binary + contents of the file.""" + try: + node = node.disambiguate(must_exist=1) + except SCons.Errors.UserError: + # There was nothing on disk with which to disambiguate + # this entry. Leave it as an Entry, but return a null + # string so calls to get_contents() in emitters and the + # like (e.g. in qt.py) don't have to disambiguate by hand + # or catch the exception. + return '' + else: + return _get_contents_map[node._func_get_contents](node) + +def get_contents_dir(node): + """Return content signatures and names of all our children + separated by new-lines. Ensure that the nodes are sorted.""" + contents = [] + for n in sorted(node.children(), key=lambda t: t.name): + contents.append('%s %s\n' % (n.get_csig(), n.name)) + return ''.join(contents) + +def get_contents_file(node): + if not node.rexists(): + return '' + fname = node.rfile().get_abspath() + try: + contents = open(fname, "rb").read() + except EnvironmentError as e: + if not e.filename: + e.filename = fname + raise + return contents + +_get_contents_map = {0 : get_contents_none, + 1 : get_contents_entry, + 2 : get_contents_dir, + 3 : get_contents_file} + +def target_from_source_none(node, prefix, suffix, splitext): + raise NotImplementedError + +def target_from_source_base(node, prefix, suffix, splitext): + return node.dir.Entry(prefix + splitext(node.name)[0] + suffix) + +_target_from_source_map = {0 : target_from_source_none, + 1 : target_from_source_base} + +# +# The new decider subsystem for Nodes +# +# We would set and overwrite the changed_since_last_build function +# before, but for being able to use slots (less memory!) we now have +# a dictionary of the different decider functions. Then in the Node +# subclasses we simply store the index to the decider that should be +# used by it. +# + +# +# First, the single decider functions +# +def changed_since_last_build_node(node, target, prev_ni): + """ + + Must be overridden in a specific subclass to return True if this + Node (a dependency) has changed since the last time it was used + to build the specified target. prev_ni is this Node's state (for + example, its file timestamp, length, maybe content signature) + as of the last time the target was built. + + Note that this method is called through the dependency, not the + target, because a dependency Node must be able to use its own + logic to decide if it changed. For example, File Nodes need to + obey if we're configured to use timestamps, but Python Value Nodes + never use timestamps and always use the content. If this method + were called through the target, then each Node's implementation + of this method would have to have more complicated logic to + handle all the different Node types on which it might depend. + """ + raise NotImplementedError + +def changed_since_last_build_alias(node, target, prev_ni): + cur_csig = node.get_csig() + try: + return cur_csig != prev_ni.csig + except AttributeError: + return 1 + +def changed_since_last_build_entry(node, target, prev_ni): + node.disambiguate() + return _decider_map[node.changed_since_last_build](node, target, prev_ni) + +def changed_since_last_build_state_changed(node, target, prev_ni): + return (node.state != SCons.Node.up_to_date) + +def decide_source(node, target, prev_ni): + return target.get_build_env().decide_source(node, target, prev_ni) + +def decide_target(node, target, prev_ni): + return target.get_build_env().decide_target(node, target, prev_ni) + +def changed_since_last_build_python(node, target, prev_ni): + cur_csig = node.get_csig() + try: + return cur_csig != prev_ni.csig + except AttributeError: + return 1 + + +# +# Now, the mapping from indices to decider functions +# +_decider_map = {0 : changed_since_last_build_node, + 1 : changed_since_last_build_alias, + 2 : changed_since_last_build_entry, + 3 : changed_since_last_build_state_changed, + 4 : decide_source, + 5 : decide_target, + 6 : changed_since_last_build_python} + +do_store_info = True + +# +# The new store_info subsystem for Nodes +# +# We would set and overwrite the store_info function +# before, but for being able to use slots (less memory!) we now have +# a dictionary of the different functions. Then in the Node +# subclasses we simply store the index to the info method that should be +# used by it. +# + +# +# First, the single info functions +# + +def store_info_pass(node): + pass + +def store_info_file(node): + # Merge our build information into the already-stored entry. + # This accommodates "chained builds" where a file that's a target + # in one build (SConstruct file) is a source in a different build. + # See test/chained-build.py for the use case. + if do_store_info: + node.dir.sconsign().store_info(node.name, node) + + +store_info_map = {0 : store_info_pass, + 1 : store_info_file} + # Classes for signature info for Nodes. class NodeInfoBase(object): @@ -114,11 +345,8 @@ class NodeInfoBase(object): Node subclasses should subclass NodeInfoBase to provide their own logic for dealing with their own Node-specific signature information. """ - current_version_id = 1 - def __init__(self, node=None): - # Create an object attribute from the class attribute so it ends up - # in the pickled data in the .sconsign file. - self._version_id = self.current_version_id + __slots__ = ('__weakref__',) + current_version_id = 2 def update(self, node): try: field_list = self.field_list @@ -138,13 +366,25 @@ class NodeInfoBase(object): def convert(self, node, val): pass def merge(self, other): - self.__dict__.update(other.__dict__) + """ + Merge the fields of another object into this object. Already existing + information is overwritten by the other instance's data. + WARNING: If a '__dict__' slot is added, it should be updated instead of + replaced. + """ + state = other.__getstate__() + self.__setstate__(state) def format(self, field_list=None, names=0): if field_list is None: try: field_list = self.field_list except AttributeError: - field_list = sorted(self.__dict__.keys()) + field_list = getattr(self, '__dict__', {}).keys() + for obj in type(self).mro(): + for slot in getattr(obj, '__slots__', ()): + if slot not in ('__weakref__', '__dict__'): + field_list.append(slot) + field_list.sort() fields = [] for field in field_list: try: @@ -157,6 +397,38 @@ class NodeInfoBase(object): fields.append(f) return fields + def __getstate__(self): + """ + Return all fields that shall be pickled. Walk the slots in the class + hierarchy and add those to the state dictionary. If a '__dict__' slot is + available, copy all entries to the dictionary. Also include the version + id, which is fixed for all instances of a class. + """ + state = getattr(self, '__dict__', {}).copy() + for obj in type(self).mro(): + for name in getattr(obj,'__slots__',()): + if hasattr(self, name): + state[name] = getattr(self, name) + + state['_version_id'] = self.current_version_id + try: + del state['__weakref__'] + except KeyError: + pass + return state + + def __setstate__(self, state): + """ + Restore the attributes from a pickled state. The version is discarded. + """ + # TODO check or discard version + del state['_version_id'] + + for key, value in state.items(): + if key not in ('__weakref__',): + setattr(self, key, value) + + class BuildInfoBase(object): """ The generic base class for build information for a Node. @@ -167,30 +439,106 @@ class BuildInfoBase(object): generic build stuff we have to track: sources, explicit dependencies, implicit dependencies, and action information. """ - current_version_id = 1 - def __init__(self, node=None): + __slots__ = ("bsourcesigs", "bdependsigs", "bimplicitsigs", "bactsig", + "bsources", "bdepends", "bact", "bimplicit", "__weakref__") + current_version_id = 2 + def __init__(self): # Create an object attribute from the class attribute so it ends up # in the pickled data in the .sconsign file. - self._version_id = self.current_version_id self.bsourcesigs = [] self.bdependsigs = [] self.bimplicitsigs = [] self.bactsig = None def merge(self, other): - self.__dict__.update(other.__dict__) + """ + Merge the fields of another object into this object. Already existing + information is overwritten by the other instance's data. + WARNING: If a '__dict__' slot is added, it should be updated instead of + replaced. + """ + state = other.__getstate__() + self.__setstate__(state) + + def __getstate__(self): + """ + Return all fields that shall be pickled. Walk the slots in the class + hierarchy and add those to the state dictionary. If a '__dict__' slot is + available, copy all entries to the dictionary. Also include the version + id, which is fixed for all instances of a class. + """ + state = getattr(self, '__dict__', {}).copy() + for obj in type(self).mro(): + for name in getattr(obj,'__slots__',()): + if hasattr(self, name): + state[name] = getattr(self, name) + + state['_version_id'] = self.current_version_id + try: + del state['__weakref__'] + except KeyError: + pass + return state + + def __setstate__(self, state): + """ + Restore the attributes from a pickled state. + """ + # TODO check or discard version + del state['_version_id'] + for key, value in state.items(): + if key not in ('__weakref__',): + setattr(self, key, value) class Node(object): """The base Node class, for entities that we know how to build, or use to build other Nodes. """ - if SCons.Memoize.use_memoizer: - __metaclass__ = SCons.Memoize.Memoized_Metaclass - - memoizer_counters = [] + __slots__ = ['sources', + 'sources_set', + '_specific_sources', + 'depends', + 'depends_set', + 'ignore', + 'ignore_set', + 'prerequisites', + 'implicit', + 'waiting_parents', + 'waiting_s_e', + 'ref_count', + 'wkids', + 'env', + 'state', + 'precious', + 'noclean', + 'nocache', + 'cached', + 'always_build', + 'includes', + 'attributes', + 'side_effect', + 'side_effects', + 'linked', + '_memo', + 'executor', + 'binfo', + 'ninfo', + 'builder', + 'is_explicit', + 'implicit_set', + 'changed_since_last_build', + 'store_info', + 'pseudo', + '_tags', + '_func_is_derived', + '_func_exists', + '_func_rexists', + '_func_get_contents', + '_func_target_from_source'] class Attrs(object): - pass + __slots__ = ('shared', '__dict__') + def __init__(self): if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.Node') @@ -234,6 +582,14 @@ class Node(object): self.side_effect = 0 # true iff this node is a side effect self.side_effects = [] # the side effects of building this target self.linked = 0 # is this node linked to the variant directory? + self.changed_since_last_build = 0 + self.store_info = 0 + self._tags = None + self._func_is_derived = 1 + self._func_exists = 1 + self._func_rexists = 1 + self._func_get_contents = 0 + self._func_target_from_source = 0 self.clear_memoized_values() @@ -248,8 +604,7 @@ class Node(object): def get_suffix(self): return '' - memoizer_counters.append(SCons.Memoize.CountValue('get_build_env')) - + @SCons.Memoize.CountMethodCall def get_build_env(self): """Fetch the appropriate Environment to build this node. """ @@ -418,21 +773,21 @@ class Node(object): pass else: self.ninfo.update(self) - self.store_info() + SCons.Node.store_info_map[self.store_info](self) def release_target_info(self): """Called just after this node has been marked up-to-date or was built completely. - + This is where we try to release as many target node infos as possible for clean builds and update runs, in order to minimize the overall memory consumption. - + By purging attributes that aren't needed any longer after a Node (=File) got built, we don't have to care that much how many KBytes a Node actually requires...as long as we free the memory shortly afterwards. - + @see: built() and File.release_target_info() """ pass @@ -546,7 +901,7 @@ class Node(object): example: source with source builders are not derived in this sense, and hence should not return true. """ - return self.has_builder() or self.side_effect + return _is_derived_map[self._func_is_derived](self) def alter_targets(self): """Return a list of alternate targets for this Node. @@ -563,34 +918,55 @@ class Node(object): """ return [] - def get_implicit_deps(self, env, scanner, path): + def get_implicit_deps(self, env, initial_scanner, path_func, kw = {}): """Return a list of implicit dependencies for this node. This method exists to handle recursive invocation of the scanner on the implicit dependencies returned by the scanner, if the scanner's recursive flag says that we should. """ - if not scanner: - return [] - - # Give the scanner a chance to select a more specific scanner - # for this Node. - #scanner = scanner.select(self) - nodes = [self] seen = {} seen[self] = 1 - deps = [] + dependencies = [] + + root_node_scanner = self._get_scanner(env, initial_scanner, None, kw) + while nodes: - n = nodes.pop(0) - d = [x for x in n.get_found_includes(env, scanner, path) if x not in seen] - if d: - deps.extend(d) - for n in d: - seen[n] = 1 - nodes.extend(scanner.recurse_nodes(d)) + node = nodes.pop(0) + + scanner = node._get_scanner(env, initial_scanner, root_node_scanner, kw) + + if not scanner: + continue + + path = path_func(scanner) + + included_deps = [x for x in node.get_found_includes(env, scanner, path) if x not in seen] + if included_deps: + dependencies.extend(included_deps) + for dep in included_deps: + seen[dep] = 1 + nodes.extend(scanner.recurse_nodes(included_deps)) - return deps + return dependencies + + def _get_scanner(self, env, initial_scanner, root_node_scanner, kw): + if not initial_scanner: + # handle implicit scanner case + scanner = self.get_env_scanner(env, kw) + if scanner: + scanner = scanner.select(self) + else: + # handle explicit scanner case + scanner = initial_scanner.select(self) + + if not scanner: + # no scanner could be found for the given node's scanner key; + # thus, make an attempt at using a default. + scanner = root_node_scanner + + return scanner def get_env_scanner(self, env, kw={}): return env.get_scanner(self.scanner_key()) @@ -706,7 +1082,7 @@ class Node(object): BuildInfo = BuildInfoBase def new_ninfo(self): - ninfo = self.NodeInfo(self) + ninfo = self.NodeInfo() return ninfo def get_ninfo(self): @@ -717,7 +1093,7 @@ class Node(object): return self.ninfo def new_binfo(self): - binfo = self.BuildInfo(self) + binfo = self.BuildInfo() return binfo def get_binfo(self): @@ -802,14 +1178,6 @@ class Node(object): def get_cachedir_csig(self): return self.get_csig() - def store_info(self): - """Make the build signature permanent (that is, store it in the - .sconsign file or equivalent).""" - pass - - def do_not_store_info(self): - pass - def get_stored_info(self): return None @@ -847,13 +1215,16 @@ class Node(object): def exists(self): """Does this node exists?""" - # All node exist by default: - return 1 + return _exists_map[self._func_exists](self) def rexists(self): """Does this node exist locally or in a repositiory?""" # There are no repositories by default: - return self.exists() + return _rexists_map[self._func_rexists](self) + + def get_contents(self): + """Fetch the contents of the entry.""" + return _get_contents_map[self._func_get_contents](self) def missing(self): return not self.is_derived() and \ @@ -912,11 +1283,6 @@ class Node(object): def _add_child(self, collection, set, child): """Adds 'child' to 'collection', first checking 'set' to see if it's already present.""" - #if type(child) is not type([]): - # child = [child] - #for c in child: - # if not isinstance(c, Node): - # raise TypeError, c added = None for c in child: if c not in set: @@ -941,11 +1307,10 @@ class Node(object): # build info that it's cached so we can re-calculate it. self.executor_cleanup() - memoizer_counters.append(SCons.Memoize.CountValue('_children_get')) - + @SCons.Memoize.CountMethodCall def _children_get(self): try: - return self._memo['children_get'] + return self._memo['_children_get'] except KeyError: pass @@ -976,7 +1341,7 @@ class Node(object): else: children = self.all_children(scan=0) - self._memo['children_get'] = children + self._memo['_children_get'] = children return children def all_children(self, scan=1): @@ -1016,9 +1381,6 @@ class Node(object): def get_state(self): return self.state - def state_has_changed(self, target, prev_ni): - return (self.state != SCons.Node.up_to_date) - def get_env(self): env = self.env if not env: @@ -1026,28 +1388,28 @@ class Node(object): env = SCons.Defaults.DefaultEnvironment() return env - def changed_since_last_build(self, target, prev_ni): - """ - - Must be overridden in a specific subclass to return True if this - Node (a dependency) has changed since the last time it was used - to build the specified target. prev_ni is this Node's state (for - example, its file timestamp, length, maybe content signature) - as of the last time the target was built. - - Note that this method is called through the dependency, not the - target, because a dependency Node must be able to use its own - logic to decide if it changed. For example, File Nodes need to - obey if we're configured to use timestamps, but Python Value Nodes - never use timestamps and always use the content. If this method - were called through the target, then each Node's implementation - of this method would have to have more complicated logic to - handle all the different Node types on which it might depend. - """ - raise NotImplementedError - def Decider(self, function): - SCons.Util.AddMethod(self, function, 'changed_since_last_build') + foundkey = None + for k, v in _decider_map.iteritems(): + if v == function: + foundkey = k + break + if not foundkey: + foundkey = len(_decider_map) + _decider_map[foundkey] = function + self.changed_since_last_build = foundkey + + def Tag(self, key, value): + """ Add a user-defined tag. """ + if not self._tags: + self._tags = {} + self._tags[key] = value + + def GetTag(self, key): + """ Return a user-defined tag. """ + if not self._tags: + return None + return self._tags.get(key, None) def changed(self, node=None, allowcache=False): """ @@ -1062,14 +1424,14 @@ class Node(object): any difference, but we now rely on checking every dependency to make sure that any necessary Node information (for example, the content signature of an #included .h file) is updated. - + The allowcache option was added for supporting the early release of the executor/builder structures, right after a File target was built. When set to true, the return value of this changed method gets cached for File nodes. Like this, the executor isn't needed any longer for subsequent calls to changed(). - + @see: FS.File.changed(), FS.File.release_target_info() """ t = 0 @@ -1095,7 +1457,7 @@ class Node(object): result = True for child, prev_ni in zip(children, then): - if child.changed_since_last_build(self, prev_ni): + if _decider_map[child.changed_since_last_build](child, self, prev_ni): if t: Trace(': %s changed' % child) result = True @@ -1266,7 +1628,7 @@ class Node(object): for k in new_bkids: if not k in old_bkids: lines.append("`%s' is a new dependency\n" % stringify(k)) - elif k.changed_since_last_build(self, osig[k]): + elif _decider_map[k.changed_since_last_build](k, self, osig[k]): lines.append("`%s' changed\n" % stringify(k)) if len(lines) == 0 and old_bkids != new_bkids: |
