summaryrefslogtreecommitdiff
path: root/engine/SCons/Node
diff options
context:
space:
mode:
Diffstat (limited to 'engine/SCons/Node')
-rw-r--r--engine/SCons/Node/Alias.py4
-rw-r--r--engine/SCons/Node/FS.py160
-rw-r--r--engine/SCons/Node/Python.py4
-rw-r--r--engine/SCons/Node/__init__.py110
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