-"""engine.SCons.Action
+"""SCons.Action
-XXX
+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.
+ This is what the Sig/*.py subsystem uses 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.
+
+ 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 Steven Knight
+# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
import string
import sys
+from SCons.Debug import logInstanceCreation
+import SCons.Errors
import SCons.Util
-print_actions = 1;
-execute_actions = 1;
-
-exitvalmap = {
- 2 : 127,
- 13 : 126,
-}
-
-if os.name == 'posix':
-
- def defaultSpawn(cmd, args, env):
- pid = os.fork()
- if not pid:
- # Child process.
- exitval = 127
- args = [ 'sh', '-c' ] + \
- [ string.join(map(lambda x: string.replace(str(x),
- ' ',
- r'\ '),
- args)) ]
- try:
- os.execvpe('sh', args, env)
- except OSError, e:
- exitval = exitvalmap[e[0]]
- sys.stderr.write("scons: %s: %s\n" % (cmd, e[1]))
- os._exit(exitval)
- else:
- # Parent process.
- pid, stat = os.waitpid(pid, 0)
- ret = stat >> 8
- return ret
-
-elif os.name == 'nt':
-
- def pathsearch(cmd, env):
- # In order to deal with the fact that 1.5.2 doesn't have
- # os.spawnvpe(), roll our own PATH search.
- if os.path.isabs(cmd):
- if not os.path.exists(cmd):
- exts = env['PATHEXT']
- if not SCons.Util.is_List(exts):
- exts = string.split(exts, os.pathsep)
- for e in exts:
- f = cmd + e
- if os.path.exists(f):
- return f
- else:
- return cmd
+class _Null:
+ pass
+
+_null = _Null
+
+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 _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:
- path = env['PATH']
- if not SCons.Util.is_List(path):
- path = string.split(path, os.pathsep)
- exts = env['PATHEXT']
- if not SCons.Util.is_List(exts):
- exts = string.split(exts, os.pathsep)
- pairs = []
- for dir in path:
- for e in exts:
- pairs.append((dir, e))
- for dir, ext in pairs:
- f = os.path.join(dir, cmd)
- if not ext is None:
- f = f + ext
- if os.path.exists(f):
- return f
- return None
-
- # Attempt to find cmd.exe (for WinNT/2k/XP) or
- # command.com for Win9x
-
- cmd_interp = ''
- # First see if we can look in the registry...
- if SCons.Util.can_read_reg:
- try:
- # Look for Windows NT system root
- k=SCons.Util.RegOpenKeyEx(SCons.Util.hkey_mod.HKEY_LOCAL_MACHINE,
- 'Software\\Microsoft\\Windows NT\\CurrentVersion')
- val, tok = SCons.Util.RegQueryValueEx(k, 'SystemRoot')
- cmd_interp = os.path.join(val, 'System32\\cmd.exe')
- except SCons.Util.RegError:
- try:
- # Okay, try the Windows 9x system root
- k=SCons.Util.RegOpenKeyEx(SCons.Util.hkey_mod.HKEY_LOCAL_MACHINE,
- 'Software\\Microsoft\\Windows\\CurrentVersion')
- val, tok = SCons.Util.RegQueryValueEx(k, 'SystemRoot')
- cmd_interp = os.path.join(val, 'command.com')
- except:
- pass
- if not cmd_interp:
- cmd_interp = pathsearch('cmd', os.environ)
- if not cmd_interp:
- cmd_interp = pathsearch('command', os.environ)
-
- # The upshot of all this is that, if you are using Python 1.5.2,
- # you had better have cmd or command.com in your PATH when you run
- # scons.
-
- def defaultSpawn(cmd, args, env):
- if not cmd_interp:
- sys.stderr.write("scons: Could not find command interpreter, is it in your PATH?\n")
- return 127
+ return ListAction(a1.list + [ a2 ])
+ else:
+ if isinstance(a2, ListAction):
+ return ListAction([ a1 ] + a2.list)
else:
- try:
-
- a = [ cmd_interp, '/C', args[0] ]
- for arg in args[1:]:
- if ' ' in arg or '\t' in arg:
- arg = '"' + arg + '"'
- a.append(arg)
- ret = os.spawnve(os.P_WAIT, cmd_interp, a, env)
- except OSError, e:
- ret = exitvalmap[e[0]]
- sys.stderr.write("scons: %s: %s\n" % (cmd, e[1]))
- return ret
-else:
- def defaultSpawn(cmd, args, env):
- sys.stderr.write("scons: Unknown os '%s', cannot spawn command interpreter.\n" % os.name)
- sys.stderr.write("scons: Set your command handler with SetCommandHandler().\n")
- return 127
-
-spawn = defaultSpawn
-
-def SetCommandHandler(func):
- global spawn
- spawn = func
-
-def GetCommandHandler():
- global spawn
- return spawn
+ return ListAction([ a1, a2 ])
class CommandGenerator:
"""
- Wrappes a command generator function so the Action() factory
+ Wraps a command generator function so the Action() factory
function can tell a generator function from a function action.
"""
def __init__(self, generator):
self.generator = generator
+ def __add__(self, other):
+ return _actionAppend(self, other)
-def Action(act):
- """A factory for action objects."""
- if isinstance(act, ActionBase):
- return act
- elif isinstance(act, CommandGenerator):
- return CommandGeneratorAction(act.generator)
- elif callable(act):
- return FunctionAction(act)
- elif SCons.Util.is_String(act):
- return CommandAction(act)
- elif SCons.Util.is_List(act):
- return ListAction(act)
- else:
- return None
+ def __radd__(self, other):
+ return _actionAppend(other, self)
-class ActionBase:
- """Base class for actions that create output objects."""
- def __cmp__(self, other):
- return cmp(self.__dict__, other.__dict__)
-
- def show(self, string):
- print string
-
- def subst_dict(self, **kw):
- """Create a dictionary for substitution of construction
- variables.
-
- This translates the following special arguments:
-
- env - the construction environment itself,
- the values of which (CC, CCFLAGS, etc.)
- are copied straight into the dictionary
+def _do_create_action(act, *args, **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.
- target - the target (object or array of objects),
- used to generate the TARGET and TARGETS
- construction variables
+ The former will create a ListAction, the latter
+ will create a CommandAction by converting the inner
+ list elements to strings."""
- source - the source (object or array of objects),
- used to generate the SOURCES construction
- variable
-
- Any other keyword arguments are copied into the
- dictionary."""
-
- dict = {}
- if kw.has_key('env'):
- dict.update(kw['env'])
- del kw['env']
+ 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 callable(act):
+ return apply(FunctionAction, (act,)+args, kw)
+ if SCons.Util.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 apply(LazyAction, (var,)+args, 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)
+ return None
- try:
- cwd = kw['dir']
- except:
- cwd = None
+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:
- del kw['dir']
+ return ListAction(acts)
+ else:
+ return apply(_do_create_action, (act,)+args, kw)
+
+class ActionBase:
+ """Base class for all types of action objects that can be held by
+ other objects (Builders, Executors, etc.) This provides the
+ common methods for manipulating and combining those actions."""
+
+ __metaclass__ = SCons.Memoize.Memoized_Metaclass
- if kw.has_key('target'):
- t = kw['target']
- del kw['target']
- if not SCons.Util.is_List(t):
- t = [t]
+ def __cmp__(self, other):
+ return cmp(self.__dict__, other)
+
+ def genstring(self, target, source, env):
+ return str(self)
+
+ def __add__(self, other):
+ return _actionAppend(self, other)
+
+ def __radd__(self, other):
+ return _actionAppend(other, self)
+
+ def presub_lines(self, env):
+ # CommandGeneratorAction needs a real environment
+ # in order to return the proper string here, since
+ # it may call 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 = string.split(str(self), '\n')
+ 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)
+
+
+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
+ self.presub = presub
+ self.chdir = chdir
+
+ def print_cmd_line(self, s, target, source, env):
+ sys.stdout.write(s + "\n")
+
+ def __call__(self, target, source, env,
+ errfunc=None,
+ presub=_null,
+ show=_null,
+ execute=_null,
+ chdir=_null):
+ if not SCons.Util.is_List(target):
+ target = [target]
+ if not SCons.Util.is_List(source):
+ source = [source]
+ if presub is _null: presub = self.presub
+ 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:
- cwd = t[0].cwd
+ chdir = str(chdir.abspath)
except AttributeError:
- pass
- dict['TARGETS'] = SCons.Util.PathList(map(os.path.normpath, map(str, t)))
- if dict['TARGETS']:
- dict['TARGET'] = dict['TARGETS'][0]
-
- if kw.has_key('source'):
- s = kw['source']
- del kw['source']
- if not SCons.Util.is_List(s):
- s = [s]
- dict['SOURCES'] = SCons.Util.PathList(map(os.path.normpath, map(str, s)))
-
- dict.update(kw)
+ if not SCons.Util.is_String(chdir):
+ 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:\n %s\n" % (t, l)
+ sys.stdout.write(out)
+ s = None
+ if show and self.strfunction:
+ s = self.strfunction(target, source, env)
+ if s:
+ if chdir:
+ s = ('os.chdir(%s)\n' % repr(chdir)) + s
+ 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(s, 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)
+ finally:
+ if save_cwd:
+ os.chdir(save_cwd)
+ if s 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 string.join(cl)
+
+class CommandAction(_ActionAction):
+ """Class for command-execution actions."""
+ def __init__(self, cmd, cmdstr=None, *args, **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')
+ apply(_ActionAction.__init__, (self,)+args, kw)
+ if SCons.Util.is_List(cmd):
+ if filter(SCons.Util.is_List, cmd):
+ raise TypeError, "CommandAction should be given only " \
+ "a single command"
+ self.cmd_list = cmd
+ self.cmdstr = cmdstr
+
+ def __str__(self):
+ if SCons.Util.is_List(self.cmd_list):
+ return string.join(map(str, self.cmd_list), ' ')
+ return str(self.cmd_list)
+
+ def strfunction(self, target, source, env):
+ if not self.cmdstr is None:
+ c = env.subst(self.cmdstr, 0, target, source)
+ if c:
+ return c
+ cmd_list = env.subst_list(self.cmd_list, 0, target, source)
+ return _string_from_cmd_list(cmd_list[0])
+
+ def execute(self, target, source, env):
+ """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.
+ """
+ from SCons.Util import is_String, is_List, flatten, escape_list
- # Autogenerate necessary construction variables.
- SCons.Util.autogenerate(dict, dir = cwd)
+ try:
+ shell = env['SHELL']
+ except KeyError:
+ raise SCons.Errors.UserError('Missing SHELL construction variable.')
- return dict
+ try:
+ spawn = env['SPAWN']
+ except KeyError:
+ raise SCons.Errors.UserError('Missing SPAWN construction variable.')
-_rm = re.compile(r'\$[()]')
-_remove = re.compile(r'\$\(([^\$]|\$[^\(])*?\$\)')
+ escape = env.get('ESCAPE', lambda x: x)
-class CommandAction(ActionBase):
- """Class for command-execution actions."""
- def __init__(self, string):
- self.command = string
-
- def execute(self, **kw):
- dict = apply(self.subst_dict, (), kw)
- import SCons.Util
- cmd_list = SCons.Util.scons_subst_list(self.command, dict, {}, _rm)
- for cmd_line in cmd_list:
- if len(cmd_line):
- if print_actions:
- cl = []
- for arg in cmd_line:
- if ' ' in arg or '\t' in arg:
- arg = '"' + arg + '"'
- cl.append(arg)
- self.show(string.join(cl))
- if execute_actions:
- try:
- ENV = kw['env']['ENV']
- except:
- import SCons.Defaults
- ENV = SCons.Defaults.ConstructionEnvironment['ENV']
- ret = spawn(cmd_line[0], cmd_line, ENV)
- if ret:
- return ret
+ 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
+
+ # 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(value)
+ ENV[key] = string.join(map(str, value), os.pathsep)
+ 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)
+
+ cmd_list = env.subst_list(self.cmd_list, 0, target,
+ map(rfile, source))
+
+ # Use len() to filter out any "command" that's zero-length.
+ for cmd_line in filter(len, cmd_list):
+ # 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
return 0
- def _sig_dict(self, kw):
- """Supply a dictionary for use in computing signatures.
-
- For signature purposes, it doesn't matter what targets or
- sources we use, so long as we use the same ones every time
- so the signature stays the same. We supply an array of two
- of each to allow for distinction between TARGET and TARGETS.
- """
- kw['target'] = ['__t1__', '__t2__']
- kw['source'] = ['__s1__', '__s2__']
- return apply(self.subst_dict, (), kw)
-
- def get_raw_contents(self, **kw):
- """Return the complete contents of this action's command line.
- """
- return SCons.Util.scons_subst(self.command, self._sig_dict(kw), {})
-
- def get_contents(self, **kw):
+ def get_contents(self, target, source, env):
"""Return the signature contents of this action's command line.
This strips $(-$) and everything in between the string,
since those parts don't affect signatures.
"""
- return SCons.Util.scons_subst(self.command, self._sig_dict(kw), {}, _remove)
+ cmd = self.cmd_list
+ if SCons.Util.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)
class CommandGeneratorAction(ActionBase):
"""Class for command-generator actions."""
- def __init__(self, generator):
+ def __init__(self, generator, *args, **kw):
+ if __debug__: logInstanceCreation(self, 'Action.CommandGeneratorAction')
self.generator = generator
+ self.gen_kw = kw
- def execute(self, **kw):
+ def _generate(self, target, source, env, for_signature):
# ensure that target is a list, to make it easier to write
# generator functions:
- import SCons.Util
- if kw.has_key("target") and not SCons.Util.is_List(kw["target"]):
- kw["target"] = [kw["target"]]
-
- gen_list = apply(self.generator, (), kw)
- gen_list = map(lambda x: map(str, x), gen_list)
-
- # Do environment variable substitution on returned command list
- dict = apply(self.subst_dict, (), kw)
- cmd_list = [ ]
- for gen_line in gen_list:
- cmd_list.append([])
- curr_line = cmd_list[-1]
- for gen_arg in gen_line:
- arg_list = SCons.Util.scons_subst_list(gen_arg, dict, {})
- curr_line.extend(arg_list[0])
- if(len(arg_list) > 1):
- cmd_list.extend(arg_list[1:])
- curr_line = cmd_list[-1]
-
- for cmd_line in filter(lambda x: x, cmd_list):
- if print_actions:
- self.show(cmd_line)
- if execute_actions:
- try:
- ENV = kw['env']['ENV']
- except:
- import SCons.Defaults
- ENV = SCons.Defaults.ConstructionEnvironment['ENV']
- ret = spawn(cmd_line[0], cmd_line, ENV)
- if ret:
- return ret
+ if not SCons.Util.is_List(target):
+ target = [target]
- return 0
+ ret = self.generator(target=target, source=source, env=env, for_signature=for_signature)
+ gen_cmd = apply(Action, (ret,), self.gen_kw)
+ if not gen_cmd:
+ raise SCons.Errors.UserError("Object returned from command generator: %s cannot be used to create an Action." % repr(ret))
+ return gen_cmd
- def get_contents(self, **kw):
+ def __str__(self):
+ try:
+ env = self.presub_env or {}
+ except AttributeError:
+ env = {}
+ 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 __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 get_contents(self, target, source, env):
"""Return the signature contents of this action's command line.
This strips $(-$) and everything in between the string,
since those parts don't affect signatures.
"""
- kw['source'] = ["__s1__", "__s2__"]
- kw['target'] = ["__t1__", "__t2__"]
- cmd_list = apply(self.generator, (), kw)
- cmd_list = map(lambda x: map(str, x), cmd_list)
- cmd_list = map(lambda x: string.join(x, "\0"), cmd_list)
- cmd_list = map(lambda x: _remove.sub('', x), cmd_list)
- cmd_list = map(lambda x: filter(lambda y: y, string.split(x, "\0")), cmd_list)
- return cmd_list
-
-class FunctionAction(ActionBase):
+ return self._generate(target, source, env, 1).get_contents(target, source, env)
+
+
+
+# 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):
+
+ __metaclass__ = SCons.Memoize.Memoized_Metaclass
+
+ def __init__(self, var, *args, **kw):
+ if __debug__: logInstanceCreation(self, 'Action.LazyAction')
+ apply(CommandAction.__init__, (self, '$'+var)+args, 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:
+ return CommandAction
+ return CommandGeneratorAction
+
+ def _generate_cache(self, env):
+ """__cacheable__"""
+ c = env.get(self.var, '')
+ 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):
+ 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)
+ return apply(c.__call__, args, kw)
+
+ def get_contents(self, target, source, env):
+ c = self.get_parent_class(env)
+ return c.get_contents(self, target, source, env)
+
+if not SCons.Memoize.has_metaclass:
+ _Base = LazyAction
+ class LazyAction(SCons.Memoize.Memoizer, _Base):
+ def __init__(self, *args, **kw):
+ SCons.Memoize.Memoizer.__init__(self)
+ apply(_Base.__init__, (self,)+args, kw)
+
+
+
+class FunctionAction(_ActionAction):
"""Class for Python function actions."""
- def __init__(self, function):
- self.function = function
-
- def execute(self, **kw):
- # if print_actions:
- # XXX: WHAT SHOULD WE PRINT HERE?
- if execute_actions:
- if kw.has_key('target'):
- if SCons.Util.is_List(kw['target']):
- kw['target'] = map(str, kw['target'])
- else:
- kw['target'] = str(kw['target'])
- if kw.has_key('source'):
- kw['source'] = map(str, kw['source'])
- return apply(self.function, (), kw)
- def get_contents(self, **kw):
+ def __init__(self, execfunction, *args, **kw):
+ if __debug__: logInstanceCreation(self, 'Action.FunctionAction')
+ self.execfunction = execfunction
+ apply(_ActionAction.__init__, (self,)+args, kw)
+ self.varlist = kw.get('varlist', [])
+
+ 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):
+ def array(a):
+ def quote(s):
+ return '"' + str(s) + '"'
+ return '[' + string.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):
+ rsources = map(rfile, source)
+ 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.
By providing direct access to the code object of the
function, Python makes this extremely easy. Hooray!
"""
- #XXX DOES NOT ACCOUNT FOR CHANGES IN ENVIRONMENT VARIABLES
- #THE FUNCTION MAY USE
try:
- # "self.function" is a function.
- code = self.function.func_code.co_code
- except:
- # "self.function" is a callable object.
- code = self.function.__call__.im_func.func_code.co_code
- return str(code)
+ # "self.execfunction" is a function.
+ contents = str(self.execfunction.func_code.co_code)
+ 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)))
class ListAction(ActionBase):
"""Class for lists of other actions."""
def __init__(self, list):
- self.list = map(lambda x: Action(x), list)
-
- def execute(self, **kw):
- for l in self.list:
- r = apply(l.execute, (), kw)
- if r != 0:
- return r
- return 0
-
- def get_contents(self, **kw):
+ 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)
+
+ def genstring(self, target, source, env):
+ return string.join(map(lambda a, t=target, s=source, e=env:
+ a.genstring(t, s, e),
+ self.list),
+ '\n')
+
+ 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))
+
+ def get_contents(self, target, source, env):
"""Return the signature contents of this action list.
Simple concatenation of the signatures of the elements.
"""
+ return string.join(map(lambda x, t=target, s=source, e=env:
+ x.get_contents(t, s, e),
+ self.list),
+ "")
+
+ def __call__(self, target, source, env, errfunc=None, presub=_null,
+ show=_null, execute=_null, chdir=_null):
+ for act in self.list:
+ stat = act(target, source, env, errfunc, presub,
+ show, execute, chdir)
+ if stat:
+ return stat
+ return 0
+
+class ActionCaller:
+ """A class for delaying calling an Action function with specific
+ (positional and keyword) arguments until the Action is actually
+ executed.
- return reduce(lambda x, y: x + str(y.get_contents()), self.list, "")
+ 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)
+ return contents
+ def subst(self, s, target, source, env):
+ # 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)
+ def subst_args(self, target, source, env):
+ return map(lambda x, self=self, t=target, s=source, e=env:
+ self.subst(x, t, s, e),
+ self.args)
+ def subst_kw(self, target, source, env):
+ kw = {}
+ for key in self.kw.keys():
+ kw[key] = self.subst(self.kw[key], target, source, env)
+ return kw
+ def __call__(self, target, source, env):
+ args = self.subst_args(target, source, env)
+ kw = self.subst_kw(target, source, env)
+ 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)
+ return apply(self.parent.strfunc, args, kw)
+ def __str__(self):
+ return apply(self.parent.strfunc, self.args, self.kw)
+
+class ActionFactory:
+ """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):
+ self.actfunc = actfunc
+ self.strfunc = strfunc
+ def __call__(self, *args, **kw):
+ ac = ActionCaller(self, args, kw)
+ action = Action(ac, strfunction=ac.strfunction)
+ return action