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*
__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
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
"""
-#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# 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__"
+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 string.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(',(' + 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
+ # function result.
+ contents.append(',(' + string.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 string.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(',(' + string.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 = map(lambda x: _object_contents(x.cell_contents), closure)
+ except AttributeError:
+ xxx = []
+ contents.append(',(' + string.join(xxx, ',') + ')')
+
+ return string.join(contents, '')
+
+
def _actionAppend(act1, act2):
# This function knows how to slap two actions together.
# Mainly, it handles ListActions by concatenating into
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
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 apply(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
# 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 ListAction([])
+ 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
other objects (Builders, Executors, etc.) This provides the
common methods for manipulating and combining those actions."""
-
- __metaclass__ = SCons.Memoize.Memoized_Metaclass
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 string.join(result, '')
+
def __add__(self, other):
return _actionAppend(self, other)
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
+ # 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 = None # don't need this any more
return lines
-if not SCons.Memoize.has_metaclass:
- _Base = ActionBase
- class ActionBase(SCons.Memoize.Memoizer, _Base):
- "Cache-backed version of ActionBase"
- def __init__(self, *args, **kw):
- apply(_Base.__init__, (self,)+args, kw)
- SCons.Memoize.Memoizer.__init__(self)
+ 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, strfunction=_null, presub=_null, chdir=None, **kw):
- if not strfunction is _null:
- self.strfunction = strfunction
- if presub is _null:
- presub = print_actions_presub
+ 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,
- chdir=_null):
- if not SCons.Util.is_List(target):
+ 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
try:
chdir = str(chdir.abspath)
except AttributeError:
- if not SCons.Util.is_String(chdir):
- chdir = str(target[0].dir)
+ 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 = string.join(map(str, target), ' and ')
l = string.join(self.presub_lines(env), '\n ')
out = "Building %s with action:\n %s\n" % (t, l)
sys.stdout.write(out)
- s = None
+ 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:
- s = ('os.chdir(%s)\n' % repr(chdir)) + s
+ cmd = ('os.chdir(%s)\n' % repr(chdir)) + cmd
try:
get = env.get
except AttributeError:
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:
if chdir:
os.chdir(chdir)
try:
- stat = self.execute(target, source, env)
- if stat and errfunc:
- errfunc(stat)
+ 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 s and save_cwd:
+ if cmd and save_cwd:
print_func('os.chdir(%s)' % repr(save_cwd), target, source, env)
+
return stat
cl.append(arg)
return string.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] = string.join(map(str, value), os.pathsep)
+ 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 apply(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, 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
# factory above does). cmd will be passed to
# Environment.subst_list() for substituting environment
# variables.
- if __debug__: logInstanceCreation(self)
- apply(_ActionAction.__init__, (self,)+args, kw)
- if SCons.Util.is_List(cmd):
- if filter(SCons.Util.is_List, cmd):
+ if __debug__: logInstanceCreation(self, 'Action.CommandAction')
+
+ #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)
- def strfunction(self, target, source, env):
- if not self.cmdstr is None:
- c = env.subst(self.cmdstr, 0, target, source)
+ 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 1:
+ 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 = env.subst_list(self.cmd_list, 0, target, source)
+ 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,
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']
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():
# 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)
+ value = flatten_sequence(value)
ENV[key] = string.join(map(str, value), os.pathsep)
else:
# If it isn't a string or a list, then we just coerce
# reasonable for just about everything else:
ENV[key] = str(value)
- cmd_list = env.subst_list(self.cmd_list, 0, target,
- map(rfile, source))
+ if executor:
+ target = executor.get_all_targets()
+ source = executor.get_all_sources()
+ cmd_list, ignore, silent = self.process(target, 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):
+ 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):
+ if is_List(cmd):
cmd = string.join(map(str, cmd))
else:
cmd = str(cmd)
- return env.subst_target_source(cmd, SCons.Util.SUBST_SIG, target, source)
+ 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, *args, **kw):
- if __debug__: logInstanceCreation(self)
+ 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)
+ 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 = 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))
def __str__(self):
try:
- env = self.presub_env or {}
+ env = self.presub_env
except AttributeError:
- env = {}
+ 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 self._generate(target, source, env, 1).genstring(target, source, env)
+ def batch_key(self, env, target, source):
+ return self._generate(target, source, env, 1).batch_key(env, target, source)
- def __call__(self, target, source, env, errfunc=None, presub=_null,
- show=_null, execute=_null, chdir=_null):
- act = self._generate(target, source, env, 0)
- return act(target, source, env, errfunc, presub,
- show, execute, chdir)
+ def genstring(self, target, source, env, executor=None):
+ return self._generate(target, source, env, 1, executor).genstring(target, source, env)
- def get_contents(self, 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(map(str, target)), repr(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).get_contents(target, source, env)
+ return self._generate(target, source, env, 1, executor).get_presig(target, source, env)
-# Ooh, polymorphism -- pretty scary, eh, kids?
-#
-# A LazyCmdAction is a kind of hybrid generator and command action for
+ 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
# 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, uses our own way of handling "generation" of an
-# action based on what's in the construction variable.
+# _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, *args, **kw):
- if __debug__: logInstanceCreation(self)
- apply(CommandAction.__init__, (self, '$'+var)+args, kw)
+
+ def __init__(self, var, kw):
+ if __debug__: logInstanceCreation(self, 'Action.LazyAction')
+ #FUTURE CommandAction.__init__(self, '${'+var+'}', **kw)
+ apply(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 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(self, target, source, env, for_signature):
- c = env.get(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 = 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
+ def _generate(self, target, source, env, for_signature, executor=None):
+ return self._generate_cache(env)
+
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_presig(self, target, source, env)
+
+ def get_varlist(self, target, source, env, executor=None):
c = self.get_parent_class(env)
- return c.get_contents(self, target, source, env)
+ return c.get_varlist(self, target, source, env, executor)
+
class FunctionAction(_ActionAction):
"""Class for Python function actions."""
- def __init__(self, execfunction, *args, **kw):
- if __debug__: logInstanceCreation(self)
+ def __init__(self, execfunction, kw):
+ if __debug__: logInstanceCreation(self, 'Action.FunctionAction')
+
self.execfunction = execfunction
- apply(_ActionAction.__init__, (self,)+args, 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)
+ apply(_ActionAction.__init__, (self,), kw)
def function_name(self):
try:
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) + '"'
+ try:
+ str_for_display = s.str_for_display
+ except AttributeError:
+ s = repr(s)
+ else:
+ s = str_for_display()
+ return s
return '[' + string.join(map(quote, a), ", ") + ']'
try:
strfunc = self.execfunction.strfunction
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):
- """Return the signature contents of this callable action.
+ if executor:
+ target = executor.get_all_targets()
+ source = executor.get_all_sources()
+ rsources = 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)
- 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):
- if __debug__: logInstanceCreation(self)
+ if __debug__: logInstanceCreation(self, 'Action.ListAction')
def list_of_actions(x):
if isinstance(x, 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 = ()
+ self.targets = '$TARGETS'
def genstring(self, target, source, env):
return string.join(map(lambda a, t=target, s=source, e=env:
def __str__(self):
return string.join(map(str, self.list), '\n')
-
+
def presub_lines(self, env):
- return SCons.Util.flatten(map(lambda a, env=env:
- a.presub_lines(env),
- self.list))
+ 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.
self.list),
"")
- def __call__(self, target, source, env, errfunc=None, presub=_null,
- show=_null, execute=_null, chdir=_null):
+ 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, errfunc, presub,
- show, execute, chdir)
+ 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
(positional and keyword) arguments until the Action is actually
self.parent = parent
self.args = args
self.kw = kw
+
def get_contents(self, target, source, env):
actfunc = self.parent.actfunc
try:
# 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
- else:
- return env.subst(s, 0, target, source)
+ 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):
+
+ def __call__(self, target, source, env, executor=None):
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:
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)
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: