Commits

Anonymous committed bfd35d4

Refactor FunctionAction objects to support -n and -s.

Comments (0)

Files changed (19)

     # this routine will change when the version number changes
     # and things will get rebuilt properly.
     global version
-    print "SCons_revision() < %s > %s" % (s, t)
     inf = open(s, 'rb')
     outf = open(t, 'wb')
     for line in inf.readlines():
 rather than let each separate Builder object
 create a separate Action.
 
-The Action method takes a single argument
+The Action method takes one or two arguments
 and returns an appropriate object for the action
-represented by the type of the argument:
+represented by the type of the first argument:
 
 .IP Action
-If the argument is already an Action object,
+If the first argument is already an Action object,
 the object is simply returned.
 
 .IP String
-If the argument is a string,
+If the first argument is a string,
 a command-line Action is returned.
 
 .ES
 
 
 .IP List
-If the argument is a list,
+If the first argument is a list,
 then a list of Action objects is returned.
 An Action object is created as necessary
 for each element in the list.
 .EE
 
 .IP Function
-If the argument is a Python function,
+If the first argument is a Python function,
 a function Action is returned.
 The Python function takes three keyword arguments,
 .B target
  
 a = Action(build_it)
 .EE
+
+The second, optional argument
+is a Python function that returns
+a string to be printed describing the action being executed.
+This function takes two arguments,
+an array of targets to be created by the function action,
+and an array of sources used to create the target(s):
+
+def build_it(target, source, env):
+    # build the target from the source
+    return 0
+
+def string_it(target, source):
+    return "building '%s' from '%s'" % (target[0], source[0])
+
+a = Action(build_it, string_it)
 .PP
 If the action argument is not one of the above,
 None is returned.
 
   - Remove Python bytecode (*.pyc) files from the scons-local packages.
 
+  - Have FunctionActions print a description of what they're doing
+    (a representation of the Python call).
+
+  - Fix the Install() method so that, like other actions, it prints
+    what would have happened when the -n option is used.
+
   From Steve Leblanc:
 
   - Add a Clean() method to support removing user-specified targets
       consistent.  All error messages now begin with "scons: ***"
       and all warning messages now begin with "scons: warning:".
 
+    - SCons now prints a description of Python functions that are
+      invoked to build a target.
+
   Please note the following important changes since release 0.08:
 
     - The SetCommandHandler() function has been superceded

src/engine/SCons/Action.py

 import sys
 import UserDict
 
+import SCons.Errors
 import SCons.Util
-import SCons.Errors
 
+class _Null:
+    pass
+
+_null = _Null
 
 print_actions = 1;
 execute_actions = 1;
     def __init__(self, generator):
         self.generator = generator
 
-def _do_create_action(act):
+def _do_create_action(act, strfunction=_null):
     """This is the actual "implementation" for the
     Action factory method, below.  This handles the
     fact that passing lists to Action() itself has
     elif isinstance(act, CommandGenerator):
         return CommandGeneratorAction(act.generator)
     elif callable(act):
-        return FunctionAction(act)
+        return FunctionAction(act, strfunction=strfunction)
     elif SCons.Util.is_String(act):
         var=SCons.Util.get_environment_var(act)
         if var:
     else:
         return None
 
-def Action(act):
+def Action(act, strfunction=_null):
     """A factory for action objects."""
     if SCons.Util.is_List(act):
-        acts = filter(lambda x: not x is None,
-                      map(_do_create_action, act))
+        acts = map(lambda x, s=strfunction: _do_create_action(x, s), act)
+        acts = filter(lambda x: not x is None, acts)
         if len(acts) == 1:
             return acts[0]
         else:
             return ListAction(acts)
     else:
-        return _do_create_action(act)
+        return _do_create_action(act, strfunction=strfunction)
 
 class ActionBase:
     """Base class for actions that create output objects."""
         handle lists of commands, even though that's not how we use it
         externally.
         """
+        import SCons.Util
+
         escape = env.get('ESCAPE', lambda x: x)
 
-        import SCons.Errors
-        
         if env.has_key('SHELL'):
             shell = env['SHELL']
         else:
         self.generator = generator
 
     def __generate(self, target, source, env, for_signature):
-        import SCons.Util
-
         # ensure that target is a list, to make it easier to write
         # generator functions:
         if not SCons.Util.is_List(target):
 
 class FunctionAction(ActionBase):
     """Class for Python function actions."""
-    def __init__(self, function):
-        self.function = function
+
+    def __init__(self, execfunction, strfunction=_null):
+        self.execfunction = execfunction
+        if strfunction is _null:
+            def strfunction(target, source, execfunction=execfunction):
+                def quote(s):
+                    return '"' + str(s) + '"'
+                try:
+                    name = execfunction.__name__
+                except AttributeError:
+                    try:
+                        name = execfunction.__class__.__name__
+                    except AttributeError:
+                        name = "unknown_python_function"
+                if len(target) == 1:
+                    tstr = quote(target[0])
+                else:
+                    tstr = str(map(lambda x, q=quote: q(x), target))
+                if len(source) == 1:
+                    sstr = quote(source[0])
+                else:
+                    sstr = str(map(lambda x, q=quote: q(x), source))
+                return "%s(%s, %s)" % (name, tstr, sstr)
+        self.strfunction = strfunction
 
     def execute(self, target, source, env):
-        # if print_actions:
-        # XXX:  WHAT SHOULD WE PRINT HERE?
+        r = 0
+        if not SCons.Util.is_List(target):
+            target = [target]
+        if not SCons.Util.is_List(source):
+            source = [source]
+        if print_actions and self.strfunction:
+            s = self.strfunction(target, source)
+            if s:
+                self.show(s)
         if execute_actions:
-            if not SCons.Util.is_List(target):
-                target = [target]
-
-            if not SCons.Util.is_List(source):
-                source = [source]
             rsources = map(rfile, source)
-
-            return self.function(target=target, source=rsources, env=env)
+            r = self.execfunction(target=target, source=rsources, env=env)
+        return r
 
     def get_contents(self, target, source, env):
         """Return the signature contents of this callable action.
         #XXX DOES NOT ACCOUNT FOR CHANGES IN ENVIRONMENT VARIABLES
         #THE FUNCTION MAY USE
         try:
-            # "self.function" is a function.
-            code = self.function.func_code.co_code
+            # "self.execfunction" is a function.
+            code = self.execfunction.func_code.co_code
         except:
-            # "self.function" is a callable object.
-            code = self.function.__call__.im_func.func_code.co_code
+            # "self.execfunction" is a callable object.
+            code = self.execfunction.__call__.im_func.func_code.co_code
         return str(code)
 
 class ListAction(ActionBase):
 
         Simple concatenation of the signatures of the elements.
         """
-
-        ret = ""
-        for a in self.list:
-            ret = ret + a.get_contents(target, source, env)
-        return ret
-
+        return string.join(map(lambda x, t=target, s=source, e=env:
+                                      x.get_contents(t, s, e),
+                               self.list),
+                           "")

src/engine/SCons/ActionTests.py

         """
         def foo():
             pass
+        def bar():
+            pass
         a1 = SCons.Action.Action(foo)
         assert isinstance(a1, SCons.Action.FunctionAction), a1
-        assert a1.function == foo, a1.function
+        assert a1.execfunction == foo, a1.execfunction
 
         a2 = SCons.Action.Action("string")
         assert isinstance(a2, SCons.Action.CommandAction), a2
         assert isinstance(a10.list[0], SCons.Action.CommandAction), a10.list[0]
         assert a10.list[0].cmd_list == ["x"], a10.list[0].cmd_list
         assert isinstance(a10.list[1], SCons.Action.FunctionAction), a10.list[1]
-        assert a10.list[1].function == foo, a10.list[1].function
+        assert a10.list[1].execfunction == foo, a10.list[1].execfunction
         assert isinstance(a10.list[2], SCons.Action.CommandAction), a10.list[2]
         assert a10.list[2].cmd_list == ["z"], a10.list[2].cmd_list
 
+        a11 = SCons.Action.Action(foo, strfunction=bar)
+        assert isinstance(a11, SCons.Action.FunctionAction), a11
+        assert a11.execfunction == foo, a11.execfunction
+        assert a11.strfunction == bar, a11.strfunction
+
 class ActionBaseTestCase(unittest.TestCase):
 
     def test_cmp(self):
     def test_init(self):
         """Test creation of a function Action
         """
-        def func():
+        def func1():
             pass
-        a = SCons.Action.FunctionAction(func)
-        assert a.function == func
+        def func2():
+            pass
+        def func3():
+            pass
+        def func4():
+            pass
+
+        a = SCons.Action.FunctionAction(func1)
+        assert a.execfunction == func1, a.execfunction
+        assert isinstance(a.strfunction, types.FunctionType)
+
+        a = SCons.Action.FunctionAction(func2, strfunction=func3)
+        assert a.execfunction == func2, a.execfunction
+        assert a.strfunction == func3, a.strfunction
+
+        a = SCons.Action.FunctionAction(func3, func4)
+        assert a.execfunction == func3, a.execfunction
+        assert a.strfunction == func4, a.strfunction
+
+        a = SCons.Action.FunctionAction(func4, None)
+        assert a.execfunction == func4, a.execfunction
+        assert a.strfunction is None, a.strfunction
 
     def test_execute(self):
         """Test executing a function Action
         c = test.read(outfile, 'r')
         assert c == "class1b\n", c
 
