diff options
Diffstat (limited to 'src/engine/SCons/Action.py')
-rw-r--r-- | src/engine/SCons/Action.py | 337 |
1 files changed, 194 insertions, 143 deletions
diff --git a/src/engine/SCons/Action.py b/src/engine/SCons/Action.py index 069400c..21c0c55 100644 --- a/src/engine/SCons/Action.py +++ b/src/engine/SCons/Action.py @@ -30,9 +30,9 @@ other modules: a pre-substitution command for debugging purposes. get_contents() - Fetches the "contents" of an Action for signature calculation. - This is what gets MD5 checksumm'ed to decide if a target needs - to be rebuilt because its action changed. + Fetches the "contents" of an Action for signature calculation + plus the varlist. This is what gets MD5 checksummed to decide + if a target needs to be rebuilt because its action changed. genstring() Returns a string representation of the Action *without* @@ -49,7 +49,7 @@ this module: __str__() 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 @@ -57,6 +57,10 @@ this module: pre-substitution representations, and *then* execute an action without worrying about the specific Actions involved. + get_presig() + Fetches the "contents" of a subclass for signature calculation. + The varlist is added to this to produce the Action's contents. + strfunction() Returns a substituted string representation of the Action. This is used by the _ActionAction.show() command to display the @@ -72,7 +76,6 @@ way for wrapping up the functions. """ -# # __COPYRIGHT__ # # Permission is hereby granted, free of charge, to any person obtaining @@ -93,7 +96,6 @@ way for wrapping up the functions. # 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__" @@ -109,11 +111,14 @@ from SCons.Debug import logInstanceCreation import SCons.Errors import SCons.Executor import SCons.Util +import SCons.Subst -class _Null: - pass +# we use these a lot, so try to optimize them +is_String = SCons.Util.is_String +is_List = SCons.Util.is_List -_null = _Null +class _null: + pass print_actions = 1 execute_actions = 1 @@ -175,7 +180,7 @@ def _callable_contents(obj): def _object_contents(obj): """Return the signature contents of any Python object. - + We have to handle the case where object contains a code object since it can be pickled directly. """ @@ -199,7 +204,7 @@ def _object_contents(obj): return _function_contents(obj) except AttributeError: - # Should be a pickable Python object. + # Should be a pickable Python object. try: return cPickle.dumps(obj) except (cPickle.PicklingError, TypeError): @@ -216,7 +221,7 @@ def _code_contents(code): By providing direct access to the code object of the function, Python makes this extremely easy. Hooray! - + Unfortunately, older versions of Python include line number indications in the compiled byte code. Boo! So we remove the line number byte codes to prevent @@ -237,13 +242,13 @@ def _code_contents(code): # The code contents depends on any constants accessed by the # function. Note that we have to call _object_contents on each # constants because the code object of nested functions can - # show-up among the constants. - # + # show-up among the constants. + # # Note that we also always ignore the first entry of co_consts # which contains the function doc string. We assume that the # function does not access its doc string. contents.append(',(' + string.join(map(_object_contents,code.co_consts[1:]),',') + ')') - + # The code contents depends on the variable names used to # accessed global variable, as changing the variable name changes # the variable actually accessed and therefore changes the @@ -283,7 +288,7 @@ def _function_contents(func): contents.append(',(' + string.join(xxx, ',') + ')') return string.join(contents, '') - + def _actionAppend(act1, act2): # This function knows how to slap two actions together. @@ -304,7 +309,34 @@ def _actionAppend(act1, act2): else: return ListAction([ a1, a2 ]) -def _do_create_action(act, *args, **kw): +def _do_create_keywords(args, kw): + """This converts any arguments after the action argument into + their equivalent keywords and adds them to the kw argument. + """ + v = kw.get('varlist', ()) + # prevent varlist="FOO" from being interpreted as ['F', 'O', 'O'] + if is_String(v): v = (v,) + kw['varlist'] = tuple(v) + if args: + # turn positional args into equivalent keywords + cmdstrfunc = args[0] + if cmdstrfunc is None or is_String(cmdstrfunc): + kw['cmdstr'] = cmdstrfunc + elif callable(cmdstrfunc): + kw['strfunction'] = cmdstrfunc + else: + raise SCons.Errors.UserError( + 'Invalid command display variable type. ' + 'You must either pass a string or a callback which ' + 'accepts (target, source, env) as parameters.') + if len(args) > 1: + kw['varlist'] = args[1:] + kw['varlist'] + if kw.get('strfunction', _null) is not _null \ + and kw.get('cmdstr', _null) is not _null: + raise SCons.Errors.UserError( + 'Cannot have both strfunction and cmdstr args to Action()') + +def _do_create_action(act, kw): """This is the actual "implementation" for the Action factory method, below. This handles the fact that passing lists to Action() itself has @@ -317,8 +349,11 @@ def _do_create_action(act, *args, **kw): if isinstance(act, ActionBase): return act - if SCons.Util.is_List(act): - return apply(CommandAction, (act,)+args, kw) + + if is_List(act): + #TODO(1.5) return CommandAction(act, **kw) + return apply(CommandAction, (act,), kw) + if callable(act): try: gen = kw['generator'] @@ -329,8 +364,9 @@ def _do_create_action(act, *args, **kw): action_type = CommandGeneratorAction else: action_type = FunctionAction - return apply(action_type, (act,)+args, kw) - if SCons.Util.is_String(act): + return action_type(act, kw) + + if is_String(act): var=SCons.Util.get_environment_var(act) if var: # This looks like a string that is purely an Environment @@ -339,30 +375,37 @@ def _do_create_action(act, *args, **kw): # of that Environment variable, so a user could put something # like a function or a CommandGenerator in that variable # instead of a string. - return apply(LazyAction, (var,)+args, kw) + return LazyAction(var, kw) commands = string.split(str(act), '\n') if len(commands) == 1: - return apply(CommandAction, (commands[0],)+args, kw) - else: - listCmdActions = map(lambda x, args=args, kw=kw: - apply(CommandAction, (x,)+args, kw), - commands) - return ListAction(listCmdActions) + #TODO(1.5) return CommandAction(commands[0], **kw) + return apply(CommandAction, (commands[0],), kw) + # The list of string commands may include a LazyAction, so we + # reprocess them via _do_create_list_action. + return _do_create_list_action(commands, kw) return None +def _do_create_list_action(act, kw): + """A factory for list actions. Convert the input list into Actions + and then wrap them in a ListAction.""" + acts = [] + for a in act: + aa = _do_create_action(a, kw) + if aa is not None: acts.append(aa) + if not acts: + return None + elif len(acts) == 1: + return acts[0] + else: + return ListAction(acts) + def Action(act, *args, **kw): """A factory for action objects.""" - if SCons.Util.is_List(act): - acts = map(lambda a, args=args, kw=kw: - apply(_do_create_action, (a,)+args, kw), - act) - acts = filter(None, acts) - if len(acts) == 1: - return acts[0] - else: - return ListAction(acts) - else: - return apply(_do_create_action, (act,)+args, kw) + # Really simple: the _do_create_* routines do the heavy lifting. + _do_create_keywords(args, kw) + if is_List(act): + return _do_create_list_action(act, kw) + return _do_create_action(act, kw) class ActionBase: """Base class for all types of action objects that can be held by @@ -375,6 +418,17 @@ class ActionBase: def genstring(self, target, source, env): return str(self) + def get_contents(self, target, source, env): + result = [ self.get_presig(target, source, env) ] + # This should never happen, as the Action() factory should wrap + # the varlist, but just in case an action is created directly, + # we duplicate this check here. + vl = self.varlist + if is_String(vl): vl = (vl,) + for v in vl: + result.append(env.subst('${'+v+'}')) + return string.join(result, '') + def __add__(self, other): return _actionAppend(self, other) @@ -400,9 +454,16 @@ class ActionBase: class _ActionAction(ActionBase): """Base class for actions that create output objects.""" - def __init__(self, strfunction=_null, presub=_null, chdir=None, exitstatfunc=None, **kw): - if not strfunction is _null: - self.strfunction = strfunction + def __init__(self, cmdstr=_null, strfunction=_null, varlist=(), + presub=_null, chdir=None, exitstatfunc=None, + **kw): + self.cmdstr = cmdstr + if strfunction is not _null: + if strfunction is None: + self.cmdstr = None + else: + self.strfunction = strfunction + self.varlist = varlist self.presub = presub self.chdir = chdir if not exitstatfunc: @@ -418,16 +479,16 @@ class _ActionAction(ActionBase): show=_null, execute=_null, chdir=_null): - if not SCons.Util.is_List(target): + if not is_List(target): target = [target] - if not SCons.Util.is_List(source): + if not is_List(source): source = [source] - if exitstatfunc is _null: exitstatfunc = self.exitstatfunc if presub is _null: presub = self.presub - if presub is _null: - presub = print_actions_presub + if presub is _null: + presub = print_actions_presub + if exitstatfunc is _null: exitstatfunc = self.exitstatfunc if show is _null: show = print_actions if execute is _null: execute = execute_actions if chdir is _null: chdir = self.chdir @@ -437,7 +498,7 @@ class _ActionAction(ActionBase): try: chdir = str(chdir.abspath) except AttributeError: - if not SCons.Util.is_String(chdir): + if not is_String(chdir): chdir = str(target[0].dir) if presub: t = string.join(map(str, target), ' and ') @@ -492,36 +553,45 @@ def _string_from_cmd_list(cmd_list): cl.append(arg) return string.join(cl) -# this function is still in draft mode. We're going to need something like -# it in the long run as more and more places use it, but I'm sure it'll have -# to be tweaked to get the full desired functionality. +# A fiddlin' little function that has an 'import SCons.Environment' which +# can't be moved to the top level without creating an import loop. Since +# this import creates a local variable named 'SCons', it blocks access to +# the global variable, so we move it here to prevent complaints about local +# variables being used uninitialized. default_ENV = None -# one special arg, 'error', to tell what to do with exceptions. +def get_default_ENV(env): + global default_ENV + try: + return env['ENV'] + except KeyError: + if not default_ENV: + import SCons.Environment + # This is a hideously expensive way to get a default shell + # environment. What it really should do is run the platform + # setup to get the default ENV. Fortunately, it's incredibly + # rare for an Environment not to have a shell environment, so + # we're not going to worry about it overmuch. + default_ENV = SCons.Environment.Environment()['ENV'] + return default_ENV + +# This function is still in draft mode. We're going to need something like +# it in the long run as more and more places use subprocess, but I'm sure +# it'll have to be tweaked to get the full desired functionality. +# one special arg (so far?), 'error', to tell what to do with exceptions. def _subproc(env, cmd, error = 'ignore', **kw): """Do setup for a subprocess.Popen() call""" - # If the env has no ENV, get a default - try: - ENV = env['ENV'] - except KeyError: - global default_ENV - if default_ENV is None: - # Unbelievably expensive. What it really should do - # is run the platform setup to get the default ENV. - # Fortunately, it should almost never happen. - default_ENV = SCons.Environment.Environment(tools=[])['ENV'] - ENV = default_ENV - + # If the env has no shell environment, get a default + ENV = get_default_ENV(env) + # Ensure that the ENV values are all strings: new_env = {} - # It's a string 99.44% of the time, so optimize this - is_String = SCons.Util.is_String for key, value in ENV.items(): if is_String(value): # Call str() even though it's a "string" because it might be # a *Unicode* string, which makes subprocess.Popen() gag. new_env[key] = str(value) - elif SCons.Util.is_List(value): + elif 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: @@ -541,7 +611,7 @@ def _subproc(env, cmd, error = 'ignore', **kw): except EnvironmentError, e: if error == 'raise': raise # return a dummy Popen instance that only returns error - class popen: + class dummyPopen: def __init__(self, e): self.exception = e def communicate(self): return ('','') def wait(self): return -self.exception.errno @@ -550,11 +620,11 @@ def _subproc(env, cmd, error = 'ignore', **kw): def read(self): return '' def readline(self): return '' stdout = stderr = f() - return popen(e) + return dummyPopen(e) class CommandAction(_ActionAction): """Class for command-execution actions.""" - def __init__(self, cmd, cmdstr=None, *args, **kw): + def __init__(self, cmd, **kw): # 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 @@ -566,25 +636,16 @@ class CommandAction(_ActionAction): # variables. if __debug__: logInstanceCreation(self, 'Action.CommandAction') - if not cmdstr is None: - if callable(cmdstr): - args = (cmdstr,)+args - elif not SCons.Util.is_String(cmdstr): - raise SCons.Errors.UserError(\ - 'Invalid command display variable type. ' \ - 'You must either pass a string or a callback which ' \ - 'accepts (target, source, env) as parameters.') - - apply(_ActionAction.__init__, (self,)+args, kw) - if SCons.Util.is_List(cmd): - if filter(SCons.Util.is_List, cmd): + #TODO(1.5) _ActionAction.__init__(self, **kw) + apply(_ActionAction.__init__, (self,), kw) + if is_List(cmd): + if filter(is_List, cmd): raise TypeError, "CommandAction should be given only " \ "a single command" self.cmd_list = cmd - self.cmdstr = cmdstr def __str__(self): - if SCons.Util.is_List(self.cmd_list): + if is_List(self.cmd_list): return string.join(map(str, self.cmd_list), ' ') return str(self.cmd_list) @@ -607,7 +668,9 @@ class CommandAction(_ActionAction): return result, ignore, silent def strfunction(self, target, source, env): - if not self.cmdstr is None: + if self.cmdstr is None: + return None + if self.cmdstr is not _null: from SCons.Subst import SUBST_RAW c = env.subst(self.cmdstr, SUBST_RAW, target, source) if c: @@ -626,11 +689,8 @@ class CommandAction(_ActionAction): handle lists of commands, even though that's not how we use it externally. """ - from SCons.Subst import escape_list - import SCons.Util + escape_list = SCons.Subst.escape_list flatten_sequence = SCons.Util.flatten_sequence - is_String = SCons.Util.is_String - is_List = SCons.Util.is_List try: shell = env['SHELL'] @@ -647,14 +707,7 @@ class CommandAction(_ActionAction): escape = env.get('ESCAPE', lambda x: x) - 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 + ENV = get_default_ENV(env) # Ensure that the ENV values are all strings: for key, value in ENV.items(): @@ -687,7 +740,7 @@ class CommandAction(_ActionAction): command=cmd_line) return 0 - def get_contents(self, target, source, env): + def get_presig(self, target, source, env): """Return the signature contents of this action's command line. This strips $(-$) and everything in between the string, @@ -695,7 +748,7 @@ class CommandAction(_ActionAction): """ from SCons.Subst import SUBST_SIG cmd = self.cmd_list - if SCons.Util.is_List(cmd): + if is_List(cmd): cmd = string.join(map(str, cmd)) else: cmd = str(cmd) @@ -703,7 +756,7 @@ class CommandAction(_ActionAction): def get_implicit_deps(self, target, source, env): icd = env.get('IMPLICIT_COMMAND_DEPENDENCIES', True) - if SCons.Util.is_String(icd) and icd[:1] == '$': + if is_String(icd) and icd[:1] == '$': icd = env.subst(icd) if not icd or icd in ('0', 'None'): return [] @@ -719,20 +772,21 @@ class CommandAction(_ActionAction): class CommandGeneratorAction(ActionBase): """Class for command-generator actions.""" - def __init__(self, generator, *args, **kw): + def __init__(self, generator, kw): if __debug__: logInstanceCreation(self, 'Action.CommandGeneratorAction') self.generator = generator - self.gen_args = args self.gen_kw = kw + self.varlist = kw.get('varlist', ()) def _generate(self, target, source, env, for_signature): # ensure that target is a list, to make it easier to write # generator functions: - if not SCons.Util.is_List(target): + if not is_List(target): target = [target] ret = self.generator(target=target, source=source, env=env, for_signature=for_signature) - gen_cmd = apply(Action, (ret,)+self.gen_args, self.gen_kw) + #TODO(1.5) gen_cmd = Action(ret, **self.gen_kw) + 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 @@ -756,13 +810,13 @@ class CommandGeneratorAction(ActionBase): return act(target, source, env, exitstatfunc, presub, show, execute, chdir) - def get_contents(self, target, source, env): + def get_presig(self, target, source, env): """Return the signature contents of this action's command line. This strips $(-$) and everything in between the string, since those parts don't affect signatures. """ - return self._generate(target, source, env, 1).get_contents(target, source, env) + return self._generate(target, source, env, 1).get_presig(target, source, env) def get_implicit_deps(self, target, source, env): return self._generate(target, source, env, 1).get_implicit_deps(target, source, env) @@ -788,22 +842,23 @@ class CommandGeneratorAction(ActionBase): class LazyAction(CommandGeneratorAction, CommandAction): - def __init__(self, var, *args, **kw): + def __init__(self, var, kw): if __debug__: logInstanceCreation(self, 'Action.LazyAction') - apply(CommandAction.__init__, (self, '$'+var)+args, kw) + #FUTURE CommandAction.__init__(self, '${'+var+'}', **kw) + apply(CommandAction.__init__, (self, '${'+var+'}'), kw) self.var = SCons.Util.to_String(var) - self.gen_args = args self.gen_kw = kw def get_parent_class(self, env): c = env.get(self.var) - if SCons.Util.is_String(c) and not '\n' in c: + if is_String(c) and not '\n' in c: return CommandAction return CommandGeneratorAction def _generate_cache(self, env): c = env.get(self.var, '') - gen_cmd = apply(Action, (c,)+self.gen_args, self.gen_kw) + #TODO(1.5) gen_cmd = Action(c, **self.gen_kw) + gen_cmd = apply(Action, (c,), self.gen_kw) if not gen_cmd: raise SCons.Errors.UserError("$%s value %s cannot be used to create an Action." % (self.var, repr(c))) return gen_cmd @@ -814,29 +869,21 @@ class LazyAction(CommandGeneratorAction, CommandAction): def __call__(self, target, source, env, *args, **kw): args = (self, target, source, env) + args c = self.get_parent_class(env) + #TODO(1.5) return c.__call__(*args, **kw) return apply(c.__call__, args, kw) - def get_contents(self, target, source, env): + def get_presig(self, target, source, env): c = self.get_parent_class(env) - return c.get_contents(self, target, source, env) + return c.get_presig(self, target, source, env) class FunctionAction(_ActionAction): """Class for Python function actions.""" - def __init__(self, execfunction, cmdstr=_null, *args, **kw): + def __init__(self, execfunction, kw): if __debug__: logInstanceCreation(self, 'Action.FunctionAction') - if not cmdstr is _null: - if callable(cmdstr): - args = (cmdstr,)+args - elif not (cmdstr is None or SCons.Util.is_String(cmdstr)): - raise SCons.Errors.UserError(\ - 'Invalid function display variable type. ' \ - 'You must either pass a string or a callback which ' \ - 'accepts (target, source, env) as parameters.') - self.execfunction = execfunction try: self.funccontents = _callable_contents(execfunction) @@ -848,12 +895,8 @@ class FunctionAction(_ActionAction): # This is weird, just do the best we can. self.funccontents = _object_contents(execfunction) - apply(_ActionAction.__init__, (self,)+args, kw) - self.varlist = kw.get('varlist', []) - if SCons.Util.is_String(self.varlist): - # prevent varlist="FOO" from being interpreted as ['F', 'O', 'O'] - self.varlist=[self.varlist] - self.cmdstr = cmdstr + #TODO(1.5) _ActionAction.__init__(self, **kw) + apply(_ActionAction.__init__, (self,), kw) def function_name(self): try: @@ -867,7 +910,7 @@ class FunctionAction(_ActionAction): def strfunction(self, target, source, env): if self.cmdstr is None: return None - if not self.cmdstr is _null: + if self.cmdstr is not _null: from SCons.Subst import SUBST_RAW c = env.subst(self.cmdstr, SUBST_RAW, target, source) if c: @@ -929,18 +972,12 @@ class FunctionAction(_ActionAction): command=self.strfunction(target, source, env)) return result - def get_contents(self, target, source, env): + def get_presig(self, target, source, env): """Return the signature contents of this callable action.""" try: - contents = self.gc(target, source, env) + return self.gc(target, source, env) except AttributeError: - contents = self.funccontents - - result = [contents] - for v in self.varlist: - result.append(env.subst('${'+v+'}')) - - return string.join(result, '') + return self.funccontents def get_implicit_deps(self, target, source, env): return [] @@ -954,6 +991,9 @@ class ListAction(ActionBase): return x return Action(x) self.list = map(list_of_actions, list) + # our children will have had any varlist + # applied; we don't need to do it again + self.varlist = () def genstring(self, target, source, env): return string.join(map(lambda a, t=target, s=source, e=env: @@ -963,12 +1003,12 @@ class ListAction(ActionBase): def __str__(self): return string.join(map(str, self.list), '\n') - + def presub_lines(self, env): return SCons.Util.flatten_sequence( map(lambda a, env=env: a.presub_lines(env), self.list)) - def get_contents(self, target, source, env): + def get_presig(self, target, source, env): """Return the signature contents of this action list. Simple concatenation of the signatures of the elements. @@ -1006,6 +1046,7 @@ class ActionCaller: self.parent = parent self.args = args self.kw = kw + def get_contents(self, target, source, env): actfunc = self.parent.actfunc try: @@ -1021,10 +1062,11 @@ class ActionCaller: contents = str(actfunc) contents = remove_set_lineno_codes(contents) return contents + def subst(self, s, target, source, env): # If s is a list, recursively apply subst() # to every element in the list - if SCons.Util.is_List(s): + if is_List(s): result = [] for elem in s: result.append(self.subst(elem, target, source, env)) @@ -1035,27 +1077,35 @@ class ActionCaller: # was called by using this hard-coded value as a special return. if s == '$__env__': return env - elif SCons.Util.is_String(s): + elif is_String(s): return env.subst(s, 1, target, source) return self.parent.convert(s) + def subst_args(self, target, source, env): return map(lambda x, self=self, t=target, s=source, e=env: self.subst(x, t, s, e), self.args) + def subst_kw(self, target, source, env): kw = {} for key in self.kw.keys(): kw[key] = self.subst(self.kw[key], target, source, env) return kw + def __call__(self, target, source, env): args = self.subst_args(target, source, env) kw = self.subst_kw(target, source, env) + #TODO(1.5) return self.parent.actfunc(*args, **kw) return apply(self.parent.actfunc, args, kw) + def strfunction(self, target, source, env): args = self.subst_args(target, source, env) kw = self.subst_kw(target, source, env) + #TODO(1.5) return self.parent.strfunc(*args, **kw) return apply(self.parent.strfunc, args, kw) + def __str__(self): + #TODO(1.5) return self.parent.strfunc(*self.args, **self.kw) return apply(self.parent.strfunc, self.args, self.kw) class ActionFactory: @@ -1071,6 +1121,7 @@ class ActionFactory: self.actfunc = actfunc self.strfunc = strfunc self.convert = convert + def __call__(self, *args, **kw): ac = ActionCaller(self, args, kw) action = Action(ac, strfunction=ac.strfunction) |