3 This encapsulates information about executing any sort of action that
4 can build one or more target Nodes (typically files) from one or more
5 source Nodes (also typically files) given a specific Environment.
7 The base class here is ActionBase. The base class supplies just a few
8 OO utility methods and some generic methods for displaying information
9 about an Action in response to the various commands that control printing.
11 The heavy lifting is handled by subclasses for the different types of
12 actions we might execute:
15 CommandGeneratorAction
19 The subclasses supply the following public interface methods used by
23 THE public interface, "calling" an Action object executes the
24 command or Python function. This also takes care of printing
25 a pre-substitution command for debugging purposes.
28 Fetches the "contents" of an Action for signature calculation.
29 This is what the Sig/*.py subsystem uses to decide if a target
30 needs to be rebuilt because its action changed.
33 Returns a string representation of the Action *without* command
34 substitution, but allows a CommandGeneratorAction to generate
35 the right action based on the specified target, source and env.
36 This is used by the Signature subsystem (through the Executor)
37 to compare the actions used to build a target last time and
40 Subclasses also supply the following methods for internal use within
44 Returns a string representation of the Action *without* command
45 substitution. This is used by the __call__() methods to display
46 the pre-substitution command whenever the --debug=presub option
50 Returns a substituted string representation of the Action.
51 This is used by the ActionBase.show() command to display the
52 command/function that will be executed to generate the target(s).
55 The internal method that really, truly, actually handles the
56 execution of a command or Python function. This is used so
57 that the __call__() methods can take care of displaying any
58 pre-substitution representations, and *then* execute an action
59 without worrying about the specific Actions involved.
61 There is a related independent ActionCaller class that looks like a
62 regular Action, and which serves as a wrapper for arbitrary functions
63 that we want to let the user specify the arguments to now, but actually
64 execute later (when an out-of-date check determines that it's needed to
65 be executed, for example). Objects of this class are returned by an
66 ActionFactory class that provides a __call__() method as a convenient
67 way for wrapping up the functions.
74 # Permission is hereby granted, free of charge, to any person obtaining
75 # a copy of this software and associated documentation files (the
76 # "Software"), to deal in the Software without restriction, including
77 # without limitation the rights to use, copy, modify, merge, publish,
78 # distribute, sublicense, and/or sell copies of the Software, and to
79 # permit persons to whom the Software is furnished to do so, subject to
80 # the following conditions:
82 # The above copyright notice and this permission notice shall be included
83 # in all copies or substantial portions of the Software.
85 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
86 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
87 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
88 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
89 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
90 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
91 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
94 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
102 from SCons.Debug import logInstanceCreation
113 print_actions_presub = 0
120 except AttributeError:
123 def _actionAppend(act1, act2):
124 # This function knows how to slap two actions together.
125 # Mainly, it handles ListActions by concatenating into
126 # a single ListAction.
129 if a1 is None or a2 is None:
130 raise TypeError, "Cannot append %s to %s" % (type(act1), type(act2))
131 if isinstance(a1, ListAction):
132 if isinstance(a2, ListAction):
133 return ListAction(a1.list + a2.list)
135 return ListAction(a1.list + [ a2 ])
137 if isinstance(a2, ListAction):
138 return ListAction([ a1 ] + a2.list)
140 return ListAction([ a1, a2 ])
142 class CommandGenerator:
144 Wraps a command generator function so the Action() factory
145 function can tell a generator function from a function action.
147 def __init__(self, generator):
148 self.generator = generator
150 def __add__(self, other):
151 return _actionAppend(self, other)
153 def __radd__(self, other):
154 return _actionAppend(other, self)
156 def _do_create_action(act, strfunction=_null, varlist=[]):
157 """This is the actual "implementation" for the
158 Action factory method, below. This handles the
159 fact that passing lists to Action() itself has
160 different semantics than passing lists as elements
163 The former will create a ListAction, the latter
164 will create a CommandAction by converting the inner
165 list elements to strings."""
167 if isinstance(act, ActionBase):
169 elif SCons.Util.is_List(act):
170 return CommandAction(act)
171 elif isinstance(act, CommandGenerator):
172 return CommandGeneratorAction(act.generator)
174 return FunctionAction(act, strfunction=strfunction, varlist=varlist)
175 elif SCons.Util.is_String(act):
176 var=SCons.Util.get_environment_var(act)
178 # This looks like a string that is purely an Environment
179 # variable reference, like "$FOO" or "${FOO}". We do
180 # something special here...we lazily evaluate the contents
181 # of that Environment variable, so a user could but something
182 # like a function or a CommandGenerator in that variable
183 # instead of a string.
184 return CommandGeneratorAction(LazyCmdGenerator(var))
185 listCmds = map(lambda x: CommandAction(x),
186 string.split(str(act), '\n'))
187 if len(listCmds) == 1:
190 return ListAction(listCmds)
194 def Action(act, strfunction=_null, varlist=[]):
195 """A factory for action objects."""
196 if SCons.Util.is_List(act):
197 acts = map(lambda x, s=strfunction, v=varlist:
198 _do_create_action(x, s, v),
200 acts = filter(lambda x: not x is None, acts)
204 return ListAction(acts)
206 return _do_create_action(act, strfunction=strfunction, varlist=varlist)
209 """Base class for actions that create output objects."""
210 def __cmp__(self, other):
211 return cmp(self.__dict__, other.__dict__)
215 sys.stdout.write(s + '\n')
217 def presub(self, target, env):
218 if print_actions_presub:
219 if not SCons.Util.is_List(target):
221 # CommandGeneratorAction needs a real environment
222 # in order to return the proper string here, since
223 # it may call LazyCmdGenerator, which looks up a key
224 # in that env. So we temporarily remember the env here,
225 # and CommandGeneratorAction will use this env
226 # when it calls its __generate method.
227 self.presub_env = env
228 lines = string.split(str(self), '\n')
229 self.presub_env = None # don't need this any more
230 sys.stdout.write("Building %s with action(s):\n %s\n"%
231 (string.join(map(lambda x: str(x), target), ' and '),
232 string.join(lines, '\n ')))
234 def genstring(self, target, source, env):
237 def get_actions(self):
240 def __add__(self, other):
241 return _actionAppend(self, other)
243 def __radd__(self, other):
244 return _actionAppend(other, self)
246 def _string_from_cmd_list(cmd_list):
247 """Takes a list of command line arguments and returns a pretty
248 representation for printing."""
250 for arg in map(str, cmd_list):
251 if ' ' in arg or '\t' in arg:
252 arg = '"' + arg + '"'
254 return string.join(cl)
256 class CommandAction(ActionBase):
257 """Class for command-execution actions."""
258 def __init__(self, cmd):
259 # Cmd list can actually be a list or a single item...basically
260 # anything that we could pass in as the first arg to
261 # Environment.subst_list().
262 if __debug__: logInstanceCreation(self)
266 return str(self.cmd_list)
268 def strfunction(self, target, source, env):
269 cmd_list = env.subst_list(self.cmd_list, 0, target, source)
270 return map(_string_from_cmd_list, cmd_list)
272 def _execute(self, target, source, env):
273 """Execute a command action.
275 This will handle lists of commands as well as individual commands,
276 because construction variable substitution may turn a single
277 "command" into a list. This means that this class can actually
278 handle lists of commands, even though that's not how we use it
283 escape = env.get('ESCAPE', lambda x: x)
285 if env.has_key('SHELL'):
288 raise SCons.Errors.UserError('Missing SHELL construction variable.')
290 # for SConf support (by now): check, if we want to pipe the command
291 # output to somewhere else
292 if env.has_key('PIPE_BUILD'):
294 if env.has_key('PSPAWN'):
295 pspawn = env['PSPAWN']
297 raise SCons.Errors.UserError('Missing PSPAWN construction variable.')
298 if env.has_key('PSTDOUT'):
299 pstdout = env['PSTDOUT']
301 raise SCons.Errors.UserError('Missing PSTDOUT construction variable.')
302 if env.has_key('PSTDERR'):
303 pstderr = env['PSTDERR']
305 raise SCons.Errors.UserError('Missing PSTDOUT construction variable.')
308 if env.has_key('SPAWN'):
311 raise SCons.Errors.UserError('Missing SPAWN construction variable.')
313 cmd_list = env.subst_list(self.cmd_list, 0, target, source)
314 for cmd_line in cmd_list:
317 self.show(_string_from_cmd_list(cmd_line))
324 import SCons.Environment
325 default_ENV = SCons.Environment.Environment()['ENV']
328 # ensure that the ENV values are all strings:
329 for key, value in ENV.items():
330 if SCons.Util.is_List(value):
331 # If the value is a list, then we assume
332 # it is a path list, because that's a pretty
333 # common list like value to stick in an environment
335 ENV[key] = string.join(map(str, value), os.pathsep)
336 elif not SCons.Util.is_String(value):
337 # If it isn't a string or a list, then
338 # we just coerce it to a string, which
339 # is proper way to handle Dir and File instances
340 # and will produce something reasonable for
341 # just about everything else:
342 ENV[key] = str(value)
344 # Escape the command line for the command
345 # interpreter we are using
346 cmd_line = SCons.Util.escape_list(cmd_line, escape)
348 ret = pspawn( shell, escape, cmd_line[0], cmd_line,
349 ENV, pstdout, pstderr )
351 ret = spawn(shell, escape, cmd_line[0], cmd_line, ENV)
356 def __call__(self, target, source, env):
357 self.presub(target, env)
358 return self._execute(target, source, env)
360 def get_contents(self, target, source, env, dict=None):
361 """Return the signature contents of this action's command line.
363 This strips $(-$) and everything in between the string,
364 since those parts don't affect signatures.
367 if SCons.Util.is_List(cmd):
368 cmd = string.join(map(str, cmd))
371 return env.subst_target_source(cmd, SCons.Util.SUBST_SIG, target, source, dict)
373 class CommandGeneratorAction(ActionBase):
374 """Class for command-generator actions."""
375 def __init__(self, generator):
376 if __debug__: logInstanceCreation(self)
377 self.generator = generator
379 def __generate(self, target, source, env, for_signature):
380 # ensure that target is a list, to make it easier to write
381 # generator functions:
382 if not SCons.Util.is_List(target):
385 ret = self.generator(target=target, source=source, env=env, for_signature=for_signature)
386 gen_cmd = Action(ret)
388 raise SCons.Errors.UserError("Object returned from command generator: %s cannot be used to create an Action." % repr(ret))
391 def strfunction(self, target, source, env):
392 if not SCons.Util.is_List(source):
394 rsources = map(rfile, source)
395 act = self.__generate(target, source, env, 0)
396 return act.strfunction(target, rsources, env)
400 env = self.presub_env or {}
401 except AttributeError:
403 act = self.__generate([], [], env, 0)
406 def genstring(self, target, source, env):
407 return str(self.__generate(target, source, env, 0))
409 def _execute(self, target, source, env):
410 if not SCons.Util.is_List(source):
412 rsources = map(rfile, source)
413 act = self.__generate(target, source, env, 0)
414 return act._execute(target, rsources, env)
416 def __call__(self, target, source, env):
417 if not SCons.Util.is_List(source):
419 rsources = map(rfile, source)
420 act = self.__generate(target, source, env, 0)
421 act.presub(target, env)
422 return act._execute(target, source, env)
424 def get_contents(self, target, source, env, dict=None):
425 """Return the signature contents of this action's command line.
427 This strips $(-$) and everything in between the string,
428 since those parts don't affect signatures.
430 return self.__generate(target, source, env, 1).get_contents(target, source, env, dict=None)
432 class LazyCmdGenerator:
433 """This is not really an Action, although it kind of looks like one.
434 This is really a simple callable class that acts as a command
435 generator. It holds on to a key into an Environment dictionary,
436 then waits until execution time to see what type it is, then tries
437 to create an Action out of it."""
438 def __init__(self, var):
439 if __debug__: logInstanceCreation(self)
440 self.var = SCons.Util.to_String(var)
442 def strfunction(self, target, source, env):
446 # The variable reference substitutes to nothing.
450 return 'LazyCmdGenerator: %s'%str(self.var)
452 def _execute(self, target, source, env, for_signature):
456 # The variable reference substitutes to nothing.
459 def __call__(self, target, source, env, for_signature):
460 return self._execute(target, source, env, for_signature)
462 def __cmp__(self, other):
463 return cmp(self.__dict__, other.__dict__)
465 class FunctionAction(ActionBase):
466 """Class for Python function actions."""
468 def __init__(self, execfunction, strfunction=_null, varlist=[]):
469 if __debug__: logInstanceCreation(self)
470 self.execfunction = execfunction
471 if strfunction is _null:
472 def strfunction(target, source, env, self=self):
474 return '"' + str(s) + '"'
475 def array(a, q=quote):
476 return '[' + string.join(map(lambda x, q=q: q(x), a), ", ") + ']'
477 name = self.function_name()
478 tstr = len(target) == 1 and quote(target[0]) or array(target)
479 sstr = len(source) == 1 and quote(source[0]) or array(source)
480 return "%s(%s, %s)" % (name, tstr, sstr)
481 self.strfunction = strfunction
482 self.varlist = varlist
484 def function_name(self):
486 return self.execfunction.__name__
487 except AttributeError:
489 return self.execfunction.__class__.__name__
490 except AttributeError:
491 return "unknown_python_function"
494 return "%s(env, target, source)" % self.function_name()
496 def _execute(self, target, source, env):
498 if not SCons.Util.is_List(target):
500 if not SCons.Util.is_List(source):
502 if print_actions and self.strfunction:
503 s = self.strfunction(target, source, env)
507 rsources = map(rfile, source)
508 r = self.execfunction(target=target, source=rsources, env=env)
511 def __call__(self, target, source, env):
512 self.presub(target, env)
513 return self._execute(target, source, env)
515 def get_contents(self, target, source, env, dict=None):
516 """Return the signature contents of this callable action.
518 By providing direct access to the code object of the
519 function, Python makes this extremely easy. Hooray!
522 # "self.execfunction" is a function.
523 contents = str(self.execfunction.func_code.co_code)
524 except AttributeError:
525 # "self.execfunction" is a callable object.
527 contents = str(self.execfunction.__call__.im_func.func_code.co_code)
528 except AttributeError:
530 # See if execfunction will do the heavy lifting for us.
531 gc = self.execfunction.get_contents
532 except AttributeError:
533 # This is weird, just do the best we can.
534 contents = str(self.execfunction)
536 contents = gc(target, source, env, dict)
537 return contents + env.subst(string.join(map(lambda v: '${'+v+'}',
540 class ListAction(ActionBase):
541 """Class for lists of other actions."""
542 def __init__(self, list):
543 if __debug__: logInstanceCreation(self)
544 self.list = map(lambda x: Action(x), list)
546 def get_actions(self):
553 return string.join(s, "\n")
555 def strfunction(self, target, source, env):
559 x = l.strfunction(target, source, env)
560 if not SCons.Util.is_List(x):
563 return string.join(s, "\n")
565 def _execute(self, target, source, env):
567 r = l._execute(target, source, env)
572 def __call__(self, target, source, env):
573 self.presub(target, env)
574 return self._execute(target, source, env)
576 def get_contents(self, target, source, env, dict=None):
577 """Return the signature contents of this action list.
579 Simple concatenation of the signatures of the elements.
581 dict = SCons.Util.subst_dict(target, source)
582 return string.join(map(lambda x, t=target, s=source, e=env, d=dict:
583 x.get_contents(t, s, e, d),
588 """A class for delaying calling an Action function with specific
589 (positional and keyword) arguments until the Action is actually
592 This class looks to the rest of the world like a normal Action object,
593 but what it's really doing is hanging on to the arguments until we
594 have a target, source and env to use for the expansion.
596 def __init__(self, parent, args, kw):
600 def get_contents(self, target, source, env, dict=None):
601 actfunc = self.parent.actfunc
603 # "self.actfunc" is a function.
604 contents = str(actfunc.func_code.co_code)
605 except AttributeError:
606 # "self.actfunc" is a callable object.
608 contents = str(actfunc.__call__.im_func.func_code.co_code)
609 except AttributeError:
610 # No __call__() method, so it might be a builtin
611 # or something like that. Do the best we can.
612 contents = str(actfunc)
614 def subst_args(self, target, source, env):
615 return map(lambda x, e=env, t=target, s=source:
618 def subst_kw(self, target, source, env):
620 for key in self.kw.keys():
621 kw[key] = env.subst(self.kw[key], 0, target, source)
623 def __call__(self, target, source, env):
624 args = self.subst_args(target, source, env)
625 kw = self.subst_kw(target, source, env)
626 return apply(self.parent.actfunc, args, kw)
627 def strfunction(self, target, source, env):
628 args = self.subst_args(target, source, env)
629 kw = self.subst_kw(target, source, env)
630 return apply(self.parent.strfunc, args, kw)
633 """A factory class that will wrap up an arbitrary function
634 as an SCons-executable Action object.
636 The real heavy lifting here is done by the ActionCaller class.
637 We just collect the (positional and keyword) arguments that we're
638 called with and give them to the ActionCaller object we create,
639 so it can hang onto them until it needs them.
641 def __init__(self, actfunc, strfunc):
642 self.actfunc = actfunc
643 self.strfunc = strfunc
644 def __call__(self, *args, **kw):
645 ac = ActionCaller(self, args, kw)
646 return Action(ac, strfunction=ac.strfunction)