+        def build_it(target, source, env, self=self):
+            self.build_it = 1
+            return 0
+        def string_it(target, source, self=self):
+            self.string_it = 1
+            return None
+        act = SCons.Action.FunctionAction(build_it, string_it)
+        r = act.execute([], [], Environment())
+        assert r == 0, r
+        assert self.build_it
+        assert self.string_it
+
     def test_get_contents(self):
         """Test fetching the contents of a function Action
         """
                  CommandGeneratorActionTestCase,
                  FunctionActionTestCase,
                  ListActionTestCase,
-                 LazyActionTestCase]
+                 LazyActionTestCase ]
     for tclass in tclasses:
         names = unittest.getTestCaseNames(tclass, 'test_')
         suite.addTests(map(tclass, names))

src/engine/SCons/BuilderTests.py

 import unittest
 
 import TestCmd
+
+import SCons.Action
 import SCons.Builder
 import SCons.Errors
 import SCons.Node.FS
         def func():
             pass
         builder = SCons.Builder.Builder(name="builder", action=func)
-        assert builder.action.function == func
+        assert isinstance(builder.action, SCons.Action.FunctionAction)
+        # Preserve the following so that the baseline test will fail.
+        # Remove it in favor of the previous test at some convenient
+        # point in the future.
+        assert builder.action.execfunction == func
 
     def test_generator(self):
         """Test Builder creation given a generator function."""

src/engine/SCons/Environment.py

 import string
 import sys
 import types
+from UserDict import UserDict
 
+import SCons.Action
 import SCons.Builder
 import SCons.Defaults
-from SCons.Errors import UserError
+import SCons.Errors
 import SCons.Node
 import SCons.Node.FS
+import SCons.Platform
+import SCons.Tool
 import SCons.Util
 import SCons.Warnings
-from UserDict import UserDict
-import SCons.Platform
-import SCons.Tool
 
-def installFunc(target, source, env):
-    try:
-        map(lambda t: os.unlink(str(t)), target)
-    except OSError:
-        pass
+def installString(target, source):
+    return 'Install file: "%s" as "%s"' % (source[0], target[0])
 
-    try:
-        SCons.Node.FS.file_link(str(source[0]), str(target[0]))
-        print 'Install file: "%s" as "%s"' % \
-              (source[0], target[0])
-        return 0
-    except IOError, e:
-        sys.stderr.write('Unable to install "%s" as "%s"\n%s\n' % \
-                         (source[0], target[0], str(e)))
-        return -1
-    except OSError, e:
-        sys.stderr.write('Unable to install "%s" as "%s"\n%s\n' % \
-                         (source[0], target[0], str(e)))
-        return -1
+installAction = SCons.Action.Action(SCons.Node.FS.LinkFunc, installString)
 
-InstallBuilder = SCons.Builder.Builder(name='Install',
-                                       action=installFunc)
+InstallBuilder = SCons.Builder.Builder(name='Install', action=installAction)
 
 def our_deepcopy(x):
    """deepcopy lists and dictionaries, and just copy the reference
                 for name, builder in bd.items():
                     setattr(self, name, BuilderWrapper(self, builder))
             else:
-                raise UserError, "The use of the BUILDERS Environment variable as a list or Builder instance is deprecated.  BUILDERS should be a dictionary of name->Builder instead."
+                raise SCons.Errors.UserError, "The use of the BUILDERS Environment variable as a list or Builder instance is deprecated.  BUILDERS should be a dictionary of name->Builder instead."
         for s in self._dict['SCANNERS']:
             setattr(self, s.name, s)
         
 	    # buildable without actually having a builder, so we allow
 	    # it to be a side effect as well.
             if side_effect.builder is not None and side_effect.builder != 1:
-                raise UserError, "Multiple ways to build the same target were specified for: %s" % str(side_effect)
+                raise SCons.Errors.UserError, "Multiple ways to build the same target were specified for: %s" % str(side_effect)
             side_effect.add_source(targets)
             side_effect.side_effect = 1
             self.Precious(side_effect)

src/engine/SCons/Node/FS.py

 
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
-import string
 import os
 import os.path
+import string
+import sys
 import types
+from UserDict import UserDict
+
+import SCons.Action
+import SCons.Errors
 import SCons.Node
-from UserDict import UserDict
-import sys
-import SCons.Errors
 import SCons.Warnings
 
-execute_actions = 1
+#
+# SCons.Action objects for interacting with the outside world.
+#
+# The Node.FS methods in this module should use these actions to
+# create and/or remove files and directories; they should *not* use
+# os.{link,symlink,unlink,mkdir}(), etc., directly.
+#
+# Using these SCons.Action objects ensures that descriptions of these
+# external activities are properly displayed, that the displays are
+# suppressed when the -s (silent) option is used, and (most importantly)
+# the actions are disabled when the the -n option is used, in which case
+# there should be *no* changes to the external file system(s)...
+#
 
-def file_link(src, dest):
+def LinkFunc(target, source, env):
+    src = source[0].path
+    dest = target[0].path
     dir, file = os.path.split(dest)
     if dir and not os.path.isdir(dir):
         os.makedirs(dir)
     # fails, try a symlink.  If that fails then just copy it.
     try :
         os.link(src, dest)
-    except (AttributeError, OSError) :
+    except (AttributeError, OSError):
         try :
             os.symlink(src, dest)
-        except (AttributeError, OSError) :
+        except (AttributeError, OSError):
             import shutil
             import stat
             shutil.copy2(src, dest)
             st=os.stat(src)
             os.chmod(dest, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
+    return 0
 
+LinkAction = SCons.Action.Action(LinkFunc, None)
 
+def LocalString(target, source):
+    return 'Local copy of %s from %s' % (target[0], source[0])
+
+LocalCopy = SCons.Action.Action(LinkFunc, LocalString)
+
+def UnlinkFunc(target, source, env):
+    os.unlink(target[0].path)
+    return 0
+
+UnlinkAction = SCons.Action.Action(UnlinkFunc, None)
+
+def MkdirFunc(target, source, env):
+    os.mkdir(target[0].path)
+    return 0
+
+MkdirAction = SCons.Action.Action(MkdirFunc, None)
+
+#
 class ParentOfRoot:
     """
     An instance of this class is used as the parent of the root of a
             if isinstance(p, ParentOfRoot):
                 raise SCons.Errors.StopError, parent.path
             parent = p
-        if not execute_actions:
-            return
         listDirs.reverse()
         for dirnode in listDirs:
+            dirnode._exists = 1
             try:
-                os.mkdir(dirnode.abspath)
-                dirnode._exists = 1
+                MkdirAction.execute(dirnode, None, None)
             except OSError:
                 pass
 
 
         if self.exists():
             if self.builder and not self.precious:
-                if execute_actions:
-                    os.unlink(self.path)
+                UnlinkAction.execute(self, None, None)
                 if hasattr(self, '_exists'):
                     delattr(self, '_exists')
         else:
         if self.duplicate and not self.builder and not self.created:
             src=self.srcnode().rfile()
             if src.exists() and src.abspath != self.abspath:
+                self._createDir()
                 try:
-                    os.unlink(self.abspath)
+                    UnlinkAction.execute(self, None, None)
                 except OSError:
                     pass
-                self._createDir()
-                file_link(src.abspath, self.abspath)
+                LinkAction.execute(self, src, None)
                 self.created = 1
 
                 # Set our exists cache accordingly
                     # ...and it's even up-to-date...
                     if self._local:
                         # ...and they'd like a local copy.
-                        print "Local copy of %s from %s" % (self.path, r.path)
-                        if execute_actions:
-                            file_link(r.path, self.path)
+                        LocalCopy.execute(self, r, None)
                         self.set_bsig(bsig)
                         self.store_bsig()
                     return 1

src/engine/SCons/Node/FSTests.py

     def runTest(self):
         """Test build dir functionality"""
         test=TestCmd(workdir='')
-        os.chdir(test.workdir)
 
         fs = SCons.Node.FS.FS()
         f1 = fs.File('build/test1')
         test.write([ 'rep1', 'build', 'var1', 'test2.out' ], 'test2.out_rep')
         test.write([ 'rep1', 'build', 'var2', 'test2.out' ], 'test2.out_rep')
 
+        os.chdir(test.workpath('work'))
+
         fs = SCons.Node.FS.FS(test.workpath('work'))
         fs.BuildDir('build/var1', 'src', duplicate=0)
         fs.BuildDir('build/var2', 'src')
         assert f8.rfile().path == os.path.normpath(test.workpath('rep1/build/var2/test2.out')),\
                f8.rfile().path
         
-        # Test to see if file_link() works...
+        # Test to see if LinkAction() works...
         test.subdir('src','build')
-        test.write('src/foo', 'foo\n')
+        test.write('src/foo', 'src/foo\n')
         os.chmod(test.workpath('src/foo'), stat.S_IRUSR)
-        SCons.Node.FS.file_link(test.workpath('src/foo'),
-                                test.workpath('build/foo'))
+        SCons.Node.FS.LinkAction.execute(fs.File(test.workpath('build/foo')),
+                                         fs.File(test.workpath('src/foo')),
+                                         None)
         os.chmod(test.workpath('src/foo'), stat.S_IRUSR | stat.S_IWRITE)
         st=os.stat(test.workpath('build/foo'))
         assert (stat.S_IMODE(st[stat.ST_MODE]) & stat.S_IWRITE), \
         os.symlink = simulator.symlink_fail
         shutil.copy2 = simulator.copy
 
-        # XXX this is just to pass the baseline test, it won't be needed once
-        # this change is integrated
-        SCons.Node.FS._link = simulator.link_fail
-
-        test.write('src/foo', 'foo\n')
+        test.write('src/foo', 'src/foo\n')
         os.chmod(test.workpath('src/foo'), stat.S_IRUSR)
-        SCons.Node.FS.file_link(test.workpath('src/foo'),
-                                test.workpath('build/foo'))
+        SCons.Node.FS.LinkAction.execute(fs.File(test.workpath('build/foo')),
+                                         fs.File(test.workpath('src/foo')),
+                                         None)
         test.unlink( "src/foo" )
         test.unlink( "build/foo" )
 

src/engine/SCons/Script/__init__.py

 #                         'lib',
 #                         'scons-%d' % SCons.__version__)] + sys.path[1:]
 
+import SCons.Errors
+import SCons.Job
 import SCons.Node
 import SCons.Node.FS
-import SCons.Job
-from SCons.Errors import *
+from SCons.Optik import OptionParser, SUPPRESS_HELP, OptionValueError
+import SCons.Script.SConscript
 import SCons.Sig
-from SCons.Taskmaster import Taskmaster
-import SCons.Builder
-import SCons.Script.SConscript
+import SCons.Taskmaster
+from SCons.Util import display
 import SCons.Warnings
-from SCons.Optik import OptionParser, SUPPRESS_HELP, OptionValueError
-from SCons.Util import display
 
 
 #
 
     def failed(self):
         e = sys.exc_value
-        if sys.exc_type == BuildError:
+        if sys.exc_type == SCons.Errors.BuildError:
             sys.stderr.write("scons: *** [%s] %s\n" % (e.node, e.errstr))
             if e.errstr == 'Exception':
                 traceback.print_exception(e.args[0], e.args[1], e.args[2])
-        elif sys.exc_type == UserError:
+        elif sys.exc_type == SCons.Errors.UserError:
             # We aren't being called out of a user frame, so
             # don't try to walk the stack, just print the error.
             sys.stderr.write("\nscons: *** %s\n" % e)
-        elif sys.exc_type == StopError:
+        elif sys.exc_type == SCons.Errors.StopError:
             s = str(e)
             if not keep_going_on_error:
                 s = s + '  Stop.'
         _setup_warn(options.warn)
     if options.noexec:
         SCons.Action.execute_actions = None
-        SCons.Node.FS.execute_actions = None
         CleanTask.execute = CleanTask.show
     if options.no_progress or options.silent:
         display.set_mode(0)
             display("scons: Entering directory %s" % script_dir)
             os.chdir(script_dir)
         else:
-            raise UserError, "No SConstruct file found."
+            raise SCons.Errors.UserError, "No SConstruct file found."
 
     SCons.Node.FS.default_fs.set_toplevel_dir(os.getcwd())
 
         SCons.Script.SConscript.print_help = 1
 
     if not scripts:
-        raise UserError, "No SConstruct file found."
+        raise SCons.Errors.UserError, "No SConstruct file found."
 
     class Unbuffered:
         def __init__(self, file):
         sys.exit(2)
     except SyntaxError, e:
         _scons_syntax_error(e)
-    except UserError, e:
+    except SCons.Errors.UserError, e:
         _scons_user_error(e)
     except:
         _scons_other_errors()

test/SetBuildSignatureType.py

 env = Environment()
 
 def copy1(env, source, target):
-    print 'copy %s -> %s'%(str(source[0]), str(target[0]))
     open(str(target[0]), 'wb').write(open(str(source[0]), 'rb').read())
 
 def copy2(env, source, target):
 test.write('foo.in', 'foo.in')
 
 test.run(arguments='foo.out.out',
-         stdout=test.wrap_stdout('copy foo.in -> foo.out\ncopy foo.out -> foo.out.out\n'))
+         stdout=test.wrap_stdout("""\
+copy2("foo.out", "foo.in")
+copy1("foo.out.out", "foo.out")
+"""))
 
 test.run(arguments='foo.out.out',
          stdout=test.wrap_stdout('scons: "foo.out.out" is up to date.\n'))
 env = Environment()
 
 def copy1(env, source, target):
-    print 'copy %s -> %s'%(str(source[0]), str(target[0]))
     open(str(target[0]), 'wb').write(open(str(source[0]), 'rb').read())
 
 def copy2(env, source, target):
 """)
 
 test.run(arguments='foo.out.out',
-         stdout=test.wrap_stdout('copy foo.in -> foo.out\nscons: "foo.out.out" is up to date.\n'))
+         stdout=test.wrap_stdout("""\
+copy2("foo.out", "foo.in")
+scons: "foo.out.out" is up to date.
+"""))
 
 test.write('SConstruct', """
 env = Environment()
 
 def copy1(env, source, target):
-    print 'copy %s -> %s'%(str(source[0]), str(target[0]))
     open(str(target[0]), 'wb').write(open(str(source[0]), 'rb').read())
 
 def copy2(env, source, target):
 """)
 
 test.run(arguments='foo.out.out',
-         stdout=test.wrap_stdout('copy foo.out -> foo.out.out\n'))
+         stdout=test.wrap_stdout("""\
+copy1("foo.out.out", "foo.out")
+"""))
 
 test.write('SConstruct', """
 env = Environment()
 
 def copy1(env, source, target):
-    print 'copy %s -> %s'%(str(source[0]), str(target[0]))
     open(str(target[0]), 'wb').write(open(str(source[0]), 'rb').read())
 
 def copy2(env, source, target):
 """)
 
 test.run(arguments='foo.out.out',
-         stdout=test.wrap_stdout('copy foo.in -> foo.out\ncopy foo.out -> foo.out.out\n'))
+         stdout=test.wrap_stdout("""\
+copy2("foo.out", "foo.in")
+copy1("foo.out.out", "foo.out")
+"""))
 
 
 test.pass_test()

test/SetContentSignatureType.py

 test.run(arguments = 'f1.out f3.out')
 
 test.run(arguments = 'f1.out f2.out f3.out f4.out',
-         stdout = test.wrap_stdout('scons: "f1.out" is up to date.\nscons: "f3.out" is up to date.\n'))
+         stdout = test.wrap_stdout("""\
+scons: "f1.out" is up to date.
+build("f2.out", "f2.in")
+scons: "f3.out" is up to date.
+build("f4.out", "f4.in")
+"""))
 
 os.utime(test.workpath('f1.in'), 
          (os.path.getatime(test.workpath('f1.in')),
           os.path.getmtime(test.workpath('f3.in'))+10))
 
 test.run(arguments = 'f1.out f2.out f3.out f4.out',
-         stdout = test.wrap_stdout('scons: "f2.out" is up to date.\nscons: "f4.out" is up to date.\n'))
+         stdout = test.wrap_stdout("""\
+build("f1.out", "f1.in")
+scons: "f2.out" is up to date.
+build("f3.out", "f3.in")
+scons: "f4.out" is up to date.
+"""))
 
 test.write('SConstruct', """
 def build(env, target, source):
 test.run(arguments = 'f1.out f3.out')
 
 test.run(arguments = 'f1.out f2.out f3.out f4.out',
-         stdout = test.wrap_stdout('scons: "f1.out" is up to date.\nscons: "f3.out" is up to date.\n'))
+         stdout = test.wrap_stdout("""\
+scons: "f1.out" is up to date.
+build("f2.out", "f2.in")
+scons: "f3.out" is up to date.
+build("f4.out", "f4.in")
+"""))
 
 os.utime(test.workpath('f1.in'), 
          (os.path.getatime(test.workpath('f1.in')),
 """)
 
 test.run(arguments = 'f1.out f2.out f3.out f4.out',
-         stdout = test.wrap_stdout('scons: "f1.out" is up to date.\nscons: "f2.out" is up to date.\nscons: "f3.out" is up to date.\nscons: "f4.out" is up to date.\n'))
+         stdout = test.wrap_stdout("""\
+scons: "f1.out" is up to date.
+scons: "f2.out" is up to date.
+scons: "f3.out" is up to date.
+scons: "f4.out" is up to date.
+"""))
 
 test.pass_test()
 

test/SideEffect.py

 test.write('SConstruct', 
 """
 def copy(source, target):
-    print 'copy() < %s > %s' % (source, target)
     open(target, "wb").write(open(source, "rb").read())
 
 def build(env, source, target):
 test.write('blat.in', 'blat.in\n')
 
 test.run(arguments = 'foo.out bar.out', stdout=test.wrap_stdout("""\
-copy() < foo.in > foo.out
-copy() < bar.in > bar.out
+build("foo.out", "foo.in")
+build("bar.out", "bar.in")
 """))
 
 expect = """\
 test.write('bar.in', 'bar.in 2 \n')
 
 test.run(arguments = 'log.txt', stdout=test.wrap_stdout("""\
-copy() < bar.in > bar.out
-copy() < blat.in > blat.out
+build("bar.out", "bar.in")
+build("blat.out", "blat.in")
 """))
 
 expect = """\
 test.write('foo.in', 'foo.in 2 \n')
 
 test.run(arguments = ".", stdout=test.wrap_stdout("""\
-copy() < foo.in > foo.out
-copy() < log.txt > log.out
+build("foo.out", "foo.in")
+build("log.out", "log.txt")
 """))
 
 expect = """\
 test.fail_test(os.path.exists(test.workpath('log.txt')))
 
 test.run(arguments = "-j 4 .", stdout=test.wrap_stdout("""\
-copy() < bar.in > bar.out
-copy() < blat.in > blat.out
-copy() < foo.in > foo.out
-copy() < log.txt > log.out
+build("bar.out", "bar.in")
+build("blat.out", "blat.in")
+build("foo.out", "foo.in")
+build("log.out", "log.txt")
 """))
 
 expect = """\
 import os
 
 def copy(source, target):
-    print 'copy() < %s > %s' % (source, target)
     open(target, "wb").write(open(source, "rb").read())
 
 def build(env, source, target):

test/chained-build.py

 test.write('SConstruct1', """
 def build(env, target, source):
     open(str(target[0]), 'wt').write(open(str(source[0]), 'rt').read())
-    print "built %s"%target[0]
 
 env=Environment(BUILDERS={'B' : Builder(action=build)})
 env.B('foo.mid', 'foo.in')
 test.write('SConstruct2', """
 def build(env, target, source):
     open(str(target[0]), 'wt').write(open(str(source[0]), 'rt').read())
-    print "built %s"%target[0]
 
 env=Environment(BUILDERS={'B' : Builder(action=build)})
 env.B('foo.out', 'foo.mid')
 test.write('foo.in', "foo.in")
 
 test.run(arguments="--max-drift=0 -f SConstruct1 foo.mid",
-         stdout = test.wrap_stdout('built foo.mid\n'))
+         stdout = test.wrap_stdout('build("foo.mid", "foo.in")\n'))
 test.run(arguments="--max-drift=0 -f SConstruct2 foo.out",
-         stdout = test.wrap_stdout('built foo.out\n'))
+         stdout = test.wrap_stdout('build("foo.out", "foo.mid")\n'))
 
 test.run(arguments="--max-drift=0 -f SConstruct1 foo.mid",
          stdout = test.wrap_stdout('scons: "foo.mid" is up to date.\n'))
 test.write('foo.in', "foo.in 2")
 
 test.run(arguments="--max-drift=0 -f SConstruct1 foo.mid",
-         stdout = test.wrap_stdout('built foo.mid\n'))
+         stdout = test.wrap_stdout('build("foo.mid", "foo.in")\n'))
 test.run(arguments="--max-drift=0 -f SConstruct2 foo.out",
-         stdout = test.wrap_stdout('built foo.out\n'))
+         stdout = test.wrap_stdout('build("foo.out", "foo.mid")\n'))
 
 test.run(arguments="--max-drift=0 -f SConstruct1 foo.mid",
          stdout = test.wrap_stdout('scons: "foo.mid" is up to date.\n'))
 test.write(['src', 'f4.in'], "src/f4.in\n")
 
 args = 'f1.out f2.out'
-expect = test.wrap_stdout("%s build.py f1.out\n%s build.py f2.out\n" % (python, python))
+expect = test.wrap_stdout("""\
+%s build.py f1.out
+%s build.py f2.out
+""" % (python, python))
 
 test.run(arguments = args, stdout = expect)
 test.fail_test(not os.path.exists(test.workpath('f1.out')))
 test.fail_test(not os.path.exists(test.workpath('f1.out')))
 test.fail_test(not os.path.exists(test.workpath('f2.out')))
 
-# XXX Because Install is a function action, it doesn't know how
-# to print what's going on when -n is used.  Following the
-# directions on the XXX lines below whenever that gets fixed.
 #
 install_f3_in = os.path.join('install', 'f3.in')
-# XXX Uncomment the next line and remove the one after it when we
-# fix the Install print during -n.
-#expect = test.wrap_stdout('Install file: "f3.in" as "%s"\n' % install_f3_in)
-expect = test.wrap_stdout('')
+expect = test.wrap_stdout('Install file: "f3.in" as "%s"\n' % install_f3_in)
 
 test.run(arguments = '-n install', stdout = expect)
 test.fail_test(os.path.exists(test.workpath('install', 'f3.in')))
 
-# XXX Remove the next line when we fix the Install print during -n.
-expect = test.wrap_stdout('Install file: "f3.in" as "%s"\n' % install_f3_in)
-
 test.run(arguments = 'install', stdout = expect)
 test.fail_test(not os.path.exists(test.workpath('install', 'f3.in')))
 
 test.write('f3.in', "f3.in again\n")
 
-# XXX Remove the next line when we fix the Install print during -n.
-expect = test.wrap_stdout('')
-
 test.run(arguments = '-n install', stdout = expect)
 test.fail_test(not os.path.exists(test.workpath('install', 'f3.in')))
 

test/scan-once.py

 
 test.run(arguments = '.',
          stdout = test.wrap_stdout("""scanning file1.s for file2.s
+echo("file2.s", "file1.s")
 create file2.s from file1.s
 scanning file1.s for file2.s
+echo("file3.s", "file2.s")
 create file3.s from file2.s
+echo("file4.s", "file3.s")
 create file4.s from file3.s
 """))
 
 test.run(arguments = '.',
          stdout = test.wrap_stdout("""scanning file1.s for file2.s
 scanning file2.s for file3.s
+echo("file3.s", "file2.s")
 create file3.s from file2.s
 scanning file2.s for file3.s
+echo("file4.s", "file3.s")
 create file4.s from file3.s
 """))
 
          stdout = test.wrap_stdout("""scanning file1.s for file2.s
 scanning file2.s for file3.s
 scanning file3.s for file4.s
+echo("file4.s", "file3.s")
 create file4.s from file3.s
 """))
 
 
 test.write('SConstruct', """
 def build1(target, source, env):
-    print '%s->%s'%(str(source[0]), str(target[0]))
     open(str(target[0]), 'wb').write(open(str(source[0]), 'rb').read())
     return None
 
 .*
 '''
 
-stdout = test.wrap_stdout('foo.in->sub1.foo.out\n')
+stdout = test.wrap_stdout('build1\("sub1/foo.out", "foo.in"\)\n')
 
 test.write(sub1__sconsign, 'not:a:sconsign:file')
 test.run(arguments = '.', stderr=stderr, stdout=stdout, status=2)

test/timestamp-fallback.py

 test.run(arguments = 'f1.out f3.out')
 
 test.run(arguments = 'f1.out f2.out f3.out f4.out',
-         stdout = test.wrap_stdout('scons: "f1.out" is up to date.\nscons: "f3.out" is up to date.\n'))
+         stdout = test.wrap_stdout("""\
+scons: "f1.out" is up to date.
+build("f2.out", "f2.in")
+scons: "f3.out" is up to date.
+build("f4.out", "f4.in")
+"""))
 
 os.utime(test.workpath('f1.in'), 
          (os.path.getatime(test.workpath('f1.in')),
           os.path.getmtime(test.workpath('f3.in'))+10))
 
 test.run(arguments = 'f1.out f2.out f3.out f4.out',
-         stdout = test.wrap_stdout('scons: "f2.out" is up to date.\nscons: "f4.out" is up to date.\n'))
+         stdout = test.wrap_stdout("""\
+build("f1.out", "f1.in")
+scons: "f2.out" is up to date.
+build("f3.out", "f3.in")
+scons: "f4.out" is up to date.
+"""))
 
 
 test.pass_test()