Fix SideEffect() and BuildDir(). (Anthony Roach)
[scons.git] / src / engine / SCons / Node / __init__.py
index a65310d8cf22b2e4f2bfd1884749265a4b24b074..f2d77ceb054b8f043bd44f0be96d1c05deca189f 100644 (file)
@@ -2,10 +2,25 @@
 
 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
@@ -31,13 +46,9 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
 
 
-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
@@ -63,6 +74,11 @@ implicit_deps_unchanged = 0
 # 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
@@ -73,66 +89,95 @@ class Node:
         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."""
@@ -156,6 +201,23 @@ class Node:
         # 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:
@@ -167,29 +229,109 @@ class Node:
     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
@@ -198,33 +340,35 @@ class Node:
         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))
 
@@ -239,33 +383,25 @@ class Node:
             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
@@ -335,9 +471,34 @@ class Node:
         """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."""
@@ -375,7 +536,23 @@ class Node:
 
     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:
@@ -392,7 +569,7 @@ class Node:
     def get_state(self):
         return self.state
 
-    def current(self):
+    def current(self, calc=None):
         return None
 
     def rfile(self):
@@ -401,6 +578,91 @@ class Node:
     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
@@ -465,7 +727,7 @@ class Walker:
 
 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,
@@ -483,11 +745,13 @@ def arg2nodes(args, node_factory=None):
     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))