Really support strfunction for all Action subclasses by refactoring the interface...
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Mon, 3 May 2004 04:45:01 +0000 (04:45 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Mon, 3 May 2004 04:45:01 +0000 (04:45 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@966 fdb21ef1-2011-0410-befe-b5e4ea1792b1

src/engine/SCons/Action.py
src/engine/SCons/ActionTests.py
src/engine/SCons/Builder.py
src/engine/SCons/Executor.py
src/engine/SCons/ExecutorTests.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
test/strfunction.py [new file with mode: 0644]

index 37665b92e74296e5cbd153c048755506e3c290d9..c95644aed984202804436b3b8b43b064a292b403 100644 (file)
@@ -51,7 +51,7 @@ this module:
         This is used by the ActionBase.show() command to display the
         command/function that will be executed to generate the target(s).
 
-    _execute()
+    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
@@ -207,29 +207,53 @@ def Action(act, strfunction=_null, varlist=[]):
 
 class ActionBase:
     """Base class for actions that create output objects."""
+    def __init__(self, strfunction=_null, **kw):
+        if not strfunction is _null:
+            self.strfunction = strfunction
+
     def __cmp__(self, other):
         return cmp(self.__dict__, other.__dict__)
 
-    def show(self, s):
-        if print_actions:
-            sys.stdout.write(s + '\n')
-
-    def presub(self, target, env):
-        if print_actions_presub:
-            if not SCons.Util.is_List(target):
-                target = [target]
-            # CommandGeneratorAction needs a real environment
-            # in order to return the proper string here, since
-            # it may call LazyCmdGenerator, which looks up a key
-            # in that env.  So we temporarily remember the env here,
-            # and CommandGeneratorAction will use this env
-            # when it calls its __generate method.
-            self.presub_env = env
-            lines = string.split(str(self), '\n')
-            self.presub_env = None      # don't need this any more
-            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 __call__(self, target, source, env,
+                               errfunc=None,
+                               presub=_null,
+                               show=_null,
+                               execute=_null):
+        if not SCons.Util.is_List(target):
+            target = [target]
+        if not SCons.Util.is_List(source):
+            source = [source]
+        if presub is _null:  presub = print_actions_presub
+        if show is _null:  show = print_actions
+        if execute is _null:  execute = execute_actions
+        if presub:
+            t = string.join(map(str, target), 'and')
+            l = string.join(self.presub(env), '\n  ')
+            out = "Building %s with action(s):\n  %s\n" % (t, l)
+            sys.stdout.write(out)
+        if show and self.strfunction:
+            s = self.strfunction(target, source, env)
+            if s:
+                sys.stdout.write(s + '\n')
+        if execute:
+            stat = self.execute(target, source, env)
+            if stat and errfunc:
+                errfunc(stat)
+            return stat
+        else:
+            return 0
+
+    def presub(self, env):
+        # CommandGeneratorAction needs a real environment
+        # in order to return the proper string here, since
+        # it may call LazyCmdGenerator, which looks up a key
+        # in that env.  So we temporarily remember the env here,
+        # and CommandGeneratorAction will use this env
+        # when it calls its __generate method.
+        self.presub_env = env
+        lines = string.split(str(self), '\n')
+        self.presub_env = None      # don't need this any more
+        return lines
 
     def genstring(self, target, source, env):
         return str(self)
@@ -255,23 +279,22 @@ def _string_from_cmd_list(cmd_list):
 
 class CommandAction(ActionBase):
     """Class for command-execution actions."""
-    def __init__(self, cmd, strfunction=_null, varlist=[]):
+    def __init__(self, cmd, **kw):
         # Cmd list can actually be a list or a single item...basically
         # anything that we could pass in as the first arg to
         # Environment.subst_list().
         if __debug__: logInstanceCreation(self)
+        apply(ActionBase.__init__, (self,), kw)
         self.cmd_list = cmd
-        if not strfunction is _null:
-            self.strfunction = strfunction
 
     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)
+        return string.join(map(_string_from_cmd_list, cmd_list), "\n")
 
-    def _execute(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,
@@ -315,50 +338,43 @@ class CommandAction(ActionBase):
         cmd_list = env.subst_list(self.cmd_list, 0, target, source)
         for cmd_line in cmd_list:
             if len(cmd_line):
-                if print_actions:
-                    self.show(_string_from_cmd_list(cmd_line))
-                if execute_actions:
-                    try:
-                        ENV = env['ENV']
-                    except KeyError:
-                        global default_ENV
-                        if not default_ENV:
-                            import SCons.Environment
-                            default_ENV = SCons.Environment.Environment()['ENV']
-                        ENV = default_ENV
-
-                    # ensure that the ENV values are all strings:
-                    for key, value in ENV.items():
-                        if SCons.Util.is_List(value):
-                            # If the value is a list, then we assume
-                            # it is a path list, because that's a pretty
-                            # common list like value to stick in an environment
-                            # variable:
-                            ENV[key] = string.join(map(str, value), os.pathsep)
-                        elif not SCons.Util.is_String(value):
-                            # If it isn't a string or a list, then
-                            # we just coerce it to a string, which
-                            # is proper way to handle Dir and File instances
-                            # and will produce something reasonable for
-                            # just about everything else:
-                            ENV[key] = str(value)
-
-                    # Escape the command line for the command
-                    # interpreter we are using
-                    cmd_line = SCons.Util.escape_list(cmd_line, escape)
-                    if pipe_build:
-                        ret = pspawn( shell, escape, cmd_line[0], cmd_line,
-                                      ENV, pstdout, pstderr )
-                    else:
-                        ret = spawn(shell, escape, cmd_line[0], cmd_line, ENV)
-                    if ret:
-                        return ret
+                try:
+                    ENV = env['ENV']
+                except KeyError:
+                    global default_ENV
+                    if not default_ENV:
+                        import SCons.Environment
+                        default_ENV = SCons.Environment.Environment()['ENV']
+                    ENV = default_ENV
+
+                # ensure that the ENV values are all strings:
+                for key, value in ENV.items():
+                    if SCons.Util.is_List(value):
+                        # If the value is a list, then we assume
+                        # it is a path list, because that's a pretty
+                        # common list like value to stick in an environment
+                        # variable:
+                        ENV[key] = string.join(map(str, value), os.pathsep)
+                    elif not SCons.Util.is_String(value):
+                        # If it isn't a string or a list, then
+                        # we just coerce it to a string, which
+                        # is proper way to handle Dir and File instances
+                        # and will produce something reasonable for
+                        # just about everything else:
+                        ENV[key] = str(value)
+
+                # Escape the command line for the command
+                # interpreter we are using
+                cmd_line = SCons.Util.escape_list(cmd_line, escape)
+                if pipe_build:
+                    ret = pspawn( shell, escape, cmd_line[0], cmd_line,
+                                  ENV, pstdout, pstderr )
+                else:
+                    ret = spawn(shell, escape, cmd_line[0], cmd_line, ENV)
+                if ret:
+                    return ret
         return 0
 
-    def __call__(self, target, source, env):
-        self.presub(target, env)
-        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.
 
@@ -374,11 +390,10 @@ class CommandAction(ActionBase):
 
 class CommandGeneratorAction(ActionBase):
     """Class for command-generator actions."""
-    def __init__(self, generator, strfunction=_null, varlist=[]):
+    def __init__(self, generator, **kw):
         if __debug__: logInstanceCreation(self)
+        apply(ActionBase.__init__, (self,), kw)
         self.generator = generator
-        if not strfunction is _null:
-            self.strfunction = strfunction
 
     def __generate(self, target, source, env, for_signature):
         # ensure that target is a list, to make it easier to write
@@ -410,20 +425,10 @@ class CommandGeneratorAction(ActionBase):
     def genstring(self, target, source, env):
         return str(self.__generate(target, source, env, 0))
 
-    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]
+    def execute(self, target, source, env):
         rsources = map(rfile, source)
         act = self.__generate(target, source, env, 0)
-        act.presub(target, env)
-        return act._execute(target, source, env)
+        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.
@@ -453,37 +458,24 @@ class LazyCmdGenerator:
     def __str__(self):
         return 'LazyCmdGenerator: %s'%str(self.var)
 
-    def _execute(self, target, source, env, for_signature):
+    def __call__(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__)
 
 class FunctionAction(ActionBase):
     """Class for Python function actions."""
 
-    def __init__(self, execfunction, strfunction=_null, varlist=[]):
+    def __init__(self, execfunction, **kw):
         if __debug__: logInstanceCreation(self)
         self.execfunction = execfunction
-        if strfunction is _null:
-            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), ", ") + ']'
-                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
+        apply(ActionBase.__init__, (self,), kw)
+        self.varlist = kw.get('varlist', [])
 
     def function_name(self):
         try:
