diff options
Diffstat (limited to 'engine/SCons/Node')
-rw-r--r-- | engine/SCons/Node/Alias.py | 4 | ||||
-rw-r--r-- | engine/SCons/Node/FS.py | 160 | ||||
-rw-r--r-- | engine/SCons/Node/Python.py | 4 | ||||
-rw-r--r-- | engine/SCons/Node/__init__.py | 110 |
4 files changed, 224 insertions, 54 deletions
diff --git a/engine/SCons/Node/Alias.py b/engine/SCons/Node/Alias.py index 02fe121..0b8f9b0 100644 --- a/engine/SCons/Node/Alias.py +++ b/engine/SCons/Node/Alias.py @@ -8,7 +8,7 @@ This creates a hash of global Aliases (dummy targets). """ # -# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 The SCons Foundation +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 The SCons Foundation # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the @@ -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 2013/03/03 09:48:35 garyo" +__revision__ = "src/engine/SCons/Node/Alias.py 2014/03/02 14:18:15 garyo" import collections diff --git a/engine/SCons/Node/FS.py b/engine/SCons/Node/FS.py index a21561f..f54b0ab 100644 --- a/engine/SCons/Node/FS.py +++ b/engine/SCons/Node/FS.py @@ -11,7 +11,7 @@ that can be used by scripts or modules looking for the canonical default. """ # -# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 The SCons Foundation +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 The SCons Foundation # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the @@ -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 2013/03/03 09:48:35 garyo" +__revision__ = "src/engine/SCons/Node/FS.py 2014/03/02 14:18:15 garyo" import fnmatch import os @@ -44,6 +44,7 @@ import time import codecs import SCons.Action +import SCons.Debug from SCons.Debug import logInstanceCreation import SCons.Errors import SCons.Memoize @@ -581,7 +582,7 @@ class Base(SCons.Node.Node): our relative and absolute paths, identify our parent directory, and indicate that this node should use signatures.""" - if __debug__: logInstanceCreation(self, 'Node.FS.Base') + if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.FS.Base') SCons.Node.Node.__init__(self) # Filenames and paths are probably reused and are intern'ed to @@ -1111,7 +1112,7 @@ class FS(LocalFS): The path argument must be a valid absolute path. """ - if __debug__: logInstanceCreation(self, 'Node.FS') + if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.FS') self._memo = {} @@ -1445,7 +1446,7 @@ class Dir(Base): BuildInfo = DirBuildInfo def __init__(self, name, directory, fs): - if __debug__: logInstanceCreation(self, 'Node.FS.Dir') + if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.FS.Dir') Base.__init__(self, name, directory, fs) self._morph() @@ -1841,7 +1842,7 @@ class Dir(Base): for entry in map(_my_normcase, entries): d[entry] = True self.on_disk_entries = d - if sys.platform == 'win32': + if sys.platform == 'win32' or sys.platform == 'cygwin': name = _my_normcase(name) result = d.get(name) if result is None: @@ -2113,7 +2114,7 @@ class RootDir(Dir): this directory. """ def __init__(self, drive, fs): - if __debug__: logInstanceCreation(self, 'Node.FS.RootDir') + 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. @@ -2361,7 +2362,7 @@ class File(Base): "Directory %s found where file expected.") def __init__(self, name, directory, fs): - if __debug__: logInstanceCreation(self, 'Node.FS.File') + if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.FS.File') Base.__init__(self, name, directory, fs) self._morph() @@ -2397,6 +2398,8 @@ class File(Base): self.scanner_paths = {} if not hasattr(self, '_local'): self._local = 0 + if not hasattr(self, 'released_target_info'): + self.released_target_info = False # If there was already a Builder set on this entry, then # we need to make sure we call the target-decider function, @@ -2724,7 +2727,7 @@ class File(Base): return self.get_build_env().get_CacheDir().retrieve(self) def visited(self): - if self.exists(): + if self.exists() and self.executor is not None: self.get_build_env().get_CacheDir().push_if_forced(self) ninfo = self.get_ninfo() @@ -2746,6 +2749,58 @@ class File(Base): self.store_info() + 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 + the source files for a built *.o file are used to figure out + 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. + + 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... + self.changed(allowcache=True) + self.get_contents_sig() + self.get_build_env() + # Now purge unneeded stuff to free memory... + self.executor = None + self._memo.pop('rfile', None) + self.prerequisites = None + # Cleanup lists, but only if they're empty + if not len(self.ignore_set): + self.ignore_set = None + if not len(self.implicit_set): + self.implicit_set = None + if not len(self.depends_set): + self.depends_set = None + if not len(self.ignore): + self.ignore = None + if not len(self.depends): + self.depends = None + # Mark this node as done, we only have to release + # the memory once... + self.released_target_info = True + def find_src_builder(self): if self.rexists(): return None @@ -2956,6 +3011,52 @@ class File(Base): SCons.Node.Node.builder_set(self, builder) self.changed_since_last_build = self.decide_target + 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 + not hasattr(self.attributes, 'keep_targetinfo')): + # Ensure that the build infos get computed and cached... + self.store_info() + # ... then release some more variables. + self._specific_sources = False + 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. + + 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: + try: + return self._memo['changed'] + except KeyError: + pass + + has_changed = SCons.Node.Node.changed(self, node) + if allowcache: + self._memo['changed'] = has_changed + return has_changed + def changed_content(self, target, prev_ni): cur_csig = self.get_csig() try: @@ -3089,25 +3190,50 @@ class File(Base): self.cachedir_csig = self.get_csig() return self.cachedir_csig + def get_contents_sig(self): + """ + A helper method for get_cachedir_bsig. + + 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()) + return result + def get_cachedir_bsig(self): + """ + Return the signature for a cached file, including + its children. + + It adds the path of the cached file to the cache signature, + because multiple targets built by the same action will all + have the same build signature, and we have to differentiate + them somehow. + """ try: return self.cachesig except AttributeError: pass - - # Add the path to the cache signature, because multiple - # targets built by the same action will all have the same - # build signature, and we have to differentiate them somehow. + + # Collect signatures for all children children = self.children() - executor = self.get_executor() - # sigs = [n.get_cachedir_csig() for n in children] sigs = [n.get_cachedir_csig() for n in children] - sigs.append(SCons.Util.MD5signature(executor.get_contents())) + # Append this node's signature... + sigs.append(self.get_contents_sig()) + # ...and it's path sigs.append(self.path) + # Merge this all into a single signature result = self.cachesig = SCons.Util.MD5collect(sigs) return result - default_fs = None def get_default_fs(): diff --git a/engine/SCons/Node/Python.py b/engine/SCons/Node/Python.py index 153e32d..8936b6d 100644 --- a/engine/SCons/Node/Python.py +++ b/engine/SCons/Node/Python.py @@ -5,7 +5,7 @@ Python nodes. """ # -# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 The SCons Foundation +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 The SCons Foundation # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the @@ -27,7 +27,7 @@ Python nodes. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # -__revision__ = "src/engine/SCons/Node/Python.py 2013/03/03 09:48:35 garyo" +__revision__ = "src/engine/SCons/Node/Python.py 2014/03/02 14:18:15 garyo" import SCons.Node diff --git a/engine/SCons/Node/__init__.py b/engine/SCons/Node/__init__.py index ece4a5a..e6a3001 100644 --- a/engine/SCons/Node/__init__.py +++ b/engine/SCons/Node/__init__.py @@ -20,7 +20,7 @@ be able to depend on any other type of "thing." """ # -# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 The SCons Foundation +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 The SCons Foundation # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the @@ -41,12 +41,13 @@ 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 2013/03/03 09:48:35 garyo" +__revision__ = "src/engine/SCons/Node/__init__.py 2014/03/02 14:18:15 garyo" import collections import copy from itertools import chain +import SCons.Debug from SCons.Debug import logInstanceCreation import SCons.Executor import SCons.Memoize @@ -57,6 +58,10 @@ from SCons.Debug import Trace def classname(obj): return str(obj.__class__).split('.')[-1] +# Set to false if we're doing a dry run. There's more than one of these +# little treats +do_store_info = True + # Node states # # These are in "priority" order, so that the maximum value for any @@ -95,6 +100,11 @@ def do_nothing(node): pass Annotate = do_nothing +# Gets set to 'True' if we're running in interactive mode. Is +# currently used to release parts of a target's info during +# clean builds and update runs (see release_target_info). +interactive = False + # Classes for signature info for Nodes. class NodeInfoBase(object): @@ -183,7 +193,7 @@ class Node(object): pass def __init__(self): - if __debug__: logInstanceCreation(self, 'Node.Node') + if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.Node') # Note that we no longer explicitly initialize a self.builder # attribute to None here. That's because the self.builder # attribute may be created on-the-fly later by a subclass (the @@ -204,7 +214,7 @@ class Node(object): self.depends_set = set() self.ignore = [] # dependencies to ignore self.ignore_set = set() - self.prerequisites = SCons.Util.UniqueList() + self.prerequisites = None self.implicit = None # implicit (scanned) dependencies (None means not scanned yet) self.waiting_parents = set() self.waiting_s_e = set() @@ -214,6 +224,7 @@ class Node(object): self.env = None self.state = no_state self.precious = None + self.pseudo = False self.noclean = 0 self.nocache = 0 self.cached = 0 # is this node pulled from cache? @@ -286,7 +297,8 @@ class Node(object): except AttributeError: pass else: - executor.cleanup() + if executor is not None: + executor.cleanup() def reset_executor(self): "Remove cached executor; forces recompute when needed." @@ -346,10 +358,11 @@ class Node(object): methods should call this base class method to get the child check and the BuildInfo structure. """ - for d in self.depends: - if d.missing(): - msg = "Explicit dependency `%s' not found, needed by target `%s'." - raise SCons.Errors.StopError(msg % (d, self)) + if self.depends is not None: + for d in self.depends: + if d.missing(): + msg = "Explicit dependency `%s' not found, needed by target `%s'." + raise SCons.Errors.StopError(msg % (d, self)) if self.implicit is not None: for i in self.implicit: if i.missing(): @@ -385,6 +398,13 @@ class Node(object): self.clear() + if self.pseudo: + if self.exists(): + raise SCons.Errors.UserError("Pseudo target " + str(self) + " must not exist") + else: + if not self.exists() and do_store_info: + SCons.Warnings.warn(SCons.Warnings.TargetNotBuiltWarning, + "Cannot find target " + str(self) + " after building") self.ninfo.update(self) def visited(self): @@ -400,6 +420,23 @@ class Node(object): self.ninfo.update(self) self.store_info() + 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 + # # # @@ -501,7 +538,7 @@ class Node(object): def is_derived(self): """ - Returns true iff this node is derived (i.e. built). + Returns true if this node is derived (i.e. built). This should return true only for nodes whose path should be in the variant directory when duplicate=0 and should contribute their build @@ -788,6 +825,10 @@ class Node(object): """Set the Node's precious value.""" self.precious = precious + def set_pseudo(self, pseudo = True): + """Set the Node's precious value.""" + self.pseudo = pseudo + def set_noclean(self, noclean = 1): """Set the Node's noclean value.""" # Make sure noclean is an integer so the --debug=stree @@ -837,6 +878,8 @@ class Node(object): def add_prerequisite(self, prerequisite): """Adds prerequisites""" + if self.prerequisites is None: + self.prerequisites = SCons.Util.UniqueList() self.prerequisites.extend(prerequisite) self._children_reset() @@ -924,20 +967,14 @@ class Node(object): # dictionary patterns I found all ended up using "not in" # internally anyway...) if self.ignore_set: - if self.implicit is None: - iter = chain(self.sources,self.depends) - else: - iter = chain(self.sources, self.depends, self.implicit) + iter = chain.from_iterable(filter(None, [self.sources, self.depends, self.implicit])) children = [] for i in iter: if i not in self.ignore_set: children.append(i) else: - if self.implicit is None: - children = self.sources + self.depends - else: - children = self.sources + self.depends + self.implicit + children = self.all_children(scan=0) self._memo['children_get'] = children return children @@ -964,10 +1001,7 @@ class Node(object): # using dictionary keys, lose the order, and the only ordered # dictionary patterns I found all ended up using "not in" # internally anyway...) - if self.implicit is None: - return self.sources + self.depends - else: - return self.sources + self.depends + self.implicit + return list(chain.from_iterable(filter(None, [self.sources, self.depends, self.implicit]))) def children(self, scan=1): """Return a list of the node's direct children, minus those @@ -1015,7 +1049,7 @@ class Node(object): def Decider(self, function): SCons.Util.AddMethod(self, function, 'changed_since_last_build') - def changed(self, node=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. The default behavior is to compare @@ -1028,6 +1062,15 @@ 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 if t: Trace('changed(%s [%s], %s)' % (self, classname(self), node)) @@ -1103,17 +1146,18 @@ class Node(object): Return a text representation, suitable for displaying to the user, of the include tree for the sources of this node. """ - if self.is_derived() and self.env: + if self.is_derived(): env = self.get_build_env() - for s in self.sources: - scanner = self.get_source_scanner(s) - if scanner: - path = self.get_build_scanner_path(scanner) - else: - path = None - def f(node, env=env, scanner=scanner, path=path): - return node.get_found_includes(env, scanner, path) - return SCons.Util.render_tree(s, f, 1) + if env: + for s in self.sources: + scanner = self.get_source_scanner(s) + if scanner: + path = self.get_build_scanner_path(scanner) + else: + path = None + def f(node, env=env, scanner=scanner, path=path): + return node.get_found_includes(env, scanner, path) + return SCons.Util.render_tree(s, f, 1) else: return None |