The Node package for the SCons software construction utility.
+This is, in many ways, the heart of SCons.
+
+A Node is where we encapsulate all of the dependency information about
+any thing that SCons can build, or about any thing which SCons can use
+to build some other thing. The canonical "thing," of course, is a file,
+but a Node can also represent something remote (like a web page) or
+something completely abstract (like an Alias).
+
+Each specific type of "thing" is specifically represented by a subclass
+of the Node base class: Node.FS.File for files, Node.Alias for aliases,
+etc. Dependency information is kept here in the base class, and
+information specific to files/aliases/etc. is in the subclass. The
+goal, if we've done this correctly, is that any type of "thing" should
+be able to depend on any other type of "thing."
+
"""
#
-# Copyright (c) 2001, 2002 Steven Knight
+# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
-import string
-import types
import copy
-import sys
-import SCons.Sig
-from SCons.Errors import BuildError, UserError
+import SCons.Sig
import SCons.Util
# Node states
# controls whether the cached implicit deps are ignored:
implicit_deps_changed = 0
+# A variable that can be set to an interface-specific function be called
+# to annotate a Node with information about its creation.
+def do_nothing(node): pass
+
+Annotate = do_nothing
class Node:
"""The base Node class, for entities that we know how to
pass
def __init__(self):
+ # 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
+ # canonical example being a builder to fetch a file from a
+ # source code system like CVS or Subversion).
+
self.sources = [] # source files used to build node
self.depends = [] # explicit dependencies (from Depends)
self.implicit = None # implicit (scanned) dependencies (None means not scanned yet)
self.ignore = [] # dependencies to ignore
self.parents = {}
self.wkids = None # Kids yet to walk, when it's an array
- self.builder = None
self.source_scanner = None # implicit scanner from scanner map
self.target_scanner = None # explicit scanner from this node's Builder
+
self.env = None
self.state = None
self.precious = None
self.found_includes = {}
self.includes = None
- self.build_args = {}
+ 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
-
- def generate_build_args(self):
- dict = copy.copy(self.env.Dictionary())
- if hasattr(self, 'cwd'):
- auto = self.env.autogenerate(dir = self.cwd)
- else:
- auto = self.env.autogenerate()
- dict.update(auto)
-
- dictArgs = { 'env' : dict,
- 'target' : self,
- 'source' : self.sources }
- dictArgs.update(self.build_args)
- return dictArgs
-
- def build(self):
- """Actually build the node. Return the status from the build."""
- # This method is called from multiple threads in a parallel build,
- # so only do thread safe stuff here. Do thread unsafe stuff in built().
- if not self.builder:
- return None
+ self.pre_actions = []
+ self.post_actions = []
+ self.linked = 0 # is this node linked to the build directory?
+
+ # 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 get_build_env(self):
+ """Fetch the appropriate Environment to build this node."""
+ executor = self.get_executor()
+ return executor.get_build_env()
+
+ def set_executor(self, executor):
+ """Set the action executor for this node."""
+ self.executor = executor
+
+ def get_executor(self, create=1):
+ """Fetch the action executor for this node. Create one if
+ there isn't already one, and requested to do so."""
try:
- # If this Builder instance has already been called,
- # there will already be an associated status.
- stat = self.builder.status
+ executor = self.executor
except AttributeError:
- try:
- stat = apply(self.builder.execute, (),
- self.generate_build_args())
- except KeyboardInterrupt:
- raise
- except UserError:
+ if not create:
raise
- except:
- raise BuildError(self, "Exception",
- sys.exc_type,
- sys.exc_value,
- sys.exc_traceback)
- if stat:
- raise BuildError(node = self, errstr = "Error %d" % stat)
+ import SCons.Builder
+ env = self.generate_build_env(self.builder.env)
+ executor = SCons.Executor.Executor(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.
+
+ 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)
- return stat
+ def build(self):
+ """Actually build the node.
+
+ This method is called from multiple threads in a parallel build,
+ 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)
def built(self):
"""Called just after this node is sucessfully built."""
# node were presumably just changed:
self.del_csig()
+ 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()
+ 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:
def builder_set(self, builder):
self.builder = builder
- def builder_sig_adapter(self):
- """Create an adapter for calculating a builder's signature.
+ def has_builder(self):
+ """Return whether this Node has a builder or not.
- The underlying signature class will call get_contents()
- to fetch the signature of a builder, but the actual
- content of that signature depends on the node and the
- environment (for construction variable substitution),
- so this adapter provides the right glue between the two.
+ In Boolean tests, this turns out to be a *lot* more efficient
+ than simply examining the builder attribute directly ("if
+ node.builder: ..."). When the builder attribute is examined
+ directly, it ends up calling __getattr__ for both the __len__
+ and __nonzero__ attributes on instances of our Builder Proxy
+ class(es), generating a bazillion extra calls and slowing
+ things down immensely.
"""
- class Adapter:
- def __init__(self, node):
- self.node = node
- def get_contents(self):
- return apply(self.node.builder.get_contents, (),
- self.node.generate_build_args())
- def get_timestamp(self):
- return None
- return Adapter(self)
+ try:
+ b = self.builder
+ 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
- def get_implicit_deps(self, env, scanner, target):
- """Return a list of implicit dependencies for this node"""
+ 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
+ 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):
+ """Return the scanned include lines (implicit dependencies)
+ found in this node.
+
+ The default is no implicit dependencies. We expect this method
+ to be overridden by any subclass that can be scanned for
+ implicit dependencies.
+ """
return []
+ def get_implicit_deps(self, env, scanner, target):
+ """Return a list of implicit dependencies for this node.
+
+ This method exists to handle recursive invocation of the scanner
+ on the implicit dependencies returned by the scanner, if the
+ scanner's recursive flag says that we should.
+ """
+ if not scanner:
+ return []
+
+ try:
+ recurse = scanner.recursive
+ except AttributeError:
+ recurse = None
+
+ 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)
+
+ 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.
+ """
+ try:
+ return self.implicit_factory_cache[path]
+ except KeyError:
+ n = self.builder.source_factory(path)
+ self.implicit_factory_cache[path] = n
+ return n
+
def scan(self):
"""Scan this node's dependents for implicit dependencies."""
# Don't bother scanning non-derived files, because we don't
if not self.implicit is None:
return
self.implicit = []
- if not self.builder:
+ if not self.has_builder():
return
if implicit_cache and not implicit_deps_changed:
implicit = self.get_stored_implicit()
if implicit is not None:
- implicit = map(self.builder.source_factory, implicit)
+ implicit = map(self.implicit_factory, implicit)
self._add_child(self.implicit, implicit)
calc = SCons.Sig.default_calc
if implicit_deps_unchanged or calc.current(self, calc.bsig(self)):
return
else:
- # one of this node's sources has changed, so
+ # one of this node's sources has changed, so
# we need to recalculate the implicit deps,
# and the bsig:
self.implicit = []
self.del_bsig()
+ build_env = self.get_build_env()
+
for child in self.children(scan=0):
self._add_child(self.implicit,
- child.get_implicit_deps(self.env,
+ child.get_implicit_deps(build_env,
child.source_scanner,
self))
# scan this node itself for implicit dependencies
self._add_child(self.implicit,
- self.get_implicit_deps(self.env,
+ self.get_implicit_deps(build_env,
self.target_scanner,
self))
return
self.env = env
- def calc_signature(self, calc):
+ def calc_signature(self, calc, cache=None):
"""
Select and calculate the appropriate build signature for a node.
self - the node
calc - the signature calculation module
+ cache - alternate node to use for the signature cache
returns - the signature
-
- This method does not store the signature in the node or
- in the .sconsign file.
"""
- if self.builder:
+ if self.has_builder():
if SCons.Sig.build_signature:
- if not hasattr(self, 'bsig'):
- self.set_bsig(calc.bsig(self))
- return self.get_bsig()
+ return calc.bsig(self, cache)
else:
- if not hasattr(self, 'csig'):
- self.set_csig(calc.csig(self))
- return self.get_csig()
+ return calc.csig(self, cache)
elif not self.exists():
return None
else:
- if not hasattr(self, 'csig'):
- self.set_csig(calc.csig(self))
- return self.get_csig()
+ return calc.csig(self, cache)
def get_bsig(self):
"""Get the node's build signature (based on the signatures
"""Set the Node's precious value."""
self.precious = precious
+ def exists(self):
+ """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: no-op by default."""
- pass
+ """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 remove(self):
+ """Remove this Node: no-op by default."""
+ return None
def add_dependency(self, depend):
"""Adds dependencies. The depend argument must be a list."""
def all_children(self, scan=1):
"""Return a list of all the node's direct children."""
- #XXX Need to remove duplicates from this
+ # 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 scan:
self.scan()
if self.implicit is None:
def get_state(self):
return self.state
- def current(self):
+ def current(self, calc=None):
return None
def rfile(self):
def rstr(self):
return str(self)
+ 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
+ user, of the include tree for the sources of this node.
+ """
+ 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)
+ return SCons.Util.render_tree(s, f, 1)
+ else:
+ return None
+
+ def get_abspath(self):
+ """
+ Return an absolute path to the Node. This will return simply
+ str(Node) by default, but for Node types that have a concept of
+ relative path, this might return something different.
+ """
+ return str(self)
+
+ def for_signature(self):
+ """
+ Return a string representation of the Node that will always
+ be the same for this particular Node, no matter what. This
+ is by contrast to the __str__() method, which might, for
+ instance, return a relative path for a file Node. The purpose
+ of this method is to generate a value to be used in signature
+ calculation for the command line used to build a target, and
+ we use this method instead of str() to avoid unnecessary
+ rebuilds. This method does not need to return something that
+ would actually work in a command line; it can return any kind of
+ nonsense, so long as it does not change.
+ """
+ return str(self)
+
+ def get_string(self, for_signature):
+ """This is a convenience function designed primarily to be
+ used in command generators (i.e., CommandGeneratorActions or
+ Environment variables that are callable), which are called
+ with a for_signature argument that is nonzero if the command
+ 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
+ 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
+ we are calculating a signature or actually constructing a
+ command line."""
+ if for_signature:
+ return self.for_signature()
+ return str(self)
+
+ def get_subst_proxy(self):
+ """
+ This method is expected to return an object that will function
+ exactly like this Node, except that it implements any additional
+ special features that we would like to be in effect for
+ Environment variable substitution. The principle use is that
+ some Nodes would like to implement a __getattr__() method,
+ but putting that in the Node type itself has a tendency to kill
+ performance. We instead put it in a proxy and return it from
+ this method. It is legal for this method to return self
+ if no new functionality is needed for Environment substitution.
+ """
+ return self
+
+
def get_children(node, parent): return node.children()
def ignore_cycle(node, stack): pass
def do_nothing(node, parent): pass
arg2nodes_lookups = []
-def arg2nodes(args, node_factory=None):
+def arg2nodes(args, node_factory=None, lookup_list=arg2nodes_lookups):
"""This function converts a string or list into a list of Node
instances. It accepts the following inputs:
- A single string,
for v in args:
if SCons.Util.is_String(v):
n = None
- for l in arg2nodes_lookups:
+ for l in lookup_list:
n = l(v)
if not n is None:
break
if not n is None:
+ if SCons.Util.is_String(n) and node_factory:
+ n = node_factory(n)
nodes.append(n)
elif node_factory:
nodes.append(node_factory(v))