# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
+from __future__ import generators ### KEEP FOR COMPATIBILITY FIXERS
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
-
-
import copy
+from itertools import chain, izip
+import UserList
from SCons.Debug import logInstanceCreation
-import SCons.Sig
+import SCons.Executor
+import SCons.Memoize
import SCons.Util
+from SCons.Debug import Trace
+
+def classname(obj):
+ return str(obj.__class__).split('.')[-1]
+
# Node states
#
# These are in "priority" order, so that the maximum value for any
# it has no builder of its own. The canonical example is a file
# system directory, which is only up to date if all of its children
# were up to date.
+no_state = 0
pending = 1
executing = 2
up_to_date = 3
executed = 4
failed = 5
-stack = 6 # nodes that are in the current Taskmaster execution stack
-# controls whether implicit depedencies are cached:
+StateString = {
+ 0 : "no_state",
+ 1 : "pending",
+ 2 : "executing",
+ 3 : "up_to_date",
+ 4 : "executed",
+ 5 : "failed",
+}
+
+# controls whether implicit dependencies are cached:
implicit_cache = 0
# controls whether implicit dep changes are ignored:
Annotate = do_nothing
+# Classes for signature info for Nodes.
+
+class NodeInfoBase:
+ """
+ The generic base class for signature information for a Node.
+
+ 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):
+ # 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
+ def update(self, node):
+ try:
+ field_list = self.field_list
+ except AttributeError:
+ return
+ for f in field_list:
+ try:
+ delattr(self, f)
+ except AttributeError:
+ pass
+ try:
+ func = getattr(node, 'get_' + f)
+ except AttributeError:
+ pass
+ else:
+ setattr(self, f, func())
+ def convert(self, node, val):
+ pass
+ def merge(self, other):
+ self.__dict__.update(other.__dict__)
+ 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())
+ fields = []
+ for field in field_list:
+ try:
+ f = getattr(self, field)
+ except AttributeError:
+ f = None
+ f = str(f)
+ if names:
+ f = field + ': ' + f
+ fields.append(f)
+ return fields
+
+class BuildInfoBase:
+ """
+ The generic base class for build information for a Node.
+
+ This is what gets stored in a .sconsign file for each target file.
+ It contains a NodeInfo instance for this node (signature information
+ that's specific to the type of Node) and direct attributes for the
+ generic build stuff we have to track: sources, explicit dependencies,
+ implicit dependencies, and action information.
+ """
+ current_version_id = 1
+ def __init__(self, node):
+ # 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__)
+
class Node:
"""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 = []
+
class Attrs:
pass
def __init__(self):
- if __debug__: logInstanceCreation(self, 'Node')
+ if __debug__: 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
# a class. (Of course, we could always still do that in the
# future if we had a good reason to...).
self.sources = [] # source files used to build node
- self.sources_dict = {}
+ self.sources_set = set()
+ self._specific_sources = False
self.depends = [] # explicit dependencies (from Depends)
- self.depends_dict = {}
+ self.depends_set = set()
self.ignore = [] # dependencies to ignore
- self.ignore_dict = {}
+ self.ignore_set = set()
+ self.prerequisites = SCons.Util.UniqueList()
self.implicit = None # implicit (scanned) dependencies (None means not scanned yet)
- self.parents = {}
+ self.waiting_parents = set()
+ self.waiting_s_e = set()
+ self.ref_count = 0
self.wkids = None # Kids yet to walk, when it's an array
- self.target_scanner = None # explicit scanner from this node's Builder
- self.source_scanner = None # source scanner
self.env = None
- self.state = None
+ self.state = no_state
self.precious = None
+ self.noclean = 0
+ self.nocache = 0
self.always_build = None
- self.found_includes = {}
self.includes = None
- self.overrides = {} # construction variable overrides for building this node
self.attributes = self.Attrs() # Generic place to stick information about the Node.
self.side_effect = 0 # true iff this node is a side effect
self.side_effects = [] # the side effects of building this target
- self.pre_actions = []
- self.post_actions = []
- self.linked = 0 # is this node linked to the build directory?
+ self.linked = 0 # is this node linked to the variant directory?
+
+ self.clear_memoized_values()
# Let the interface in which the build engine is embedded
# annotate this Node with its own info (like a description of
# what line in what file created the node, for example).
Annotate(self)
- def generate_build_env(self, env):
- """Generate the appropriate Environment to build this node."""
- return env
+ def disambiguate(self, must_exist=None):
+ return self
+
+ def get_suffix(self):
+ return ''
+
+ memoizer_counters.append(SCons.Memoize.CountValue('get_build_env'))
def get_build_env(self):
- """Fetch the appropriate Environment to build this node."""
- executor = self.get_executor()
- return executor.get_build_env()
+ """Fetch the appropriate Environment to build this node.
+ """
+ try:
+ return self._memo['get_build_env']
+ except KeyError:
+ pass
+ result = self.get_executor().get_build_env()
+ self._memo['get_build_env'] = result
+ return result
+
+ def get_build_scanner_path(self, scanner):
+ """Fetch the appropriate scanner path for this node."""
+ return self.get_executor().get_build_scanner_path(scanner)
def set_executor(self, executor):
"""Set the action executor for this node."""
except AttributeError:
if not create:
raise
- import SCons.Executor
- env = self.generate_build_env(self.builder.env)
- executor = SCons.Executor.Executor(self.builder,
- env,
- self.builder.overrides,
- [self],
- self.sources)
+ try:
+ act = self.builder.action
+ except AttributeError:
+ executor = SCons.Executor.Null(targets=[self])
+ else:
+ executor = SCons.Executor.Executor(act,
+ self.env or self.builder.env,
+ [self.builder.overrides],
+ [self],
+ self.sources)
self.executor = executor
return executor
- def _for_each_action(self, func):
- """Call a function for each action required to build a node.
+ def executor_cleanup(self):
+ """Let the executor clean up any cached information."""
+ try:
+ executor = self.get_executor(create=None)
+ except AttributeError:
+ pass
+ else:
+ executor.cleanup()
- The purpose here is to have one place for the logic that
- collects and executes all of the actions for a node's builder,
- even though multiple sections of code elsewhere need this logic
- to do different things."""
- if not self.has_builder():
- return
- executor = self.get_executor()
- executor(self, func)
+ def reset_executor(self):
+ "Remove cached executor; forces recompute when needed."
+ try:
+ delattr(self, 'executor')
+ except AttributeError:
+ pass
+
+ def push_to_cache(self):
+ """Try to push a node into a cache
+ """
+ pass
def retrieve_from_cache(self):
"""Try to retrieve the node's content from a cache
Returns true iff the node was successfully retrieved.
"""
return 0
-
- def build(self):
+
+ #
+ # Taskmaster interface subsystem
+ #
+
+ def make_ready(self):
+ """Get a Node ready for evaluation.
+
+ This is called before the Taskmaster decides if the Node is
+ up-to-date or not. Overriding this method allows for a Node
+ subclass to be disambiguated if necessary, or for an implicit
+ source builder to be attached.
+ """
+ pass
+
+ def prepare(self):
+ """Prepare for this Node to be built.
+
+ This is called after the Taskmaster has decided that the Node
+ is out-of-date and must be rebuilt, but before actually calling
+ the method to build the Node.
+
+ This default implementation checks that explicit or implicit
+ dependencies either exist or are derived, and initializes the
+ BuildInfo structure that will hold the information about how
+ this node is, uh, built.
+
+ (The existence of source files is checked separately by the
+ Executor, which aggregates checks for all of the targets built
+ by a specific action.)
+
+ Overriding this method allows for for a Node subclass to remove
+ the underlying file from the file system. Note that subclass
+ 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.implicit is not None:
+ for i in self.implicit:
+ if i.missing():
+ msg = "Implicit dependency `%s' not found, needed by target `%s'."
+ raise SCons.Errors.StopError, msg % (i, self)
+ self.binfo = self.get_binfo()
+
+ def build(self, **kw):
"""Actually build the node.
+ This is called by the Taskmaster after it's decided that the
+ Node is out-of-date and must be rebuilt, and after the prepare()
+ method has gotten everything, uh, prepared.
+
This method is called from multiple threads in a parallel build,
- so only do thread safe stuff here. Do thread unsafe stuff in
- built().
+ so only do thread safe stuff here. Do thread unsafe stuff
+ in built().
+
"""
- def do_action(action, targets, sources, env, self=self):
- stat = action(targets, sources, env)
- if stat:
- raise SCons.Errors.BuildError(node = self,
- errstr = "Error %d" % stat)
- self._for_each_action(do_action)
+ try:
+ self.get_executor()(self, **kw)
+ except SCons.Errors.BuildError, e:
+ e.node = self
+ raise
def built(self):
- """Called just after this node is sucessfully built."""
- self.store_bsig()
-
- # Clear out the implicit dependency caches:
- # XXX this really should somehow be made more general and put
- # under the control of the scanners.
- if self.source_scanner:
- self.found_includes = {}
- self.includes = None
-
- def get_parents(node, parent): return node.get_parents()
- def clear_cache(node, parent):
- node.implicit = None
- node.del_bsig()
- w = Walker(self, get_parents, ignore_cycle, clear_cache)
- while w.next(): pass
-
- # clear out the content signature, since the contents of this
- # node were presumably just changed:
- self.del_csig()
+ """Called just after this node is successfully built."""
+
+ # Clear the implicit dependency caches of any Nodes
+ # waiting for this Node to be built.
+ for parent in self.waiting_parents:
+ parent.implicit = None
+
+ self.clear()
+
+ self.ninfo.update(self)
+
+ def visited(self):
+ """Called just after this node has been visited (with or
+ without a build)."""
+ try:
+ binfo = self.binfo
+ except AttributeError:
+ # Apparently this node doesn't need build info, so
+ # don't bother calculating or storing it.
+ pass
+ else:
+ self.ninfo.update(self)
+ self.store_info()
+
+ #
+ #
+ #
+
+ def add_to_waiting_s_e(self, node):
+ self.waiting_s_e.add(node)
+
+ def add_to_waiting_parents(self, node):
+ """
+ Returns the number of nodes added to our waiting parents list:
+ 1 if we add a unique waiting parent, 0 if not. (Note that the
+ returned values are intended to be used to increment a reference
+ count, so don't think you can "clean up" this function by using
+ True and False instead...)
+ """
+ wp = self.waiting_parents
+ if node in wp:
+ return 0
+ wp.add(node)
+ return 1
+
+ def postprocess(self):
+ """Clean up anything we don't need to hang onto after we've
+ been built."""
+ self.executor_cleanup()
+ self.waiting_parents = set()
def clear(self):
"""Completely clear a Node of all its cached state (so that it
can be re-evaluated by interfaces that do continuous integration
builds).
"""
- self.set_state(None)
- self.del_bsig()
- self.del_csig()
+ # The del_binfo() call here isn't necessary for normal execution,
+ # but is for interactive mode, where we might rebuild the same
+ # target and need to start from scratch.
+ self.del_binfo()
+ self.clear_memoized_values()
+ self.ninfo = self.new_ninfo()
+ self.executor_cleanup()
try:
delattr(self, '_calculated_sig')
except AttributeError:
pass
- try:
- delattr(self, '_tempbsig')
- except AttributeError:
- pass
self.includes = None
- self.found_includes = {}
- self.implicit = None
-
- def visited(self):
- """Called just after this node has been visited
- without requiring a build.."""
- pass
- def depends_on(self, nodes):
- """Does this node depend on any of 'nodes'?"""
- for node in nodes:
- if node in self.children():
- return 1
-
- return 0
+ def clear_memoized_values(self):
+ self._memo = {}
def builder_set(self, builder):
self.builder = builder
+ try:
+ del self.executor
+ except AttributeError:
+ pass
def has_builder(self):
"""Return whether this Node has a builder or not.
except AttributeError:
# There was no explicit builder for this Node, so initialize
# the self.builder attribute to None now.
- self.builder = None
- b = self.builder
- return not b is None
+ b = self.builder = None
+ return b is not None
+
+ def set_explicit(self, is_explicit):
+ self.is_explicit = is_explicit
+
+ def has_explicit_builder(self):
+ """Return whether this Node has an explicit builder
+
+ This allows an internal Builder created by SCons to be marked
+ non-explicit, so that it can be overridden by an explicit
+ builder that the user supplies (the canonical example being
+ directories)."""
+ try:
+ return self.is_explicit
+ except AttributeError:
+ self.is_explicit = None
+ return self.is_explicit
+
+ def get_builder(self, default_builder=None):
+ """Return the set builder, or a specified default value"""
+ try:
+ return self.builder
+ except AttributeError:
+ return default_builder
+
+ multiple_side_effect_has_builder = has_builder
def is_derived(self):
"""
Returns true iff this node is derived (i.e. built).
This should return true only for nodes whose path should be in
- the build directory when duplicate=0 and should contribute their build
+ the variant directory when duplicate=0 and should contribute their build
signatures when they are used as source files to other derived files. For
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
- def is_pseudo_derived(self):
- """
- Returns true iff this node is built, but should use a source path
- when duplicate=0 and should contribute a content signature (i.e.
- source signature) when used as a source for other derived files.
- """
- return 0
-
def alter_targets(self):
"""Return a list of alternate targets for this Node.
"""
return [], None
- def get_found_includes(self, env, scanner, target):
+ def get_found_includes(self, env, scanner, path):
"""Return the scanned include lines (implicit dependencies)
found in this node.
"""
return []
- def get_implicit_deps(self, env, scanner, target):
+ def get_implicit_deps(self, env, scanner, path):
"""Return a list of implicit dependencies for this node.
This method exists to handle recursive invocation of the scanner
if not scanner:
return []
- try:
- recurse = scanner.recursive
- except AttributeError:
- recurse = None
+ # Give the scanner a chance to select a more specific scanner
+ # for this Node.
+ #scanner = scanner.select(self)
nodes = [self]
seen = {}
seen[self] = 1
deps = []
while nodes:
- n = nodes.pop(0)
- d = filter(lambda x, seen=seen: not seen.has_key(x),
- n.get_found_includes(env, scanner, target))
- if d:
- deps.extend(d)
- for n in d:
- seen[n] = 1
- if recurse:
- nodes.extend(d)
+ n = nodes.pop(0)
+ d = [x for x in n.get_found_includes(env, scanner, path) if x not in seen]
+ if d:
+ deps.extend(d)
+ for n in d:
+ seen[n] = 1
+ nodes.extend(scanner.recurse_nodes(d))
return deps
- # cache used to make implicit_factory fast.
- implicit_factory_cache = {}
-
- def implicit_factory(self, path):
- """
- Turn a cache implicit dependency path into a node.
- This is called so many times that doing caching
- here is a significant perforamnce boost.
+ def get_env_scanner(self, env, kw={}):
+ return env.get_scanner(self.scanner_key())
+
+ def get_target_scanner(self):
+ return self.builder.target_scanner
+
+ def get_source_scanner(self, node):
+ """Fetch the source scanner for the specified node
+
+ NOTE: "self" is the target being built, "node" is
+ the source file for which we want to fetch the scanner.
+
+ Implies self.has_builder() is true; again, expect to only be
+ called from locations where this is already verified.
+
+ This function may be called very often; it attempts to cache
+ the scanner found to improve performance.
"""
+ scanner = None
try:
- return self.implicit_factory_cache[path]
- except KeyError:
- n = self.builder.source_factory(path)
- self.implicit_factory_cache[path] = n
- return n
+ scanner = self.builder.source_scanner
+ except AttributeError:
+ pass
+ if not scanner:
+ # The builder didn't have an explicit scanner, so go look up
+ # a scanner from env['SCANNERS'] based on the node's scanner
+ # key (usually the file extension).
+ scanner = self.get_env_scanner(self.get_build_env())
+ if scanner:
+ scanner = scanner.select(node)
+ return scanner
+
+ def add_to_implicit(self, deps):
+ if not hasattr(self, 'implicit') or self.implicit is None:
+ self.implicit = []
+ self.implicit_set = set()
+ self._children_reset()
+ self._add_child(self.implicit, self.implicit_set, deps)
def scan(self):
"""Scan this node's dependents for implicit dependencies."""
# Don't bother scanning non-derived files, because we don't
# care what their dependencies are.
# Don't scan again, if we already have scanned.
- if not self.implicit is None:
+ if self.implicit is not None:
return
self.implicit = []
- self.implicit_dict = {}
+ self.implicit_set = set()
self._children_reset()
if not self.has_builder():
return
+ build_env = self.get_build_env()
+ executor = self.get_executor()
+
+ # Here's where we implement --implicit-cache.
if implicit_cache and not implicit_deps_changed:
implicit = self.get_stored_implicit()
if implicit is not None:
- implicit = map(self.implicit_factory, implicit)
- self._add_child(self.implicit, self.implicit_dict, implicit)
- calc = SCons.Sig.default_calc
- if implicit_deps_unchanged or calc.current(self, calc.bsig(self)):
+ # We now add the implicit dependencies returned from the
+ # stored .sconsign entry to have already been converted
+ # to Nodes for us. (We used to run them through a
+ # source_factory function here.)
+
+ # Update all of the targets with them. This
+ # essentially short-circuits an N*M scan of the
+ # sources for each individual target, which is a hell
+ # of a lot more efficient.
+ for tgt in executor.get_all_targets():
+ tgt.add_to_implicit(implicit)
+
+ if implicit_deps_unchanged or self.is_up_to_date():
return
- else:
- # one of this node's sources has changed, so
- # we need to recalculate the implicit deps,
- # and the bsig:
- self.implicit = []
- self.implicit_dict = {}
- self._children_reset()
- self.del_bsig()
+ # one of this node's sources has changed,
+ # so we must recalculate the implicit deps:
+ self.implicit = []
+ self.implicit_set = set()
- build_env = self.get_build_env()
+ # Have the executor scan the sources.
+ executor.scan_sources(self.builder.source_scanner)
- for child in self.children(scan=0):
- scanner = child.source_scanner
- if scanner:
- self._add_child(self.implicit,
- self.implicit_dict,
- child.get_implicit_deps(build_env,
- scanner,
- self))
-
- # scan this node itself for implicit dependencies
- self._add_child(self.implicit,
- self.implicit_dict,
- self.get_implicit_deps(build_env,
- self.target_scanner,
- self))
-
- if implicit_cache:
- self.store_implicit()
+ # If there's a target scanner, have the executor scan the target
+ # node itself and associated targets that might be built.
+ scanner = self.get_target_scanner()
+ if scanner:
+ executor.scan_targets(scanner)
def scanner_key(self):
return None
+ def select_scanner(self, scanner):
+ """Selects a scanner for this Node.
+
+ This is a separate method so it can be overridden by Node
+ subclasses (specifically, Node.FS.Dir) that *must* use their
+ own Scanner and don't select one the Scanner.Selector that's
+ configured for the target.
+ """
+ return scanner.select(self)
+
def env_set(self, env, safe=0):
if safe and self.env:
return
self.env = env
- def calculator(self):
- import SCons.Defaults
-
- env = self.env or SCons.Defaults.DefaultEnvironment()
- return env.get_calculator()
+ #
+ # SIGNATURE SUBSYSTEM
+ #
- def calc_signature(self, calc):
- """
- Select and calculate the appropriate build signature for a node.
+ NodeInfo = NodeInfoBase
+ BuildInfo = BuildInfoBase
- self - the node
- calc - the signature calculation module
- returns - the signature
- """
+ def new_ninfo(self):
+ ninfo = self.NodeInfo(self)
+ return ninfo
+
+ def get_ninfo(self):
try:
- return self._calculated_sig
+ return self.ninfo
except AttributeError:
- if self.is_derived():
- import SCons.Defaults
-
- env = self.env or SCons.Defaults.DefaultEnvironment()
- if env.use_build_signature():
- sig = self.rfile().calc_bsig(calc, self)
- else:
- sig = self.rfile().calc_csig(calc, self)
- elif not self.rexists():
- sig = None
- else:
- sig = self.rfile().calc_csig(calc, self)
- self._calculated_sig = sig
- return sig
+ self.ninfo = self.new_ninfo()
+ return self.ninfo
- def calc_bsig(self, calc, cache=None):
- """Return the node's build signature, calculating it first
- if necessary.
+ def new_binfo(self):
+ binfo = self.BuildInfo(self)
+ return binfo
- Note that we don't save it in the "real" build signature
- attribute if we have to calculate it here; the "real" build
- signature only gets updated after a file is actually built.
+ def get_binfo(self):
"""
- if cache is None: cache = self
- try:
- return cache.bsig
- except AttributeError:
- try:
- return cache._tempbsig
- except AttributeError:
- cache._tempbsig = calc.bsig(self, cache)
- return cache._tempbsig
+ Fetch a node's build information.
- def get_bsig(self):
- """Get the node's build signature (based on the signatures
- of its dependency files and build information)."""
- try:
- return self.bsig
- except AttributeError:
- return None
+ node - the node whose sources will be collected
+ cache - alternate node to use for the signature cache
+ returns - the build signature
- def set_bsig(self, bsig):
- """Set the node's build signature (based on the signatures
- of its dependency files and build information)."""
- self.bsig = bsig
+ This no longer handles the recursive descent of the
+ node's children's signatures. We expect that they're
+ already built and updated by someone else, if that's
+ what's wanted.
+ """
try:
- delattr(self, '_tempbsig')
+ return self.binfo
except AttributeError:
pass
- def store_bsig(self):
- """Make the build signature permanent (that is, store it in the
- .sconsign file or equivalent)."""
- pass
+ binfo = self.new_binfo()
+ self.binfo = binfo
+
+ executor = self.get_executor()
+ ignore_set = self.ignore_set
- def del_bsig(self):
- """Delete the bsig from this node."""
+ if self.has_builder():
+ binfo.bact = str(executor)
+ binfo.bactsig = SCons.Util.MD5signature(executor.get_contents())
+
+ if self._specific_sources:
+ sources = []
+ for s in self.sources:
+ if s not in ignore_set:
+ sources.append(s)
+ else:
+ sources = executor.get_unignored_sources(self, self.ignore)
+ seen = set()
+ bsources = []
+ bsourcesigs = []
+ for s in sources:
+ if not s in seen:
+ seen.add(s)
+ bsources.append(s)
+ bsourcesigs.append(s.get_ninfo())
+ binfo.bsources = bsources
+ binfo.bsourcesigs = bsourcesigs
+
+ depends = self.depends
+ dependsigs = []
+ for d in depends:
+ if d not in ignore_set:
+ dependsigs.append(d.get_ninfo())
+ binfo.bdepends = depends
+ binfo.bdependsigs = dependsigs
+
+ implicit = self.implicit or []
+ implicitsigs = []
+ for i in implicit:
+ if i not in ignore_set:
+ implicitsigs.append(i.get_ninfo())
+ binfo.bimplicit = implicit
+ binfo.bimplicitsigs = implicitsigs
+
+ return binfo
+
+ def del_binfo(self):
+ """Delete the build info from this node."""
try:
- delattr(self, 'bsig')
+ delattr(self, 'binfo')
except AttributeError:
pass
def get_csig(self):
- """Get the signature of the node's content."""
- try:
- return self.csig
- except AttributeError:
- return None
-
- def calc_csig(self, calc, cache=None):
- """Return the node's content signature, calculating it first
- if necessary.
- """
- if cache is None: cache = self
try:
- return cache.csig
+ return self.ninfo.csig
except AttributeError:
- cache.csig = calc.csig(self, cache)
- return cache.csig
+ ninfo = self.get_ninfo()
+ ninfo.csig = SCons.Util.MD5signature(self.get_contents())
+ return self.ninfo.csig
- def set_csig(self, csig):
- """Set the signature of the node's content."""
- self.csig = csig
+ def get_cachedir_csig(self):
+ return self.get_csig()
- def store_csig(self):
- """Make the content signature permanent (that is, store it in the
+ def store_info(self):
+ """Make the build signature permanent (that is, store it in the
.sconsign file or equivalent)."""
pass
- def del_csig(self):
- """Delete the csig from this node."""
- try:
- delattr(self, 'csig')
- except AttributeError:
- pass
-
- def get_prevsiginfo(self):
- """Fetch the previous signature information from the
- .sconsign entry."""
- return SCons.Sig._SConsign.null_siginfo
-
- def get_timestamp(self):
- return 0
-
- def store_timestamp(self):
- """Make the timestamp permanent (that is, store it in the
- .sconsign file or equivalent)."""
+ def do_not_store_info(self):
pass
- def store_implicit(self):
- """Make the implicit deps permanent (that is, store them in the
- .sconsign file or equivalent)."""
- pass
+ def get_stored_info(self):
+ return None
def get_stored_implicit(self):
"""Fetch the stored implicit dependencies"""
return None
+ #
+ #
+ #
+
def set_precious(self, precious = 1):
"""Set the Node's precious value."""
self.precious = precious
+ def set_noclean(self, noclean = 1):
+ """Set the Node's noclean value."""
+ # Make sure noclean is an integer so the --debug=stree
+ # output in Util.py can use it as an index.
+ self.noclean = noclean and 1 or 0
+
+ def set_nocache(self, nocache = 1):
+ """Set the Node's nocache value."""
+ # Make sure nocache is an integer so the --debug=stree
+ # output in Util.py can use it as an index.
+ self.nocache = nocache and 1 or 0
+
def set_always_build(self, always_build = 1):
"""Set the Node's always_build value."""
self.always_build = always_build
"""Does this node exists?"""
# All node exist by default:
return 1
-
+
def rexists(self):
"""Does this node exist locally or in a repositiory?"""
# There are no repositories by default:
return self.exists()
-
- def prepare(self):
- """Prepare for this Node to be created.
- The default implemenation checks that all children either exist
- or are derived.
- """
- def missing(node):
- return not node.is_derived() and \
- not node.is_pseudo_derived() and \
- not node.linked and \
- not node.rexists()
- missing_sources = filter(missing, self.children())
- if missing_sources:
- desc = "No Builder for target `%s', needed by `%s'." % (missing_sources[0], self)
- raise SCons.Errors.StopError, desc
+
+ def missing(self):
+ return not self.is_derived() and \
+ not self.linked and \
+ not self.rexists()
def remove(self):
"""Remove this Node: no-op by default."""
def add_dependency(self, depend):
"""Adds dependencies."""
try:
- self._add_child(self.depends, self.depends_dict, depend)
+ self._add_child(self.depends, self.depends_set, depend)
except TypeError, e:
e = e.args[0]
if SCons.Util.is_List(e):
- s = map(str, e)
+ s = list(map(str, e))
else:
s = str(e)
raise SCons.Errors.UserError("attempted to add a non-Node dependency to %s:\n\t%s is a %s, not a Node" % (str(self), s, type(e)))
+ def add_prerequisite(self, prerequisite):
+ """Adds prerequisites"""
+ self.prerequisites.extend(prerequisite)
+ self._children_reset()
+
def add_ignore(self, depend):
"""Adds dependencies to ignore."""
try:
- self._add_child(self.ignore, self.ignore_dict, depend)
+ self._add_child(self.ignore, self.ignore_set, depend)
except TypeError, e:
e = e.args[0]
if SCons.Util.is_List(e):
- s = map(str, e)
+ s = list(map(str, e))
else:
s = str(e)
raise SCons.Errors.UserError("attempted to ignore a non-Node dependency of %s:\n\t%s is a %s, not a Node" % (str(self), s, type(e)))
def add_source(self, source):
"""Adds sources."""
+ if self._specific_sources:
+ return
try:
- self._add_child(self.sources, self.sources_dict, source)
+ self._add_child(self.sources, self.sources_set, source)
except TypeError, e:
e = e.args[0]
if SCons.Util.is_List(e):
- s = map(str, e)
+ s = list(map(str, e))
else:
s = str(e)
raise SCons.Errors.UserError("attempted to add a non-Node as source of %s:\n\t%s is a %s, not a Node" % (str(self), s, type(e)))
- def _add_child(self, collection, dict, child):
- """Adds 'child' to 'collection', first checking 'dict' to see
- if it's already present."""
- if type(child) is not type([]):
- child = [child]
- for c in child:
- if not isinstance(c, Node):
- raise TypeError, c
+ def _add_child(self, collection, set, child):
+ """Adds 'child' to 'collection', first checking 'set' to see if it's
+ already present."""
+ #if type(child) is not type([]):
+ # child = [child]
+ #for c in child:
+ # if not isinstance(c, Node):
+ # raise TypeError, c
added = None
for c in child:
- if not dict.has_key(c):
+ if c not in set:
+ set.add(c)
collection.append(c)
- dict[c] = 1
added = 1
- c.parents[self] = 1
if added:
self._children_reset()
+ def set_specific_source(self, source):
+ self.add_source(source)
+ self._specific_sources = True
+
def add_wkid(self, wkid):
"""Add a node to the list of kids waiting to be evaluated"""
- if self.wkids != None:
+ if self.wkids is not None:
self.wkids.append(wkid)
def _children_reset(self):
+ self.clear_memoized_values()
+ # We need to let the Executor clear out any calculated
+ # build info that it's cached so we can re-calculate it.
+ self.executor_cleanup()
+
+ memoizer_counters.append(SCons.Memoize.CountValue('_children_get'))
+
+ def _children_get(self):
try:
- delattr(self, '_children')
- except AttributeError:
+ return self._memo['children_get']
+ except KeyError:
pass
- def children(self, scan=1):
- """Return a list of the node's direct children, minus those
- that are ignored by this node."""
- if scan:
- self.scan()
- try:
- return self._children
- except AttributeError:
- c = filter(lambda x, i=self.ignore: x not in i,
- self.all_children(scan=0))
- self._children = c
- return c
+ # The return list may contain duplicate Nodes, especially in
+ # source trees where there are a lot of repeated #includes
+ # of a tangle of .h files. Profiling shows, however, that
+ # eliminating the duplicates with a brute-force approach that
+ # preserves the order (that is, something like:
+ #
+ # u = []
+ # for n in list:
+ # if n not in u:
+ # u.append(n)"
+ #
+ # takes more cycles than just letting the underlying methods
+ # hand back cached values if a Node's information is requested
+ # multiple times. (Other methods of removing duplicates, like
+ # using dictionary keys, lose the order, and the only ordered
+ # 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)
+
+ 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
+
+ self._memo['children_get'] = children
+ return children
def all_children(self, scan=1):
"""Return a list of all the node's direct children."""
+ if scan:
+ self.scan()
+
# The return list may contain duplicate Nodes, especially in
# source trees where there are a lot of repeated #includes
# of a tangle of .h files. Profiling shows, however, that
# using dictionary keys, lose the order, and the only ordered
# dictionary patterns I found all ended up using "not in"
# internally anyway...)
- if scan:
- self.scan()
if self.implicit is None:
return self.sources + self.depends
else:
return self.sources + self.depends + self.implicit
- def get_parents(self):
- return self.parents.keys()
+ def children(self, scan=1):
+ """Return a list of the node's direct children, minus those
+ that are ignored by this node."""
+ if scan:
+ self.scan()
+ return self._children_get()
def set_state(self, state):
self.state = state
def get_state(self):
return self.state
- def current(self, calc=None):
+ 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:
+ import SCons.Defaults
+ 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')
+
+ def changed(self, node=None):
+ """
+ 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
+ it against our own previously stored BuildInfo, but the stored
+ BuildInfo from another Node (typically one in a Repository)
+ can be used instead.
+
+ Note that we now *always* check every dependency. We used to
+ short-circuit the check by returning as soon as we detected
+ 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.
+ """
+ t = 0
+ if t: Trace('changed(%s [%s], %s)' % (self, classname(self), node))
+ if node is None:
+ node = self
+
+ result = False
+
+ bi = node.get_stored_info().binfo
+ then = bi.bsourcesigs + bi.bdependsigs + bi.bimplicitsigs
+ children = self.children()
+
+ diff = len(children) - len(then)
+ if diff:
+ # The old and new dependency lists are different lengths.
+ # This always indicates that the Node must be rebuilt.
+ # We also extend the old dependency list with enough None
+ # entries to equal the new dependency list, for the benefit
+ # of the loop below that updates node information.
+ then.extend([None] * diff)
+ if t: Trace(': old %s new %s' % (len(then), len(children)))
+ result = True
+
+ for child, prev_ni in izip(children, then):
+ if child.changed_since_last_build(self, prev_ni):
+ if t: Trace(': %s changed' % child)
+ result = True
+
+ contents = self.get_executor().get_contents()
+ if self.has_builder():
+ import SCons.Util
+ newsig = SCons.Util.MD5signature(contents)
+ if bi.bactsig != newsig:
+ if t: Trace(': bactsig %s != newsig %s' % (bi.bactsig, newsig))
+ result = True
+
+ if not result:
+ if t: Trace(': up to date')
+
+ if t: Trace('\n')
+
+ return result
+
+ def is_up_to_date(self):
+ """Default check for whether the Node is current: unknown Node
+ subtypes are always out of date, so they will always get built."""
return None
- def rfile(self):
- return self
+ def children_are_up_to_date(self):
+ """Alternate check for whether the Node is current: If all of
+ our children were up-to-date, then this Node was up-to-date, too.
- def rstr(self):
- return str(self)
+ The SCons.Node.Alias and SCons.Node.Python.Value subclasses
+ rebind their current() method to this method."""
+ # Allow the children to calculate their signatures.
+ self.binfo = self.get_binfo()
+ if self.always_build:
+ return None
+ state = 0
+ for kid in self.children(None):
+ s = kid.get_state()
+ if s and (not state or s > state):
+ state = s
+ return (state == 0 or state == SCons.Node.up_to_date)
def is_literal(self):
"""Always pass the string representation of a Node to
the command interpreter literally."""
return 1
- def add_pre_action(self, act):
- """Adds an Action performed on this Node only before
- building it."""
- self.pre_actions.append(act)
-
- def add_post_action(self, act):
- """Adds and Action performed on this Node only after
- building it."""
- self.post_actions.append(act)
-
def render_include_tree(self):
"""
Return a text representation, suitable for displaying to the
if self.is_derived() and self.env:
env = self.get_build_env()
for s in self.sources:
- def f(node, env=env, scanner=s.source_scanner, target=self):
- return node.get_found_includes(env, scanner, target)
+ 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
generator is being called to generate a signature for the
command line, which determines if we should rebuild or not.
- Such command generators shoud use this method in preference
+ Such command generators should use this method in preference
to str(Node) when converting a Node to a string, passing
in the for_signature parameter, such that we will call
Node.for_signature() or str(Node) properly, depending on whether
if no new functionality is needed for Environment substitution.
"""
return self
-
+
+ def explain(self):
+ if not self.exists():
+ return "building `%s' because it doesn't exist\n" % self
+
+ if self.always_build:
+ return "rebuilding `%s' because AlwaysBuild() is specified\n" % self
+
+ old = self.get_stored_info()
+ if old is None:
+ return None
+
+ old = old.binfo
+ old.prepare_dependencies()
+
+ try:
+ old_bkids = old.bsources + old.bdepends + old.bimplicit
+ old_bkidsigs = old.bsourcesigs + old.bdependsigs + old.bimplicitsigs
+ except AttributeError:
+ return "Cannot explain why `%s' is being rebuilt: No previous build information found\n" % self
+
+ new = self.get_binfo()
+
+ new_bkids = new.bsources + new.bdepends + new.bimplicit
+ new_bkidsigs = new.bsourcesigs + new.bdependsigs + new.bimplicitsigs
+
+ osig = dict(izip(old_bkids, old_bkidsigs))
+ nsig = dict(izip(new_bkids, new_bkidsigs))
+
+ # The sources and dependencies we'll want to report are all stored
+ # as relative paths to this target's directory, but we want to
+ # report them relative to the top-level SConstruct directory,
+ # so we only print them after running them through this lambda
+ # to turn them into the right relative Node and then return
+ # its string.
+ def stringify( s, E=self.dir.Entry ) :
+ if hasattr( s, 'dir' ) :
+ return str(E(s))
+ return str(s)
+
+ lines = []
+
+ removed = [x for x in old_bkids if not x in new_bkids]
+ if removed:
+ removed = list(map(stringify, removed))
+ fmt = "`%s' is no longer a dependency\n"
+ lines.extend([fmt % s for s in removed])
+
+ 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]):
+ lines.append("`%s' changed\n" % stringify(k))
+
+ if len(lines) == 0 and old_bkids != new_bkids:
+ lines.append("the dependency order changed:\n" +
+ "%sold: %s\n" % (' '*15, list(map(stringify, old_bkids))) +
+ "%snew: %s\n" % (' '*15, list(map(stringify, new_bkids))))
+
+ if len(lines) == 0:
+ def fmt_with_title(title, strlines):
+ lines = strlines.split('\n')
+ sep = '\n' + ' '*(15 + len(title))
+ return ' '*15 + title + sep.join(lines) + '\n'
+ if old.bactsig != new.bactsig:
+ if old.bact == new.bact:
+ lines.append("the contents of the build action changed\n" +
+ fmt_with_title('action: ', new.bact))
+ else:
+ lines.append("the build action changed:\n" +
+ fmt_with_title('old: ', old.bact) +
+ fmt_with_title('new: ', new.bact))
+
+ if len(lines) == 0:
+ return "rebuilding `%s' for unknown reasons\n" % self
+
+ preamble = "rebuilding `%s' because" % self
+ if len(lines) == 1:
+ return "%s %s" % (preamble, lines[0])
+ else:
+ lines = ["%s:\n" % preamble] + lines
+ return ( ' '*11).join(lines)
+
+try:
+ [].extend(UserList.UserList([]))
+except TypeError:
+ # Python 1.5.2 doesn't allow a list to be extended by list-like
+ # objects (such as UserList instances), so just punt and use
+ # real lists.
+ def NodeList(l):
+ return l
+else:
+ class NodeList(UserList.UserList):
+ def __str__(self):
+ return str(list(map(str, self.data)))
def get_children(node, parent): return node.children()
def ignore_cycle(node, stack): pass
node = self.stack[-1].wkids.pop(0)
if not self.stack[-1].wkids:
self.stack[-1].wkids = None
- if self.history.has_key(node):
+ if node in self.history:
self.cycle_func(node, self.stack)
else:
node.wkids = copy.copy(self.kids_func(node, self.stack[-1]))
parent = None
self.eval_func(node, parent)
return node
+ return None
def is_done(self):
return not self.stack
arg2nodes_lookups = []
+
+# Local Variables:
+# tab-width:4
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=4 shiftwidth=4: