From 72c39ad485d174c2e8e1fef34b8e9e392a94458a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Frings-F=C3=BCrst?= Date: Mon, 28 Sep 2015 18:23:34 +0200 Subject: Imported Upstream version 2.4.0 --- engine/SCons/Node/Alias.py | 51 +++- engine/SCons/Node/FS.py | 592 ++++++++++++++++++++++++++---------------- engine/SCons/Node/Python.py | 42 ++- engine/SCons/Node/__init__.py | 474 ++++++++++++++++++++++++++++----- 4 files changed, 862 insertions(+), 297 deletions(-) (limited to 'engine/SCons/Node') diff --git a/engine/SCons/Node/Alias.py b/engine/SCons/Node/Alias.py index 0bd6422..7e13b00 100644 --- a/engine/SCons/Node/Alias.py +++ b/engine/SCons/Node/Alias.py @@ -30,7 +30,7 @@ This creates a hash of global Aliases (dummy targets). # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # -__revision__ = "src/engine/SCons/Node/Alias.py rel_2.3.5:3347:d31d5a4e74b6 2015/07/31 14:36:10 bdbaddog" +__revision__ = "src/engine/SCons/Node/Alias.py rel_2.4.0:3365:9259ea1c13d7 2015/09/21 14:03:43 bdbaddog" import collections @@ -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/engine/SCons/Node/FS.py b/engine/SCons/Node/FS.py index 8d71f7e..b6a1bb5 100644 --- a/engine/SCons/Node/FS.py +++ b/engine/SCons/Node/FS.py @@ -32,7 +32,7 @@ that can be used by scripts or modules looking for the canonical default. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -__revision__ = "src/engine/SCons/Node/FS.py rel_2.3.5:3347:d31d5a4e74b6 2015/07/31 14:36:10 bdbaddog" +__revision__ = "src/engine/SCons/Node/FS.py rel_2.4.0:3365:9259ea1c13d7 2015/09/21 14:03:43 bdbaddog" import fnmatch import os @@ -56,10 +56,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 @@ -268,8 +281,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) @@ -302,7 +315,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) @@ -310,7 +323,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) @@ -403,7 +416,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 @@ -573,7 +586,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. @@ -591,27 +617,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__() + '"' @@ -624,17 +649,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.""" @@ -643,8 +689,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'] @@ -681,21 +726,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() @@ -717,7 +761,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: @@ -752,7 +796,7 @@ 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: @@ -785,7 +829,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, @@ -811,13 +874,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 @@ -856,8 +918,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'] @@ -878,6 +939,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 @@ -887,6 +959,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 @@ -917,7 +1011,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 @@ -939,17 +1033,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. @@ -989,10 +1073,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() @@ -1003,9 +1084,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) @@ -1019,9 +1097,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 @@ -1101,8 +1176,6 @@ class LocalFS(object): class FS(LocalFS): - memoizer_counters = [] - def __init__(self, path = None): """Initialize the Node.FS subsystem. @@ -1128,8 +1201,8 @@ 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 @@ -1160,7 +1233,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 @@ -1249,9 +1322,9 @@ class FS(LocalFS): # 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) @@ -1285,9 +1358,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) @@ -1393,7 +1466,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() @@ -1412,8 +1485,9 @@ class FS(LocalFS): 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 @@ -1425,11 +1499,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('[*?[]') @@ -1440,7 +1515,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 @@ -1470,6 +1560,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 @@ -1562,8 +1668,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']) @@ -1589,7 +1694,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): @@ -1598,8 +1703,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. """ @@ -1628,7 +1732,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: @@ -1643,10 +1747,10 @@ 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) @@ -1713,7 +1817,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: @@ -1753,10 +1857,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 @@ -1770,8 +1871,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.""" @@ -1795,12 +1894,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 @@ -1817,17 +1912,34 @@ 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 @@ -1841,7 +1953,7 @@ class Dir(Base): except AttributeError: d = {} try: - entries = os.listdir(self.abspath) + entries = os.listdir(self._abspath) except OSError: pass else: @@ -1854,7 +1966,7 @@ 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: @@ -1887,8 +1999,7 @@ class Dir(Base): break return rentry_exists - memoizer_counters.append(SCons.Memoize.CountValue('srcdir_list')) - + @SCons.Memoize.CountMethodCall def srcdir_list(self): try: return self._memo['srcdir_list'] @@ -1929,8 +2040,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'] @@ -2105,7 +2215,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) @@ -2151,18 +2261,12 @@ 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 == '': @@ -2178,33 +2282,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 @@ -2253,19 +2409,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: @@ -2283,7 +2439,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'] @@ -2298,11 +2455,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): """ @@ -2317,7 +2506,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: @@ -2358,6 +2547,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): @@ -2371,6 +2562,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) @@ -2378,7 +2571,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 @@ -2429,6 +2637,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 @@ -2440,22 +2656,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 '' - fname = self.rfile().abspath - try: - contents = open(fname, "rb").read() - except EnvironmentError, 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 @@ -2482,7 +2689,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) @@ -2492,9 +2699,7 @@ class File(Base): raise return cs - - memoizer_counters.append(SCons.Memoize.CountValue('get_size')) - + @SCons.Memoize.CountMethodCall def get_size(self): try: return self._memo['get_size'] @@ -2510,8 +2715,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'] @@ -2527,14 +2731,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', @@ -2647,8 +2843,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'] @@ -2688,8 +2883,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 @@ -2773,9 +2967,9 @@ 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 @@ -2875,7 +3069,7 @@ class File(Base): def _rmv_existing(self): self.clear_memoized_values() - if print_duplicate: + if SCons.Node.print_duplicate: print "dup: removing existing target %s"%self e = Unlink(self, [], None) if isinstance(e, SCons.Errors.BuildError): @@ -2911,18 +3105,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: + if SCons.Node.print_duplicate: print "dup: relinking variant '%s' from '%s'"%(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) + desc = "Cannot duplicate `%s' in `%s': %s." % (src.get_internal_path(), self.dir._path, e.errstr) raise SCons.Errors.StopError(desc) self.linked = 1 # The Link() action may or may not have actually @@ -2931,36 +3125,14 @@ 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 @@ -3037,7 +3209,7 @@ 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. @@ -3054,10 +3226,10 @@ class File(Base): if (not SCons.Node.interactive and not hasattr(self.attributes, 'keep_targetinfo')): # Ensure that the build infos get computed and cached... - self.store_info() + 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 @@ -3116,16 +3288,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) @@ -3143,7 +3305,7 @@ class File(Base): e = LocalCopy(self, r, None) if isinstance(e, SCons.Errors.BuildError): raise - self.store_info() + SCons.Node.store_info_map[self.store_info](self) if T: Trace(' 1\n') return 1 self.changed() @@ -3154,8 +3316,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'] @@ -3257,7 +3418,7 @@ class File(Base): # 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 @@ -3273,10 +3434,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 = {} @@ -3319,8 +3476,7 @@ 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] diff --git a/engine/SCons/Node/Python.py b/engine/SCons/Node/Python.py index 99773ab..b247ff6 100644 --- a/engine/SCons/Node/Python.py +++ b/engine/SCons/Node/Python.py @@ -27,20 +27,54 @@ Python nodes. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # -__revision__ = "src/engine/SCons/Node/Python.py rel_2.3.5:3347:d31d5a4e74b6 2015/07/31 14:36:10 bdbaddog" +__revision__ = "src/engine/SCons/Node/Python.py rel_2.4.0:3365:9259ea1c13d7 2015/09/21 14:03:43 bdbaddog" 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/engine/SCons/Node/__init__.py b/engine/SCons/Node/__init__.py index a261ee7..79db894 100644 --- a/engine/SCons/Node/__init__.py +++ b/engine/SCons/Node/__init__.py @@ -41,7 +41,7 @@ be able to depend on any other type of "thing." # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -__revision__ = "src/engine/SCons/Node/__init__.py rel_2.3.5:3347:d31d5a4e74b6 2015/07/31 14:36:10 bdbaddog" +__revision__ = "src/engine/SCons/Node/__init__.py rel_2.4.0:3365:9259ea1c13d7 2015/09/21 14:03:43 bdbaddog" import collections import copy @@ -55,6 +55,8 @@ import SCons.Util from SCons.Debug import Trace +print_duplicate = 0 + def classname(obj): return str(obj.__class__).split('.')[-1] @@ -105,6 +107,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, 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 +343,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 +364,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 +395,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 +437,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,7 +580,15 @@ 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() # Let the interface in which the build engine is embedded @@ -248,8 +602,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,7 +771,7 @@ 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 @@ -546,7 +899,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. @@ -706,7 +1059,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 +1070,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 +1155,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 +1192,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 \ @@ -941,11 +1289,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 +1323,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 +1363,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 +1370,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): """ @@ -1095,7 +1439,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 +1610,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: -- cgit v1.2.3