Refactor action execution so it's controlled by the interface-specific Taskmaster...
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Tue, 17 Dec 2002 14:01:28 +0000 (14:01 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Tue, 17 Dec 2002 14:01:28 +0000 (14:01 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@525 fdb21ef1-2011-0410-befe-b5e4ea1792b1

14 files changed:
src/engine/SCons/Action.py
src/engine/SCons/ActionTests.py
src/engine/SCons/Builder.py
src/engine/SCons/BuilderTests.py
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/Script/__init__.py
src/engine/SCons/Taskmaster.py
src/engine/SCons/TaskmasterTests.py
test/errors.py
test/exceptions.py
test/option--debug.py

index 534b813f7b226350e84cc21c62072453ffcb0b42..8732bd7dd0fa2e4a37c51fc9e414170f87c3f118 100644 (file)
@@ -129,6 +129,9 @@ class ActionBase:
     def show(self, string):
         print string
 
+    def get_actions(self):
+        return [self]
+
     def subst_dict(self, target, source, env):
         """Create a dictionary for substitution of construction
         variables.
@@ -191,6 +194,14 @@ class CommandAction(ActionBase):
         self.cmd_list = cmd
 
     def execute(self, target, source, env):
+        """Execute a command action.
+
+        This will handle lists of commands as well as individual commands,
+        because construction variable substitution may turn a single
+        "command" into a list.  This means that this class can actually
+        handle lists of commands, even though that's not how we use it
+        externally.
+        """
         escape = env.get('ESCAPE', lambda x: x)
 
         import SCons.Errors
@@ -206,7 +217,6 @@ class CommandAction(ActionBase):
             raise SCons.Errors.UserError('Missing SPAWN construction variable.')
 
         dict = self.subst_dict(target, source, env)
-        import SCons.Util
         cmd_list = SCons.Util.scons_subst_list(self.cmd_list, dict, {}, _rm)
         for cmd_line in cmd_list:
             if len(cmd_line):
@@ -342,6 +352,9 @@ class ListAction(ActionBase):
     def __init__(self, list):
         self.list = map(lambda x: Action(x), list)
 
+    def get_actions(self):
+        return self.list
+
     def execute(self, target, source, env):
         for l in self.list:
             r = l.execute(target, source, env)
index 86b84b9dbe8a77b67c5ec57c0b2275f500d7ced4..dfa41ada7cb9bd2f3700e158a4a68de9ba606ebd 100644 (file)
@@ -46,7 +46,7 @@ def Environment(dict):
 
 class ActionTestCase(unittest.TestCase):
 
-    def runTest(self):
+    def test_factory(self):
         """Test the Action factory
         """
         def foo():
@@ -56,6 +56,7 @@ class ActionTestCase(unittest.TestCase):
 
         a2 = SCons.Action.Action("string")
         assert isinstance(a2, SCons.Action.CommandAction), a2
+        assert a2.cmd_list == ["string"], a2.cmd_list
 
         if hasattr(types, 'UnicodeType'):
             exec "a3 = SCons.Action.Action(u'string')"
@@ -64,8 +65,11 @@ class ActionTestCase(unittest.TestCase):
         a4 = SCons.Action.Action(["x", "y", "z", [ "a", "b", "c"]])
         assert isinstance(a4, SCons.Action.ListAction), a4
         assert isinstance(a4.list[0], SCons.Action.CommandAction), a4.list[0]
+        assert a4.list[0].cmd_list == ["x"], a4.list[0].cmd_list
         assert isinstance(a4.list[1], SCons.Action.CommandAction), a4.list[1]
+        assert a4.list[1].cmd_list == ["y"], a4.list[1].cmd_list
         assert isinstance(a4.list[2], SCons.Action.CommandAction), a4.list[2]
+        assert a4.list[2].cmd_list == ["z"], a4.list[2].cmd_list
         assert isinstance(a4.list[3], SCons.Action.CommandAction), a4.list[3]
         assert a4.list[3].cmd_list == [ "a", "b", "c" ], a4.list[3].cmd_list
 
@@ -83,6 +87,15 @@ class ActionTestCase(unittest.TestCase):
         assert isinstance(a8, SCons.Action.CommandAction), a8
         assert a8.cmd_list == [ "a8" ], a8.cmd_list
 
+        a9 = SCons.Action.Action("x\ny\nz")
+        assert isinstance(a9, SCons.Action.ListAction), a9
+        assert isinstance(a9.list[0], SCons.Action.CommandAction), a9.list[0]
+        assert a9.list[0].cmd_list == ["x"], a9.list[0].cmd_list
+        assert isinstance(a9.list[1], SCons.Action.CommandAction), a9.list[1]
+        assert a9.list[1].cmd_list == ["y"], a9.list[1].cmd_list
+        assert isinstance(a9.list[2], SCons.Action.CommandAction), a9.list[2]
+        assert a9.list[2].cmd_list == ["z"], a9.list[2].cmd_list
+
 class ActionBaseTestCase(unittest.TestCase):
 
     def test_cmp(self):
@@ -95,6 +108,13 @@ class ActionBaseTestCase(unittest.TestCase):
         assert a1 != a3
         assert a2 != a3
 
+    def test_get_actions(self):
+        """Test the get_actions() method
+        """
+        a = SCons.Action.Action("x")
+        l = a.get_actions()
+        assert l == [a], l
+
     def test_subst_dict(self):
         """Test substituting dictionary values in an Action
         """
@@ -110,7 +130,6 @@ class ActionBaseTestCase(unittest.TestCase):
         assert str(d['SOURCES']) == 's', d['SOURCES']
         assert str(d['SOURCE']) == 's', d['SOURCE']
 
-
         d = a.subst_dict(target = ['t1', 't2'], source = ['s1', 's2'], env=Environment({}))
         TARGETS = map(lambda x: str(x), d['TARGETS'])
         TARGETS.sort()
@@ -198,9 +217,9 @@ class CommandActionTestCase(unittest.TestCase):
         """
         a = SCons.Action.CommandAction(["|", "$(", "$foo", "|", "$bar",
                                         "$)", "|"])
-        c = a.get_contents(target=[], source=[],
-                           foo = 'FFF', bar = 'BBB')
-        assert c == "| $( FFF | BBB $) |"
+        c = a.get_raw_contents(target=[], source=[],
+                               env=Environment({'foo':'FFF', 'bar':'BBB'}))
+        assert c == "| $( FFF | BBB $) |", c
 
     def test_get_contents(self):
         """Test fetching the contents of a command Action
@@ -348,6 +367,19 @@ class ListActionTestCase(unittest.TestCase):
         assert isinstance(a.list[2], SCons.Action.ListAction)
         assert a.list[2].list[0].cmd_list == [ 'y' ]
 
+    def test_get_actions(self):
+        """Test the get_actions() method for ListActions
+        """
+        a = SCons.Action.ListAction(["x", "y"])
+        l = a.get_actions()
+        assert len(l) == 2, l
+        assert isinstance(l[0], SCons.Action.CommandAction), l[0]
+        g = l[0].get_actions()
+        assert g == [l[0]], g
+        assert isinstance(l[1], SCons.Action.CommandAction), l[1]
+        g = l[1].get_actions()
+        assert g == [l[1]], g
+
     def test_execute(self):
         """Test executing a list of subsidiary Actions
         """
@@ -390,7 +422,7 @@ class LazyActionTestCase(unittest.TestCase):
         assert a10.generator.var == 'FOO', a10.generator.var
 
     def test_execute(self):
-        """Test executing a lazy-evalueation Action
+        """Test executing a lazy-evaluation Action
         """
         def f(target, source, env):
             s = env['s']
@@ -411,15 +443,15 @@ class LazyActionTestCase(unittest.TestCase):
 
 if __name__ == "__main__":
     suite = unittest.TestSuite()
-    suite.addTest(ActionTestCase())
-    suite.addTest(ActionBaseTestCase("test_cmp"))
-    suite.addTest(ActionBaseTestCase("test_subst_dict"))
-    for tclass in [CommandActionTestCase,
-                   CommandGeneratorActionTestCase,
-                   FunctionActionTestCase,
-                   ListActionTestCase,
-                   LazyActionTestCase]:
-        for func in ["test_init", "test_execute", "test_get_contents"]:
-            suite.addTest(tclass(func))
+    tclasses = [ ActionTestCase,
+                 ActionBaseTestCase,
+                 CommandActionTestCase,
+                 CommandGeneratorActionTestCase,
+                 FunctionActionTestCase,
+                 ListActionTestCase,
+                 LazyActionTestCase]
+    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 02f9e2d5efb28a69e1e6782173b04276b1e99f6a..08d8f0cf05b5f7ced3638b3d16fc8808dd236afa 100644 (file)
@@ -317,6 +317,9 @@ class BuilderBase:
 
         return tlist
 
+    def get_actions(self):
+        return self.action.get_actions()
+
     def execute(self, target, source, env):
         """Execute a builder's action to create an output object.
         """
index b087d7bc6b079091e3733e8d845b2ea99311bfaa..3c81d38474accd4c86ec11842182c0067787581a 100644 (file)
@@ -406,6 +406,18 @@ class BuilderTestCase(unittest.TestCase):
         r = builder.execute([],[],Environment(out = outfile))
         assert r == expect_nonexecutable, "r == %d" % r
 
+    def test_get_actions(self):
+        """Test fetching the Builder's Action list
+
+        Verify that we call the underlying Action's method
+        """
+        builder = SCons.Builder.Builder(name="builder", action=SCons.Action.ListAction(["x", "y", "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.CommandAction), a[1]
+        assert isinstance(a[2], SCons.Action.CommandAction), a[2]
+
     def test_get_contents(self):
         """Test returning the signature contents of a Builder
         """
index a3ac7beeebebbee453ba6c9a9458f6baa449f3b3..4ed46649f1a40f12e903651ba8d21e8a748602b8 100644 (file)
@@ -659,6 +659,10 @@ class Dir(Entry):
         kids.sort(c)
         return kids + SCons.Node.Node.all_children(self, 0)
 
+    def get_actions(self):
+        """A null "builder" for directories."""
+        return []
+
     def build(self):
         """A null "builder" for directories."""
         pass
index 8386f8301b77a62b91f0534494c7a4f44c3f7fe9..5917044061f066baed3624c78dbfc3667810ca69 100644 (file)
@@ -944,6 +944,15 @@ class prepareTestCase(unittest.TestCase):
             exc_caught = 1
         assert exc_caught, "Should have caught a StopError."
 
+class get_actionsTestCase(unittest.TestCase):
+    def runTest(self):
+        """Test the Dir's get_action() method"""
+
+        fs = SCons.Node.FS.FS()
+        dir = fs.Dir('.')
+        a = dir.get_actions()
+        assert a == [], a
+
 
 if __name__ == "__main__":
     suite = unittest.TestSuite()
@@ -953,5 +962,6 @@ if __name__ == "__main__":
     suite.addTest(find_fileTestCase())
     suite.addTest(StringDirTestCase())
     suite.addTest(prepareTestCase())
+    suite.addTest(get_actionsTestCase())
     if not unittest.TextTestRunner().run(suite).wasSuccessful():
         sys.exit(1)
index cd2d2e3973d6e139d29be122a3f8cba03b6d6d66..4054bb9fe69a80a939a35eb5b3fd2816ca07bda2 100644 (file)
@@ -46,6 +46,8 @@ class Builder:
         built_source = source
         built_args = env
         return 0
+    def get_actions(self):
+        return 'xyzzy'
     def get_contents(self, target, source, env):
         return 7
 
@@ -242,6 +244,14 @@ class NodeTestCase(unittest.TestCase):
         node.env_set(e)
         assert node.env == e
 
+    def test_get_actions(self):
+        """Test fetching a Node's action list
+        """
+        node = SCons.Node.Node()
+        node.builder_set(Builder())
+        a = node.get_actions()
+        assert a == 'xyzzy', a
+
     def test_set_bsig(self):
         """Test setting a Node's signature
         """
index 20336636f1e6b6104ea1492b659f06542a8ba772..82dfd7e4e32d05e41d6d204f72295bb82ea41ecf 100644 (file)
@@ -110,6 +110,10 @@ class Node:
     def generate_build_env(self):
         return self.env.Override(self.overrides)
 
+    def get_actions(self):
+        """Fetch the action list to build."""
+        return self.builder.get_actions()
+
     def build(self):
         """Actually build the node.   Return the status from the build."""
         # This method is called from multiple threads in a parallel build,
index ad1a6411ee406c602c928ac505f5a1c13423c6ad..add52e4871005a1565da22ba6044499744c22883 100644 (file)
@@ -73,14 +73,34 @@ from SCons.Optik import OptionParser, SUPPRESS_HELP, OptionValueError
 class BuildTask(SCons.Taskmaster.Task):
     """An SCons build task."""
     def execute(self):
-        t = self.targets[0]
-        if t.get_state() == SCons.Node.up_to_date:
-            if self.top and t.builder:
-                display('scons: "%s" is up to date.' % str(self.targets[0]))
-        else:
+        target = self.targets[0]
+        if target.get_state() == SCons.Node.up_to_date:
+            if self.top and target.builder:
+                display('scons: "%s" is up to date.' % str(target))
+        elif target.builder and not hasattr(target.builder, 'status'):
+            action_list = target.get_actions()
+            if not action_list:
+                return
+            env = target.generate_build_env()
             if print_time:
                 start_time = time.time()
-            self.targets[0].build()
+            try:
+                for action in action_list:
+                    stat = action.execute(self.targets, target.sources, env)
+                    if stat:
+                        raise BuildError(node = target,
+                                         errstr = "Error %d" % stat)
+            except KeyboardInterrupt:
+                raise
+            except UserError:
+                raise
+            except BuildError:
+                raise
+            except:
+                raise BuildError(target, "Exception",
+                                 sys.exc_type,
+                                 sys.exc_value,
+                                 sys.exc_traceback)
             if print_time:
                 finish_time = time.time()
                 global command_time
index a32def4d52edd6b5ce0de3211cceb42b8f843dd9..043ce7da5d294276bfcf8acb376ec7d25625d9d7 100644 (file)
@@ -60,11 +60,14 @@ class Task:
         self.top = top
         self.node = node
 
-
     def prepare(self):
-        """Called just before the task is executed."""
+        """Called just before the task is executed.
+
+        This unlinks all targets and makes all directories before
+        building anything."""
         if self.targets[0].get_state() != SCons.Node.up_to_date:
-            self.targets[0].prepare()
+            for t in self.targets:
+                t.prepare()
 
     def execute(self):
         """Called to execute the task.
index 23d7e0ad215b6c7bdd2c4b5c8a09d147dc5824f4..c89f3560c69e773725589d034c4962425666108b 100644 (file)
@@ -45,6 +45,7 @@ class Node:
         self.bsig = None
         self.csig = None
         self.state = None
+        self.prepared = None
         self.parents = []
         self.side_effect = 0
         self.side_effects = []
@@ -61,7 +62,7 @@ class Node:
         built_text = built_text + " really"
 
     def prepare(self):
-        pass
+        self.prepared = 1
 
     def children(self):
         if not self.scanned:
@@ -410,6 +411,25 @@ class TaskmasterTestCase(unittest.TestCase):
     def test_executed(self):
         pass
 
+    def test_prepare(self):
+        """Test preparation of multiple Nodes for a task
+
+        """
+        n1 = Node("n1")
+        n2 = Node("n2")
+        tm = SCons.Taskmaster.Taskmaster([n1, n2])
+        t = tm.next_task()
+        # This next line is moderately bogus.  We're just reaching
+        # in and setting the targets for this task to an array.  The
+        # "right" way to do this would be to have the next_task() call
+        # set it up by having something that approximates a real Builder
+        # return this list--but that's more work than is probably
+        # warranted right now.
+        t.targets = [n1, n2]
+        t.prepare()
+        assert n1.prepared
+        assert n2.prepared
+
 
 
 if __name__ == "__main__":
index 17ac8eb5c4aaeb2c9834c806a94078a8029f02c4..a79ed6e1bdc9b340e8dbce70588634689a765e80 100644 (file)
@@ -54,12 +54,13 @@ env.exit('exit.out', 'exit.in')
 
 stderr = """scons: \*\*\* \[exit.out\] Exception
 Traceback \((most recent call|innermost) last\):
-  File ".+", line \d+, in .+
-  File ".+", line \d+, in .+
-  File ".+", line \d+, in .+
-  File ".+", line \d+, in .+
-    .+
-.+
+  File ".+", line \d+, in \S+
+    [^\n]+
+  File ".+", line \d+, in \S+
+    [^\n]+
+  File ".+", line \d+, in \S+
+    [^\n]+
+\S.+
 """
 
 test.run(arguments='foo.out exit.out', stderr=stderr, status=2)
index b24e581cebc3846b59c047908c0cb9900f82f50e..f21fdfafcc386ef9e49235a03c5b212d8f01da77 100644 (file)
@@ -43,9 +43,10 @@ test.write('foo.in', "foo.in\n")
 
 test.run(arguments = "foo.out", stderr = """scons: \*\*\* \[foo.out\] Exception
 Traceback \((most recent call|innermost) last\):
-  File ".+", line \d+, in .+
-  File ".+", line \d+, in .+
-  File ".+", line \d+, in .+
+  File ".+", line \d+, in \S+
+    [^\n]+
+  File ".+", line \d+, in \S+
+    [^\n]+
   File "SConstruct", line 3, in func
     raise "func exception"
 func exception
index d70cfc995f1e0a0f277392ec6d6270a9f4ac5962..cf6c4e06d495c615f7adcaff0bf00a0b11504599 100644 (file)
@@ -163,7 +163,6 @@ cmdline = filter(lambda x: x[:23] == "Command execution time:", line)
 expected_command_time = num(r'Command execution time: (\d+\.\d+) seconds', cmdline[0])
 expected_command_time = expected_command_time + num(r'Command execution time: (\d+\.\d+) seconds', cmdline[1])
 expected_command_time = expected_command_time + num(r'Command execution time: (\d+\.\d+) seconds', cmdline[2])
-expected_command_time = expected_command_time + num(r'Command execution time: (\d+\.\d+) seconds', cmdline[3])
 
 totalline = filter(lambda x: x[:6] == "Total ", line)