Add a --debug= option to print commands before substitution. (Gary Oberbrunner)
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Tue, 23 Mar 2004 05:23:20 +0000 (05:23 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Tue, 23 Mar 2004 05:23:20 +0000 (05:23 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@928 fdb21ef1-2011-0410-befe-b5e4ea1792b1

doc/man/scons.1
src/engine/SCons/Action.py
src/engine/SCons/ActionTests.py
src/engine/SCons/Script/__init__.py
test/option--debug.py

index c12595ef0c5ababeda21b2f526304ecea1d6c408..3f8b2820cb015df5b0931b7b5001a148c25f6744 100644 (file)
@@ -494,11 +494,20 @@ This only works when run under Python 2.1 or later.
 Re-run SCons under the control of the
 .RI pdb
 Python debugger.
-The
---debug=pdb
-argument will be stripped from the command-line,
-but all other arguments will be passed in-order
-to the SCons invocation run by the debugger.
+.EE
+
+.TP
+--debug=presub
+Print the raw command line used to build each target
+before the construction environment variables are substituted.
+Also shows which targets are being built by this command.
+Output looks something like this:
+.ES
+$ scons --debug=presub
+Building myprog.o with action(s):
+  $SHCC $SHCCFLAGS $CPPFLAGS $_CPPINCFLAGS -c -o $TARGET $SOURCES
+...
+.EE
 
 .TP
 --debug=time
index 6759b02b98a07b2574304bcc21791e054af1730f..c074de4a064d128dccdb6966699f019fea19988e 100644 (file)
@@ -1,6 +1,54 @@
-"""engine.SCons.Action
-
-XXX
+"""SCons.Action
+
+This encapsulates information about executing any sort of action that
+can build one or more target Nodes (typically files) from one or more
+source Nodes (also typically files) given a specific Environment.
+
+The base class here is ActionBase.  The base class supplies just a few
+OO utility methods and some generic methods for displaying information
+about an Action in response to the various commands that control printing.
+
+The heavy lifting is handled by subclasses for the different types of
+actions we might execute:
+
+    CommandAction
+    CommandGeneratorAction
+    FunctionAction
+    ListAction
+
+The subclasses supply the following public interface methods used by
+other modules:
+
+    __call__()
+        THE public interface, "calling" an Action object executes the
+        command or Python function.  This also takes care of printing
+        a pre-substitution command for debugging purposes.
+
+    get_contents()
+        Fetches the "contents" of an Action for signature calculation.
+        This is what the Sig/*.py subsystem uses to decide if a target
+        needs to be rebuilt because its action changed.
+
+Subclasses also supply the following methods for internal use within
+this module:
+
+    __str__()
+        Returns a string representation of the Action *without* command
+        substitution.  This is used by the __call__() methods to display
+        the pre-substitution command whenever the --debug=presub option
+        is used.
+
+    strfunction()
+        Returns a substituted string representation of the Action.
+        This is used by the ActionBase.show() command to display the
+        command/function that will be executed to generate the target(s).
+
+    _execute()
+        The internal method that really, truly, actually handles the
+        execution of a command or Python function.  This is used so
+        that the __call__() methods can take care of displaying any
+        pre-substitution representations, and *then* execute an action
+        without worrying about the specific Actions involved.
 
 """
 
@@ -44,8 +92,9 @@ class _Null:
 
 _null = _Null
 
-print_actions = 1;
-execute_actions = 1;
+print_actions = 1
+execute_actions = 1
+print_actions_presub = 0
 
 default_ENV = None
 
@@ -145,9 +194,18 @@ class ActionBase:
     def __cmp__(self, other):
         return cmp(self.__dict__, other.__dict__)
 
-    def show(self, string):
+    def show(self, s):
         if print_actions:
-            sys.stdout.write(string + '\n')
+            sys.stdout.write(s + '\n')
+
+    def presub(self, target):
+        if print_actions_presub:
+            if not SCons.Util.is_List(target):
+                target = [target]
+            lines = string.split(str(self), '\n')
+            sys.stdout.write("Building %s with action(s):\n  %s\n"%
+                (string.join(map(lambda x: str(x), target), ' and '),
+                 string.join(lines, '\n  ')))
 
     def get_actions(self):
         return [self]
@@ -177,11 +235,14 @@ class CommandAction(ActionBase):
         if __debug__: logInstanceCreation(self)
         self.cmd_list = cmd
 
+    def __str__(self):
+        return str(self.cmd_list)
+
     def strfunction(self, target, source, env):
         cmd_list = env.subst_list(self.cmd_list, 0, target, source)
         return map(_string_from_cmd_list, cmd_list)
 
-    def __call__(self, target, source, env):
+    def _execute(self, target, source, env):
         """Execute a command action.
 
         This will handle lists of commands as well as individual commands,
@@ -265,15 +326,9 @@ class CommandAction(ActionBase):
                         return ret
         return 0
 
-    def get_raw_contents(self, target, source, env, dict=None):
-        """Return the complete contents of this action's command line.
-        """
-        cmd = self.cmd_list
-        if SCons.Util.is_List(cmd):
-            cmd = string.join(map(str, cmd))
-        else:
-            cmd = str(cmd)
-        return env.subst(cmd, SCons.Util.SUBST_RAW, target, source, dict)
+    def __call__(self, target, source, env):
+        self.presub(target)
+        return self._execute(target, source, env)
 
     def get_contents(self, target, source, env, dict=None):
         """Return the signature contents of this action's command line.
@@ -313,12 +368,24 @@ class CommandGeneratorAction(ActionBase):
         act = self.__generate(target, source, env, 0)
         return act.strfunction(target, rsources, env)
 
+    def __str__(self):
+        act = self.__generate([], [], {}, 0)
+        return str(act)
+
+    def _execute(self, target, source, env):
+        if not SCons.Util.is_List(source):
+            source = [source]
+        rsources = map(rfile, source)
+        act = self.__generate(target, source, env, 0)
+        return act._execute(target, rsources, env)
+
     def __call__(self, target, source, env):
         if not SCons.Util.is_List(source):
             source = [source]
         rsources = map(rfile, source)
         act = self.__generate(target, source, env, 0)
-        return act(target, rsources, env)
+        act.presub(target)
+        return act._execute(target, source, env)
 
     def get_contents(self, target, source, env, dict=None):
         """Return the signature contents of this action's command line.
@@ -329,10 +396,11 @@ class CommandGeneratorAction(ActionBase):
         return self.__generate(target, source, env, 1).get_contents(target, source, env, dict=None)
 
 class LazyCmdGenerator:
-    """This is a simple callable class that acts as a command generator.
-    It holds on to a key into an Environment dictionary, then waits
-    until execution time to see what type it is, then tries to
-    create an Action out of it."""
+    """This is not really an Action, although it kind of looks like one.
+    This is really a simple callable class that acts as a command
+    generator.  It holds on to a key into an Environment dictionary,
+    then waits until execution time to see what type it is, then tries
+    to create an Action out of it."""
     def __init__(self, var):
         if __debug__: logInstanceCreation(self)
         self.var = SCons.Util.to_String(var)
@@ -344,13 +412,16 @@ class LazyCmdGenerator:
             # The variable reference substitutes to nothing.
             return ''
 
-    def __call__(self, target, source, env, for_signature):
+    def _execute(self, target, source, env, for_signature):
         try:
             return env[self.var]
         except KeyError:
             # The variable reference substitutes to nothing.
             return ''
 
+    def __call__(self, target, source, env, for_signature):
+        return self._execute(target, source, env, for_signature)
+
     def __cmp__(self, other):
         return cmp(self.__dict__, other.__dict__)
 
@@ -361,25 +432,31 @@ class FunctionAction(ActionBase):
         if __debug__: logInstanceCreation(self)
         self.execfunction = execfunction
         if strfunction is _null:
-            def strfunction(target, source, env, execfunction=execfunction):
+            def strfunction(target, source, env, self=self):
                 def quote(s):
                     return '"' + str(s) + '"'
                 def array(a, q=quote):
                     return '[' + string.join(map(lambda x, q=q: q(x), a), ", ") + ']'
-                try:
-                    name = execfunction.__name__
-                except AttributeError:
-                    try:
-                        name = execfunction.__class__.__name__
-                    except AttributeError:
-                        name = "unknown_python_function"
+                name = self.function_name()
                 tstr = len(target) == 1 and quote(target[0]) or array(target)
                 sstr = len(source) == 1 and quote(source[0]) or array(source)
                 return "%s(%s, %s)" % (name, tstr, sstr)
         self.strfunction = strfunction
         self.varlist = varlist
 
-    def __call__(self, target, source, env):
+    def function_name(self):
+        try:
+            return self.execfunction.__name__
+        except AttributeError:
+            try:
+                return self.execfunction.__class__.__name__
+            except AttributeError:
+                return "unknown_python_function"
+
+    def __str__(self):
+        return "%s(env, target, source)" % self.function_name()
+
+    def _execute(self, target, source, env):
         r = 0
         if not SCons.Util.is_List(target):
             target = [target]
@@ -394,6 +471,10 @@ class FunctionAction(ActionBase):
             r = self.execfunction(target=target, source=rsources, env=env)
         return r
 
+    def __call__(self, target, source, env):
+        self.presub(target)
+        return self._execute(target, source, env)
+
     def get_contents(self, target, source, env, dict=None):
         """Return the signature contents of this callable action.
 
@@ -418,6 +499,12 @@ class ListAction(ActionBase):
     def get_actions(self):
         return self.list
 
+    def __str__(self):
+        s = []
+        for l in self.list:
+            s.append(str(l))
+        return string.join(s, "\n")
+
     def strfunction(self, target, source, env):
         s = []
         for l in self.list:
@@ -428,13 +515,17 @@ class ListAction(ActionBase):
                 s.extend(x)
         return string.join(s, "\n")
 
-    def __call__(self, target, source, env):
+    def _execute(self, target, source, env):
         for l in self.list:
-            r = l(target, source, env)
+            r = l._execute(target, source, env)
             if r:
                 return r
         return 0
 
+    def __call__(self, target, source, env):
+        self.presub(target)
+        return self._execute(target, source, env)
+
     def get_contents(self, target, source, env, dict=None):
         """Return the signature contents of this action list.
 
index b7006f9a6afe65f954da6979f56147690c4e4ba0..75c82ae39b04d89d5a746086e2cecfe3a9e3a542 100644 (file)
@@ -241,7 +241,7 @@ class ActionTestCase(unittest.TestCase):
 
 class ActionBaseTestCase(unittest.TestCase):
 
-    def test_cmp(self):
+    def test___cmp__(self):
         """Test Action comparison
         """
         a1 = SCons.Action.Action("x")
@@ -256,26 +256,58 @@ class ActionBaseTestCase(unittest.TestCase):
         """
         save_stdout = sys.stdout
 
-        save = SCons.Action.print_actions
+        save_print_actions = SCons.Action.print_actions
         SCons.Action.print_actions = 0
 
-        sio = StringIO.StringIO()
-        sys.stdout = sio
-        a = SCons.Action.Action("x")
-        a.show("xyzzy")
-        s = sio.getvalue()
-        assert s == "", s
+        try:
+            a = SCons.Action.Action("x")
+
+            sio = StringIO.StringIO()
+            sys.stdout = sio
+            a.show("xyzzy")
+            s = sio.getvalue()
+            assert s == "", s
+
+            SCons.Action.print_actions = 1
+
+            sio = StringIO.StringIO()
+            sys.stdout = sio
+            a.show("foobar")
+            s = sio.getvalue()
+            assert s == "foobar\n", s
+
+        finally:
+            SCons.Action.print_actions = save_print_actions
+            sys.stdout = save_stdout
+
+    def test_presub(self):
+        """Test the presub() method
+        """
+        save_stdout = sys.stdout
+
+        save_print_actions_presub = SCons.Action.print_actions_presub
+        SCons.Action.print_actions_presub = 0
+
+        try:
+            a = SCons.Action.Action("x")
+
+            sio = StringIO.StringIO()
+            sys.stdout = sio
+            a.presub("xyzzy")
+            s = sio.getvalue()
+            assert s == "", s
 
-        SCons.Action.print_actions = 1
+            SCons.Action.print_actions_presub = 1
 
-        sio = StringIO.StringIO()
-        sys.stdout = sio
-        a.show("foobar")
-        s = sio.getvalue()
-        assert s == "foobar\n", s
+            sio = StringIO.StringIO()
+            sys.stdout = sio
+            a.presub("foobar")
+            s = sio.getvalue()
+            assert s == "Building foobar with action(s):\n  x\n", s
 
-        SCons.Action.print_actions = save
-        sys.stdout = save_stdout
+        finally:
+            SCons.Action.print_actions_presub = save_print_actions_presub
+            sys.stdout = save_stdout
 
     def test_get_actions(self):
         """Test the get_actions() method
@@ -363,12 +395,26 @@ class ActionBaseTestCase(unittest.TestCase):
 
 class CommandActionTestCase(unittest.TestCase):
 
-    def test_init(self):
+    def test___init__(self):
         """Test creation of a command Action
         """
         a = SCons.Action.CommandAction(["xyzzy"])
         assert a.cmd_list == [ "xyzzy" ], a.cmd_list
 
+    def test___str__(self):
+        """Test fetching the pre-substitution string for command Actions
+        """
+        env = Environment()
+        act = SCons.Action.CommandAction('xyzzy $TARGET $SOURCE')
+        s = str(act)
+        assert s == 'xyzzy $TARGET $SOURCE', s
+
+        act = SCons.Action.CommandAction(['xyzzy',
+                                          '$TARGET', '$SOURCE',
+                                          '$TARGETS', '$SOURCES'])
+        s = str(act)
+        assert s == "['xyzzy', '$TARGET', '$SOURCE', '$TARGETS', '$SOURCES']", s
+
     def test_strfunction(self):
         """Test fetching the string representation of command Actions
         """
@@ -526,7 +572,6 @@ class CommandActionTestCase(unittest.TestCase):
         r = act([], [], env.Copy(out = outfile))
         assert r == expect_nonexecutable, "r == %d" % r
 
-
     def test_pipe_execute(self):
         """Test capturing piped output from an action
         """
@@ -635,67 +680,6 @@ class CommandActionTestCase(unittest.TestCase):
         a([], [], e)
         assert t.executed == [ '**xyzzy**' ], t.executed
 
-    def test_get_raw_contents(self):
-        """Test fetching the contents of a command Action
-        """
-        def CmdGen(target, source, env, for_signature):
-            assert for_signature
-            return "%s %s" % \
-                   (env["foo"], env["bar"])
-
-        # The number 1 is there to make sure all args get converted to strings.
-        a = SCons.Action.CommandAction(["|", "$(", "$foo", "|", "$bar",
-                                        "$)", "|", "$baz", 1])
-        c = a.get_raw_contents(target=[], source=[],
-                               env=Environment(foo = 'FFF', bar = 'BBB',
-                                               baz = CmdGen))
-        assert c == "| $( FFF | BBB $) | FFF BBB 1", c
-
-        # We've discusssed using the real target and source names in a
-        # CommandAction's signature contents.  This would have have the
-        # advantage of recompiling when a file's name changes (keeping
-        # debug info current), but it would currently break repository
-        # logic that will change the file name based on whether the
-        # files come from a repository or locally.  If we ever move to
-        # that scheme, then all of the '__t1__' and '__s6__' file names
-        # in the asserts below would change to 't1' and 's6' and the
-        # like.
-        t = map(DummyNode, ['t1', 't2', 't3', 't4', 't5', 't6'])
-        s = map(DummyNode, ['s1', 's2', 's3', 's4', 's5', 's6'])
-        env = Environment()
-
-        a = SCons.Action.CommandAction(["$TARGET"])
-        c = a.get_raw_contents(target=t, source=s, env=env)
-        assert c == "t1", c
-
-        a = SCons.Action.CommandAction(["$TARGETS"])
-        c = a.get_raw_contents(target=t, source=s, env=env)
-        assert c == "t1 t2 t3 t4 t5 t6", c
-
-        a = SCons.Action.CommandAction(["${TARGETS[2]}"])
-        c = a.get_raw_contents(target=t, source=s, env=env)
-        assert c == "t3", c
-
-        a = SCons.Action.CommandAction(["${TARGETS[3:5]}"])
-        c = a.get_raw_contents(target=t, source=s, env=env)
-        assert c == "t4 t5", c
-
-        a = SCons.Action.CommandAction(["$SOURCE"])
-        c = a.get_raw_contents(target=t, source=s, env=env)
-        assert c == "s1", c
-
-        a = SCons.Action.CommandAction(["$SOURCES"])
-        c = a.get_raw_contents(target=t, source=s, env=env)
-        assert c == "s1 s2 s3 s4 s5 s6", c
-
-        a = SCons.Action.CommandAction(["${SOURCES[2]}"])
-        c = a.get_raw_contents(target=t, source=s, env=env)
-        assert c == "s3", c
-
-        a = SCons.Action.CommandAction(["${SOURCES[3:5]}"])
-        c = a.get_raw_contents(target=t, source=s, env=env)
-        assert c == "s4 s5", c
-
     def test_get_contents(self):
         """Test fetching the contents of a command Action
         """
@@ -767,7 +751,7 @@ class CommandActionTestCase(unittest.TestCase):
 
 class CommandGeneratorActionTestCase(unittest.TestCase):
 
-    def test_init(self):
+    def test___init__(self):
         """Test creation of a command generator Action
         """
         def f(target, source, env):
@@ -775,6 +759,15 @@ class CommandGeneratorActionTestCase(unittest.TestCase):
         a = SCons.Action.CommandGeneratorAction(f)
         assert a.generator == f
 
+    def test___str__(self):
+        """Test the pre-substitution strings for command generator Actions
+        """
+        def f(target, source, env, for_signature, self=self):
+            return "FOO"
+        a = SCons.Action.CommandGeneratorAction(f)
+        s = str(a)
+        assert s == 'FOO', s
+
     def test_strfunction(self):
         """Test the command generator Action string function
         """
@@ -866,7 +859,7 @@ class CommandGeneratorActionTestCase(unittest.TestCase):
 
 class FunctionActionTestCase(unittest.TestCase):
 
-    def test_init(self):
+    def test___init__(self):
         """Test creation of a function Action
         """
         def func1():
@@ -894,6 +887,22 @@ class FunctionActionTestCase(unittest.TestCase):
         assert a.execfunction == func4, a.execfunction
         assert a.strfunction is None, a.strfunction
 
+    def test___str__(self):
+        """Test the __str__() method for function Actions
+        """
+        def func1():
+            pass
+        a = SCons.Action.FunctionAction(func1)
+        s = str(a)
+        assert s == "func1(env, target, source)", s
+
+        class class1:
+            def __call__(self):
+                pass
+        a = SCons.Action.FunctionAction(class1())
+        s = str(a)
+        assert s == "class1(env, target, source)", s
+
     def test_execute(self):
         """Test executing a function Action
         """
@@ -984,7 +993,7 @@ class FunctionActionTestCase(unittest.TestCase):
 
 class ListActionTestCase(unittest.TestCase):
 
-    def test_init(self):
+    def test___init__(self):
         """Test creation of a list of subsidiary Actions
         """
         def func():
@@ -1008,6 +1017,17 @@ class ListActionTestCase(unittest.TestCase):
         g = l[1].get_actions()
         assert g == [l[1]], g
 
+    def test___str__(self):
+        """Test the __str__() method ffor a list of subsidiary Actions
+        """
+        def f(target,source,env):
+            pass
+        def g(target,source,env):
+            pass
+        a = SCons.Action.ListAction([f, g, "XXX", f])
+        s = str(a)
+        assert s == "f(env, target, source)\ng(env, target, source)\nXXX\nf(env, target, source)", s
+
     def test_strfunction(self):
         """Test the string function for a list of subsidiary Actions
         """
@@ -1069,7 +1089,7 @@ class ListActionTestCase(unittest.TestCase):
         assert c == "xyz", c
 
 class LazyActionTestCase(unittest.TestCase):
-    def test_init(self):
+    def test___init__(self):
         """Test creation of a lazy-evaluation Action
         """
         # Environment variable references should create a special
index 47fef2b6191377a60bc12abcfe267a11a436d24e..c59ce69a7911d3e3fdabf21e4e846c17240cd723 100644 (file)
@@ -409,6 +409,8 @@ def _set_globals(options):
                 memory_outf = sys.stdout
             elif options.debug == "objects":
                 print_objects = 1
+            elif options.debug == "presub":
+                SCons.Action.print_actions_presub = 1
             elif options.debug == "time":
                 print_time = 1
             elif options.debug == "tree":
@@ -492,7 +494,8 @@ class OptParser(OptionParser):
                              "build all Default() targets.")
 
         def opt_debug(option, opt, value, parser):
-            if value in ["count", "dtree", "includes", "memory", "objects", "pdb", "time", "tree"]:
+            if value in ["count", "dtree", "includes", "memory", "objects",
+                         "pdb", "presub", "time", "tree"]:
                 setattr(parser.values, 'debug', value)
             else:
                 raise OptionValueError("Warning:  %s is not a valid debug type" % value)
index dfa55036be1f5c29d44152380a2bbbfb01a41a93..089317081f1f2c3a5bf9615428b0ba8cf955c2b0 100644 (file)
@@ -230,5 +230,158 @@ else:
     # output at this point.
     test.run(arguments = "--debug=objects")
 
+############################
+# test --debug=presub
+
+test.write('cat.py', """\
+import sys
+open(sys.argv[2], "wb").write(open(sys.argv[1], "rb").read())
+sys.exit(0)
+""")
+
+test.write('SConstruct', """\
+def cat(env, source, target):
+    target = str(target[0])
+    source = map(str, source)
+    f = open(target, "wb")
+    for src in source:
+        f.write(open(src, "rb").read())
+    f.close()
+FILE = Builder(action="$FILECOM")
+TEMP = Builder(action="$TEMPCOM")
+LIST = Builder(action="$LISTCOM")
+FUNC = Builder(action=cat)
+env = Environment(PYTHON='%s',
+                  BUILDERS = {'FILE':FILE, 'TEMP':TEMP, 'LIST':LIST, 'FUNC':FUNC},
+                  FILECOM="$PYTHON cat.py $SOURCES $TARGET",
+                  TEMPCOM="$PYTHON cat.py $SOURCES temp\\n$PYTHON cat.py temp $TARGET",
+                  LISTCOM=["$PYTHON cat.py $SOURCES temp", "$PYTHON cat.py temp $TARGET"],
+                  FUNCCOM=cat)
+env.Command('file01.out', 'file01.in', "$FILECOM")
+env.Command('file02.out', 'file02.in', ["$FILECOM"])
+env.Command('file03.out', 'file03.in', "$TEMPCOM")
+env.Command('file04.out', 'file04.in', ["$TEMPCOM"])
+env.Command('file05.out', 'file05.in', "$LISTCOM")
+env.Command('file06.out', 'file06.in', ["$LISTCOM"])
+env.Command('file07.out', 'file07.in', cat)
+env.Command('file08.out', 'file08.in', "$FUNCCOM")
+env.Command('file09.out', 'file09.in', ["$FUNCCOM"])
+env.FILE('file11.out', 'file11.in')
+env.FILE('file12.out', 'file12.in')
+env.TEMP('file13.out', 'file13.in')
+env.TEMP('file14.out', 'file14.in')
+env.LIST('file15.out', 'file15.in')
+env.LIST('file16.out', 'file16.in')
+env.FUNC('file17.out', 'file17.in')
+env.FUNC('file18.out', 'file18.in')
+""" % TestSCons.python)
+
+test.write('file01.in', "file01.in\n")
+test.write('file02.in', "file02.in\n")
+test.write('file03.in', "file03.in\n")
+test.write('file04.in', "file04.in\n")
+test.write('file05.in', "file05.in\n")
+test.write('file06.in', "file06.in\n")
+test.write('file07.in', "file07.in\n")
+test.write('file08.in', "file08.in\n")
+test.write('file09.in', "file09.in\n")
+test.write('file11.in', "file11.in\n")
+test.write('file12.in', "file12.in\n")
+test.write('file13.in', "file13.in\n")
+test.write('file14.in', "file14.in\n")
+test.write('file15.in', "file15.in\n")
+test.write('file16.in', "file16.in\n")
+test.write('file17.in', "file17.in\n")
+test.write('file18.in', "file18.in\n")
+
+expect = """\
+Building file01.out with action(s):
+  $PYTHON cat.py $SOURCES $TARGET
+__PYTHON__ cat.py file01.in file01.out
+Building file02.out with action(s):
+  $PYTHON cat.py $SOURCES $TARGET
+__PYTHON__ cat.py file02.in file02.out
+Building file03.out with action(s):
+  $PYTHON cat.py $SOURCES temp
+  $PYTHON cat.py temp $TARGET
+__PYTHON__ cat.py file03.in temp
+__PYTHON__ cat.py temp file03.out
+Building file04.out with action(s):
+  $PYTHON cat.py $SOURCES temp
+  $PYTHON cat.py temp $TARGET
+__PYTHON__ cat.py file04.in temp
+__PYTHON__ cat.py temp file04.out
+Building file05.out with action(s):
+  $PYTHON cat.py $SOURCES temp
+  $PYTHON cat.py temp $TARGET
+__PYTHON__ cat.py file05.in temp
+__PYTHON__ cat.py temp file05.out
+Building file06.out with action(s):
+  $PYTHON cat.py $SOURCES temp
+  $PYTHON cat.py temp $TARGET
+__PYTHON__ cat.py file06.in temp
+__PYTHON__ cat.py temp file06.out
+Building file07.out with action(s):
+  cat(env, target, source)
+cat("file07.out", "file07.in")
+Building file08.out with action(s):
+  cat(env, target, source)
+cat("file08.out", "file08.in")
+Building file09.out with action(s):
+  cat(env, target, source)
+cat("file09.out", "file09.in")
+Building file11.out with action(s):
+  $PYTHON cat.py $SOURCES $TARGET
+__PYTHON__ cat.py file11.in file11.out
+Building file12.out with action(s):
+  $PYTHON cat.py $SOURCES $TARGET
+__PYTHON__ cat.py file12.in file12.out
+Building file13.out with action(s):
+  $PYTHON cat.py $SOURCES temp
+  $PYTHON cat.py temp $TARGET
+__PYTHON__ cat.py file13.in temp
+__PYTHON__ cat.py temp file13.out
+Building file14.out with action(s):
+  $PYTHON cat.py $SOURCES temp
+  $PYTHON cat.py temp $TARGET
+__PYTHON__ cat.py file14.in temp
+__PYTHON__ cat.py temp file14.out
+Building file15.out with action(s):
+  $PYTHON cat.py $SOURCES temp
+  $PYTHON cat.py temp $TARGET
+__PYTHON__ cat.py file15.in temp
+__PYTHON__ cat.py temp file15.out
+Building file16.out with action(s):
+  $PYTHON cat.py $SOURCES temp
+  $PYTHON cat.py temp $TARGET
+__PYTHON__ cat.py file16.in temp
+__PYTHON__ cat.py temp file16.out
+Building file17.out with action(s):
+  cat(env, target, source)
+cat("file17.out", "file17.in")
+Building file18.out with action(s):
+  cat(env, target, source)
+cat("file18.out", "file18.in")
+"""
+expect = string.replace(expect, '__PYTHON__', TestSCons.python)
+test.run(arguments = "--debug=presub .", stdout=test.wrap_stdout(expect))
+
+test.must_match('file01.out', "file01.in\n")
+test.must_match('file02.out', "file02.in\n")
+test.must_match('file03.out', "file03.in\n")
+test.must_match('file04.out', "file04.in\n")
+test.must_match('file05.out', "file05.in\n")
+test.must_match('file06.out', "file06.in\n")
+test.must_match('file07.out', "file07.in\n")
+test.must_match('file08.out', "file08.in\n")
+test.must_match('file09.out', "file09.in\n")
+test.must_match('file11.out', "file11.in\n")
+test.must_match('file12.out', "file12.in\n")
+test.must_match('file13.out', "file13.in\n")
+test.must_match('file14.out', "file14.in\n")
+test.must_match('file15.out', "file15.in\n")
+test.must_match('file16.out', "file16.in\n")
+test.must_match('file17.out', "file17.in\n")
+test.must_match('file18.out', "file18.in\n")
 
 test.pass_test()