Group --debug=count output by object modules.
[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 _actionAppend(act1, act2):
128     # This function knows how to slap two actions together.
129     # Mainly, it handles ListActions by concatenating into
130     # a single ListAction.
131     a1 = Action(act1)
132     a2 = Action(act2)
133     if a1 is None or a2 is None:
134         raise TypeError, "Cannot append %s to %s" % (type(act1), type(act2))
135     if isinstance(a1, ListAction):
136         if isinstance(a2, ListAction):
137             return ListAction(a1.list + a2.list)
138         else:
139             return ListAction(a1.list + [ a2 ])
140     else:
141         if isinstance(a2, ListAction):
142             return ListAction([ a1 ] + a2.list)
143         else:
144             return ListAction([ a1, a2 ])
145
146 class CommandGenerator:
147     """
148     Wraps a command generator function so the Action() factory
149     function can tell a generator function from a function action.
150     """
151     def __init__(self, generator):
152         self.generator = generator
153
154     def __add__(self, other):
155         return _actionAppend(self, other)
156
157     def __radd__(self, other):
158         return _actionAppend(other, self)
159
160 def _do_create_action(act, *args, **kw):
161     """This is the actual "implementation" for the
162     Action factory method, below.  This handles the
163     fact that passing lists to Action() itself has
164     different semantics than passing lists as elements
165     of lists.
166
167     The former will create a ListAction, the latter
168     will create a CommandAction by converting the inner
169     list elements to strings."""
170
171     if isinstance(act, ActionBase):
172         return act
173     if SCons.Util.is_List(act):
174         return apply(CommandAction, (act,)+args, kw)
175     if isinstance(act, CommandGenerator):
176         return apply(CommandGeneratorAction, (act.generator,)+args, kw)
177     if callable(act):
178         return apply(FunctionAction, (act,)+args, kw)
179     if SCons.Util.is_String(act):
180         var=SCons.Util.get_environment_var(act)
181         if var:
182             # This looks like a string that is purely an Environment
183             # variable reference, like "$FOO" or "${FOO}".  We do
184             # something special here...we lazily evaluate the contents
185             # of that Environment variable, so a user could put something
186             # like a function or a CommandGenerator in that variable
187             # instead of a string.
188             return apply(LazyAction, (var,)+args, kw)
189         commands = string.split(str(act), '\n')
190         if len(commands) == 1:
191             return apply(CommandAction, (commands[0],)+args, kw)
192         else:
193             listCmdActions = map(lambda x, args=args, kw=kw:
194                                  apply(CommandAction, (x,)+args, kw),
195                                  commands)
196             return ListAction(listCmdActions)
197     return None
198
199 def Action(act, *args, **kw):
200     """A factory for action objects."""
201     if SCons.Util.is_List(act):
202         acts = map(lambda a, args=args, kw=kw:
203                           apply(_do_create_action, (a,)+args, kw),
204                    act)
205         acts = filter(None, acts)
206         if len(acts) == 1:
207             return acts[0]
208         else:
209             return ListAction(acts)
210     else:
211         return apply(_do_create_action, (act,)+args, kw)
212
213 class ActionBase:
214     """Base class for all types of action objects that can be held by
215     other objects (Builders, Executors, etc.)  This provides the
216     common methods for manipulating and combining those actions."""
217     
218     __metaclass__ = SCons.Memoize.Memoized_Metaclass
219
220     def __cmp__(self, other):
221         return cmp(self.__dict__, other)
222
223     def genstring(self, target, source, env):
224         return str(self)
225
226     def __add__(self, other):
227         return _actionAppend(self, other)
228
229     def __radd__(self, other):
230         return _actionAppend(other, self)
231
232     def presub_lines(self, env):
233         # CommandGeneratorAction needs a real environment
234         # in order to return the proper string here, since
235         # it may call LazyAction, which looks up a key
236         # in that env.  So we temporarily remember the env here,
237         # and CommandGeneratorAction will use this env
238         # when it calls its _generate method.
239         self.presub_env = env
240         lines = string.split(str(self), '\n')
241         self.presub_env = None      # don't need this any more
242         return lines
243
244 if not SCons.Memoize.has_metaclass:
245     _Base = ActionBase
246     class ActionBase(SCons.Memoize.Memoizer, _Base):
247         "Cache-backed version of ActionBase"
248         def __init__(self, *args, **kw):
249             apply(_Base.__init__, (self,)+args, kw)
250             SCons.Memoize.Memoizer.__init__(self)
251
252
253 class _ActionAction(ActionBase):
254     """Base class for actions that create output objects."""
255     def __init__(self, strfunction=_null, presub=_null, chdir=None, **kw):
256         if not strfunction is _null:
257             self.strfunction = strfunction
258         if presub is _null:
259             presub = print_actions_presub
260         self.presub = presub
261         self.chdir = chdir
262
263     def print_cmd_line(self, s, target, source, env):
264         sys.stdout.write(s + "\n")
265
266     def __call__(self, target, source, env,
267                                errfunc=None,
268                                presub=_null,
269                                show=_null,
270                                execute=_null,
271                                chdir=_null):
272         if not SCons.Util.is_List(target):
273             target = [target]
274         if not SCons.Util.is_List(source):
275             source = [source]
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                 if stat and errfunc:
315                     errfunc(stat)
316             finally:
317                 if save_cwd:
318                     os.chdir(save_cwd)
319         if s and save_cwd:
320             print_func('os.chdir(%s)' % repr(save_cwd), target, source, env)
321         return stat
322
323
324 def _string_from_cmd_list(cmd_list):
325     """Takes a list of command line arguments and returns a pretty
326     representation for printing."""
327     cl = []
328     for arg in map(str, cmd_list):
329         if ' ' in arg or '\t' in arg:
330             arg = '"' + arg + '"'
331         cl.append(arg)
332     return string.join(cl)
333
334 class CommandAction(_ActionAction):
335     """Class for command-execution actions."""
336     def __init__(self, cmd, cmdstr=None, *args, **kw):
337         # Cmd can actually be a list or a single item; if it's a
338         # single item it should be the command string to execute; if a
339         # list then it should be the words of the command string to
340         # execute.  Only a single command should be executed by this
341         # object; lists of commands should be handled by embedding
342         # these objects in a ListAction object (which the Action()
343         # factory above does).  cmd will be passed to
344         # Environment.subst_list() for substituting environment
345         # variables.
346         if __debug__: logInstanceCreation(self, 'Action.CommandAction')
347         apply(_ActionAction.__init__, (self,)+args, kw)
348         if SCons.Util.is_List(cmd):
349             if filter(SCons.Util.is_List, cmd):
350                 raise TypeError, "CommandAction should be given only " \
351                       "a single command"
352         self.cmd_list = cmd
353         self.cmdstr = cmdstr
354
355     def __str__(self):
356         if SCons.Util.is_List(self.cmd_list):
357             return string.join(map(str, self.cmd_list), ' ')
358         return str(self.cmd_list)
359
360     def strfunction(self, target, source, env):
361         if not self.cmdstr is None:
362             c = env.subst(self.cmdstr, 0, target, source)
363             if c:
364                 return c
365         cmd_list = env.subst_list(self.cmd_list, 0, target, source)
366         return _string_from_cmd_list(cmd_list[0])
367
368     def execute(self, target, source, env):
369         """Execute a command action.
370
371         This will handle lists of commands as well as individual commands,
372         because construction variable substitution may turn a single
373         "command" into a list.  This means that this class can actually
374         handle lists of commands, even though that's not how we use it
375         externally.
376         """
377         from SCons.Util import is_String, is_List, flatten, escape_list
378
379         try:
380             shell = env['SHELL']
381         except KeyError:
382             raise SCons.Errors.UserError('Missing SHELL construction variable.')
383
384         try:
385             spawn = env['SPAWN']
386         except KeyError:
387             raise SCons.Errors.UserError('Missing SPAWN construction variable.')
388
389         escape = env.get('ESCAPE', lambda x: x)
390
391         try:
392             ENV = env['ENV']
393         except KeyError:
394             global default_ENV
395             if not default_ENV:
396                 import SCons.Environment
397                 default_ENV = SCons.Environment.Environment()['ENV']
398             ENV = default_ENV
399
400         # Ensure that the ENV values are all strings:
401         for key, value in ENV.items():
402             if not is_String(value):
403                 if is_List(value):
404                     # If the value is a list, then we assume it is a
405                     # path list, because that's a pretty common list-like
406                     # value to stick in an environment variable:
407                     value = flatten(value)
408                     ENV[key] = string.join(map(str, value), os.pathsep)
409                 else:
410                     # If it isn't a string or a list, then we just coerce
411                     # it to a string, which is the proper way to handle
412                     # Dir and File instances and will produce something
413                     # reasonable for just about everything else:
414                     ENV[key] = str(value)
415
416         cmd_list = env.subst_list(self.cmd_list, 0, target,
417                                   map(rfile, source))
418
419         # Use len() to filter out any "command" that's zero-length.
420         for cmd_line in filter(len, cmd_list):
421             # Escape the command line for the interpreter we are using.
422             cmd_line = escape_list(cmd_line, escape)
423             result = spawn(shell, escape, cmd_line[0], cmd_line, ENV)
424             if result:
425                 return result
426         return 0
427
428     def get_contents(self, target, source, env):
429         """Return the signature contents of this action's command line.
430
431         This strips $(-$) and everything in between the string,
432         since those parts don't affect signatures.
433         """
434         cmd = self.cmd_list
435         if SCons.Util.is_List(cmd):
436             cmd = string.join(map(str, cmd))
437         else:
438             cmd = str(cmd)
439         return env.subst_target_source(cmd, SCons.Util.SUBST_SIG, target, source)
440
441 class CommandGeneratorAction(ActionBase):
442     """Class for command-generator actions."""
443     def __init__(self, generator, *args, **kw):
444         if __debug__: logInstanceCreation(self, 'Action.CommandGeneratorAction')
445         self.generator = generator
446         self.gen_kw = kw
447
448     def _generate(self, target, source, env, for_signature):
449         # ensure that target is a list, to make it easier to write
450         # generator functions:
451         if not SCons.Util.is_List(target):
452             target = [target]
453
454         ret = self.generator(target=target, source=source, env=env, for_signature=for_signature)
455         gen_cmd = apply(Action, (ret,), self.gen_kw)
456         if not gen_cmd:
457             raise SCons.Errors.UserError("Object returned from command generator: %s cannot be used to create an Action." % repr(ret))
458         return gen_cmd
459
460     def __str__(self):
461         try:
462             env = self.presub_env or {}
463         except AttributeError:
464             env = {}
465         act = self._generate([], [], env, 1)
466         return str(act)
467
468     def genstring(self, target, source, env):
469         return self._generate(target, source, env, 1).genstring(target, source, env)
470
471     def __call__(self, target, source, env, errfunc=None, presub=_null,
472                  show=_null, execute=_null, chdir=_null):
473         act = self._generate(target, source, env, 0)
474         return act(target, source, env, errfunc, presub,
475                    show, execute, chdir)
476
477     def get_contents(self, target, source, env):
478         """Return the signature contents of this action's command line.
479
480         This strips $(-$) and everything in between the string,
481         since those parts don't affect signatures.
482         """
483         return self._generate(target, source, env, 1).get_contents(target, source, env)
484
485
486
487 # A LazyAction is a kind of hybrid generator and command action for
488 # strings of the form "$VAR".  These strings normally expand to other
489 # strings (think "$CCCOM" to "$CC -c -o $TARGET $SOURCE"), but we also
490 # want to be able to replace them with functions in the construction
491 # environment.  Consequently, we want lazy evaluation and creation of
492 # an Action in the case of the function, but that's overkill in the more
493 # normal case of expansion to other strings.
494 #
495 # So we do this with a subclass that's both a generator *and*
496 # a command action.  The overridden methods all do a quick check
497 # of the construction variable, and if it's a string we just call
498 # the corresponding CommandAction method to do the heavy lifting.
499 # If not, then we call the same-named CommandGeneratorAction method.
500 # The CommandGeneratorAction methods work by using the overridden
501 # _generate() method, that is, our own way of handling "generation" of
502 # an action based on what's in the construction variable.
503
504 class LazyAction(CommandGeneratorAction, CommandAction):
505
506     __metaclass__ = SCons.Memoize.Memoized_Metaclass
507
508     def __init__(self, var, *args, **kw):
509         if __debug__: logInstanceCreation(self, 'Action.LazyAction')
510         apply(CommandAction.__init__, (self, '$'+var)+args, kw)
511         self.var = SCons.Util.to_String(var)
512         self.gen_kw = kw
513
514     def get_parent_class(self, env):
515         c = env.get(self.var)
516         if SCons.Util.is_String(c) and not '\n' in c:
517             return CommandAction
518         return CommandGeneratorAction
519
520     def _generate_cache(self, env):
521         """__cacheable__"""
522         c = env.get(self.var, '')
523         gen_cmd = apply(Action, (c,), self.gen_kw)
524         if not gen_cmd:
525             raise SCons.Errors.UserError("$%s value %s cannot be used to create an Action." % (self.var, repr(c)))
526         return gen_cmd
527
528     def _generate(self, target, source, env, for_signature):
529         return self._generate_cache(env)
530
531     def __call__(self, target, source, env, *args, **kw):
532         args = (self, target, source, env) + args
533         c = self.get_parent_class(env)
534         return apply(c.__call__, args, kw)
535
536     def get_contents(self, target, source, env):
537         c = self.get_parent_class(env)
538         return c.get_contents(self, target, source, env)
539
540 if not SCons.Memoize.has_metaclass:
541     _Base = LazyAction
542     class LazyAction(SCons.Memoize.Memoizer, _Base):
543         def __init__(self, *args, **kw):
544             SCons.Memoize.Memoizer.__init__(self)
545             apply(_Base.__init__, (self,)+args, kw)
546
547
548
549 class FunctionAction(_ActionAction):
550     """Class for Python function actions."""
551
552     def __init__(self, execfunction, *args, **kw):
553         if __debug__: logInstanceCreation(self, 'Action.FunctionAction')
554         self.execfunction = execfunction
555         apply(_ActionAction.__init__, (self,)+args, kw)
556         self.varlist = kw.get('varlist', [])
557
558     def function_name(self):
559         try:
560             return self.execfunction.__name__
561         except AttributeError:
562             try:
563                 return self.execfunction.__class__.__name__
564             except AttributeError:
565                 return "unknown_python_function"
566
567     def strfunction(self, target, source, env):
568         def array(a):
569             def quote(s):
570                 return '"' + str(s) + '"'
571             return '[' + string.join(map(quote, a), ", ") + ']'
572         try:
573             strfunc = self.execfunction.strfunction
574         except AttributeError:
575             pass
576         else:
577             if strfunc is None:
578                 return None
579             if callable(strfunc):
580                 return strfunc(target, source, env)
581         name = self.function_name()
582         tstr = array(target)
583         sstr = array(source)
584         return "%s(%s, %s)" % (name, tstr, sstr)
585
586     def __str__(self):
587         name = self.function_name()
588         if name == 'ActionCaller':
589             return str(self.execfunction)
590         return "%s(target, source, env)" % name
591
592     def execute(self, target, source, env):
593         rsources = map(rfile, source)
594         try:
595             result = self.execfunction(target=target, source=rsources, env=env)
596         except EnvironmentError, e:
597             # If an IOError/OSError happens, raise a BuildError.
598             raise SCons.Errors.BuildError(node=target, errstr=e.strerror)
599         return result
600
601     def get_contents(self, target, source, env):
602         """Return the signature contents of this callable action.
603
604         By providing direct access to the code object of the
605         function, Python makes this extremely easy.  Hooray!
606         """
607         try:
608             # "self.execfunction" is a function.
609             contents = str(self.execfunction.func_code.co_code)
610         except AttributeError:
611             # "self.execfunction" is a callable object.
612             try:
613                 contents = str(self.execfunction.__call__.im_func.func_code.co_code)
614             except AttributeError:
615                 try:
616                     # See if execfunction will do the heavy lifting for us.
617                     gc = self.execfunction.get_contents
618                 except AttributeError:
619                     # This is weird, just do the best we can.
620                     contents = str(self.execfunction)
621                 else:
622                     contents = gc(target, source, env)
623         return contents + env.subst(string.join(map(lambda v: '${'+v+'}',
624                                                      self.varlist)))
625
626 class ListAction(ActionBase):
627     """Class for lists of other actions."""
628     def __init__(self, list):
629         if __debug__: logInstanceCreation(self, 'Action.ListAction')
630         def list_of_actions(x):
631             if isinstance(x, ActionBase):
632                 return x
633             return Action(x)
634         self.list = map(list_of_actions, list)
635
636     def genstring(self, target, source, env):
637         return string.join(map(lambda a, t=target, s=source, e=env:
638                                   a.genstring(t, s, e),
639                                self.list),
640                            '\n')
641
642     def __str__(self):
643         return string.join(map(str, self.list), '\n')
644     
645     def presub_lines(self, env):
646         return SCons.Util.flatten(map(lambda a, env=env:
647                                       a.presub_lines(env),
648                                       self.list))
649
650     def get_contents(self, target, source, env):
651         """Return the signature contents of this action list.
652
653         Simple concatenation of the signatures of the elements.
654         """
655         return string.join(map(lambda x, t=target, s=source, e=env:
656                                       x.get_contents(t, s, e),
657                                self.list),
658                            "")
659
660     def __call__(self, target, source, env, errfunc=None, presub=_null,
661                  show=_null, execute=_null, chdir=_null):
662         for act in self.list:
663             stat = act(target, source, env, errfunc, presub,
664                        show, execute, chdir)
665             if stat:
666                 return stat
667         return 0
668
669 class ActionCaller:
670     """A class for delaying calling an Action function with specific
671     (positional and keyword) arguments until the Action is actually
672     executed.
673
674     This class looks to the rest of the world like a normal Action object,
675     but what it's really doing is hanging on to the arguments until we
676     have a target, source and env to use for the expansion.
677     """
678     def __init__(self, parent, args, kw):
679         self.parent = parent
680         self.args = args
681         self.kw = kw
682     def get_contents(self, target, source, env):
683         actfunc = self.parent.actfunc
684         try:
685             # "self.actfunc" is a function.
686             contents = str(actfunc.func_code.co_code)
687         except AttributeError:
688             # "self.actfunc" is a callable object.
689             try:
690                 contents = str(actfunc.__call__.im_func.func_code.co_code)
691             except AttributeError:
692                 # No __call__() method, so it might be a builtin
693                 # or something like that.  Do the best we can.
694                 contents = str(actfunc)
695         return contents
696     def subst(self, s, target, source, env):
697         # Special-case hack:  Let a custom function wrapped in an
698         # ActionCaller get at the environment through which the action
699         # was called by using this hard-coded value as a special return.
700         if s == '$__env__':
701             return env
702         else:
703             return env.subst(s, 0, target, source)
704     def subst_args(self, target, source, env):
705         return map(lambda x, self=self, t=target, s=source, e=env:
706                           self.subst(x, t, s, e),
707                    self.args)
708     def subst_kw(self, target, source, env):
709         kw = {}
710         for key in self.kw.keys():
711             kw[key] = self.subst(self.kw[key], target, source, env)
712         return kw
713     def __call__(self, target, source, env):
714         args = self.subst_args(target, source, env)
715         kw = self.subst_kw(target, source, env)
716         return apply(self.parent.actfunc, args, kw)
717     def strfunction(self, target, source, env):
718         args = self.subst_args(target, source, env)
719         kw = self.subst_kw(target, source, env)
720         return apply(self.parent.strfunc, args, kw)
721     def __str__(self):
722         return apply(self.parent.strfunc, self.args, self.kw)
723
724 class ActionFactory:
725     """A factory class that will wrap up an arbitrary function
726     as an SCons-executable Action object.
727
728     The real heavy lifting here is done by the ActionCaller class.
729     We just collect the (positional and keyword) arguments that we're
730     called with and give them to the ActionCaller object we create,
731     so it can hang onto them until it needs them.
732     """
733     def __init__(self, actfunc, strfunc):
734         self.actfunc = actfunc
735         self.strfunc = strfunc
736     def __call__(self, *args, **kw):
737         ac = ActionCaller(self, args, kw)
738         action = Action(ac, strfunction=ac.strfunction)
739         return action