http://scons.tigris.org/issues/show_bug.cgi?id=2329
[scons.git] / src / engine / SCons / Node / __init__.py
index 4d513709ca9268f27049bd56fffcb8b071b87af0..455487ec7423cf9c9c2bcdd5edb2e84e0c0bfb2e 100644 (file)
@@ -41,17 +41,24 @@ 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.
 #
+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
@@ -59,14 +66,23 @@ import SCons.Util
 # 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:
@@ -81,16 +97,95 @@ def do_nothing(node): pass
 
 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
@@ -105,44 +200,60 @@ class Node:
         # 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."""
@@ -156,27 +267,39 @@ class 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
@@ -188,78 +311,150 @@ class Node:
         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.
@@ -277,36 +472,52 @@ class Node:
         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.
 
@@ -316,7 +527,7 @@ class 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
@@ -326,242 +537,269 @@ class Node:
         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
@@ -570,26 +808,16 @@ class Node:
         """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."""
@@ -598,83 +826,127 @@ class Node:
     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
@@ -692,15 +964,17 @@ class Node:
         # 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
@@ -708,30 +982,122 @@ class Node:
     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
@@ -740,8 +1106,13 @@ class 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)
+                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
@@ -777,7 +1148,7 @@ class Node:
         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
@@ -800,7 +1171,101 @@ class Node:
         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
@@ -843,7 +1308,7 @@ class Walker:
                 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]))
@@ -859,9 +1324,16 @@ class Walker:
                         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: