More flexible (and Make-like) ignoring command exit status, and suppressing printing...
[scons.git] / src / engine / SCons / Action.py
1 """SCons.Action
2
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.
6
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.
10
11 A second-level base class is _ActionAction.  This extends ActionBase
12 by providing the methods that can be used to show and perform an
13 action.  True Action objects will subclass _ActionAction; Action
14 factory class objects will subclass ActionBase.
15
16 The heavy lifting is handled by subclasses for the different types of
17 actions we might execute:
18
19     CommandAction
20     CommandGeneratorAction
21     FunctionAction
22     ListAction
23
24 The subclasses supply the following public interface methods used by
25 other modules:
26
27     __call__()
28         THE public interface, "calling" an Action object executes the
29         command or Python function.  This also takes care of printing
30         a pre-substitution command for debugging purposes.
31
32     get_contents()
33         Fetches the "contents" of an Action for signature calculation.
34         This is what the Sig/*.py subsystem uses to decide if a target
35         needs to be rebuilt because its action changed.
36
37     genstring()
38         Returns a string representation of the Action *without*
39         command substitution, but allows a CommandGeneratorAction to
40         generate the right action based on the specified target,
41         source and env.  This is used by the Signature subsystem
42         (through the Executor) to obtain an (imprecise) representation
43         of the Action operation for informative purposes.
44
45
46 Subclasses also supply the following methods for internal use within
47 this module:
48
49     __str__()
50         Returns a string approximation of the Action; no variable
51         substitution is performed.
52         
53     execute()
54         The internal method that really, truly, actually handles the
55         execution of a command or Python function.  This is used so
56         that the __call__() methods can take care of displaying any
57         pre-substitution representations, and *then* execute an action
58         without worrying about the specific Actions involved.
59
60     strfunction()
61         Returns a substituted string representation of the Action.
62         This is used by the _ActionAction.show() command to display the
63         command/function that will be executed to generate the target(s).
64
65 There is a related independent ActionCaller class that looks like a
66 regular Action, and which serves as a wrapper for arbitrary functions
67 that we want to let the user specify the arguments to now, but actually
68 execute later (when an out-of-date check determines that it's needed to
69 be executed, for example).  Objects of this class are returned by an
70 ActionFactory class that provides a __call__() method as a convenient
71 way for wrapping up the functions.
72
73 """
74
75 #
76 # __COPYRIGHT__
77 #
78 # Permission is hereby granted, free of charge, to any person obtaining
79 # a copy of this software and associated documentation files (the
80 # "Software"), to deal in the Software without restriction, including
81 # without limitation the rights to use, copy, modify, merge, publish,
82 # distribute, sublicense, and/or sell copies of the Software, and to
83 # permit persons to whom the Software is furnished to do so, subject to
84 # the following conditions:
85 #
86 # The above copyright notice and this permission notice shall be included
87 # in all copies or substantial portions of the Software.
88 #
89 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
90 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
91 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
92 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
93 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
94 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
95 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
96 #
97
98 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
99
100 import os
101 import os.path
102 import re
103 import string
104 import sys
105
106 from SCons.Debug import logInstanceCreation
107 import SCons.Errors
108 import SCons.Util
109
110 class _Null:
111     pass
112
113 _null = _Null
114
115 print_actions = 1
116 execute_actions = 1
117 print_actions_presub = 0
118
119 default_ENV = None
120
121 def rfile(n):
122     try:
123         return n.rfile()
124     except AttributeError:
125         return n
126
127 def default_exitstatfunc(s):
128     return s
129
130 def _actionAppend(act1, act2):
131     # This function knows how to slap two actions together.
132     # Mainly, it handles ListActions by concatenating into
133     # a single ListAction.
134     a1 = Action(act1)
135     a2 = Action(act2)
136     if a1 is None or a2 is None:
137         raise TypeError, "Cannot append %s to %s" % (type(act1), type(act2))
138     if isinstance(a1, ListAction):
139         if isinstance(a2, ListAction):
140             return ListAction(a1.list + a2.list)
141         else:
142             return ListAction(a1.list + [ a2 ])
143     else:
144         if isinstance(a2, ListAction):
145             return ListAction([ a1 ] + a2.list)
146         else:
147             return ListAction([ a1, a2 ])
148
149 def _do_create_action(act, *args, **kw):
150     """This is the actual "implementation" for the
151     Action factory method, below.  This handles the
152     fact that passing lists to Action() itself has
153     different semantics than passing lists as elements
154     of lists.
155
156     The former will create a ListAction, the latter
157     will create a CommandAction by converting the inner
158     list elements to strings."""
159
160     if isinstance(act, ActionBase):
161         return act
162     if SCons.Util.is_List(act):
163         return apply(CommandAction, (act,)+args, kw)
164     if callable(act):
165         try:
166             gen = kw['generator']
167             del kw['generator']
168         except KeyError:
169             gen = 0
170         if gen:
171             action_type = CommandGeneratorAction
172         else:
173             action_type = FunctionAction
174         return apply(action_type, (act,)+args, kw)
175     if SCons.Util.is_String(act):
176         var=SCons.Util.get_environment_var(act)
177         if var:
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 put something
182             # like a function or a CommandGenerator in that variable
183             # instead of a string.
184             return apply(LazyAction, (var,)+args, kw)
185         commands = string.split(str(act), '\n')
186         if len(commands) == 1:
187             return apply(CommandAction, (commands[0],)+args, kw)
188         else:
189             listCmdActions = map(lambda x, args=args, kw=kw:
190                                  apply(CommandAction, (x,)+args, kw),
191                                  commands)
192             return ListAction(listCmdActions)
193     return None
194
195 def Action(act, *args, **kw):
196     """A factory for action objects."""
197     if SCons.Util.is_List(act):
198         acts = map(lambda a, args=args, kw=kw:
199                           apply(_do_create_action, (a,)+args, kw),
200                    act)
201         acts = filter(None, acts)
202         if len(acts) == 1:
203             return acts[0]
204         else:
205             return ListAction(acts)
206     else:
207         return apply(_do_create_action, (act,)+args, kw)
208
209 class ActionBase:
210     """Base class for all types of action objects that can be held by
211     other objects (Builders, Executors, etc.)  This provides the
212     common methods for manipulating and combining those actions."""
213     
214     __metaclass__ = SCons.Memoize.Memoized_Metaclass
215
216     def __cmp__(self, other):
217         return cmp(self.__dict__, other)
218
219     def genstring(self, target, source, env):
220         return str(self)
221
222     def __add__(self, other):
223         return _actionAppend(self, other)
224
225     def __radd__(self, other):
226         return _actionAppend(other, self)
227
228     def presub_lines(self, env):
229         # CommandGeneratorAction needs a real environment
230         # in order to return the proper string here, since
231         # it may call LazyAction, which looks up a key
232         # in that env.  So we temporarily remember the env here,
233         # and CommandGeneratorAction will use this env
234         # when it calls its _generate method.
235         self.presub_env = env
236         lines = string.split(str(self), '\n')
237         self.presub_env = None      # don't need this any more
238         return lines
239
240 if not SCons.Memoize.has_metaclass:
241     _Base = ActionBase
242     class ActionBase(SCons.Memoize.Memoizer, _Base):
243         "Cache-backed version of ActionBase"
244         def __init__(self, *args, **kw):
245             apply(_Base.__init__, (self,)+args, kw)
246             SCons.Memoize.Memoizer.__init__(self)
247
248
249 class _ActionAction(ActionBase):
250     """Base class for actions that create output objects."""
251     def __init__(self, strfunction=_null, presub=_null, chdir=None, exitstatfunc=None, **kw):
252         if not strfunction is _null:
253             self.strfunction = strfunction
254         if presub is _null:
255             presub = print_actions_presub
256         self.presub = presub
257         self.chdir = chdir
258         if not exitstatfunc:
259             exitstatfunc = default_exitstatfunc
260         self.exitstatfunc = exitstatfunc
261
262     def print_cmd_line(self, s, target, source, env):
263         sys.stdout.write(s + "\n")
264
265     def __call__(self, target, source, env,
266                                exitstatfunc=_null,
267                                presub=_null,
268                                show=_null,
269                                execute=_null,
270                                chdir=_null):
271         if not SCons.Util.is_List(target):
272             target = [target]
273         if not SCons.Util.is_List(source):
274             source = [source]
275         if exitstatfunc is _null: exitstatfunc = self.exitstatfunc
276         if presub is _null:  presub = self.presub
277         if show is _null:  show = print_actions
278         if execute is _null:  execute = execute_actions
279         if chdir is _null: chdir = self.chdir
280         save_cwd = None
281         if chdir:
282             save_cwd = os.getcwd()
283             try:
284                 chdir = str(chdir.abspath)
285             except AttributeError:
286                 if not SCons.Util.is_String(chdir):
287                     chdir = str(target[0].dir)
288         if presub:
289             t = string.join(map(str, target), ' and ')
290             l = string.join(self.presub_lines(env), '\n  ')
291             out = "Building %s with action:\n  %s\n" % (t, l)
292             sys.stdout.write(out)
293         s = None
294         if show and self.strfunction:
295             s = self.strfunction(target, source, env)
296             if s:
297                 if chdir:
298                     s = ('os.chdir(%s)\n' % repr(chdir)) + s
299                 try:
300                     get = env.get
301                 except AttributeError:
302                     print_func = self.print_cmd_line
303                 else:
304                     print_func = get('PRINT_CMD_LINE_FUNC')
305                     if not print_func:
306                         print_func = self.print_cmd_line
307                 print_func(s, target, source, env)
308         stat = 0
309         if execute:
310             if chdir:
311                 os.chdir(chdir)
312             try:
313                 stat = self.execute(target, source, env)
314                 stat = exitstatfunc(stat)
315             finally:
316                 if save_cwd:
317                     os.chdir(save_cwd)
318         if s and save_cwd:
319             print_func('os.chdir(%s)' % repr(save_cwd), target, source, env)
320         return stat
321
322
323 def _string_from_cmd_list(cmd_list):
324     """Takes a list of command line arguments and returns a pretty
325     representation for printing."""
326     cl = []
327     for arg in map(str, cmd_list):
328         if ' ' in arg or '\t' in arg:
329             arg = '"' + arg + '"'
330         cl.append(arg)
331     return string.join(cl)
332
333 class CommandAction(_ActionAction):
334     """Class for command-execution actions."""
335     def __init__(self, cmd, cmdstr=None, *args, **kw):
336         # Cmd can actually be a list or a single item; if it's a
337         # single item it should be the command string to execute; if a
338         # list then it should be the words of the command string to
339         # execute.  Only a single command should be executed by this
340         # object; lists of commands should be handled by embedding
341         # these objects in a ListAction object (which the Action()
342         # factory above does).  cmd will be passed to
343         # Environment.subst_list() for substituting environment
344         # variables.
345         if __debug__: logInstanceCreation(self, 'Action.CommandAction')
346         apply(_ActionAction.__init__, (self,)+args, kw)
347         if SCons.Util.is_List(cmd):
348             if filter(SCons.Util.is_List, cmd):
349                 raise TypeError, "CommandAction should be given only " \
350                       "a single command"
351         self.cmd_list = cmd
352         self.cmdstr = cmdstr
353
354     def __str__(self):
355         if SCons.Util.is_List(self.cmd_list):
356             return string.join(map(str, self.cmd_list), ' ')
357         return str(self.cmd_list)
358
359     def process(self, target, source, env):
360         result = env.subst_list(self.cmd_list, 0, target, source)
361         silent = None
362         ignore = None
363         while 1:
364             try: c = result[0][0][0]
365             except IndexError: c = None
366             if c == '@': silent = 1
367             elif c == '-': ignore = 1
368             else: break
369             result[0][0] = result[0][0][1:]
370         try:
371             if not result[0][0]:
372                 result[0] = result[0][1:]
373         except IndexError:
374             pass
375         return result, ignore, silent
376
377     def strfunction(self, target, source, env):
378         if not self.cmdstr is None:
379             c = env.subst(self.cmdstr, 0, target, source)
380             if c:
381                 return c
382         cmd_list, ignore, silent = self.process(target, source, env)
383         if silent:
384             return ''
385         return _string_from_cmd_list(cmd_list[0])
386
387     def execute(self, target, source, env):
388         """Execute a command action.
389
390         This will handle lists of commands as well as individual commands,
391         because construction variable substitution may turn a single
392         "command" into a list.  This means that this class can actually
393         handle lists of commands, even though that's not how we use it
394         externally.
395         """
396         from SCons.Util import is_String, is_List, flatten, escape_list
397
398         try:
399             shell = env['SHELL']
400         except KeyError:
401             raise SCons.Errors.UserError('Missing SHELL construction variable.')
402
403         try:
404             spawn = env['SPAWN']
405         except KeyError:
406             raise SCons.Errors.UserError('Missing SPAWN construction variable.')
407
408         escape = env.get('ESCAPE', lambda x: x)
409
410         try:
411             ENV = env['ENV']
412         except KeyError:
413             global default_ENV
414             if not default_ENV:
415                 import SCons.Environment
416                 default_ENV = SCons.Environment.Environment()['ENV']
417             ENV = default_ENV
418
419         # Ensure that the ENV values are all strings:
420         for key, value in ENV.items():
421             if not is_String(value):
422                 if is_List(value):
423                     # If the value is a list, then we assume it is a
424                     # path list, because that's a pretty common list-like
425                     # value to stick in an environment variable:
426                     value = flatten(value)
427                     ENV[key] = string.join(map(str, value), os.pathsep)
428                 else:
429                     # If it isn't a string or a list, then we just coerce
430                     # it to a string, which is the proper way to handle
431                     # Dir and File instances and will produce something
432                     # reasonable for just about everything else:
433                     ENV[key] = str(value)
434
435         cmd_list, ignore, silent = self.process(target, map(rfile, source), env)
436
437         # Use len() to filter out any "command" that's zero-length.
438         for cmd_line in filter(len, cmd_list):
439             # Escape the command line for the interpreter we are using.
440             cmd_line = escape_list(cmd_line, escape)
441             result = spawn(shell, escape, cmd_line[0], cmd_line, ENV)
442             if not ignore and result:
443                 return result
444         return 0
445
446     def get_contents(self, target, source, env):
447         """Return the signature contents of this action's command line.
448
449         This strips $(-$) and everything in between the string,
450         since those parts don't affect signatures.
451         """
452         cmd = self.cmd_list
453         if SCons.Util.is_List(cmd):
454             cmd = string.join(map(str, cmd))
455         else:
456             cmd = str(cmd)
457         return env.subst_target_source(cmd, SCons.Util.SUBST_SIG, target, source)
458
459 class CommandGeneratorAction(ActionBase):
460     """Class for command-generator actions."""
461     def __init__(self, generator, *args, **kw):
462         if __debug__: logInstanceCreation(self, 'Action.CommandGeneratorAction')
463         self.generator = generator
464         self.gen_kw = kw
465
466     def _generate(self, target, source, env, for_signature):
467         # ensure that target is a list, to make it easier to write
468         # generator functions:
469         if not SCons.Util.is_List(target):
470             target = [target]
471
472         ret = self.generator(target=target, source=source, env=env, for_signature=for_signature)
473         gen_cmd = apply(Action, (ret,), self.gen_kw)
474         if not gen_cmd:
475             raise SCons.Errors.UserError("Object returned from command generator: %s cannot be used to create an Action." % repr(ret))
476         return gen_cmd
477
478     def __str__(self):
479         try:
480             env = self.presub_env or {}
481         except AttributeError:
482             env = {}
483         act = self._generate([], [], env, 1)
484         return str(act)
485
486     def genstring(self, target, source, env):
487         return self._generate(target, source, env, 1).genstring(target, source, env)
488
489     def __call__(self, target, source, env, exitstatfunc=_null, presub=_null,
490                  show=_null, execute=_null, chdir=_null):
491         act = self._generate(target, source, env, 0)
492         return act(target, source, env, exitstatfunc, presub,
493                    show, execute, chdir)
494
495     def get_contents(self, target, source, env):
496         """Return the signature contents of this action's command line.
497
498         This strips $(-$) and everything in between the string,
499         since those parts don't affect signatures.
500         """
501         return self._generate(target, source, env, 1).get_contents(target, source, env)
502
503
504
505 # A LazyAction is a kind of hybrid generator and command action for
506 # strings of the form "$VAR".  These strings normally expand to other
507 # strings (think "$CCCOM" to "$CC -c -o $TARGET $SOURCE"), but we also
508 # want to be able to replace them with functions in the construction
509 # environment.  Consequently, we want lazy evaluation and creation of
510 # an Action in the case of the function, but that's overkill in the more
511 # normal case of expansion to other strings.
512 #
513 # So we do this with a subclass that's both a generator *and*
514 # a command action.  The overridden methods all do a quick check
515 # of the construction variable, and if it's a string we just call
516 # the corresponding CommandAction method to do the heavy lifting.
517 # If not, then we call the same-named CommandGeneratorAction method.
518 # The CommandGeneratorAction methods work by using the overridden
519 # _generate() method, that is, our own way of handling "generation" of
520 # an action based on what's in the construction variable.
521
522 class LazyAction(CommandGeneratorAction, CommandAction):
523
524     __metaclass__ = SCons.Memoize.Memoized_Metaclass
525
526     def __init__(self, var, *args, **kw):
527         if __debug__: logInstanceCreation(self, 'Action.LazyAction')
528         apply(CommandAction.__init__, (self, '$'+var)+args, kw)
529         self.var = SCons.Util.to_String(var)
530         self.gen_kw = kw
531
532     def get_parent_class(self, env):
533         c = env.get(self.var)
534         if SCons.Util.is_String(c) and not '\n' in c:
535             return CommandAction
536         return CommandGeneratorAction
537
538     def _generate_cache(self, env):
539         """__cacheable__"""
540         c = env.get(self.var, '')
541         gen_cmd = apply(Action, (c,), self.gen_kw)
542         if not gen_cmd:
543             raise SCons.Errors.UserError("$%s value %s cannot be used to create an Action." % (self.var, repr(c)))
544         return gen_cmd
545
546     def _generate(self, target, source, env, for_signature):
547         return self._generate_cache(env)
548
549     def __call__(self, target, source, env, *args, **kw):
550         args = (self, target, source, env) + args
551         c = self.get_parent_class(env)
552         return apply(c.__call__, args, kw)
553
554     def get_contents(self, target, source, env):
555         c = self.get_parent_class(env)
556         return c.get_contents(self, target, source, env)
557
558 if not SCons.Memoize.has_metaclass:
559     _Base = LazyAction
560     class LazyAction(SCons.Memoize.Memoizer, _Base):
561         def __init__(self, *args, **kw):
562             SCons.Memoize.Memoizer.__init__(self)
563             apply(_Base.__init__, (self,)+args, kw)
564
565
566
567 class FunctionAction(_ActionAction):
568     """Class for Python function actions."""
569
570     def __init__(self, execfunction, *args, **kw):
571         if __debug__: logInstanceCreation(self, 'Action.FunctionAction')
572         self.execfunction = execfunction
573         apply(_ActionAction.__init__, (self,)+args, kw)
574         self.varlist = kw.get('varlist', [])
575
576     def function_name(self):
577         try:
578             return self.execfunction.__name__
579         except AttributeError:
580             try:
581                 return self.execfunction.__class__.__name__
582             except AttributeError:
583                 return "unknown_python_function"
584
585     def strfunction(self, target, source, env):
586         def array(a):
587             def quote(s):
588                 return '"' + str(s) + '"'
589             return '[' + string.join(map(quote, a), ", ") + ']'
590         try:
591             strfunc = self.execfunction.strfunction
592         except AttributeError:
593             pass
594         else:
595             if strfunc is None:
596                 return None
597             if callable(strfunc):
598                 return strfunc(target, source, env)
599         name = self.function_name()
600         tstr = array(target)
601         sstr = array(source)
602         return "%s(%s, %s)" % (name, tstr, sstr)
603
604     def __str__(self):
605         name = self.function_name()
606         if name == 'ActionCaller':
607             return str(self.execfunction)
608         return "%s(target, source, env)" % name
609
610     def execute(self, target, source, env):
611         rsources = map(rfile, source)
612         try:
613             result = self.execfunction(target=target, source=rsources, env=env)
614         except EnvironmentError, e:
615             # If an IOError/OSError happens, raise a BuildError.
616             raise SCons.Errors.BuildError(node=target, errstr=e.strerror)
617         return result
618
619     def get_contents(self, target, source, env):
620         """Return the signature contents of this callable action.
621
622         By providing direct access to the code object of the
623         function, Python makes this extremely easy.  Hooray!
624         """
625         try:
626             # "self.execfunction" is a function.
627             contents = str(self.execfunction.func_code.co_code)
628         except AttributeError:
629             # "self.execfunction" is a callable object.
630             try:
631                 contents = str(self.execfunction.__call__.im_func.func_code.co_code)
632             except AttributeError:
633                 try:
634                     # See if execfunction will do the heavy lifting for us.
635                     gc = self.execfunction.get_contents
636                 except AttributeError:
637                     # This is weird, just do the best we can.
638                     contents = str(self.execfunction)
639                 else:
640                     contents = gc(target, source, env)
641         return contents + env.subst(string.join(map(lambda v: '${'+v+'}',
642                                                      self.varlist)))
643
644 class ListAction(ActionBase):
645     """Class for lists of other actions."""
646     def __init__(self, list):
647         if __debug__: logInstanceCreation(self, 'Action.ListAction')
648         def list_of_actions(x):
649             if isinstance(x, ActionBase):
650                 return x
651             return Action(x)
652         self.list = map(list_of_actions, list)
653
654     def genstring(self, target, source, env):
655         return string.join(map(lambda a, t=target, s=source, e=env:
656                                   a.genstring(t, s, e),
657                                self.list),
658                            '\n')
659
660     def __str__(self):
661         return string.join(map(str, self.list), '\n')
662     
663     def presub_lines(self, env):
664         return SCons.Util.flatten(map(lambda a, env=env:
665                                       a.presub_lines(env),
666                                       self.list))
667
668     def get_contents(self, target, source, env):
669         """Return the signature contents of this action list.
670
671         Simple concatenation of the signatures of the elements.
672         """
673         return string.join(map(lambda x, t=target, s=source, e=env:
674                                       x.get_contents(t, s, e),
675                                self.list),
676                            "")
677
678     def __call__(self, target, source, env, exitstatfunc=_null, presub=_null,
679                  show=_null, execute=_null, chdir=_null):
680         for act in self.list:
681             stat = act(target, source, env, exitstatfunc, presub,
682                        show, execute, chdir)
683             if stat:
684                 return stat
685         return 0
686
687 class ActionCaller:
688     """A class for delaying calling an Action function with specific
689     (positional and keyword) arguments until the Action is actually
690     executed.
691
692     This class looks to the rest of the world like a normal Action object,
693     but what it's really doing is hanging on to the arguments until we
694     have a target, source and env to use for the expansion.
695     """
696     def __init__(self, parent, args, kw):
697         self.parent = parent
698         self.args = args
699         self.kw = kw
700     def get_contents(self, target, source, env):
701         actfunc = self.parent.actfunc
702         try:
703             # "self.actfunc" is a function.
704             contents = str(actfunc.func_code.co_code)
705         except AttributeError:
706             # "self.actfunc" is a callable object.
707             try:
708                 contents = str(actfunc.__call__.im_func.func_code.co_code)
709             except AttributeError:
710                 # No __call__() method, so it might be a builtin
711                 # or something like that.  Do the best we can.
712                 contents = str(actfunc)
713         return contents
714     def subst(self, s, target, source, env):
715         # Special-case hack:  Let a custom function wrapped in an
716         # ActionCaller get at the environment through which the action
717         # was called by using this hard-coded value as a special return.
718         if s == '$__env__':
719             return env
720         else:
721             return env.subst(s, 0, target, source)
722     def subst_args(self, target, source, env):
723         return map(lambda x, self=self, t=target, s=source, e=env:
724                           self.subst(x, t, s, e),
725                    self.args)
726     def subst_kw(self, target, source, env):
727         kw = {}
728         for key in self.kw.keys():
729             kw[key] = self.subst(self.kw[key], target, source, env)
730         return kw
731     def __call__(self, target, source, env):
732         args = self.subst_args(target, source, env)
733         kw = self.subst_kw(target, source, env)
734         return apply(self.parent.actfunc, args, kw)
735     def strfunction(self, target, source, env):
736         args = self.subst_args(target, source, env)
737         kw = self.subst_kw(target, source, env)
738         return apply(self.parent.strfunc, args, kw)
739     def __str__(self):
740         return apply(self.parent.strfunc, self.args, self.kw)
741
742 class ActionFactory:
743     """A factory class that will wrap up an arbitrary function
744     as an SCons-executable Action object.
745
746     The real heavy lifting here is done by the ActionCaller class.
747     We just collect the (positional and keyword) arguments that we're
748     called with and give them to the ActionCaller object we create,
749     so it can hang onto them until it needs them.
750     """
751     def __init__(self, actfunc, strfunc):
752         self.actfunc = actfunc
753         self.strfunc = strfunc
754     def __call__(self, *args, **kw):
755         ac = ActionCaller(self, args, kw)
756         action = Action(ac, strfunction=ac.strfunction)
757         return action