@@ -494,27 +486,22 @@ class FunctionAction(ActionBase):
             except AttributeError:
                 return "unknown_python_function"
 
+    def strfunction(self, target, source, env):
+        def quote(s):
+            return '"' + str(s) + '"'
+        def array(a, q=quote):
+            return '[' + string.join(map(lambda x, q=q: q(x), a), ", ") + ']'
+        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)
+
     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]
-        if not SCons.Util.is_List(source):
-            source = [source]
-        if print_actions and self.strfunction:
-            s = self.strfunction(target, source, env)
-            if s:
-                self.show(s)
-        if execute_actions:
-            rsources = map(rfile, source)
-            r = self.execfunction(target=target, source=rsources, env=env)
-        return r
-
-    def __call__(self, target, source, env):
-        self.presub(target, env)
-        return self._execute(target, source, env)
+    def execute(self, target, source, env):
+        rsources = map(rfile, source)
+        return self.execfunction(target=target, source=rsources, env=env)
 
     def get_contents(self, target, source, env, dict=None):
         """Return the signature contents of this callable action.
@@ -543,11 +530,10 @@ class FunctionAction(ActionBase):
 
 class ListAction(ActionBase):
     """Class for lists of other actions."""
-    def __init__(self, list, strfunction=_null, varlist=[]):
+    def __init__(self, list, **kw):
         if __debug__: logInstanceCreation(self)
+        apply(ActionBase.__init__, (self,), kw)
         self.list = map(lambda x: Action(x), list)
-        if not strfunction is _null:
-            self.strfunction = strfunction
 
     def get_actions(self):
         return self.list
@@ -568,17 +554,13 @@ class ListAction(ActionBase):
                 s.extend(x)
         return string.join(s, "\n")
 
-    def _execute(self, target, source, env):
+    def execute(self, target, source, env):
         for l in self.list:
-            r = l._execute(target, source, env)
+            r = l.execute(target, source, env)
             if r:
                 return r
         return 0
 
-    def __call__(self, target, source, env):
-        self.presub(target, env)
-        return self._execute(target, source, env)
-
     def get_contents(self, target, source, env, dict=None):
         """Return the signature contents of this action list.
 
