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