Index: scons-2.0.1/engine/SCons/Action.py |
=================================================================== |
--- scons-2.0.1/engine/SCons/Action.py (revision 0) |
+++ scons-2.0.1/engine/SCons/Action.py (revision 0) |
@@ -0,0 +1,1241 @@ |
+"""SCons.Action |
+ |
+This encapsulates information about executing any sort of action that |
+can build one or more target Nodes (typically files) from one or more |
+source Nodes (also typically files) given a specific Environment. |
+ |
+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: |
+ |
+ CommandAction |
+ CommandGeneratorAction |
+ FunctionAction |
+ ListAction |
+ |
+The subclasses supply the following public interface methods used by |
+other modules: |
+ |
+ __call__() |
+ THE public interface, "calling" an Action object executes the |
+ command or Python function. This also takes care of printing |
+ a pre-substitution command for debugging purposes. |
+ |
+ get_contents() |
+ 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 obtain an (imprecise) representation |
+ of the Action operation for informative purposes. |
+ |
+ |
+Subclasses also supply the following methods for internal use within |
+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 |
+ that the __call__() methods can take care of displaying any |
+ 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 |
+execute later (when an out-of-date check determines that it's needed to |
+be executed, for example). Objects of this class are returned by an |
+ActionFactory class that provides a __call__() method as a convenient |
+way for wrapping up the functions. |
+ |
+""" |
+ |
+# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 The SCons Foundation |
+# |
+# 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__ = "src/engine/SCons/Action.py 5134 2010/08/16 23:02:40 bdeegan" |
+ |
+import SCons.compat |
+ |
+import dis |
+import os |
+# compat layer imports "cPickle" for us if it's available. |
+import pickle |
+import re |
+import sys |
+import subprocess |
+ |
+from SCons.Debug import logInstanceCreation |
+import SCons.Errors |
+import SCons.Executor |
+import SCons.Util |
+import SCons.Subst |
+ |
+# we use these a lot, so try to optimize them |
+is_String = SCons.Util.is_String |
+is_List = SCons.Util.is_List |
+ |
+class _null(object): |
+ pass |
+ |
+print_actions = 1 |
+execute_actions = 1 |
+print_actions_presub = 0 |
+ |
+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 pickle.dumps(obj) |
+ except (pickle.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 |
+ # a single ListAction. |
+ a1 = Action(act1) |
+ a2 = Action(act2) |
+ if a1 is None or a2 is None: |
+ raise TypeError("Cannot append %s to %s" % (type(act1), type(act2))) |
+ if isinstance(a1, ListAction): |
+ if isinstance(a2, ListAction): |
+ return ListAction(a1.list + a2.list) |
+ else: |
+ return ListAction(a1.list + [ a2 ]) |
+ else: |
+ if isinstance(a2, ListAction): |
+ return ListAction([ a1 ] + a2.list) |
+ else: |
+ return ListAction([ a1, a2 ]) |
+ |
+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 |
+ different semantics than passing lists as elements |
+ of lists. |
+ |
+ The former will create a ListAction, the latter |
+ will create a CommandAction by converting the inner |
+ list elements to strings.""" |
+ |
+ if isinstance(act, ActionBase): |
+ return act |
+ |
+ if is_List(act): |
+ return CommandAction(act, **kw) |
+ |
+ if callable(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 |
+ # variable reference, like "$FOO" or "${FOO}". We do |
+ # something special here...we lazily evaluate the contents |
+ # of that Environment variable, so a user could put something |
+ # like a function or a CommandGenerator in that variable |
+ # instead of a string. |
+ return LazyAction(var, kw) |
+ commands = str(act).split('\n') |
+ if len(commands) == 1: |
+ 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 _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 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(object): |
+ """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 + u"\n") |
+ |
+ def __call__(self, target, source, env, |
+ exitstatfunc=_null, |
+ presub=_null, |
+ show=_null, |
+ execute=_null, |
+ chdir=_null, |
+ executor=None): |
+ if not is_List(target): |
+ target = [target] |
+ if not is_List(source): |
+ source = [source] |
+ |
+ 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: |
+ 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 = u"Building %s with action:\n %s\n" % (t, l) |
+ sys.stdout.write(out) |
+ cmd = None |
+ if show and self.strfunction: |
+ 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: |
+ print_func = self.print_cmd_line |
+ else: |
+ print_func = get('PRINT_CMD_LINE_FUNC') |
+ if not print_func: |
+ print_func = self.print_cmd_line |
+ print_func(cmd, target, source, env) |
+ stat = 0 |
+ if execute: |
+ 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) |
+ |
+ return stat |
+ |
+ |
+def _string_from_cmd_list(cmd_list): |
+ """Takes a list of command line arguments and returns a pretty |
+ representation for printing.""" |
+ cl = [] |
+ for arg in map(str, cmd_list): |
+ if ' ' in arg or '\t' in arg: |
+ arg = '"' + arg + '"' |
+ cl.append(arg) |
+ 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(scons_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(scons_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 |
+ |
+ 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(object): |
+ def __init__(self, e): self.exception = e |
+ def communicate(self): return ('','') |
+ def wait(self): return -self.exception.errno |
+ stdin = None |
+ class f(object): |
+ 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 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') |
+ |
+ _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 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, executor=None): |
+ """Execute a command action. |
+ |
+ This will handle lists of commands as well as individual commands, |
+ because construction variable substitution may turn a single |
+ "command" into a list. This means that this class can actually |
+ handle lists of commands, even though that's not how we use it |
+ externally. |
+ """ |
+ escape_list = SCons.Subst.escape_list |
+ flatten_sequence = SCons.Util.flatten_sequence |
+ |
+ try: |
+ shell = env['SHELL'] |
+ except KeyError: |
+ raise SCons.Errors.UserError('Missing SHELL construction variable.') |
+ |
+ try: |
+ 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) |
+ |
+ ENV = get_default_ENV(env) |
+ |
+ # Ensure that the ENV values are all strings: |
+ for key, value in ENV.items(): |
+ if not is_String(value): |
+ 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 = 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 |
+ # Dir and File instances and will produce something |
+ # reasonable for just about everything else: |
+ ENV[key] = str(value) |
+ |
+ 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 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_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 is_List(cmd): |
+ cmd = ' '.join(map(str, cmd)) |
+ else: |
+ cmd = str(cmd) |
+ 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, '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, executor=None): |
+ # ensure that target is a list, to make it easier to write |
+ # generator functions: |
+ if not is_List(target): |
+ target = [target] |
+ |
+ 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) |
+ 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 __str__(self): |
+ try: |
+ env = self.presub_env |
+ except AttributeError: |
+ env = None |
+ if env is None: |
+ env = SCons.Defaults.DefaultEnvironment() |
+ act = self._generate([], [], env, 1) |
+ return str(act) |
+ |
+ 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 __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_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, 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 get_parent_class(self, env): |
+ c = env.get(self.var) |
+ if is_String(c) and not '\n' in c: |
+ return CommandAction |
+ return CommandGeneratorAction |
+ |
+ def _generate_cache(self, env): |
+ if env: |
+ c = env.get(self.var, '') |
+ else: |
+ c = '' |
+ 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 _generate(self, target, source, env, for_signature, executor=None): |
+ return self._generate_cache(env) |
+ |
+ 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(_ActionAction): |
+ """Class for Python function actions.""" |
+ |
+ def __init__(self, execfunction, kw): |
+ if __debug__: logInstanceCreation(self, 'Action.FunctionAction') |
+ |
+ self.execfunction = execfunction |
+ 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) |
+ |
+ _ActionAction.__init__(self, **kw) |
+ |
+ def function_name(self): |
+ try: |
+ return self.execfunction.__name__ |
+ except AttributeError: |
+ try: |
+ return self.execfunction.__class__.__name__ |
+ except AttributeError: |
+ return "unknown_python_function" |
+ |
+ 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): |
+ 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): |
+ name = self.function_name() |
+ if name == 'ActionCaller': |
+ return str(self.execfunction) |
+ return "%s(target, source, env)" % name |
+ |
+ def execute(self, target, source, env, executor=None): |
+ exc_info = (None,None,None) |
+ try: |
+ 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() |
+ |
+ 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: |
+ return self.gc(target, source, env) |
+ except AttributeError: |
+ return self.funccontents |
+ |
+ def get_implicit_deps(self, target, source, env): |
+ return [] |
+ |
+class ListAction(ActionBase): |
+ """Class for lists of other actions.""" |
+ 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 genstring(self, target, source, env): |
+ return '\n'.join([a.genstring(target, source, env) for a in self.list]) |
+ |
+ def __str__(self): |
+ return '\n'.join(map(str, self.list)) |
+ |
+ def presub_lines(self, env): |
+ return SCons.Util.flatten_sequence( |
+ [a.presub_lines(env) for a in self.list]) |
+ |
+ def get_presig(self, target, source, env): |
+ """Return the signature contents of this action list. |
+ |
+ Simple concatenation of the signatures of the elements. |
+ """ |
+ 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 list(result.keys()) |
+ |
+class ActionCaller(object): |
+ """A class for delaying calling an Action function with specific |
+ (positional and keyword) arguments until the Action is actually |
+ executed. |
+ |
+ This class looks to the rest of the world like a normal Action object, |
+ but what it's really doing is hanging on to the arguments until we |
+ have a target, source and env to use for the expansion. |
+ """ |
+ def __init__(self, parent, args, kw): |
+ self.parent = parent |
+ self.args = args |
+ self.kw = kw |
+ |
+ def get_contents(self, target, source, env): |
+ actfunc = self.parent.actfunc |
+ try: |
+ # "self.actfunc" is a function. |
+ contents = str(actfunc.func_code.co_code) |
+ except AttributeError: |
+ # "self.actfunc" is a callable object. |
+ try: |
+ contents = str(actfunc.__call__.im_func.func_code.co_code) |
+ except AttributeError: |
+ # 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 [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] = self.subst(self.kw[key], target, source, env) |
+ return kw |
+ |
+ def __call__(self, target, source, env, executor=None): |
+ args = self.subst_args(target, source, env) |
+ kw = self.subst_kw(target, source, env) |
+ 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 self.parent.strfunc(*args, **kw) |
+ |
+ def __str__(self): |
+ return self.parent.strfunc(*self.args, **self.kw) |
+ |
+class ActionFactory(object): |
+ """A factory class that will wrap up an arbitrary function |
+ as an SCons-executable Action object. |
+ |
+ The real heavy lifting here is done by the ActionCaller class. |
+ We just collect the (positional and keyword) arguments that we're |
+ 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, convert=lambda x: x): |
+ 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) |
+ return action |
+ |
+# Local Variables: |
+# tab-width:4 |
+# indent-tabs-mode:nil |
+# End: |
+# vim: set expandtab tabstop=4 shiftwidth=4: |
Property changes on: scons-2.0.1/engine/SCons/Action.py |
___________________________________________________________________ |
Added: svn:eol-style |
+ LF |