index 3def8574674944efa8933f847fed66bfdd2fe6e5..b3d7e8c818a2ecdfddffcdc38561beee0fe57ca4 100644 (file)
@@ -320,6 +320,26 @@ class ActionTestCase(unittest.TestCase):
 
 class ActionBaseTestCase(unittest.TestCase):
 
+    def test__init__(self):
+        """Test creation of ActionBase objects
+        """
+
+        def func():
+            pass
+
+        a = SCons.Action.ActionBase()
+        assert not hasattr(a, 'strfunction')
+
+        assert SCons.Action.ActionBase(kwarg = 1)
+        assert not hasattr(a, 'strfunction')
+        assert not hasattr(a, 'kwarg')
+
+        a = SCons.Action.ActionBase(func)
+        assert a.strfunction is func, a.strfunction
+
+        a = SCons.Action.ActionBase(strfunction=func)
+        assert a.strfunction is func, a.strfunction
+
     def test___cmp__(self):
         """Test Action comparison
         """
@@ -330,112 +350,115 @@ class ActionBaseTestCase(unittest.TestCase):
         assert a1 != a3
         assert a2 != a3
 
-    def test_show(self):
-        """Test the show() method
+    def test___call__(self):
+        """Test calling an Action
         """
         save_stdout = sys.stdout
 
         save_print_actions = SCons.Action.print_actions
-        SCons.Action.print_actions = 0
-
-        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
+        save_execute_actions = SCons.Action.execute_actions
+        #SCons.Action.print_actions = 0
 
         try:
-            a = SCons.Action.Action("x")
             env = Environment()
 
-            sio = StringIO.StringIO()
-            sys.stdout = sio
-            a.presub("xyzzy", env)
-            s = sio.getvalue()
-            assert s == "", s
-
-            SCons.Action.print_actions_presub = 1
+            def execfunc(target, source, env):
+                assert type(target) is type([]), type(target)
+                assert type(source) is type([]), type(source)
+                return 7
+            a = SCons.Action.Action(execfunc)
 
             sio = StringIO.StringIO()
             sys.stdout = sio
-            a.presub("foobar", env)
+            result = a("out", "in", env)
+            assert result == 7, result
             s = sio.getvalue()
-            assert s == "Building foobar with action(s):\n  x\n", s
+            assert s == 'execfunc("out", "in")\n', s
 
-            a = SCons.Action.Action(["y", "z"])
+            SCons.Action.execute_actions = 0
 
             sio = StringIO.StringIO()
             sys.stdout = sio
-            a.presub("foobar", env)
+            result = a("out", "in", env)
+            assert result == 0, result
             s = sio.getvalue()
-            assert s == "Building foobar with action(s):\n  y\n  z\n", s
+            assert s == 'execfunc("out", "in")\n', s
 
-            def func():
-                pass
-            a = SCons.Action.Action(func)
+            SCons.Action.print_actions_presub = 1
 
             sio = StringIO.StringIO()
             sys.stdout = sio
-            a.presub("foobar", env)
+            result = a("out", "in", env)
+            assert result == 0, result
             s = sio.getvalue()
-            assert s == "Building foobar with action(s):\n  func(env, target, source)\n", s
-
-            def gen(target, source, env, for_signature):
-                return 'generat' + env.get('GEN', 'or')
-            a = SCons.Action.Action(SCons.Action.CommandGenerator(gen))
+            assert s == 'Building out with action(s):\n  execfunc(env, target, source)\nexecfunc("out", "in")\n', s
 
             sio = StringIO.StringIO()
             sys.stdout = sio
-            a.presub("foobar", env)
+            result = a("out", "in", env, presub=0)
+            assert result == 0, result
             s = sio.getvalue()
-            assert s == "Building foobar with action(s):\n  generator\n", s
+            assert s == 'execfunc("out", "in")\n', s
 
             sio = StringIO.StringIO()
             sys.stdout = sio
-            a.presub("foobar", Environment(GEN = 'ed'))
+            result = a("out", "in", env, presub=0, execute=1, show=0)
+            assert result == 7, result
             s = sio.getvalue()
-            assert s == "Building foobar with action(s):\n  generated\n", s
+            assert s == '', s
+
+            sys.stdout = save_stdout
+            errfunc_result = []
 
-            a = SCons.Action.Action("$ACT")
+            def errfunc(stat, result=errfunc_result):
+                result.append(stat)
 
-            sio = StringIO.StringIO()
-            sys.stdout = sio
-            a.presub("foobar", env)
-            s = sio.getvalue()
-            assert s == "Building foobar with action(s):\n  \n", s
+            result = a("out", "in", env, errfunc=errfunc)
+            assert result == 0, result
+            assert errfunc_result == [], errfunc_result
 
-            sio = StringIO.StringIO()
-            sys.stdout = sio
-            a.presub("foobar", Environment(ACT = 'expanded action'))
-            s = sio.getvalue()
-            assert s == "Building foobar with action(s):\n  expanded action\n", s
+            result = a("out", "in", env, execute=1, errfunc=errfunc)
+            assert result == 7, result
+            assert errfunc_result == [7], errfunc_result
 
         finally:
-            SCons.Action.print_actions_presub = save_print_actions_presub
             sys.stdout = save_stdout
+            SCons.Action.print_actions = save_print_actions
+            SCons.Action.print_actions_presub = save_print_actions_presub
+            SCons.Action.execute_actions = save_execute_actions
+
+    def test_presub(self):
+        """Test the presub() method
+        """
+        env = Environment()
+        a = SCons.Action.Action("x")
+        s = a.presub(env)
+        assert s == ['x'], s
+
+        a = SCons.Action.Action(["y", "z"])
+        s = a.presub(env)
+        assert s == ['y', 'z'], s
+
+        def func():
+            pass
+        a = SCons.Action.Action(func)
+        s = a.presub(env)
+        assert s == ["func(env, target, source)"], s
+
+        def gen(target, source, env, for_signature):
+            return 'generat' + env.get('GEN', 'or')
+        a = SCons.Action.Action(SCons.Action.CommandGenerator(gen))
+        s = a.presub(env)
+        assert s == ["generator"], s
+        s = a.presub(Environment(GEN = 'ed'))
+        assert s == ["generated"], s
+
+        a = SCons.Action.Action("$ACT")
+        s = a.presub(env)
+        assert s == [''], s
+        s = a.presub(Environment(ACT = 'expanded action'))
+        assert s == ['expanded action'], s
 
     def test_get_actions(self):
         """Test the get_actions() method
@@ -592,29 +615,29 @@ class CommandActionTestCase(unittest.TestCase):
         s2 = DummyNode('s2')
         act = SCons.Action.CommandAction('xyzzy $TARGET $SOURCE')
         s = act.strfunction([], [], env)
-        assert s == ['xyzzy'], s
+        assert s == 'xyzzy', s
         s = act.strfunction([t1], [s1], env)
-        assert s == ['xyzzy t1 s1'], s
+        assert s == 'xyzzy t1 s1', s
         s = act.strfunction([t1, t2], [s1, s2], env)
-        assert s == ['xyzzy t1 s1'], s
+        assert s == 'xyzzy t1 s1', s
 
         act = SCons.Action.CommandAction('xyzzy $TARGETS $SOURCES')
         s = act.strfunction([], [], env)
-        assert s == ['xyzzy'], s
+        assert s == 'xyzzy', s
         s = act.strfunction([t1], [s1], env)
-        assert s == ['xyzzy t1 s1'], s
+        assert s == 'xyzzy t1 s1', s
         s = act.strfunction([t1, t2], [s1, s2], env)
-        assert s == ['xyzzy t1 t2 s1 s2'], s
+        assert s == 'xyzzy t1 t2 s1 s2', s
 
         act = SCons.Action.CommandAction(['xyzzy',
                                           '$TARGET', '$SOURCE',
                                           '$TARGETS', '$SOURCES'])
         s = act.strfunction([], [], env)
-        assert s == ['xyzzy'], s
+        assert s == 'xyzzy', s
         s = act.strfunction([t1], [s1], env)
-        assert s == ['xyzzy t1 s1 t1 s1'], s
+        assert s == 'xyzzy t1 s1 t1 s1', s
         s = act.strfunction([t1, t2], [s1, s2], env)
-        assert s == ['xyzzy t1 s1 t1 t2 s1 s2'], s
+        assert s == 'xyzzy t1 s1 t1 t2 s1 s2', s
 
         def sf(target, source, env):
             return "sf was called"
@@ -975,7 +998,7 @@ class CommandGeneratorActionTestCase(unittest.TestCase):
         self.dummy = 0
         s = a.strfunction([], [], env=Environment(FOO='xyzzy', dummy=1))
         assert self.dummy == 1, self.dummy
-        assert s == ['xyzzy'], s
+        assert s == 'xyzzy', s
 
         def sf(target, source, env):
             return "sf was called"
@@ -1075,20 +1098,12 @@ class FunctionActionTestCase(unittest.TestCase):
 
         a = SCons.Action.FunctionAction(func1)
         assert a.execfunction == func1, a.execfunction
-        assert isinstance(a.strfunction, types.FunctionType)
+        assert isinstance(a.strfunction, types.MethodType), type(a.strfunction)
 
         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___str__(self):
         """Test the __str__() method for function Actions
         """
@@ -1172,7 +1187,7 @@ class FunctionActionTestCase(unittest.TestCase):
         def string_it(target, source, env, self=self):
             self.string_it = 1
             return None
-        act = SCons.Action.FunctionAction(build_it, string_it)
+        act = SCons.Action.FunctionAction(build_it, strfunction=string_it)
         r = act([], [], Environment())
         assert r == 0, r
         assert self.build_it
index 4bca1c8131649af8b9041ccb15a245053c8cbd0c..55ea07fd7acc77efa2daa69eb0c2bce9002eff71 100644 (file)
@@ -324,9 +324,9 @@ def _init_nodes(builder, env, overrides, tlist, slist):
         else:
             executor.add_sources(slist)
     if executor is None:
