Refactor how actions get executed to eliminate a lot of redundant signature calcualat...
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Thu, 15 May 2003 14:09:42 +0000 (14:09 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Thu, 15 May 2003 14:09:42 +0000 (14:09 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@681 fdb21ef1-2011-0410-befe-b5e4ea1792b1

14 files changed:
bin/files
src/CHANGES.txt
src/engine/MANIFEST.in
src/engine/SCons/Builder.py
src/engine/SCons/BuilderTests.py
src/engine/SCons/Executor.py [new file with mode: 0644]
src/engine/SCons/ExecutorTests.py [new file with mode: 0644]
src/engine/SCons/Node/FS.py
src/engine/SCons/Node/FSTests.py
src/engine/SCons/Node/NodeTests.py
src/engine/SCons/Node/__init__.py
src/engine/SCons/SConf.py
src/engine/SCons/Sig/SigTests.py
src/engine/SCons/Sig/__init__.py

index dec67505a018a44f5c099645054bb4d08380dffa..86f297c5d12aa8e8fb021db0879650f6a120ac79 100644 (file)
--- a/bin/files
+++ b/bin/files
@@ -3,6 +3,7 @@
 ./SCons/Defaults.py
 ./SCons/Environment.py
 ./SCons/Errors.py
+./SCons/Executor.py
 ./SCons/Job.py
 ./SCons/Node/Alias.py
 ./SCons/Node/FS.py
index e1d842cb4c4d399596b2e2acd9748c5815941461..509ed4c87d3bd65b0d963e0bcd3cac9b200443e4 100644 (file)
@@ -80,8 +80,11 @@ RELEASE 0.14 - XXX
   - Interpolate the null string if an out-of-range subscript is used
     for a construction variable.
 
-  - Fix the internal Link function so that it creates properly links or
-    copies of files in subsidiary BuildDir directories.
+  - Fix the internal Link function so that it properly links or copies
+    files in subsidiary BuildDir directories.
+
+  - Refactor the internal representation of a single execution instance
+    of an action to eliminate redundant signature calculations.
 
   From Damyan Pepper:
 
index 24277b418ef36f516c0157af151a53aee9aa3e77..65d6886ee4b344dba241b2d13e9c24ccc1135e9a 100644 (file)
@@ -4,6 +4,7 @@ SCons/Builder.py
 SCons/Defaults.py
 SCons/Environment.py
 SCons/Errors.py
+SCons/Executor.py
 SCons/Job.py
 SCons/exitfuncs.py
 SCons/Node/__init__.py
index 8b0ac85c0b9c4fdb1486d3f189e7613cb3797ebd..414c5de683c36a931258974b31733aff040ad65f 100644 (file)
@@ -49,6 +49,7 @@ import os.path
 from SCons.Errors import InternalError, UserError
 
 import SCons.Action
+import SCons.Executor
 import SCons.Node
 import SCons.Node.FS
 import SCons.Util
@@ -135,12 +136,8 @@ def _init_nodes(builder, env, overrides, tlist, slist):
     the proper Builder information.
     """
 
-    for s in slist:
-        src_key = s.scanner_key()        # the file suffix
-        scanner = env.get_scanner(src_key)
-        if scanner:
-            s.source_scanner = scanner
-
+    # First, figure out if there are any errors in the way the targets
+    # were specified.
     for t in tlist:
         if t.side_effect:
             raise UserError, "Multiple ways to build the same target were specified for: %s" % str(t)
@@ -161,14 +158,43 @@ def _init_nodes(builder, env, overrides, tlist, slist):
             elif t.sources != slist:
                 raise UserError, "Multiple ways to build the same target were specified for: %s" % str(t)
 
+    # The targets are fine, so find or make the appropriate Executor to
+    # build this particular list of targets from this particular list of
+    # sources.
+    executor = None
+    if builder.multi:
+        try:
+            executor = tlist[0].get_executor(create = 0)
+        except AttributeError:
+            pass
+        else:
+            executor.add_sources(slist)
+    if executor is None:
+        executor = SCons.Executor.Executor(builder,
+                                           tlist[0].generate_build_env(env),
+                                           overrides,
+                                           tlist,
+                                           slist)
+
+    # Now set up the relevant information in the target Nodes themselves.
+    for t in tlist:
         t.overrides = overrides
         t.cwd = SCons.Node.FS.default_fs.getcwd()
         t.builder_set(builder)
         t.env_set(env)
         t.add_source(slist)
+        t.set_executor(executor)
         if builder.scanner:
             t.target_scanner = builder.scanner
 
+    # Last, add scanners from the Environment to the source Nodes.
+    for s in slist:
+        src_key = s.scanner_key()        # the file suffix
+        scanner = env.get_scanner(src_key)
+        if scanner:
+            s.source_scanner = scanner
+
+
 def _adjust_suffix(suff):
     if suff and not suff[0] in [ '.', '$' ]:
         return '.' + suff
@@ -336,20 +362,6 @@ class BuilderBase:
 
         return tlist
 
-    def get_actions(self):
-        return self.action.get_actions()
-
-    def get_raw_contents(self, target, source, env):
-        """Fetch the "contents" of the builder's action.
-        """
-        return self.action.get_raw_contents(target, source, env)
-
-    def get_contents(self, target, source, env):
-        """Fetch the "contents" of the builder's action
-        (for signature calculation).
-        """
-        return self.action.get_contents(target, source, env)
-
     def src_suffixes(self, env):
         return map(lambda x, e=env: e.subst(_adjust_suffix(x)),
                    self.src_suffix)
index 6def6d57eef11fd9c3c83fcc4c2d13da5e87507a..f7b329def68434a8510bbc1808fafcd374d2a10d 100644 (file)
@@ -39,8 +39,8 @@ import TestCmd
 
 import SCons.Action
 import SCons.Builder
-import SCons.Errors
 import SCons.Environment
+import SCons.Errors
 
 # Initial setup of the common environment for all tests,
 # a temporary working directory containing a
@@ -82,6 +82,8 @@ class Environment:
         return {}
     def autogenerate(self, dir=''):
         return {}
+    def __setitem__(self, item, var):
+        self.d[item] = var
     def __getitem__(self, item):
         return self.d[item]
     def has_key(self, item):
@@ -127,6 +129,14 @@ class MyNode:
         return self.name
     def is_derived(self):
         return self.has_builder()
+    def generate_build_env(self, env):
+        return env
+    def get_build_env(self):
+        return self.executor.get_build_env()
+    def set_executor(self, executor):
+        self.executor = executor
+    def get_executor(self, create=1):
+        return self.executor
 
 class BuilderTestCase(unittest.TestCase):
 
@@ -162,9 +172,10 @@ class BuilderTestCase(unittest.TestCase):
         n1 = MyNode("n1");
         n2 = MyNode("n2");
         builder(env, target = n1, source = n2)
-        assert n1.env == env
-        assert n1.builder == builder
-        assert n1.sources == [n2]
+        assert n1.env == env, n1.env
+        assert n1.builder == builder, n1.builder
+        assert n1.sources == [n2], n1.sources
+        assert n1.executor, "no executor found"
         assert not hasattr(n2, 'env')
 
         target = builder(env, target = 'n3', source = 'n4')
@@ -179,6 +190,14 @@ class BuilderTestCase(unittest.TestCase):
         assert target.name == 'n8 n9'
         assert target.sources[0].name == 'n10 n11'
 
+        # A test to be uncommented when we freeze the environment
+        # as part of calling the builder.
+        #env1 = Environment(VAR='foo')
+        #target = builder(env1, target = 'n12', source = 'n13')
+        #env1['VAR'] = 'bar'
+        #be = target.get_build_env()
+        #assert be['VAR'] == 'foo', be['VAR']
+
         if not hasattr(types, 'UnicodeType'):
             uni = str
         else:
@@ -230,52 +249,6 @@ class BuilderTestCase(unittest.TestCase):
         assert b1 != b3
         assert b2 != b3
 
-    def test_get_actions(self):
-        """Test fetching the Builder's Action list
-        """
-        def func():
-            pass
-        builder = SCons.Builder.Builder(action=SCons.Action.ListAction(["x",
-                                                                        func,
-                                                                        "z"]))
-        a = builder.get_actions()
-        assert len(a) == 3, a
-        assert isinstance(a[0], SCons.Action.CommandAction), a[0]
-        assert isinstance(a[1], SCons.Action.FunctionAction), a[1]
-        assert isinstance(a[2], SCons.Action.CommandAction), a[2]
-
-    def test_get_contents(self):
-        """Test returning the signature contents of a Builder
-        """
-
-        class DummyNode:
-            def __init__(self, name):
-                self.name = name
-            def __str__(self):
-                return self.name
-            def rfile(self):
-                return self
-            def get_subst_proxy(self):
-                return self
-
-        target = map(DummyNode, map(lambda x: "__t%d__" % x, range(1, 7)))
-        source = map(DummyNode, map(lambda x: "__s%d__" % x, range(1, 7)))
-        b1 = SCons.Builder.Builder(action = "foo ${TARGETS[5]}")
-        contents = b1.get_contents(target,source,Environment())
-        assert contents == "foo __t6__", contents
-
-        b1 = SCons.Builder.Builder(action = "bar ${SOURCES[3:5]}")
-        contents = b1.get_contents(target,source,Environment())
-        assert contents == "bar __s4__ __s5__", contents
-
-        b2 = SCons.Builder.Builder(action = Func)
-        contents = b2.get_contents(target,source,Environment())
-        assert contents == "\177\036\000\177\037\000d\000\000S", repr(contents)
-
-        b3 = SCons.Builder.Builder(action = SCons.Action.ListAction(["foo", Func, "bar"]))
-        contents = b3.get_contents(target,source,Environment())
-        assert contents == "foo\177\036\000\177\037\000d\000\000Sbar", repr(contents)
-
     def test_node_factory(self):
         """Test a Builder that creates nodes of a specified class
         """
diff --git a/src/engine/SCons/Executor.py b/src/engine/SCons/Executor.py
new file mode 100644 (file)
index 0000000..2b4e634
--- /dev/null
@@ -0,0 +1,141 @@
+"""SCons.Executor
+
+A module for executing actions with specific lists of target and source
+Nodes.
+
+"""
+
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+
+class Executor:
+    """A class for controlling instances of executing an action.
+
+    This largely exists to hold a single association of a builder,
+    environment, environment overrides, targets and sources for later
+    processing as needed.
+    """
+
+    def __init__(self, builder, env, overrides, targets, sources):
+        self.builder = builder
+        self.env = env
+        self.overrides = overrides
+        self.targets = targets
+        self.sources = sources[:]
+
+    def get_build_env(self):
+        """Fetch or create the appropriate build Environment
+        for this Executor.
+        """
+        try:
+            return self.build_env
+        except AttributeError:
+            if self.env is None:
+                # There was no Environment specifically associated with
+                # this set of targets (which kind of implies that it
+                # is--or they are--source files, but who knows...).
+                # So use the environment associated with the Builder
+                # itself.
+                env = self.builder.env
+                overrides = self.builder.overrides
+            else:
+                # The normal case:  use the Environment that was
+                # used to specify how these targets will be built.
+                env = self.env
+                overrides = self.overrides
+            self.build_env = env.Override(overrides)
+            return self.build_env
+
+    def get_action_list(self, target):
+        """Fetch or create the appropriate action list (for this target).
+
+        There is an architectural mistake here: we cache the action list
+        for the Executor and re-use it regardless of which target is
+        being asked for.  In practice, this doesn't seem to be a problem
+        because executing the action list will update all of the targets
+        involved, so only one target's pre- and post-actions will win,
+        anyway.  This is probably a bug we should fix...
+        """
+        try:
+            al = self.action_list
+        except AttributeError:
+            al = self.builder.action.get_actions()
+            self.action_list = al
+        # XXX shouldn't reach into node attributes like this
+        return target.pre_actions + al + target.post_actions
+
+    def __call__(self, target, func):
+        """Actually execute the action list."""
+        action_list = self.get_action_list(target)
+        if not action_list:
+            return
+        env = self.get_build_env()
+        for action in action_list:
+            func(action, self.targets, self.sources, env)
+
+    def add_sources(self, sources):
+        """Add source files to this Executor's list.  This is necessary
+        for "multi" Builders that can be called repeatedly to build up
+        a source file list for a given target."""
+        slist = filter(lambda x, s=self.sources: x not in s, sources)
+        self.sources.extend(slist)
+
+    def get_raw_contents(self):
+        """Fetch the raw signature contents.  This, along with
+        get_contents(), is the real reason this class exists, so we can
+        compute this once and cache it regardless of how many target or
+        source Nodes there are.
+        """
+        try:
+            return self.raw_contents
+        except AttributeError:
+            action = self.builder.action
+            self.raw_contents = action.get_raw_contents(self.targets,
+                                                        self.sources,
+                                                        self.get_build_env())
+            return self.raw_contents
+
+    def get_contents(self):
+        """Fetch the signature contents.  This, along with
+        get_raw_contents(), is the real reason this class exists, so we
+        can compute this once and cache it regardless of how many target
+        or source Nodes there are.
+        """
+        try:
+            return self.contents
+        except AttributeError:
+            action = self.builder.action
+            self.contents = action.get_contents(self.targets,
+                                                self.sources,
+                                                self.get_build_env())
+            return self.contents
+
+    def get_timestamp(self):
+        """Fetch a time stamp for this Executor.  We don't have one, of
+        course (only files do), but this is the interface used by the
+        timestamp module.
+        """
+        return None
diff --git a/src/engine/SCons/ExecutorTests.py b/src/engine/SCons/ExecutorTests.py
new file mode 100644 (file)
index 0000000..6af5794
--- /dev/null
@@ -0,0 +1,173 @@
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import string
+import sys
+import unittest
+
+import SCons.Executor
+
+
+class MyEnvironment:
+    def __init__(self, **kw):
+        self._dict = {}
+        self._dict.update(kw)
+    def Override(self, overrides):
+        d = self._dict.copy()
+        d.update(overrides)
+        return d
+
+class MyAction:
+    actions = ['action1', 'action2']
+    def get_actions(self):
+        return self.actions
+    def get_raw_contents(self, target, source, env):
+        return string.join(['RAW'] + self.actions + target + source)
+    def get_contents(self, target, source, env):
+        return string.join(self.actions + target + source)
+
+class MyBuilder:
+    def __init__(self, env, overrides):
+        self.env = env
+        self.overrides = overrides
+        self.action = MyAction()
+
+class MyNode:
+    def __init__(self, pre, post):
+        self.pre_actions = pre
+        self.post_actions = post
+        
+
+class ExecutorTestCase(unittest.TestCase):
+
+    def test__init__(self):
+        """Test creating an Executor"""
+        source_list = ['s1', 's2']
+        x = SCons.Executor.Executor('b', 'e', 'o', 't', source_list)
+        assert x.builder == 'b', x.builder
+        assert x.env == 'e', x.env
+        assert x.overrides == 'o', x.overrides
+        assert x.targets == 't', x.targets
+        source_list.append('s3')
+        assert x.sources == ['s1', 's2'], x.sources
+
+    def test_get_build_env(self):
+        """Test fetching and generating a build environment"""
+        x = SCons.Executor.Executor('b', 'e', 'o', 't', ['s1', 's2'])
+        x.build_env = 'eee'
+        be = x.get_build_env()
+        assert be == 'eee', be
+
+        x = SCons.Executor.Executor('b',
+                                    MyEnvironment(X='xxx'),
+                                    {'O':'ooo'},
+                                    't',
+                                    ['s1', 's2'])
+        be = x.get_build_env()
+        assert be == {'O':'ooo', 'X':'xxx'}, be
+
+        env = MyEnvironment(Y='yyy')
+        over = {'O':'ooo'}
+        x = SCons.Executor.Executor(MyBuilder(env, over), None, {}, 't', 's')
+        be = x.get_build_env()
+        assert be == {'O':'ooo', 'Y':'yyy'}, be
+
+    def test_get_action_list(self):
+        """Test fetching and generating an action list"""
+        x = SCons.Executor.Executor('b', 'e', 'o', 't', 's')
+        x.action_list = ['aaa']
+        al = x.get_action_list(MyNode([], []))
+        assert al == ['aaa'], al
+        al = x.get_action_list(MyNode(['PRE'], ['POST']))
+        assert al == ['PRE', 'aaa', 'POST'], al
+
+        x = SCons.Executor.Executor(MyBuilder('e', 'o'), None, {}, 't', 's')
+        al = x.get_action_list(MyNode(['pre'], ['post']))
+        assert al == ['pre', 'action1', 'action2', 'post'], al
+
+    def test__call__(self):
+        """Test calling an Executor"""
+        actions = []
+        env = MyEnvironment(CALL='call')
+        b = MyBuilder(env, {})
+        x = SCons.Executor.Executor(b, None, {}, ['t1', 't2'], ['s1', 's2'])
+        def func(action, target, source, env, a=actions):
+            a.append(action)
+            assert target == ['t1', 't2'], target
+            assert source == ['s1', 's2'], source
+            assert env == {'CALL':'call'}, env
+        x(MyNode(['pre'], ['post']), func)
+        assert actions == ['pre', 'action1', 'action2', 'post'], actions
+
+    def test_add_sources(self):
+        """Test adding sources to an Executor"""
+        x = SCons.Executor.Executor('b', 'e', 'o', 't', ['s1', 's2'])
+        assert x.sources == ['s1', 's2'], x.sources
+        x.add_sources(['s1', 's2'])
+        assert x.sources == ['s1', 's2'], x.sources
+        x.add_sources(['s3', 's1', 's4'])
+        assert x.sources == ['s1', 's2', 's3', 's4'], x.sources
+
+    def test_get_raw_contents(self):
+        """Test fetching the raw signatures contents"""
+        env = MyEnvironment(RC='raw contents')
+
+        x = SCons.Executor.Executor(MyBuilder(env, {}), None, {}, ['t'], ['s'])
+        x.raw_contents = 'raw raw raw'
+        rc = x.get_raw_contents()
+        assert rc == 'raw raw raw', rc
+
+        x = SCons.Executor.Executor(MyBuilder(env, {}), None, {}, ['t'], ['s'])
+        rc = x.get_raw_contents()
+        assert rc == 'RAW action1 action2 t s', rc
+
+    def test_get_contents(self):
+        """Test fetching the signatures contents"""
+        env = MyEnvironment(C='contents')
+
+        x = SCons.Executor.Executor(MyBuilder(env, {}), None, {}, ['t'], ['s'])
+        x.contents = 'contents'
+        c = x.get_contents()
+        assert c == 'contents', c
+
+        x = SCons.Executor.Executor(MyBuilder(env, {}), None, {}, ['t'], ['s'])
+        c = x.get_contents()
+        assert c == 'action1 action2 t s', c
+
+    def test_get_timetstamp(self):
+        """Test fetching the "timestamp" """
+        x = SCons.Executor.Executor('b', 'e', 'o', 't', ['s1', 's2'])
+        ts = x.get_timestamp()
+        assert ts is None, ts
+
+
+if __name__ == "__main__":
+    suite = unittest.TestSuite()
+    tclasses = [ ExecutorTestCase ]
+    for tclass in tclasses:
+        names = unittest.getTestCaseNames(tclass, 'test_')
+        suite.addTests(map(tclass, names))
+    if not unittest.TextTestRunner().run(suite).wasSuccessful():
+        sys.exit(1)
index b4641a7ec5eec51b097add8e18515bb79cd4a92d..c715adebac44cb1ff0278d4d9e1a678ce4b3cde8 100644 (file)
@@ -1073,9 +1073,8 @@ class File(Entry):
         return self.fs.Rsearchall(pathlist, clazz=Dir, must_exist=0,
                                   cwd=self.cwd)
     
-    def generate_build_env(self):
-        env = SCons.Node.Node.generate_build_env(self)
-        
+    def generate_build_env(self, env):
+        """Generate an appropriate Environment to build this File."""
         return env.Override({'Dir' : self.Dir,
                              'File' : self.File,
                              'RDirs' : self.RDirs})
index 1f582641c4aa5ecad3253bdbc9d1e796acdbe763..13f171e9ed3bd7ec544234369a7384ef3be28fd8 100644 (file)
@@ -46,28 +46,6 @@ except NameError :
     True = 1 ; False = 0
 
 
-class Builder:
-    def __init__(self, factory):
-        self.factory = factory
-
-    def get_actions(self):
-        class Action:
-            def __call__(self, targets, sources, env):
-                global built_it
-                built_it = 1
-                return 0
-            def show(self, string):
-                pass
-            def strfunction(self, targets, sources, env):
-                return ""
-        return [Action()]
-
-    def targets(self, t):
-        return [t]
-    
-    def source_factory(self, name):
-        return self.factory(name)
-
 scanner_count = 0
 
 class Scanner:
@@ -95,6 +73,34 @@ class Environment:
     def Override(self, overrides):
         return self
 
+class Action:
+    def __call__(self, targets, sources, env):
+        global built_it
+        built_it = 1
+        return 0
+    def show(self, string):
+        pass
+    def strfunction(self, targets, sources, env):
+        return ""
+    def get_actions(self):
+        return [self]
+
+class Builder:
+    def __init__(self, factory):
+        self.factory = factory
+        self.env = Environment()
+        self.overrides = {}
+        self.action = Action()
+
+    def get_actions(self):
+        return [self]
+
+    def targets(self, t):
+        return [t]
+    
+    def source_factory(self, name):
+        return self.factory(name)
+
 class BuildDirTestCase(unittest.TestCase):
     def runTest(self):
         """Test build dir functionality"""
@@ -916,7 +922,7 @@ class FSTestCase(unittest.TestCase):
         test.write("remove_me", "\n")
         assert os.path.exists(test.workpath("remove_me"))
         f1 = fs.File(test.workpath("remove_me"))
-        f1.builder = 1
+        f1.builder = Builder(fs.File)
         f1.env_set(Environment())
         f1.prepare()
         assert not os.path.exists(test.workpath("remove_me"))
index 2cf6d4b7caea8d90af8f0910c49a4cd21dbce4a1..ff578719de070d1bdaecd225ac3e4f73978b2964 100644 (file)
@@ -53,6 +53,9 @@ class MyAction:
         self.order = built_order
         return 0
 
+    def get_actions(self):
+        return [self]
+
 class MyNonGlobalAction:
     def __init__(self):
         self.order = 0
@@ -71,11 +74,24 @@ class MyNonGlobalAction:
         self.order = built_order
         return 0
 
+    def get_actions(self):
+        return [self]
+
+class Environment:
+    def Dictionary(self, *args):
+        return {}
+    def Override(self, overrides):
+        return overrides
+
 class Builder:
+    def __init__(self):
+        self.env = Environment()
+        self.overrides = {}
+        self.action = MyAction()
     def targets(self, t):
         return [t]
     def get_actions(self):
-        return [MyAction()]
+        return [self.action]
     def get_contents(self, target, source, env):
         return 7
 
@@ -86,6 +102,7 @@ class NoneBuilder(Builder):
 
 class ListBuilder(Builder):
     def __init__(self, *nodes):
+        Builder.__init__(self)
         self.nodes = nodes
     def execute(self, target, source, env):
         if hasattr(self, 'status'):
@@ -107,12 +124,6 @@ class ExceptBuilder2:
     def execute(self, target, source, env):
         raise "foo"
 
-class Environment:
-    def Dictionary(self, *args):
-        return {}
-    def Override(self, overrides):
-        return overrides
-
 class Scanner:
     called = None
     def __call__(self, node):
@@ -154,7 +165,7 @@ class NodeTestCase(unittest.TestCase):
         node.sources = ["yyy", "zzz"]
         node.build()
         assert built_it
-        assert built_target[0] == node, built_target[0]
+        assert built_target == [node], built_target
         assert built_source == ["yyy", "zzz"], built_source
 
         built_it = None
@@ -163,10 +174,10 @@ class NodeTestCase(unittest.TestCase):
         node.env_set(Environment())
         node.path = "qqq"
         node.sources = ["rrr", "sss"]
-        node.overrides = { "foo" : 1, "bar" : 2 }
+        node.builder.overrides = { "foo" : 1, "bar" : 2 }
         node.build()
         assert built_it
-        assert built_target[0] == node, built_target[0]
+        assert built_target == [node], built_target
         assert built_source == ["rrr", "sss"], built_source
         assert built_args["foo"] == 1, built_args
         assert built_args["bar"] == 2, built_args
@@ -184,6 +195,17 @@ class NodeTestCase(unittest.TestCase):
         fff.sources = ["hhh", "iii"]
         ggg.sources = ["hhh", "iii"]
         # [Charles C. 1/7/2002] Uhhh, why are there no asserts here?
+        # [SK, 15 May 2003] I dunno, let's add some...
+        built_it = None
+        fff.build()
+        assert built_it
+        assert built_target == [fff], built_target
+        assert built_source == ["hhh", "iii"], built_source
+        built_it = None
+        ggg.build()
+        assert built_it
+        assert built_target == [ggg], built_target
+        assert built_source == ["hhh", "iii"], built_source
 
         built_it = None
         jjj = MyNode("jjj")
@@ -285,31 +307,6 @@ class NodeTestCase(unittest.TestCase):
         assert t == [], t
         assert m == None, m
 
-    def test_builder_sig_adapter(self):
-        """Test the node's adapter for builder signatures
-        """
-        node = SCons.Node.Node()
-        node.builder_set(Builder())
-        node.env_set(Environment())
-        c = node.builder_sig_adapter().get_contents()
-        assert c == 7, c
-
-        class ListBuilder:
-            def __init__(self, targets):
-                self.tgt = targets
-            def targets(self, t):
-                return self.tgt
-            def get_contents(self, target, source, env):
-                assert target == self.tgt
-                return 8
-
-        node1 = SCons.Node.Node()
-        node2 = SCons.Node.Node()
-        node.builder_set(ListBuilder([node1, node2]))
-        node.env_set(Environment())
-        c = node.builder_sig_adapter().get_contents()
-        assert c == 8, c
-
     def test_current(self):
         """Test the default current() method
         """
@@ -605,7 +602,7 @@ class NodeTestCase(unittest.TestCase):
         """Test Scanner functionality
         """
         node = MyNode("nnn")
-        node.builder = 1
+        node.builder = Builder()
         node.env_set(Environment())
         s = Scanner()
 
index 5231f902aed9d1abaa38b7167427ce3e1c62d8d1..09c930b60671bc54f161fbcda8f4cdb8c517e7e1 100644 (file)
@@ -122,21 +122,36 @@ class Node:
         # what line in what file created the node, for example).
         Annotate(self)
 
-    def generate_build_env(self):
+    def generate_build_env(self, env):
         """Generate the appropriate Environment to build this node."""
-        if self.env is None:
-            # The node itself doesn't have an associated Environment
-            # (which kind of implies it's a source code file, but who
-            # knows...).  Regardless of why, use the environment (if
-            # any) associated with the Builder itself.
-            env = self.builder.env
-            overrides = self.builder.overrides
-        else:
-            # The normal case: use the Environment used to specify how
-            # this Node is to be built.
-            env = self.env
-            overrides = self.overrides
-        return env.Override(overrides)
+        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:
+            executor = self.executor
+        except AttributeError:
+            if not create:
+                raise
+            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.
@@ -147,15 +162,8 @@ class Node:
         to do different things."""
         if not self.has_builder():
             return
-        action_list = self.pre_actions + \
-                      self.builder.get_actions() + \
-                      self.post_actions
-        if not action_list:
-            return
-        targets = self.builder.targets(self)
-        env = self.generate_build_env()
-        for action in action_list:
-            func(action, targets, self.sources, env)
+        executor = self.get_executor()
+        executor(self, func)
 
     def build(self):
         """Actually build the node.
@@ -249,24 +257,6 @@ class Node:
         """
         return [], None
 
-    def builder_sig_adapter(self):
-        """Create an adapter for calculating a builder's signature.
-
-        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.
-        """
-        class Adapter:
-            def __init__(self, node):
-                self.node = node
-            def get_contents(self):
-                return self.node.builder.get_contents(self.node.builder.targets(self.node), self.node.sources, self.node.generate_build_env())
-            def get_timestamp(self):
-                return None
-        return Adapter(self)
-
     def get_found_includes(self, env, scanner, target):
         """Return the scanned include lines (implicit dependencies)
         found in this node.
@@ -351,7 +341,7 @@ class Node:
                     self.implicit = []
                     self.del_bsig()
 
-        build_env = self.generate_build_env()
+        build_env = self.get_build_env()
 
         for child in self.children(scan=0):
             self._add_child(self.implicit,
@@ -589,7 +579,7 @@ class Node:
         user, of the include tree for the sources of this node.
         """
         if self.has_builder() and self.env:
-            env = self.generate_build_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)
index 64cd5f9f6f09758f76ac97e1725523ca76c62280..830b94a78b90a722e6d43e76ec23c5481e2499f3 100644 (file)
@@ -166,7 +166,6 @@ class SConf:
             except:
                 pass
 
-
             for n in nodes:
                 state = n.get_state()
                 if (state != SCons.Node.executed and
@@ -193,6 +192,7 @@ class SConf:
         #target = self.confdir.File("conftest_" + str(_ac_build_counter))
         f = "conftest_" + str(_ac_build_counter)
         target = os.path.join(str(self.confdir), f)
+        self.env['SCONF_TEXT'] = text
         if text != None:
             source = self.confdir.File(f + extension)
             sourceNode = self.env.SConfSourceBuilder(target=source,
@@ -200,7 +200,6 @@ class SConf:
             nodesToBeBuilt.append(sourceNode)
         else:
             source = None
-        self.env['SCONF_TEXT'] = text
 
         node = builder(target = target, source = source)
         nodesToBeBuilt.append(node)
@@ -301,7 +300,7 @@ class SConf:
         # We record errors in the cache. Only non-exisiting targets may
         # have recorded errors
         needs_rebuild = target[0].exists()
-        buildSig = target[0].builder.get_contents(target, source, env)
+        buildSig = target[0].builder.action.get_contents(target, source, env)
         for node in source:
             if node.get_state() != SCons.Node.up_to_date:
                 # if any of the sources has changed, we cannot use our cache
index 7e1cc40038c040c2cea12715750443742fabec92..bb3efb8f9ec5978af761a4d798bb80686142bf55 100644 (file)
@@ -138,7 +138,7 @@ class DummyNode:
     def store_timestamp(self):
         pass
 
-    def builder_sig_adapter(self):
+    def get_executor(self):
         class Adapter:
             def get_contents(self):
                 return 111
index cd6fe7e8a4fcced4415fc332a808740e6e25f037..cf7a86f05be409828f2f98b1527d871da8db32a2 100644 (file)
@@ -282,7 +282,7 @@ class Calculator:
         
         sigs = map(lambda n, c=self: n.calc_signature(c), children)
         if node.has_builder():
-            sigs.append(self.module.signature(node.builder_sig_adapter()))
+            sigs.append(self.module.signature(node.get_executor()))
 
         bsig = self.module.collect(filter(lambda x: not x is None, sigs))