From 6ce2aee59aead3833c5b86979c6b51ab2ae9b966 Mon Sep 17 00:00:00 2001 From: Steven Knight Date: Mon, 3 May 2004 04:45:01 +0000 Subject: Really support strfunction for all Action subclasses by refactoring the interface between Executor and Action, and collecting all the print/execute logic in a single ActionBase.__call__() method. --- src/engine/SCons/Action.py | 236 +++++++++++++++++-------------------- src/engine/SCons/ActionTests.py | 193 ++++++++++++++++-------------- src/engine/SCons/Builder.py | 6 +- src/engine/SCons/Executor.py | 52 ++++---- src/engine/SCons/ExecutorTests.py | 98 +++++++++------ src/engine/SCons/Node/FS.py | 19 +-- src/engine/SCons/Node/FSTests.py | 5 +- src/engine/SCons/Node/NodeTests.py | 6 +- src/engine/SCons/Node/__init__.py | 30 ++--- test/strfunction.py | 143 ++++++++++++++++++++++ 10 files changed, 463 insertions(+), 325 deletions(-) create mode 100644 test/strfunction.py diff --git a/src/engine/SCons/Action.py b/src/engine/SCons/Action.py index 37665b9..c95644a 100644 --- a/src/engine/SCons/Action.py +++ b/src/engine/SCons/Action.py @@ -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. diff --git a/src/engine/SCons/ActionTests.py b/src/engine/SCons/ActionTests.py index 3def857..b3d7e8c 100644 --- a/src/engine/SCons/ActionTests.py +++ b/src/engine/SCons/ActionTests.py @@ -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 diff --git a/src/engine/SCons/Builder.py b/src/engine/SCons/Builder.py index 4bca1c8..55ea07f 100644 --- a/src/engine/SCons/Builder.py +++ b/src/engine/SCons/Builder.py @@ -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) diff --git a/src/engine/SCons/Executor.py b/src/engine/SCons/Executor.py index ac209e0..b3e6f88 100644 --- a/src/engine/SCons/Executor.py +++ b/src/engine/SCons/Executor.py @@ -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()) diff --git a/src/engine/SCons/ExecutorTests.py b/src/engine/SCons/ExecutorTests.py index 23b7719..c4a5552 100644 --- a/src/engine/SCons/ExecutorTests.py +++ b/src/engine/SCons/ExecutorTests.py @@ -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 diff --git a/src/engine/SCons/Node/FS.py b/src/engine/SCons/Node/FS.py index a30994d..008976b 100644 --- a/src/engine/SCons/Node/FS.py +++ b/src/engine/SCons/Node/FS.py @@ -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) diff --git a/src/engine/SCons/Node/FSTests.py b/src/engine/SCons/Node/FSTests.py index d5e04e1..9a5be45 100644 --- a/src/engine/SCons/Node/FSTests.py +++ b/src/engine/SCons/Node/FSTests.py @@ -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 diff --git a/src/engine/SCons/Node/NodeTests.py b/src/engine/SCons/Node/NodeTests.py index 4ea73ab..7a6c39a 100644 --- a/src/engine/SCons/Node/NodeTests.py +++ b/src/engine/SCons/Node/NodeTests.py @@ -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()) diff --git a/src/engine/SCons/Node/__init__.py b/src/engine/SCons/Node/__init__.py index 9897d1a..9a4162c 100644 --- a/src/engine/SCons/Node/__init__.py +++ b/src/engine/SCons/Node/__init__.py @@ -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 index 0000000..4ed75ee --- /dev/null +++ b/test/strfunction.py @@ -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() -- cgit v0.12