-        executor = SCons.Executor.Executor(builder,
-                                           env,
-                                           overrides,
+        executor = SCons.Executor.Executor(builder.action,
+                                           env or builder.env,
+                                           [builder.overrides, overrides],
                                            tlist,
                                            slist)
 
index ac209e0519932af81358607f4f462e6182041348..b3e6f888b00ff02f4e80b5d6c12c343f29cef1c1 100644 (file)
@@ -38,16 +38,16 @@ import SCons.Util
 class Executor:
     """A class for controlling instances of executing an action.
 
-    This largely exists to hold a single association of a builder,
-    environment, environment overrides, targets and sources for later
-    processing as needed.
+    This largely exists to hold a single association of an action,
+    environment, list of environment override dictionaries, targets
+    and sources for later processing as needed.
     """
 
-    def __init__(self, builder, env, overrides, targets, sources):
+    def __init__(self, action, env=None, overridelist=[], targets=[], sources=[]):
         if __debug__: logInstanceCreation(self)
-        self.builder = builder
+        self.action = action
         self.env = env
-        self.overrides = overrides
+        self.overridelist = overridelist
         self.targets = targets
         self.sources = sources[:]
 
@@ -58,32 +58,23 @@ class Executor:
         try:
             return self.build_env
         except AttributeError:
-            if self.env is None:
-                # There was no Environment specifically associated with
-                # this set of targets (which kind of implies that it
-                # is--or they are--source files, but who knows...).
-                # So use the environment associated with the Builder
-                # itself.
-                env = self.builder.env
-            else:
-                # The normal case:  use the Environment that was
-                # used to specify how these targets will be built.
-                env = self.env
-
             # Create the build environment instance with appropriate
             # overrides.  These get evaluated against the current
             # environment's construction variables so that users can
             # add to existing values by referencing the variable in
             # the expansion.
             overrides = {}
-            overrides.update(self.builder.overrides)
-            overrides.update(self.overrides)
+            for odict in self.overridelist:
+                overrides.update(odict)
             try:
                 generate_build_dict = self.targets[0].generate_build_dict
-            except AttributeError:
+            except (AttributeError, IndexError):
                 pass
             else:
                 overrides.update(generate_build_dict())
+
+            import SCons.Defaults
+            env = self.env or SCons.Defaults.DefaultEnvironment()
             self.build_env = env.Override(overrides)
 
             # Now update the build environment with the things that we
@@ -106,19 +97,22 @@ class Executor:
         try:
             al = self.action_list
         except AttributeError:
-            al = self.builder.action.get_actions()
+            al = self.action.get_actions()
             self.action_list = al
-        # XXX shouldn't reach into node attributes like this
-        return target.pre_actions + al + target.post_actions
+        try:
+            # XXX shouldn't reach into node attributes like this
+            return target.pre_actions + al + target.post_actions
+        except AttributeError:
+            return al
 
-    def __call__(self, target, func):
+    def __call__(self, target, errfunc, **kw):
         """Actually execute the action list."""
         action_list = self.get_action_list(target)
         if not action_list:
             return
         env = self.get_build_env()
         for action in action_list:
-            func(action, self.targets, self.sources, env)
+            apply(action, (self.targets, self.sources, env, errfunc), kw)
 
     def cleanup(self):
         try:
@@ -137,7 +131,7 @@ class Executor:
         try:
             return self.string
         except AttributeError:
-            action = self.builder.action
+            action = self.action
             self.string = action.genstring(self.targets,
                                            self.sources,
                                            self.get_build_env())
@@ -152,7 +146,7 @@ class Executor:
         try:
             return self.raw_contents
         except AttributeError:
-            action = self.builder.action
+            action = self.action
             self.raw_contents = action.get_raw_contents(self.targets,
                                                         self.sources,
                                                         self.get_build_env())
@@ -167,7 +161,7 @@ class Executor:
         try:
             return self.contents
         except AttributeError:
-            action = self.builder.action
+            action = self.action
             self.contents = action.get_contents(self.targets,
                                                 self.sources,
                                                 self.get_build_env())
index 23b7719e7421bb1afa808331a45a4d2ea8d4b874..c4a5552b27d042558dd33530d000315485e150cf 100644 (file)
@@ -44,7 +44,8 @@ class MyEnvironment:
         self._dict.update(dict)
 
 class MyAction:
-    actions = ['action1', 'action2']
+    def __init__(self, actions=['action1', 'action2']):
+        self.actions = actions
     def get_actions(self):
         return self.actions
     def genstring(self, target, source, env):
@@ -71,28 +72,25 @@ class ExecutorTestCase(unittest.TestCase):
     def test__init__(self):
         """Test creating an Executor"""
         source_list = ['s1', 's2']
-        x = SCons.Executor.Executor('b', 'e', 'o', 't', source_list)
-        assert x.builder == 'b', x.builder
+        x = SCons.Executor.Executor('a', 'e', ['o'], 't', source_list)
+        assert x.action == 'a', x.builder
         assert x.env == 'e', x.env
-        assert x.overrides == 'o', x.overrides
+        assert x.overridelist == ['o'], x.overridelist
         assert x.targets == 't', x.targets
         source_list.append('s3')
         assert x.sources == ['s1', 's2'], x.sources
 
     def test_get_build_env(self):
         """Test fetching and generating a build environment"""
-        x = SCons.Executor.Executor(MyBuilder('e', {}),
-                                    'e',
-                                    {},
-                                    't',
-                                    ['s1', 's2'])
+        x = SCons.Executor.Executor(MyAction(), 'e', [], 't', ['s1', 's2'])
         x.build_env = 'eee'
         be = x.get_build_env()
         assert be == 'eee', be
 
-        x = SCons.Executor.Executor(MyBuilder('e', {}),
-                                    MyEnvironment(X='xxx'),
-                                    {'O':'o2'},
+        env = MyEnvironment(X='xxx')
+        x = SCons.Executor.Executor(MyAction(),
+                                    env,
+                                    [{'O':'o2'}],
                                     't',
                                     ['s1', 's2'])
         be = x.get_build_env()
@@ -100,19 +98,13 @@ class ExecutorTestCase(unittest.TestCase):
         assert be['X'] == 'xxx', be['X']
 
         env = MyEnvironment(Y='yyy')
-        x = SCons.Executor.Executor(MyBuilder(env, {'O':'ob3'}),
-                                    None,
-                                    {'O':'oo3'},
-                                    't',
-                                    's')
+        overrides = [{'O':'ob3'}, {'O':'oo3'}]
+        x = SCons.Executor.Executor(MyAction(), env, overrides, 't', 's')
         be = x.get_build_env()
         assert be['O'] == 'oo3', be['O']
         assert be['Y'] == 'yyy', be['Y']
-        x = SCons.Executor.Executor(MyBuilder(env, {'O':'ob3'}),
-                                    None,
-                                    {},
-                                    't',
-                                    's')
+        overrides = [{'O':'ob3'}]
+        x = SCons.Executor.Executor(MyAction(), env, overrides, 't', 's')
         be = x.get_build_env()
         assert be['O'] == 'ob3', be['O']
         assert be['Y'] == 'yyy', be['Y']
@@ -126,23 +118,51 @@ class ExecutorTestCase(unittest.TestCase):
         al = x.get_action_list(MyNode(['PRE'], ['POST']))
         assert al == ['PRE', 'aaa', 'POST'], al
 
-        x = SCons.Executor.Executor(MyBuilder('e', 'o'), None, {}, 't', 's')
+        x = SCons.Executor.Executor(MyAction(), None, {}, 't', 's')
         al = x.get_action_list(MyNode(['pre'], ['post']))
         assert al == ['pre', 'action1', 'action2', 'post'], al
 
     def test__call__(self):
         """Test calling an Executor"""
-        actions = []
-        env = MyEnvironment(CALL='call')
-        b = MyBuilder(env, {})
-        x = SCons.Executor.Executor(b, None, {}, ['t1', 't2'], ['s1', 's2'])
-        def func(action, target, source, env, a=actions):
-            a.append(action)
-            assert target == ['t1', 't2'], target
-            assert source == ['s1', 's2'], source
-            assert env['CALL'] == 'call', env['CALL']
-        x(MyNode(['pre'], ['post']), func)
-        assert actions == ['pre', 'action1', 'action2', 'post'], actions
+        result = []
+        def pre(target, source, env, errfunc, result=result, **kw):
+            result.append('pre')
+        def action1(target, source, env, errfunc, result=result, **kw):
+            result.append('action1')
+        def action2(target, source, env, errfunc, result=result, **kw):
+            result.append('action2')
+        def post(target, source, env, errfunc, result=result, **kw):
+            result.append('post')
+
+        env = MyEnvironment()
+        a = MyAction([action1, action2])
+        x = SCons.Executor.Executor(a, env, [], ['t1', 't2'], ['s1', 's2'])
+
+        x(MyNode([pre], [post]), None)
+        assert result == ['pre', 'action1', 'action2', 'post'], result
+        del result[:]
+
+        def pre_err(target, source, env, errfunc, result=result, **kw):
+            result.append('pre_err')
+            if errfunc:
+                errfunc(1)
+            return 1
+
+        x(MyNode([pre_err], [post]), None)
+        assert result == ['pre_err', 'action1', 'action2', 'post'], result
+        del result[:]
+
+        def errfunc(stat):
+            raise "errfunc %s" % stat
+
+        try:
+            x(MyNode([pre_err], [post]), errfunc)
+        except:
+            assert sys.exc_type == "errfunc 1", sys.exc_type
+        else:
+            assert 0, "did not catch expected exception"
+        assert result == ['pre_err'], result
+        del result[:]
 
     def test_cleanup(self):
         """Test cleaning up an Executor"""
@@ -171,7 +191,7 @@ class ExecutorTestCase(unittest.TestCase):
         """Test the __str__() method"""
         env = MyEnvironment(S='string')
 
-        x = SCons.Executor.Executor(MyBuilder(env, {}), None, {}, ['t'], ['s'])
+        x = SCons.Executor.Executor(MyAction(), env, [], ['t'], ['s'])
         c = str(x)
         assert c == 'GENSTRING action1 action2 t s', c
 
@@ -179,12 +199,12 @@ class ExecutorTestCase(unittest.TestCase):
         """Test fetching the raw signatures contents"""
         env = MyEnvironment(RC='raw contents')
 
-        x = SCons.Executor.Executor(MyBuilder(env, {}), None, {}, ['t'], ['s'])
+        x = SCons.Executor.Executor(MyAction(), env, [], ['t'], ['s'])
         x.raw_contents = 'raw raw raw'
         rc = x.get_raw_contents()
         assert rc == 'raw raw raw', rc
 
-        x = SCons.Executor.Executor(MyBuilder(env, {}), None, {}, ['t'], ['s'])
+        x = SCons.Executor.Executor(MyAction(), env, [], ['t'], ['s'])
         rc = x.get_raw_contents()
         assert rc == 'RAW action1 action2 t s', rc
 
@@ -192,12 +212,12 @@ class ExecutorTestCase(unittest.TestCase):
         """Test fetching the signatures contents"""
         env = MyEnvironment(C='contents')
 
-        x = SCons.Executor.Executor(MyBuilder(env, {}), None, {}, ['t'], ['s'])
+        x = SCons.Executor.Executor(MyAction(), env, [], ['t'], ['s'])
         x.contents = 'contents'
         c = x.get_contents()
         assert c == 'contents', c
 
-        x = SCons.Executor.Executor(MyBuilder(env, {}), None, {}, ['t'], ['s'])
+        x = SCons.Executor.Executor(MyAction(), env, [], ['t'], ['s'])
         c = x.get_contents()
         assert c == 'action1 action2 t s', c
 
index a30994d8d0e9f47619dadba0f70c42a8d2b90b8b..008976b3c2a9c6e4c014840bf5233f2883954752 100644 (file)
@@ -1476,7 +1476,7 @@ class File(Base):
         listDirs.reverse()
         for dirnode in listDirs:
             try:
-                Mkdir(dirnode, None, None)
+                Mkdir(dirnode, [], None)
                 # The Mkdir() action may or may not have actually
                 # created the directory, depending on whether the -n
                 # option was used or not.  Delete the _exists and
@@ -1506,17 +1506,10 @@ class File(Base):
             return None
         if b and self.fs.CachePath:
             if self.fs.cache_show:
-                if CacheRetrieveSilent(self, None, None) == 0:
-                    def do_print(action, targets, sources, env, s=self):
-                        if action.strfunction:
-                            al = action.strfunction(targets, s.sources, env)
-                            if not SCons.Util.is_List(al):
-                                al = [al]
-                            for a in al:
-                                action.show(a)
-                    self._for_each_action(do_print)
+                if CacheRetrieveSilent(self, [], None) == 0:
+                    self.build(presub=0, execute=0)
                     return 1
-            elif CacheRetrieve(self, None, None) == 0:
+            elif CacheRetrieve(self, [], None) == 0:
                 return 1
         return None
 
@@ -1526,7 +1519,7 @@ class File(Base):
         # method has a chance to clear the build signature, which it
         # will do if this file has a source scanner.
         if self.fs.CachePath and self.fs.exists(self.path):
-            CachePush(self, None, None)
+            CachePush(self, [], None)
         SCons.Node.Node.built(self)
         self.found_includes = {}
         try:
@@ -1596,7 +1589,7 @@ class File(Base):
             if self.exists():
                 if self.is_derived() and not self.precious:
                     try:
-                        Unlink(self, None, None)
+                        Unlink(self, [], None)
                     except OSError, e:
                         raise SCons.Errors.BuildError(node = self,
                                                       errstr = e.strerror)
index d5e04e1881503cac61cb63a04dba829c55c462f0..9a5be4599daa7e271554f3f5034302f0cf9b0d0c 100644 (file)
@@ -76,9 +76,10 @@ class Environment:
         pass
 
 class Action:
-    def __call__(self, targets, sources, env):
+    def __call__(self, targets, sources, env, errfunc, **kw):
         global built_it
-        built_it = 1
+        if kw.get('execute', 1):
+            built_it = 1
         return 0
     def show(self, string):
         pass
index 4ea73abe1889b97eb56d443c2f0a8527f9c90e39..7a6c39a5850c504dbc6d40de56e63eacf3e2a50f 100644 (file)
@@ -43,7 +43,7 @@ class MyAction:
     def __init__(self):
         self.order = 0
 
-    def __call__(self, target, source, env):
+    def __call__(self, target, source, env, errfunc):
         global built_it, built_target, built_source, built_args, built_order
         built_it = 1
         built_target = target
@@ -63,7 +63,7 @@ class MyNonGlobalAction:
         self.built_target =  None
         self.built_source =  None
 
-    def __call__(self, target, source, env):
+    def __call__(self, target, source, env, errfunc):
         # Okay, so not ENTIRELY non-global...
         global built_order
         self.built_it = 1
@@ -166,6 +166,8 @@ class NodeTestCase(unittest.TestCase):
         node = MyNode("www")
         node.build()
         assert built_it == None
+        node.build(extra_kw_argument = 1)
+        assert built_it == None
 
         node = MyNode("xxx")
         node.builder_set(Builder())
index 9897d1ac79014e779e29ef877150137e2b2656d0..9a4162c75037318cdac2d44df95f9a0bb5a40f95 100644 (file)
@@ -161,26 +161,14 @@ class Node:
             if not create:
                 raise
             import SCons.Executor
-            executor = SCons.Executor.Executor(self.builder,
+            executor = SCons.Executor.Executor(self.builder.action,
                                                self.builder.env,
-                                               {},
+                                               [self.builder.overrides],
                                                [self],
                                                self.sources)
             self.executor = executor
         return executor
 
-    def _for_each_action(self, func):
-        """Call a function for each action required to build a node.
-
-        The purpose here is to have one place for the logic that
-        collects and executes all of the actions for a node's builder,
-        even though multiple sections of code elsewhere need this logic
-        to do different things."""
-        if not self.has_builder():
-            return
-        executor = self.get_executor()
-        executor(self, func)
-
     def retrieve_from_cache(self):
         """Try to retrieve the node's content from a cache
 
@@ -192,19 +180,19 @@ class Node:
         """
         return 0
         
-    def build(self):
+    def build(self, **kw):
         """Actually build the node.
 
         This method is called from multiple threads in a parallel build,
         so only do thread safe stuff here. Do thread unsafe stuff in
         built().
         """
-        def do_action(action, targets, sources, env, s=self):
-            stat = action(targets, sources, env)
-            if stat:
-                raise SCons.Errors.BuildError(node = s,
-                                              errstr = "Error %d" % stat)
-        self._for_each_action(do_action)
+        if not self.has_builder():
+            return
+        def errfunc(stat, node=self):
+            raise SCons.Errors.BuildError(node=node, errstr="Error %d" % stat)
+        executor = self.get_executor()
+        apply(executor, (self, errfunc), kw)
 
     def built(self):
         """Called just after this node is sucessfully built."""
diff --git a/test/strfunction.py b/test/strfunction.py
new file mode 100644 (file)
index 0000000..4ed75ee
--- /dev/null
@@ -0,0 +1,143 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Test how using strfunction() to report different types of
+"""
+
+import TestSCons
+
+python = TestSCons.python
+
+test = TestSCons.TestSCons()
+
+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 strfunction(target, source, env):
+    t = str(target[0])
+    s = str(source[0])
+    return "Building %%s from %%s" %% (t, s)
+def func(target, source, env):
+    t = str(target[0])
+    s = str(source[0])
+    open(t, 'w').write(open(s, 'r').read())
+funcaction = Action(func, strfunction=strfunction)
+cmd = r"%s cat.py $SOURCE $TARGET"
+cmdaction = Action(cmd, strfunction=strfunction)
+list = [ r"%s cat.py $SOURCE .temp", r"%s cat.py .temp $TARGET" ]
+listaction = Action(list, strfunction=strfunction)
+lazy = '$LAZY'
+lazyaction = Action(lazy, strfunction=strfunction)
+dict = {
+    '.cmd'      : cmd,
+    '.cmdstr'   : cmdaction,
+    '.func'     : func,
+    '.funcstr'  : funcaction,
+    '.list'     : list,
+    '.liststr'  : listaction,
+    '.lazy'     : lazy,
+    '.lazystr'  : lazyaction,
+}
+env = Environment(BUILDERS = {
+                        'Cmd'           : Builder(action=cmd),
+                        'CmdStr'        : Builder(action=cmdaction),
+                        'Func'          : Builder(action=func),
+                        'FuncStr'       : Builder(action=funcaction),
+                        'Lazy'          : Builder(action=lazy),
+                        'LazyStr'       : Builder(action=lazyaction),
+                        'List'          : Builder(action=list),
+                        'ListStr'       : Builder(action=listaction),
+
+                        'Dict'          : Builder(action=dict),
+                  },
+                  LAZY = r"%s cat.py $SOURCE $TARGET")
+env.Cmd('cmd.out', 'cmd.in')
+env.CmdStr('cmdstr.out', 'cmdstr.in')
+env.Func('func.out', 'func.in')
+env.FuncStr('funcstr.out', 'funcstr.in')
+env.Lazy('lazy.out', 'lazy.in')
+env.LazyStr('lazystr.out', 'lazystr.in')
+env.List('list.out', 'list.in')
+env.ListStr('liststr.out', 'liststr.in')
+
+env.Dict('dict1.out', 'dict1.cmd')
+env.Dict('dict2.out', 'dict2.cmdstr')
+env.Dict('dict3.out', 'dict3.func')
+env.Dict('dict4.out', 'dict4.funcstr')
+env.Dict('dict5.out', 'dict5.lazy')
+env.Dict('dict6.out', 'dict6.lazystr')
+env.Dict('dict7.out', 'dict7.list')
+env.Dict('dict8.out', 'dict8.liststr')
+""" % (python, python, python, python))
+
+test.write('func.in',          "func.in\n")
+test.write('funcstr.in',       "funcstr.in\n")
+test.write('cmd.in',           "cmd.in\n")
+test.write('cmdstr.in',        "cmdstr.in\n")
+test.write('lazy.in',          "lazy.in\n")
+test.write('lazystr.in',       "lazystr.in\n")
+test.write('list.in',          "list.in\n")
+test.write('liststr.in',       "liststr.in\n")
+
+test.write('dict1.cmd',         "dict1.cmd\n")
+test.write('dict2.cmdstr',      "dict2.cmdstr\n")
+test.write('dict3.func',        "dict3.func\n")
+test.write('dict4.funcstr',     "dict4.funcstr\n")
+test.write('dict5.lazy',        "dict4.lazy\n")
+test.write('dict6.lazystr',     "dict6.lazystr\n")
+test.write('dict7.list',        "dict7.list\n")
+test.write('dict8.liststr',     "dict8.liststr\n")
+
+test.run(arguments = '.', stdout=test.wrap_stdout("""\
+%s cat.py cmd.in cmd.out
+Building cmdstr.out from cmdstr.in
+%s cat.py dict1.cmd dict1.out
+Building dict2.out from dict2.cmdstr
+func("dict3.out", "dict3.func")
+Building dict4.out from dict4.funcstr
+%s cat.py dict5.lazy dict5.out
+Building dict6.out from dict6.lazystr
+%s cat.py dict7.list .temp
+%s cat.py .temp dict7.out
+Building dict8.out from dict8.liststr
+func("func.out", "func.in")
+Building funcstr.out from funcstr.in
+%s cat.py lazy.in lazy.out
+Building lazystr.out from lazystr.in
+%s cat.py list.in .temp
+%s cat.py .temp list.out
+Building liststr.out from liststr.in
+Building liststr.out from liststr.in
+""") % (python, python, python, python, python, python, python, python))
+# XXX The duplication of "Buiding liststr.out" above is WRONG!
+# A follow-on fix should take care of this.
+
+test.pass_test()