summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSteven Knight <knight@baldmt.com>2004-03-23 05:23:20 (GMT)
committerSteven Knight <knight@baldmt.com>2004-03-23 05:23:20 (GMT)
commit6cb2c3b91699f1221810f3d1a20bcf3fc6c4e148 (patch)
tree9f6d601ec3b50d626b2612a2230c81050d235d5e
parent837e39eaf030cafcf619ddb390cc8a5854784185 (diff)
downloadSCons-6cb2c3b91699f1221810f3d1a20bcf3fc6c4e148.zip
SCons-6cb2c3b91699f1221810f3d1a20bcf3fc6c4e148.tar.gz
SCons-6cb2c3b91699f1221810f3d1a20bcf3fc6c4e148.tar.bz2
Add a --debug= option to print commands before substitution. (Gary Oberbrunner)
-rw-r--r--doc/man/scons.119
-rw-r--r--src/engine/SCons/Action.py159
-rw-r--r--src/engine/SCons/ActionTests.py186
-rw-r--r--src/engine/SCons/Script/__init__.py5
-rw-r--r--test/option--debug.py153
5 files changed, 399 insertions, 123 deletions
diff --git a/doc/man/scons.1 b/doc/man/scons.1
index c12595e..3f8b282 100644
--- a/doc/man/scons.1
+++ b/doc/man/scons.1
@@ -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
diff --git a/src/engine/SCons/Action.py b/src/engine/SCons/Action.py
index 6759b02..c074de4 100644
--- a/src/engine/SCons/Action.py
+++ b/src/engine/SCons/Action.py
@@ -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.
diff --git a/src/engine/SCons/ActionTests.py b/src/engine/SCons/ActionTests.py
index b7006f9..75c82ae 100644
--- a/src/engine/SCons/ActionTests.py
+++ b/src/engine/SCons/ActionTests.py
@@ -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
diff --git a/src/engine/SCons/Script/__init__.py b/src/engine/SCons/Script/__init__.py
index 47fef2b..c59ce69 100644
--- a/src/engine/SCons/Script/__init__.py
+++ b/src/engine/SCons/Script/__init__.py
@@ -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)
diff --git a/test/option--debug.py b/test/option--debug.py
index dfa5503..0893170 100644
--- a/test/option--debug.py
+++ b/test/option--debug.py
@@ -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()