summaryrefslogtreecommitdiffstats
path: root/src/engine/SCons/Action.py
diff options
context:
space:
mode:
authorSteven Knight <knight@baldmt.com>2004-10-24 03:57:51 (GMT)
committerSteven Knight <knight@baldmt.com>2004-10-24 03:57:51 (GMT)
commit7739efc3870f2814ee4b2ea6f8751ccc7407e069 (patch)
tree23128dee9f60768a6b230b965281b3b1fb91b700 /src/engine/SCons/Action.py
parent35a89330d1df50811fc6912df0047148b1d98450 (diff)
downloadSCons-7739efc3870f2814ee4b2ea6f8751ccc7407e069.zip
SCons-7739efc3870f2814ee4b2ea6f8751ccc7407e069.tar.gz
SCons-7739efc3870f2814ee4b2ea6f8751ccc7407e069.tar.bz2
Refactor Action/Executor interaction. (Kevin Quick)
Diffstat (limited to 'src/engine/SCons/Action.py')
-rw-r--r--src/engine/SCons/Action.py219
1 files changed, 117 insertions, 102 deletions
diff --git a/src/engine/SCons/Action.py b/src/engine/SCons/Action.py
index ba1c240..5f89cc2 100644
--- a/src/engine/SCons/Action.py
+++ b/src/engine/SCons/Action.py
@@ -8,6 +8,11 @@ The base class here is ActionBase. The base class supplies just a few
OO utility methods and some generic methods for displaying information
about an Action in response to the various commands that control printing.
+A second-level base class is _ActionAction. This extends ActionBase
+by providing the methods that can be used to show and perform an
+action. True Action objects will subclass _ActionAction; Action
+factory class objects will subclass ActionBase.
+
The heavy lifting is handled by subclasses for the different types of
actions we might execute:
@@ -30,27 +35,21 @@ other modules:
needs to be rebuilt because its action changed.
genstring()
- Returns a string representation of the Action *without* command
- substitution, but allows a CommandGeneratorAction to generate
- the right action based on the specified target, source and env.
- This is used by the Signature subsystem (through the Executor)
- to compare the actions used to build a target last time and
- this time.
+ Returns a string representation of the Action *without*
+ command substitution, but allows a CommandGeneratorAction to
+ generate the right action based on the specified target,
+ source and env. This is used by the Signature subsystem
+ (through the Executor) to obtain an (imprecise) representation
+ of the Action operation for informative purposes.
- strfunction()
- Returns a substituted string representation of the Action.
- This is used by the ActionBase.show() command to display the
- command/function that will be executed to generate the target(s).
Subclasses also supply the following methods for internal use within
this module:
__str__()
- Returns a string representation of the Action *without* command
- substitution. This is used by the __call__() methods to display
- the pre-substitution command whenever the --debug=presub option
- is used.
-
+ Returns a string approximation of the Action; no variable
+ substitution is performed.
+
execute()
The internal method that really, truly, actually handles the
execution of a command or Python function. This is used so
@@ -58,6 +57,11 @@ this module:
pre-substitution representations, and *then* execute an action
without worrying about the specific Actions involved.
+ strfunction()
+ Returns a substituted string representation of the Action.
+ This is used by the _ActionAction.show() command to display the
+ command/function that will be executed to generate the target(s).
+
There is a related independent ActionCaller class that looks like a
regular Action, and which serves as a wrapper for arbitrary functions
that we want to let the user specify the arguments to now, but actually
@@ -187,8 +191,10 @@ def _do_create_action(act, *args, **kw):
if len(commands) == 1:
return apply(CommandAction, (commands[0],)+args, kw)
else:
- listCmdActions = map(lambda x: CommandAction(x), commands)
- return apply(ListAction, (listCmdActions,)+args, kw)
+ listCmdActions = map(lambda x, args=args, kw=kw:
+ apply(CommandAction, (x,)+args, kw),
+ commands)
+ return ListAction(listCmdActions)
return None
def Action(act, *args, **kw):
@@ -201,11 +207,41 @@ def Action(act, *args, **kw):
if len(acts) == 1:
return acts[0]
else:
- return apply(ListAction, (acts,)+args, kw)
+ return ListAction(acts)
else:
return apply(_do_create_action, (act,)+args, kw)
class ActionBase:
+ """Base class for all types of action objects that can be held by
+ other objects (Builders, Executors, etc.) This provides the
+ common methods for manipulating and combining those actions."""
+
+ def __cmp__(self, other):
+ return cmp(self.__dict__, other)
+
+ def genstring(self, target, source, env):
+ return str(self)
+
+ def __add__(self, other):
+ return _actionAppend(self, other)
+
+ def __radd__(self, other):
+ return _actionAppend(other, self)
+
+ def presub_lines(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
+
+
+class _ActionAction(ActionBase):
"""Base class for actions that create output objects."""
def __init__(self, strfunction=_null, presub=_null, chdir=None, **kw):
if not strfunction is _null:
@@ -215,9 +251,6 @@ class ActionBase:
self.presub = presub
self.chdir = chdir
- def __cmp__(self, other):
- return cmp(self.__dict__, other)
-
def print_cmd_line(self, s, target, source, env):
sys.stdout.write(s + "\n")
@@ -244,9 +277,9 @@ class ActionBase:
if not SCons.Util.is_String(chdir):
chdir = str(target[0].dir)
if presub:
- t = string.join(map(str, target), 'and')
+ t = string.join(map(str, target), ' and ')
l = string.join(self.presub_lines(env), '\n ')
- out = "Building %s with action(s):\n %s\n" % (t, l)
+ out = "Building %s with action:\n %s\n" % (t, l)
sys.stdout.write(out)
s = None
if show and self.strfunction:
@@ -278,26 +311,6 @@ class ActionBase:
print_func('os.chdir(%s)' % repr(save_cwd), target, source, env)
return stat
- def presub_lines(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)
-
- def __add__(self, other):
- return _actionAppend(self, other)
-
- def __radd__(self, other):
- return _actionAppend(other, self)
def _string_from_cmd_list(cmd_list):
"""Takes a list of command line arguments and returns a pretty
@@ -309,22 +322,34 @@ def _string_from_cmd_list(cmd_list):
cl.append(arg)
return string.join(cl)
-class CommandAction(ActionBase):
+class CommandAction(_ActionAction):
"""Class for command-execution actions."""
def __init__(self, cmd, *args, **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().
+ # Cmd can actually be a list or a single item; if it's a
+ # single item it should be the command string to execute; if a
+ # list then it should be the words of the command string to
+ # execute. Only a single command should be executed by this
+ # object; lists of commands should be handled by embedding
+ # these objects in a ListAction object (which the Action()
+ # factory above does). cmd will be passed to
+ # Environment.subst_list() for substituting environment
+ # variables.
if __debug__: logInstanceCreation(self)
- apply(ActionBase.__init__, (self,)+args, kw)
+ apply(_ActionAction.__init__, (self,)+args, kw)
+ if SCons.Util.is_List(cmd):
+ if filter(SCons.Util.is_List, cmd):
+ raise TypeError, "CommandAction should be given only " \
+ "a single command"
self.cmd_list = cmd
def __str__(self):
+ if SCons.Util.is_List(self.cmd_list):
+ return string.join(map(str, self.cmd_list), ' ')
return str(self.cmd_list)
def strfunction(self, target, source, env):
cmd_list = env.subst_list(self.cmd_list, 0, target, source)
- return string.join(map(_string_from_cmd_list, cmd_list), "\n")
+ return _string_from_cmd_list(cmd_list[0])
def execute(self, target, source, env):
"""Execute a command action.
@@ -374,7 +399,8 @@ class CommandAction(ActionBase):
# reasonable for just about everything else:
ENV[key] = str(value)
- cmd_list = env.subst_list(self.cmd_list, 0, target, source)
+ cmd_list = env.subst_list(self.cmd_list, 0, target,
+ map(rfile, source))
# Use len() to filter out any "command" that's zero-length.
for cmd_line in filter(len, cmd_list):
@@ -402,8 +428,8 @@ class CommandGeneratorAction(ActionBase):
"""Class for command-generator actions."""
def __init__(self, generator, *args, **kw):
if __debug__: logInstanceCreation(self)
- apply(ActionBase.__init__, (self,)+args, kw)
self.generator = generator
+ self.gen_kw = kw
def __generate(self, target, source, env, for_signature):
# ensure that target is a list, to make it easier to write
@@ -412,36 +438,27 @@ class CommandGeneratorAction(ActionBase):
target = [target]
ret = self.generator(target=target, source=source, env=env, for_signature=for_signature)
- gen_cmd = Action(ret)
+ gen_cmd = apply(Action, (ret,), self.gen_kw)
if not gen_cmd:
raise SCons.Errors.UserError("Object returned from command generator: %s cannot be used to create an Action." % repr(ret))
return gen_cmd
- def strfunction(self, target, source, env):
- if not SCons.Util.is_List(source):
- source = [source]
- rsources = map(rfile, source)
- act = self.__generate(target, source, env, 0)
- if act.strfunction:
- return act.strfunction(target, rsources, env)
- else:
- return None
-
def __str__(self):
try:
env = self.presub_env or {}
except AttributeError:
env = {}
- act = self.__generate([], [], env, 0)
+ act = self.__generate([], [], env, 1)
return str(act)
def genstring(self, target, source, env):
- return str(self.__generate(target, source, env, 0))
+ return self.__generate(target, source, env, 1).genstring(target, source, env)
- def execute(self, target, source, env):
- rsources = map(rfile, source)
+ def __call__(self, target, source, env, errfunc=None, presub=_null,
+ show=_null, execute=_null, chdir=_null):
act = self.__generate(target, source, env, 0)
- return act.execute(target, source, env)
+ return act(target, source, env, errfunc, presub,
+ show, execute, chdir)
def get_contents(self, target, source, env, dict=None):
"""Return the signature contents of this action's command line.
@@ -461,13 +478,6 @@ class LazyCmdGenerator:
if __debug__: logInstanceCreation(self)
self.var = SCons.Util.to_String(var)
- def strfunction(self, target, source, env):
- try:
- return env[self.var]
- except KeyError:
- # The variable reference substitutes to nothing.
- return ''
-
def __str__(self):
return 'LazyCmdGenerator: %s'%str(self.var)
@@ -481,13 +491,13 @@ class LazyCmdGenerator:
def __cmp__(self, other):
return cmp(self.__dict__, other)
-class FunctionAction(ActionBase):
+class FunctionAction(_ActionAction):
"""Class for Python function actions."""
def __init__(self, execfunction, *args, **kw):
if __debug__: logInstanceCreation(self)
self.execfunction = execfunction
- apply(ActionBase.__init__, (self,)+args, kw)
+ apply(_ActionAction.__init__, (self,)+args, kw)
self.varlist = kw.get('varlist', [])
def function_name(self):
@@ -519,7 +529,7 @@ class FunctionAction(ActionBase):
return "%s(%s, %s)" % (name, tstr, sstr)
def __str__(self):
- return "%s(env, target, source)" % self.function_name()
+ return "%s(target, source, env)" % self.function_name()
def execute(self, target, source, env):
rsources = map(rfile, source)
@@ -557,33 +567,21 @@ class FunctionAction(ActionBase):
class ListAction(ActionBase):
"""Class for lists of other actions."""
- def __init__(self, list, *args, **kw):
+ def __init__(self, list):
if __debug__: logInstanceCreation(self)
- apply(ActionBase.__init__, (self,)+args, kw)
- self.list = map(lambda x: Action(x), list)
+ def list_of_actions(x):
+ if isinstance(x, ActionBase):
+ return x
+ return Action(x)
+ self.list = map(list_of_actions, list)
def __str__(self):
- s = []
- for l in self.list:
- s.append(str(l))
- return string.join(s, "\n")
-
- def strfunction(self, target, source, env):
- s = []
- for l in self.list:
- if l.strfunction:
- x = l.strfunction(target, source, env)
- if not SCons.Util.is_List(x):
- x = [x]
- s.extend(x)
- return string.join(s, "\n")
-
- def execute(self, target, source, env):
- for l in self.list:
- r = l.execute(target, source, env)
- if r:
- return r
- return 0
+ return string.join(map(str, self.list), '\n')
+
+ def presub_lines(self, env):
+ return SCons.Util.flatten(map(lambda a, env=env:
+ a.presub_lines(env),
+ self.list))
def get_contents(self, target, source, env, dict=None):
"""Return the signature contents of this action list.
@@ -596,6 +594,15 @@ class ListAction(ActionBase):
self.list),
"")
+ def __call__(self, target, source, env, errfunc=None, presub=_null,
+ show=_null, execute=_null, chdir=_null):
+ for act in self.list:
+ stat = act(target, source, env, errfunc, presub,
+ show, execute, chdir)
+ if stat:
+ return stat
+ return 0
+
class ActionCaller:
"""A class for delaying calling an Action function with specific
(positional and keyword) arguments until the Action is actually
@@ -655,4 +662,12 @@ class ActionFactory:
self.strfunc = strfunc
def __call__(self, *args, **kw):
ac = ActionCaller(self, args, kw)
- return Action(ac, strfunction=ac.strfunction)
+ action = Action(ac, strfunction=ac.strfunction)
+ # action will be a FunctionAction; if left to its own devices,
+ # a genstr or str of this action will just show
+ # "ActionCaller(target, source, env)". Override that with the
+ # description from strfunc. Note that the apply is evaluated
+ # right now; __str__ is set to a (lambda) function that just
+ # returns the stored result of the evaluation whenever called.
+ action.__str__ = lambda name=apply(self.strfunc, args, kw): name
+ return action