X-Git-Url: http://git.tremily.us/?a=blobdiff_plain;f=src%2Fengine%2FSCons%2FAction.py;h=9d497526856b40e17c2a2434697c0c1f362ea6c6;hb=704f6e2480ef60718f1aa42c266f04afc9c79580;hp=c131693354b3592570039d4aa2d3ac4c99d5d217;hpb=38bebc242d7a8626b7524edf56de7a635220d647;p=scons.git diff --git a/src/engine/SCons/Action.py b/src/engine/SCons/Action.py index c1316933..9d497526 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: @@ -25,31 +30,25 @@ other modules: a pre-substitution command for debugging purposes. get_contents() - Fetches the "contents" of an Action for signature calculation. - This is what the Sig/*.py subsystem uses 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* 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 @@ -58,6 +57,15 @@ 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 + 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 @@ -68,7 +76,6 @@ way for wrapping up the functions. """ -# # __COPYRIGHT__ # # Permission is hereby granted, free of charge, to any person obtaining @@ -89,37 +96,202 @@ 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. -# +from __future__ import generators ### KEEP FOR COMPATIBILITY FIXERS __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" +import cPickle +import dis import os -import os.path import re -import string import sys +import subprocess 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 print_actions_presub = 0 -default_ENV = None - def rfile(n): try: return n.rfile() except AttributeError: return n +def default_exitstatfunc(s): + return s + +try: + SET_LINENO = dis.SET_LINENO + HAVE_ARGUMENT = dis.HAVE_ARGUMENT +except AttributeError: + remove_set_lineno_codes = lambda x: x +else: + def remove_set_lineno_codes(code): + result = [] + n = len(code) + i = 0 + while i < n: + c = code[i] + op = ord(c) + if op >= HAVE_ARGUMENT: + if op != SET_LINENO: + result.append(code[i:i+3]) + i = i+3 + else: + result.append(c) + i = i+1 + return ''.join(result) + +strip_quotes = re.compile('^[\'"](.*)[\'"]$') + + +def _callable_contents(obj): + """Return the signature contents of a callable Python object. + """ + try: + # Test if obj is a method. + return _function_contents(obj.im_func) + + except AttributeError: + try: + # Test if obj is a callable object. + return _function_contents(obj.__call__.im_func) + + except AttributeError: + try: + # Test if obj is a code object. + return _code_contents(obj) + + except AttributeError: + # Test if obj is a function object. + return _function_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. + """ + try: + # Test if obj is a method. + return _function_contents(obj.im_func) + + except AttributeError: + try: + # Test if obj is a callable object. + return _function_contents(obj.__call__.im_func) + + except AttributeError: + try: + # Test if obj is a code object. + return _code_contents(obj) + + except AttributeError: + try: + # Test if obj is a function object. + return _function_contents(obj) + + except AttributeError: + # Should be a pickable Python object. + try: + return cPickle.dumps(obj) + except (cPickle.PicklingError, TypeError): + # This is weird, but it seems that nested classes + # are unpickable. The Python docs say it should + # always be a PicklingError, but some Python + # versions seem to return TypeError. Just do + # the best we can. + return str(obj) + + +def _code_contents(code): + """Return the signature contents of a code object. + + 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 + recompilations from moving a Python function. + """ + + contents = [] + + # The code contents depends on the number of local variables + # but not their actual names. + contents.append("%s,%s" % (code.co_argcount, len(code.co_varnames))) + try: + contents.append(",%s,%s" % (len(code.co_cellvars), len(code.co_freevars))) + except AttributeError: + # Older versions of Python do not support closures. + contents.append(",0,0") + + # 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. + # + # 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(',(' + ','.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 + # function result. + contents.append(',(' + ','.join(map(_object_contents,code.co_names)) + ')') + + + # The code contents depends on its actual code!!! + contents.append(',(' + str(remove_set_lineno_codes(code.co_code)) + ')') + + return ''.join(contents) + + +def _function_contents(func): + """Return the signature contents of a function.""" + + contents = [_code_contents(func.func_code)] + + # The function contents depends on the value of defaults arguments + if func.func_defaults: + contents.append(',(' + ','.join(map(_object_contents,func.func_defaults)) + ')') + else: + contents.append(',()') + + # The function contents depends on the closure captured cell values. + try: + closure = func.func_closure or [] + except AttributeError: + # Older versions of Python do not support closures. + closure = [] + + #xxx = [_object_contents(x.cell_contents) for x in closure] + try: + xxx = [_object_contents(x.cell_contents) for x in closure] + except AttributeError: + xxx = [] + contents.append(',(' + ','.join(xxx) + ')') + + return ''.join(contents) + + def _actionAppend(act1, act2): # This function knows how to slap two actions together. # Mainly, it handles ListActions by concatenating into @@ -139,21 +311,34 @@ def _actionAppend(act1, act2): else: return ListAction([ a1, a2 ]) -class CommandGenerator: - """ - Wraps a command generator function so the Action() factory - function can tell a generator function from a function action. +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. """ - def __init__(self, generator): - self.generator = generator - - def __add__(self, other): - return _actionAppend(self, other) - - def __radd__(self, other): - return _actionAppend(other, self) - -def _do_create_action(act, *args, **kw): + 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 @@ -166,13 +351,24 @@ 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 isinstance(act, CommandGenerator): - return apply(CommandGeneratorAction, (act.generator,)+args, kw) + + if is_List(act): + #TODO(1.5) return CommandAction(act, **kw) + return CommandAction(act, **kw) + if callable(act): - return apply(FunctionAction, (act,)+args, kw) - if SCons.Util.is_String(act): + try: + gen = kw['generator'] + del kw['generator'] + except KeyError: + gen = 0 + if gen: + action_type = CommandGeneratorAction + else: + action_type = FunctionAction + 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 @@ -181,66 +377,179 @@ 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. - lcg = LazyCmdGenerator(var) - return apply(CommandGeneratorAction, (lcg,)+args, kw) - commands = string.split(str(act), '\n') + return LazyAction(var, kw) + commands = str(act).split('\n') 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) + #TODO(1.5) return CommandAction(commands[0], **kw) + return 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 Action(act, strfunction=_null, varlist=[], presub=_null): - """A factory for action objects.""" - if SCons.Util.is_List(act): - acts = map(lambda x, s=strfunction, v=varlist, ps=presub: - _do_create_action(x, strfunction=s, varlist=v, presub=ps), - act) - acts = filter(lambda x: not x is None, acts) - if len(acts) == 1: - return acts[0] - else: - return ListAction(acts, strfunction=strfunction, varlist=varlist, presub=presub) +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 ListAction([]) + elif len(acts) == 1: + return acts[0] else: - return _do_create_action(act, strfunction=strfunction, varlist=varlist, presub=presub) + return ListAction(acts) + +def Action(act, *args, **kw): + """A factory for action objects.""" + # 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 actions that create output objects.""" - def __init__(self, strfunction=_null, presub=_null, **kw): - if not strfunction is _null: - self.strfunction = strfunction - if presub is _null: - self.presub = print_actions_presub - else: - self.presub = presub + """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 no_batch_key(self, env, target, source): + return None + + batch_key = no_batch_key + + 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.get_varlist(target, source, env) + if is_String(vl): vl = (vl,) + for v in vl: + result.append(env.subst('${'+v+'}')) + return ''.join(result) + + 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 LazyAction, 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 = str(self).split('\n') + self.presub_env = None # don't need this any more + return lines + + def get_varlist(self, target, source, env, executor=None): + return self.varlist + + def get_targets(self, env, executor): + """ + Returns the type of targets ($TARGETS, $CHANGED_TARGETS) used + by this action. + """ + return self.targets + +class _ActionAction(ActionBase): + """Base class for actions that create output objects.""" + def __init__(self, cmdstr=_null, strfunction=_null, varlist=(), + presub=_null, chdir=None, exitstatfunc=None, + batch_key=None, targets='$TARGETS', + **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: + exitstatfunc = default_exitstatfunc + self.exitstatfunc = exitstatfunc + + self.targets = targets + + if batch_key: + if not callable(batch_key): + # They have set batch_key, but not to their own + # callable. The default behavior here will batch + # *all* targets+sources using this action, separated + # for each construction environment. + def default_batch_key(self, env, target, source): + return (id(self), id(env)) + batch_key = default_batch_key + SCons.Util.AddMethod(self, batch_key, 'batch_key') + def print_cmd_line(self, s, target, source, env): sys.stdout.write(s + "\n") def __call__(self, target, source, env, - errfunc=None, + exitstatfunc=_null, presub=_null, show=_null, - execute=_null): - if not SCons.Util.is_List(target): + execute=_null, + chdir=_null, + executor=None): + if not is_List(target): target = [target] - if not SCons.Util.is_List(source): + if not is_List(source): source = [source] - if presub is _null: presub = self.presub + + if presub is _null: + presub = self.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 + save_cwd = None + if chdir: + save_cwd = os.getcwd() + try: + chdir = str(chdir.abspath) + except AttributeError: + if not is_String(chdir): + if executor: + chdir = str(executor.batches[0].targets[0].dir) + else: + chdir = str(target[0].dir) if presub: - 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) + if executor: + target = executor.get_all_targets() + source = executor.get_all_sources() + t = ' and '.join(map(str, target)) + l = '\n '.join(self.presub_lines(env)) + out = "Building %s with action:\n %s\n" % (t, l) sys.stdout.write(out) + cmd = None if show and self.strfunction: - s = self.strfunction(target, source, env) - if s: + if executor: + target = executor.get_all_targets() + source = executor.get_all_sources() + try: + cmd = self.strfunction(target, source, env, executor) + except TypeError: + cmd = self.strfunction(target, source, env) + if cmd: + if chdir: + cmd = ('os.chdir(%s)\n' % repr(chdir)) + cmd try: get = env.get except AttributeError: @@ -249,38 +558,29 @@ class ActionBase: print_func = get('PRINT_CMD_LINE_FUNC') if not print_func: print_func = self.print_cmd_line - print_func(s, target, source, env) + print_func(cmd, target, source, env) + stat = 0 if execute: - stat = self.execute(target, source, env) - if stat and errfunc: - errfunc(stat) - return stat - else: - return 0 - - 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 get_actions(self): - return [self] + if chdir: + os.chdir(chdir) + try: + stat = self.execute(target, source, env, executor=executor) + if isinstance(stat, SCons.Errors.BuildError): + s = exitstatfunc(stat.status) + if s: + stat.status = s + else: + stat = s + else: + stat = exitstatfunc(stat) + finally: + if save_cwd: + os.chdir(save_cwd) + if cmd and save_cwd: + print_func('os.chdir(%s)' % repr(save_cwd), target, source, env) - def __add__(self, other): - return _actionAppend(self, other) + return stat - 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 @@ -290,26 +590,151 @@ def _string_from_cmd_list(cmd_list): if ' ' in arg or '\t' in arg: arg = '"' + arg + '"' cl.append(arg) - return string.join(cl) + return ' '.join(cl) + +# 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 +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 common setup for a subprocess.Popen() call""" + # allow std{in,out,err} to be "'devnull'" + io = kw.get('stdin') + if is_String(io) and io == 'devnull': + kw['stdin'] = open(os.devnull) + io = kw.get('stdout') + if is_String(io) and io == 'devnull': + kw['stdout'] = open(os.devnull, 'w') + io = kw.get('stderr') + if is_String(io) and io == 'devnull': + kw['stderr'] = open(os.devnull, 'w') + + # Figure out what shell environment to use + ENV = kw.get('env', None) + if ENV is None: ENV = get_default_ENV(env) + + # Ensure that the ENV values are all strings: + new_env = {} + for key, value in ENV.items(): + if 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: + value = SCons.Util.flatten_sequence(value) + new_env[key] = os.pathsep.join(map(str, value)) + else: + # It's either a string or something else. If it's a string, + # we still want to call str() because it might be a *Unicode* + # string, which makes subprocess.Popen() gag. If it isn't a + # string or a list, then we just coerce it to a string, which + # is the proper way to handle Dir and File instances and will + # produce something reasonable for just about everything else: + new_env[key] = str(value) + kw['env'] = new_env -class CommandAction(ActionBase): + try: + #FUTURE return subprocess.Popen(cmd, **kw) + return subprocess.Popen(cmd, **kw) + except EnvironmentError, e: + if error == 'raise': raise + # return a dummy Popen instance that only returns error + class dummyPopen: + def __init__(self, e): self.exception = e + def communicate(self): return ('','') + def wait(self): return -self.exception.errno + stdin = None + class f: + def read(self): return '' + def readline(self): return '' + stdout = stderr = f() + return dummyPopen(e) + +class CommandAction(_ActionAction): """Class for command-execution actions.""" 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) + # 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, 'Action.CommandAction') + + #TODO(1.5) _ActionAction.__init__(self, **kw) + _ActionAction.__init__(self, **kw) + if is_List(cmd): + if list(filter(is_List, cmd)): + raise TypeError, "CommandAction should be given only " \ + "a single command" self.cmd_list = cmd def __str__(self): + if is_List(self.cmd_list): + return ' '.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") + def process(self, target, source, env, executor=None): + if executor: + result = env.subst_list(self.cmd_list, 0, executor=executor) + else: + result = env.subst_list(self.cmd_list, 0, target, source) + silent = None + ignore = None + while True: + try: c = result[0][0][0] + except IndexError: c = None + if c == '@': silent = 1 + elif c == '-': ignore = 1 + else: break + result[0][0] = result[0][0][1:] + try: + if not result[0][0]: + result[0] = result[0][1:] + except IndexError: + pass + return result, ignore, silent + + def strfunction(self, target, source, env, executor=None): + if self.cmdstr is None: + return None + if self.cmdstr is not _null: + from SCons.Subst import SUBST_RAW + if executor: + c = env.subst(self.cmdstr, SUBST_RAW, executor=executor) + else: + c = env.subst(self.cmdstr, SUBST_RAW, target, source) + if c: + return c + cmd_list, ignore, silent = self.process(target, source, env, executor) + if silent: + return '' + return _string_from_cmd_list(cmd_list[0]) - def execute(self, target, source, env): + def execute(self, target, source, env, executor=None): """Execute a command action. This will handle lists of commands as well as individual commands, @@ -318,7 +743,8 @@ class CommandAction(ActionBase): handle lists of commands, even though that's not how we use it externally. """ - from SCons.Util import is_String, is_List, flatten, escape_list + escape_list = SCons.Subst.escape_list + flatten_sequence = SCons.Util.flatten_sequence try: shell = env['SHELL'] @@ -329,17 +755,13 @@ class CommandAction(ActionBase): spawn = env['SPAWN'] except KeyError: raise SCons.Errors.UserError('Missing SPAWN construction variable.') + else: + if is_String(spawn): + spawn = env.subst(spawn, raw=1, conv=lambda x: x) 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(): @@ -348,8 +770,8 @@ class CommandAction(ActionBase): # 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: - value = flatten(value) - ENV[key] = string.join(map(str, value), os.pathsep) + value = flatten_sequence(value) + ENV[key] = os.pathsep.join(map(str, value)) else: # If it isn't a string or a list, then we just coerce # it to a string, which is the proper way to handle @@ -357,121 +779,215 @@ class CommandAction(ActionBase): # reasonable for just about everything else: ENV[key] = str(value) - cmd_list = env.subst_list(self.cmd_list, 0, target, source) + if executor: + target = executor.get_all_targets() + source = executor.get_all_sources() + cmd_list, ignore, silent = self.process(target, list(map(rfile, source)), env, executor) # Use len() to filter out any "command" that's zero-length. for cmd_line in filter(len, cmd_list): # Escape the command line for the interpreter we are using. cmd_line = escape_list(cmd_line, escape) result = spawn(shell, escape, cmd_line[0], cmd_line, ENV) - if result: - return result + if not ignore and result: + msg = "Error %s" % result + return SCons.Errors.BuildError(errstr=msg, + status=result, + action=self, + command=cmd_line) return 0 - def get_contents(self, target, source, env, dict=None): + def get_presig(self, target, source, env, executor=None): """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. """ + from SCons.Subst import SUBST_SIG cmd = self.cmd_list - if SCons.Util.is_List(cmd): - cmd = string.join(map(str, cmd)) + if is_List(cmd): + cmd = ' '.join(map(str, cmd)) else: cmd = str(cmd) - return env.subst_target_source(cmd, SCons.Util.SUBST_SIG, target, source, dict) + if executor: + return env.subst_target_source(cmd, SUBST_SIG, executor=executor) + else: + return env.subst_target_source(cmd, SUBST_SIG, target, source) + + def get_implicit_deps(self, target, source, env, executor=None): + icd = env.get('IMPLICIT_COMMAND_DEPENDENCIES', True) + if is_String(icd) and icd[:1] == '$': + icd = env.subst(icd) + if not icd or icd in ('0', 'None'): + return [] + from SCons.Subst import SUBST_SIG + if executor: + cmd_list = env.subst_list(self.cmd_list, SUBST_SIG, executor=executor) + else: + cmd_list = env.subst_list(self.cmd_list, SUBST_SIG, target, source) + res = [] + for cmd_line in cmd_list: + if cmd_line: + d = str(cmd_line[0]) + m = strip_quotes.match(d) + if m: + d = m.group(1) + d = env.WhereIs(d) + if d: + res.append(env.fs.File(d)) + return res class CommandGeneratorAction(ActionBase): """Class for command-generator actions.""" - def __init__(self, generator, **kw): - if __debug__: logInstanceCreation(self) - apply(ActionBase.__init__, (self,), kw) + def __init__(self, generator, kw): + if __debug__: logInstanceCreation(self, 'Action.CommandGeneratorAction') self.generator = generator + self.gen_kw = kw + self.varlist = kw.get('varlist', ()) + self.targets = kw.get('targets', '$TARGETS') - def __generate(self, target, source, env, for_signature): + def _generate(self, target, source, env, for_signature, executor=None): # 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 = Action(ret) + if executor: + target = executor.get_all_targets() + source = executor.get_all_sources() + ret = self.generator(target=target, + source=source, + env=env, + for_signature=for_signature) + #TODO(1.5) gen_cmd = Action(ret, **self.gen_kw) + gen_cmd = 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 {} + env = self.presub_env except AttributeError: - env = {} - act = self.__generate([], [], env, 0) + env = None + if env is None: + env = SCons.Defaults.DefaultEnvironment() + act = self._generate([], [], env, 1) return str(act) - def genstring(self, target, source, env): - return str(self.__generate(target, source, env, 0)) + def batch_key(self, env, target, source): + return self._generate(target, source, env, 1).batch_key(env, target, source) + + def genstring(self, target, source, env, executor=None): + return self._generate(target, source, env, 1, executor).genstring(target, source, env) - def execute(self, target, source, env): - rsources = map(rfile, source) - act = self.__generate(target, source, env, 0) - return act.execute(target, source, env) + def __call__(self, target, source, env, exitstatfunc=_null, presub=_null, + show=_null, execute=_null, chdir=_null, executor=None): + act = self._generate(target, source, env, 0, executor) + if act is None: + raise UserError("While building `%s': " + "Cannot deduce file extension from source files: %s" + % (repr(list(map(str, target))), repr(list(map(str, source))))) + return act(target, source, env, exitstatfunc, presub, + show, execute, chdir, executor) - def get_contents(self, target, source, env, dict=None): + def get_presig(self, target, source, env, executor=None): """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, dict=None) - -class LazyCmdGenerator: - """This is not really an Action, although it kind of looks like one. - This is really a simple callable class that acts as a command - generator. It holds on to a key into an Environment dictionary, - then waits until execution time to see what type it is, then tries - to create an Action out of it.""" - def __init__(self, var): - if __debug__: logInstanceCreation(self) + return self._generate(target, source, env, 1, executor).get_presig(target, source, env) + + def get_implicit_deps(self, target, source, env, executor=None): + return self._generate(target, source, env, 1, executor).get_implicit_deps(target, source, env) + + def get_varlist(self, target, source, env, executor=None): + return self._generate(target, source, env, 1, executor).get_varlist(target, source, env, executor) + + def get_targets(self, env, executor): + return self._generate(None, None, env, 1, executor).get_targets(env, executor) + + + +# A LazyAction is a kind of hybrid generator and command action for +# strings of the form "$VAR". These strings normally expand to other +# strings (think "$CCCOM" to "$CC -c -o $TARGET $SOURCE"), but we also +# want to be able to replace them with functions in the construction +# environment. Consequently, we want lazy evaluation and creation of +# an Action in the case of the function, but that's overkill in the more +# normal case of expansion to other strings. +# +# So we do this with a subclass that's both a generator *and* +# a command action. The overridden methods all do a quick check +# of the construction variable, and if it's a string we just call +# the corresponding CommandAction method to do the heavy lifting. +# If not, then we call the same-named CommandGeneratorAction method. +# The CommandGeneratorAction methods work by using the overridden +# _generate() method, that is, our own way of handling "generation" of +# an action based on what's in the construction variable. + +class LazyAction(CommandGeneratorAction, CommandAction): + + def __init__(self, var, kw): + if __debug__: logInstanceCreation(self, 'Action.LazyAction') + #FUTURE CommandAction.__init__(self, '${'+var+'}', **kw) + CommandAction.__init__(self, '${'+var+'}', **kw) self.var = SCons.Util.to_String(var) + self.gen_kw = kw - def strfunction(self, target, source, env): - try: - return env[self.var] - except KeyError: - # The variable reference substitutes to nothing. - return '' + def get_parent_class(self, env): + c = env.get(self.var) + if is_String(c) and not '\n' in c: + return CommandAction + return CommandGeneratorAction - def __str__(self): - return 'LazyCmdGenerator: %s'%str(self.var) + def _generate_cache(self, env): + if env: + c = env.get(self.var, '') + else: + c = '' + #TODO(1.5) gen_cmd = Action(c, **self.gen_kw) + gen_cmd = 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 - def __call__(self, target, source, env, for_signature): - try: - return env[self.var] - except KeyError: - # The variable reference substitutes to nothing. - return '' + def _generate(self, target, source, env, for_signature, executor=None): + return self._generate_cache(env) - def __cmp__(self, other): - return cmp(self.__dict__, other) + def __call__(self, target, source, env, *args, **kw): + c = self.get_parent_class(env) + return c.__call__(self, target, source, env, *args, **kw) + + def get_presig(self, target, source, env): + c = self.get_parent_class(env) + return c.get_presig(self, target, source, env) + + def get_varlist(self, target, source, env, executor=None): + c = self.get_parent_class(env) + return c.get_varlist(self, target, source, env, executor) -class FunctionAction(ActionBase): + +class FunctionAction(_ActionAction): """Class for Python function actions.""" - def __init__(self, execfunction, **kw): - if __debug__: logInstanceCreation(self) + def __init__(self, execfunction, kw): + if __debug__: logInstanceCreation(self, 'Action.FunctionAction') + self.execfunction = execfunction - apply(ActionBase.__init__, (self,), kw) - self.varlist = kw.get('varlist', []) + try: + self.funccontents = _callable_contents(execfunction) + except AttributeError: + try: + # See if execfunction will do the heavy lifting for us. + self.gc = execfunction.get_contents + except AttributeError: + # This is weird, just do the best we can. + self.funccontents = _object_contents(execfunction) + + #TODO(1.5) _ActionAction.__init__(self, **kw) + _ActionAction.__init__(self, **kw) def function_name(self): try: @@ -482,96 +998,156 @@ class FunctionAction(ActionBase): except AttributeError: return "unknown_python_function" - def strfunction(self, target, source, env): + def strfunction(self, target, source, env, executor=None): + if self.cmdstr is None: + return None + if self.cmdstr is not _null: + from SCons.Subst import SUBST_RAW + if executor: + c = env.subst(self.cmdstr, SUBST_RAW, executor=executor) + else: + c = env.subst(self.cmdstr, SUBST_RAW, target, source) + if c: + return c def array(a): def quote(s): - return '"' + str(s) + '"' - return '[' + string.join(map(quote, a), ", ") + ']' + try: + str_for_display = s.str_for_display + except AttributeError: + s = repr(s) + else: + s = str_for_display() + return s + return '[' + ", ".join(map(quote, a)) + ']' + try: + strfunc = self.execfunction.strfunction + except AttributeError: + pass + else: + if strfunc is None: + return None + if callable(strfunc): + return strfunc(target, source, env) name = self.function_name() tstr = array(target) sstr = array(source) return "%s(%s, %s)" % (name, tstr, sstr) def __str__(self): - return "%s(env, target, source)" % self.function_name() + name = self.function_name() + if name == 'ActionCaller': + return str(self.execfunction) + return "%s(target, source, env)" % name - def execute(self, target, source, env): - rsources = map(rfile, source) + def execute(self, target, source, env, executor=None): + exc_info = (None,None,None) try: - result = self.execfunction(target=target, source=rsources, env=env) - except EnvironmentError, e: - # If an IOError/OSError happens, raise a BuildError. - raise SCons.Errors.BuildError(node=target, errstr=e.strerror) - return result - - def get_contents(self, target, source, env, dict=None): - """Return the signature contents of this callable action. + if executor: + target = executor.get_all_targets() + source = executor.get_all_sources() + rsources = list(map(rfile, source)) + try: + result = self.execfunction(target=target, source=rsources, env=env) + except KeyboardInterrupt, e: + raise + except SystemExit, e: + raise + except Exception, e: + result = e + exc_info = sys.exc_info() - By providing direct access to the code object of the - function, Python makes this extremely easy. Hooray! - """ + if result: + result = SCons.Errors.convert_to_BuildError(result, exc_info) + result.node=target + result.action=self + try: + result.command=self.strfunction(target, source, env, executor) + except TypeError: + result.command=self.strfunction(target, source, env) + + # FIXME: This maintains backward compatibility with respect to + # which type of exceptions were returned by raising an + # exception and which ones were returned by value. It would + # probably be best to always return them by value here, but + # some codes do not check the return value of Actions and I do + # not have the time to modify them at this point. + if (exc_info[1] and + not isinstance(exc_info[1],EnvironmentError)): + raise result + + return result + finally: + # Break the cycle between the traceback object and this + # function stack frame. See the sys.exc_info() doc info for + # more information about this issue. + del exc_info + + + def get_presig(self, target, source, env): + """Return the signature contents of this callable action.""" try: - # "self.execfunction" is a function. - contents = str(self.execfunction.func_code.co_code) + return self.gc(target, source, env) except AttributeError: - # "self.execfunction" is a callable object. - try: - contents = str(self.execfunction.__call__.im_func.func_code.co_code) - except AttributeError: - try: - # See if execfunction will do the heavy lifting for us. - gc = self.execfunction.get_contents - except AttributeError: - # This is weird, just do the best we can. - contents = str(self.execfunction) - else: - contents = gc(target, source, env, dict) - return contents + env.subst(string.join(map(lambda v: '${'+v+'}', - self.varlist))) + return self.funccontents + + def get_implicit_deps(self, target, source, env): + return [] class ListAction(ActionBase): """Class for lists of other actions.""" - def __init__(self, list, **kw): - if __debug__: logInstanceCreation(self) - apply(ActionBase.__init__, (self,), kw) - self.list = map(lambda x: Action(x), list) + def __init__(self, actionlist): + if __debug__: logInstanceCreation(self, 'Action.ListAction') + def list_of_actions(x): + if isinstance(x, ActionBase): + return x + return Action(x) + self.list = list(map(list_of_actions, actionlist)) + # our children will have had any varlist + # applied; we don't need to do it again + self.varlist = () + self.targets = '$TARGETS' - def get_actions(self): - return self.list + def genstring(self, target, source, env): + return '\n'.join([a.genstring(target, source, env) for a in self.list]) def __str__(self): - s = [] - for l in self.list: - s.append(str(l)) - return string.join(s, "\n") + return '\n'.join(map(str, self.list)) - 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 + def presub_lines(self, env): + return SCons.Util.flatten_sequence( + [a.presub_lines(env) for a in self.list]) - def get_contents(self, target, source, env, dict=None): + def get_presig(self, target, source, env): """Return the signature contents of this action list. Simple concatenation of the signatures of the elements. """ - dict = SCons.Util.subst_dict(target, source) - return string.join(map(lambda x, t=target, s=source, e=env, d=dict: - x.get_contents(t, s, e, d), - self.list), - "") + return "".join([x.get_contents(target, source, env) for x in self.list]) + + def __call__(self, target, source, env, exitstatfunc=_null, presub=_null, + show=_null, execute=_null, chdir=_null, executor=None): + if executor: + target = executor.get_all_targets() + source = executor.get_all_sources() + for act in self.list: + stat = act(target, source, env, exitstatfunc, presub, + show, execute, chdir, executor) + if stat: + return stat + return 0 + + def get_implicit_deps(self, target, source, env): + result = [] + for act in self.list: + result.extend(act.get_implicit_deps(target, source, env)) + return result + + def get_varlist(self, target, source, env, executor=None): + result = SCons.Util.OrderedDict() + for act in self.list: + for var in act.get_varlist(target, source, env, executor): + result[var] = True + return result.keys() class ActionCaller: """A class for delaying calling an Action function with specific @@ -586,7 +1162,8 @@ class ActionCaller: self.parent = parent self.args = args self.kw = kw - def get_contents(self, target, source, env, dict=None): + + def get_contents(self, target, source, env): actfunc = self.parent.actfunc try: # "self.actfunc" is a function. @@ -599,24 +1176,51 @@ class ActionCaller: # No __call__() method, so it might be a builtin # or something like that. Do the best we can. 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 is_List(s): + result = [] + for elem in s: + result.append(self.subst(elem, target, source, env)) + return self.parent.convert(result) + + # Special-case hack: Let a custom function wrapped in an + # ActionCaller get at the environment through which the action + # was called by using this hard-coded value as a special return. + if s == '$__env__': + return env + 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, e=env, t=target, s=source: - e.subst(x, 0, t, s), - self.args) + return [self.subst(x, target, source, env) for x in self.args] + def subst_kw(self, target, source, env): kw = {} for key in self.kw.keys(): - kw[key] = env.subst(self.kw[key], 0, target, source) + kw[key] = self.subst(self.kw[key], target, source, env) return kw - def __call__(self, target, source, env): + + def __call__(self, target, source, env, executor=None): args = self.subst_args(target, source, env) kw = self.subst_kw(target, source, env) - return apply(self.parent.actfunc, args, kw) + #TODO(1.5) return self.parent.actfunc(*args, **kw) + return 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) - return apply(self.parent.strfunc, args, kw) + #TODO(1.5) return self.parent.strfunc(*args, **kw) + return self.parent.strfunc(*args, **kw) + + def __str__(self): + #TODO(1.5) return self.parent.strfunc(*self.args, **self.kw) + return self.parent.strfunc(*self.args, **self.kw) class ActionFactory: """A factory class that will wrap up an arbitrary function @@ -627,9 +1231,18 @@ class ActionFactory: called with and give them to the ActionCaller object we create, so it can hang onto them until it needs them. """ - def __init__(self, actfunc, strfunc): + def __init__(self, actfunc, strfunc, convert=lambda x: x): self.actfunc = actfunc self.strfunc = strfunc + self.convert = convert + def __call__(self, *args, **kw): ac = ActionCaller(self, args, kw) - return Action(ac, strfunction=ac.strfunction) + action = Action(ac, strfunction=ac.strfunction) + return action + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: