Really support strfunction for all Action subclasses by refactoring the interface...
[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 The heavy lifting is handled by subclasses for the different types of
12 actions we might execute:
13
14     CommandAction
15     CommandGeneratorAction
16     FunctionAction
17     ListAction
18
19 The subclasses supply the following public interface methods used by
20 other modules:
21
22     __call__()
23         THE public interface, "calling" an Action object executes the
24         command or Python function.  This also takes care of printing
25         a pre-substitution command for debugging purposes.
26
27     get_contents()
28         Fetches the "contents" of an Action for signature calculation.
29         This is what the Sig/*.py subsystem uses to decide if a target
30         needs to be rebuilt because its action changed.
31
32     genstring()
33         Returns a string representation of the Action *without* command
34         substitution, but allows a CommandGeneratorAction to generate
35         the right action based on the specified target, source and env.
36         This is used by the Signature subsystem (through the Executor)
37         to compare the actions used to build a target last time and
38         this time.
39
40 Subclasses also supply the following methods for internal use within
41 this module:
42
43     __str__()
44         Returns a string representation of the Action *without* command
45         substitution.  This is used by the __call__() methods to display
46         the pre-substitution command whenever the --debug=presub option
47         is used.
48
49     strfunction()
50         Returns a substituted string representation of the Action.
51         This is used by the ActionBase.show() command to display the
52         command/function that will be executed to generate the target(s).
53
54     execute()
55         The internal method that really, truly, actually handles the
56         execution of a command or Python function.  This is used so
57         that the __call__() methods can take care of displaying any
58         pre-substitution representations, and *then* execute an action
59         without worrying about the specific Actions involved.
60
61 There is a related independent ActionCaller class that looks like a
62 regular Action, and which serves as a wrapper for arbitrary functions
63 that we want to let the user specify the arguments to now, but actually
64 execute later (when an out-of-date check determines that it's needed to
65 be executed, for example).  Objects of this class are returned by an
66 ActionFactory class that provides a __call__() method as a convenient
67 way for wrapping up the functions.
68
69 """
70
71 #
72 # __COPYRIGHT__
73 #
74 # Permission is hereby granted, free of charge, to any person obtaining
75 # a copy of this software and associated documentation files (the
76 # "Software"), to deal in the Software without restriction, including
77 # without limitation the rights to use, copy, modify, merge, publish,
78 # distribute, sublicense, and/or sell copies of the Software, and to
79 # permit persons to whom the Software is furnished to do so, subject to
80 # the following conditions:
81 #
82 # The above copyright notice and this permission notice shall be included
83 # in all copies or substantial portions of the Software.
84 #
85 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
86 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
87 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
88 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
89 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
90 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
91 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
92 #
93
94 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
95
96 import os
97 import os.path
98 import re
99 import string
100 import sys
101
102 from SCons.Debug import logInstanceCreation
103 import SCons.Errors
104 import SCons.Util
105
106 class _Null:
107     pass
108
109 _null = _Null
110
111 print_actions = 1
112 execute_actions = 1
113 print_actions_presub = 0
114
115 default_ENV = None
116
117 def rfile(n):
118     try:
119         return n.rfile()
120     except AttributeError:
121         return n
122
123 def _actionAppend(act1, act2):
124     # This function knows how to slap two actions together.
125     # Mainly, it handles ListActions by concatenating into
126     # a single ListAction.
127     a1 = Action(act1)
128     a2 = Action(act2)
129     if a1 is None or a2 is None:
130         raise TypeError, "Cannot append %s to %s" % (type(act1), type(act2))
131     if isinstance(a1, ListAction):
132         if isinstance(a2, ListAction):
133             return ListAction(a1.list + a2.list)
134         else:
135             return ListAction(a1.list + [ a2 ])
136     else:
137         if isinstance(a2, ListAction):
138             return ListAction([ a1 ] + a2.list)
139         else:
140             return ListAction([ a1, a2 ])
141
142 class CommandGenerator:
143     """
144     Wraps a command generator function so the Action() factory
145     function can tell a generator function from a function action.
146     """
147     def __init__(self, generator):
148         self.generator = generator
149
150     def __add__(self, other):
151         return _actionAppend(self, other)
152
153     def __radd__(self, other):
154         return _actionAppend(other, self)
155
156 def _do_create_action(act, *args, **kw):
157     """This is the actual "implementation" for the
158     Action factory method, below.  This handles the
159     fact that passing lists to Action() itself has
160     different semantics than passing lists as elements
161     of lists.
162
163     The former will create a ListAction, the latter
164     will create a CommandAction by converting the inner
165     list elements to strings."""
166
167     if isinstance(act, ActionBase):
168         return act
169     if SCons.Util.is_List(act):
170         return apply(CommandAction, (act,)+args, kw)
171     if isinstance(act, CommandGenerator):
172         return apply(CommandGeneratorAction, (act.generator,)+args, kw)
173     if callable(act):
174         return apply(FunctionAction, (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             lcg = LazyCmdGenerator(var)
185             return apply(CommandGeneratorAction, (lcg,)+args, kw)
186         commands = string.split(str(act), '\n')
187         if len(commands) == 1:
188             return apply(CommandAction, (commands[0],)+args, kw)
189         else:
190             listCmdActions = map(lambda x: CommandAction(x), commands)
191             return apply(ListAction, (listCmdActions,)+args, kw)
192     return None
193
194 def Action(act, strfunction=_null, varlist=[]):
195     """A factory for action objects."""
196     if SCons.Util.is_List(act):
197         acts = map(lambda x, s=strfunction, v=varlist:
198                           _do_create_action(x, strfunction=s, varlist=v),
199                    act)
200         acts = filter(lambda x: not x is None, acts)
201         if len(acts) == 1:
202             return acts[0]
203         else:
204             return ListAction(acts, strfunction=strfunction, varlist=varlist)
205     else:
206         return _do_create_action(act, strfunction=strfunction, varlist=varlist)
207
208 class ActionBase:
209     """Base class for actions that create output objects."""
210     def __init__(self, strfunction=_null, **kw):
211         if not strfunction is _null:
212             self.strfunction = strfunction
213
214     def __cmp__(self, other):
215         return cmp(self.__dict__, other.__dict__)
216
217     def __call__(self, target, source, env,
218                                errfunc=None,
219                                presub=_null,
220                                show=_null,
221                                execute=_null):
222         if not SCons.Util.is_List(target):
223             target = [target]
224         if not SCons.Util.is_List(source):
225             source = [source]
226         if presub is _null:  presub = print_actions_presub
227         if show is _null:  show = print_actions
228         if execute is _null:  execute = execute_actions
229         if presub:
230             t = string.join(map(str, target), 'and')
231             l = string.join(self.presub(env), '\n  ')
232             out = "Building %s with action(s):\n  %s\n" % (t, l)
233             sys.stdout.write(out)
234         if show and self.strfunction:
235             s = self.strfunction(target, source, env)
236             if s:
237                 sys.stdout.write(s + '\n')
238         if execute:
239             stat = self.execute(target, source, env)
240             if stat and errfunc:
241                 errfunc(stat)
242             return stat
243         else:
244             return 0
245
246     def presub(self, env):
247         # CommandGeneratorAction needs a real environment
248         # in order to return the proper string here, since
249         # it may call LazyCmdGenerator, which looks up a key
250         # in that env.  So we temporarily remember the env here,
251         # and CommandGeneratorAction will use this env
252         # when it calls its __generate method.
253         self.presub_env = env
254         lines = string.split(str(self), '\n')
255         self.presub_env = None      # don't need this any more
256         return lines
257
258     def genstring(self, target, source, env):
259         return str(self)
260
261     def get_actions(self):
262         return [self]
263
264     def __add__(self, other):
265         return _actionAppend(self, other)
266
267     def __radd__(self, other):
268         return _actionAppend(other, self)
269
270 def _string_from_cmd_list(cmd_list):
271     """Takes a list of command line arguments and returns a pretty
272     representation for printing."""
273     cl = []
274     for arg in map(str, cmd_list):
275         if ' ' in arg or '\t' in arg:
276             arg = '"' + arg + '"'
277         cl.append(arg)
278     return string.join(cl)
279
280 class CommandAction(ActionBase):
281     """Class for command-execution actions."""
282     def __init__(self, cmd, **kw):
283         # Cmd list can actually be a list or a single item...basically
284         # anything that we could pass in as the first arg to
285         # Environment.subst_list().
286         if __debug__: logInstanceCreation(self)
287         apply(ActionBase.__init__, (self,), kw)
288         self.cmd_list = cmd
289
290     def __str__(self):
291         return str(self.cmd_list)
292
293     def strfunction(self, target, source, env):
294         cmd_list = env.subst_list(self.cmd_list, 0, target, source)
295         return string.join(map(_string_from_cmd_list, cmd_list), "\n")
296
297     def execute(self, target, source, env):
298         """Execute a command action.
299
300         This will handle lists of commands as well as individual commands,
301         because construction variable substitution may turn a single
302         "command" into a list.  This means that this class can actually
303         handle lists of commands, even though that's not how we use it
304         externally.
305         """
306         import SCons.Util
307
308         escape = env.get('ESCAPE', lambda x: x)
309
310         if env.has_key('SHELL'):
311             shell = env['SHELL']
312         else:
313             raise SCons.Errors.UserError('Missing SHELL construction variable.')
314
315         # for SConf support (by now): check, if we want to pipe the command
316         # output to somewhere else
317         if env.has_key('PIPE_BUILD'):
318             pipe_build = 1
319             if env.has_key('PSPAWN'):
320                 pspawn = env['PSPAWN']
321             else:
322                 raise SCons.Errors.UserError('Missing PSPAWN construction variable.')
323             if env.has_key('PSTDOUT'):
324                 pstdout = env['PSTDOUT']
325             else:
326                 raise SCons.Errors.UserError('Missing PSTDOUT construction variable.')
327             if env.has_key('PSTDERR'):
328                 pstderr = env['PSTDERR']
329             else:
330                 raise SCons.Errors.UserError('Missing PSTDOUT construction variable.')
331         else:
332             pipe_build = 0
333             if env.has_key('SPAWN'):
334                 spawn = env['SPAWN']
335             else:
336                 raise SCons.Errors.UserError('Missing SPAWN construction variable.')
337
338         cmd_list = env.subst_list(self.cmd_list, 0, target, source)
339         for cmd_line in cmd_list:
340             if len(cmd_line):
341                 try:
342                     ENV = env['ENV']
343                 except KeyError:
344                     global default_ENV
345                     if not default_ENV:
346                         import SCons.Environment
347                         default_ENV = SCons.Environment.Environment()['ENV']
348                     ENV = default_ENV
349
350                 # ensure that the ENV values are all strings:
351                 for key, value in ENV.items():
352                     if SCons.Util.is_List(value):
353                         # If the value is a list, then we assume
354                         # it is a path list, because that's a pretty
355                         # common list like value to stick in an environment
356                         # variable:
357                         ENV[key] = string.join(map(str, value), os.pathsep)
358                     elif not SCons.Util.is_String(value):
359                         # If it isn't a string or a list, then
360                         # we just coerce it to a string, which
361                         # is proper way to handle Dir and File instances
362                         # and will produce something reasonable for
363                         # just about everything else:
364                         ENV[key] = str(value)
365
366                 # Escape the command line for the command
367                 # interpreter we are using
368                 cmd_line = SCons.Util.escape_list(cmd_line, escape)
369                 if pipe_build:
370                     ret = pspawn( shell, escape, cmd_line[0], cmd_line,
371                                   ENV, pstdout, pstderr )
372                 else:
373                     ret = spawn(shell, escape, cmd_line[0], cmd_line, ENV)
374                 if ret:
375                     return ret
376         return 0
377
378     def get_contents(self, target, source, env, dict=None):
379         """Return the signature contents of this action's command line.
380
381         This strips $(-$) and everything in between the string,
382         since those parts don't affect signatures.
383         """
384         cmd = self.cmd_list
385         if SCons.Util.is_List(cmd):
386             cmd = string.join(map(str, cmd))
387         else:
388             cmd = str(cmd)
389         return env.subst_target_source(cmd, SCons.Util.SUBST_SIG, target, source, dict)
390
391 class CommandGeneratorAction(ActionBase):
392     """Class for command-generator actions."""
393     def __init__(self, generator, **kw):
394         if __debug__: logInstanceCreation(self)
395         apply(ActionBase.__init__, (self,), kw)
396         self.generator = generator
397
398     def __generate(self, target, source, env, for_signature):
399         # ensure that target is a list, to make it easier to write
400         # generator functions:
401         if not SCons.Util.is_List(target):
402             target = [target]
403
404         ret = self.generator(target=target, source=source, env=env, for_signature=for_signature)
405         gen_cmd = Action(ret)
406         if not gen_cmd:
407             raise SCons.Errors.UserError("Object returned from command generator: %s cannot be used to create an Action." % repr(ret))
408         return gen_cmd
409
410     def strfunction(self, target, source, env):
411         if not SCons.Util.is_List(source):
412             source = [source]
413         rsources = map(rfile, source)
414         act = self.__generate(target, source, env, 0)
415         return act.strfunction(target, rsources, env)
416
417     def __str__(self):
418         try:
419             env = self.presub_env or {}
420         except AttributeError:
421             env = {}
422         act = self.__generate([], [], env, 0)
423         return str(act)
424
425     def genstring(self, target, source, env):
426         return str(self.__generate(target, source, env, 0))
427
428     def execute(self, target, source, env):
429         rsources = map(rfile, source)
430         act = self.__generate(target, source, env, 0)
431         return act.execute(target, source, env)
432
433     def get_contents(self, target, source, env, dict=None):
434         """Return the signature contents of this action's command line.
435
436         This strips $(-$) and everything in between the string,
437         since those parts don't affect signatures.
438         """
439         return self.__generate(target, source, env, 1).get_contents(target, source, env, dict=None)
440
441 class LazyCmdGenerator:
442     """This is not really an Action, although it kind of looks like one.
443     This is really a simple callable class that acts as a command
444     generator.  It holds on to a key into an Environment dictionary,
445     then waits until execution time to see what type it is, then tries
446     to create an Action out of it."""
447     def __init__(self, var):
448         if __debug__: logInstanceCreation(self)
449         self.var = SCons.Util.to_String(var)
450
451     def strfunction(self, target, source, env):
452         try:
453             return env[self.var]
454         except KeyError:
455             # The variable reference substitutes to nothing.
456             return ''
457
458     def __str__(self):
459         return 'LazyCmdGenerator: %s'%str(self.var)
460
461     def __call__(self, target, source, env, for_signature):
462         try:
463             return env[self.var]
464         except KeyError:
465             # The variable reference substitutes to nothing.
466             return ''
467
468     def __cmp__(self, other):
469         return cmp(self.__dict__, other.__dict__)
470
471 class FunctionAction(ActionBase):
472     """Class for Python function actions."""
473
474     def __init__(self, execfunction, **kw):
475         if __debug__: logInstanceCreation(self)
476         self.execfunction = execfunction
477         apply(ActionBase.__init__, (self,), kw)
478         self.varlist = kw.get('varlist', [])
479
480     def function_name(self):
481         try:
482             return self.execfunction.__name__
483         except AttributeError:
484             try:
485                 return self.execfunction.__class__.__name__
486             except AttributeError:
487                 return "unknown_python_function"
488
489     def strfunction(self, target, source, env):
490         def quote(s):
491             return '"' + str(s) + '"'
492         def array(a, q=quote):
493             return '[' + string.join(map(lambda x, q=q: q(x), a), ", ") + ']'
494         name = self.function_name()
495         tstr = len(target) == 1 and quote(target[0]) or array(target)
496         sstr = len(source) == 1 and quote(source[0]) or array(source)
497         return "%s(%s, %s)" % (name, tstr, sstr)
498
499     def __str__(self):
500         return "%s(env, target, source)" % self.function_name()
501
502     def execute(self, target, source, env):
503         rsources = map(rfile, source)
504         return self.execfunction(target=target, source=rsources, env=env)
505
506     def get_contents(self, target, source, env, dict=None):
507         """Return the signature contents of this callable action.
508
509         By providing direct access to the code object of the
510         function, Python makes this extremely easy.  Hooray!
511         """
512         try:
513             # "self.execfunction" is a function.
514             contents = str(self.execfunction.func_code.co_code)
515         except AttributeError:
516             # "self.execfunction" is a callable object.
517             try:
518                 contents = str(self.execfunction.__call__.im_func.func_code.co_code)
519             except AttributeError:
520                 try:
521                     # See if execfunction will do the heavy lifting for us.
522                     gc = self.execfunction.get_contents
523                 except AttributeError:
524                     # This is weird, just do the best we can.
525                     contents = str(self.execfunction)
526                 else:
527                     contents = gc(target, source, env, dict)
528         return contents + env.subst(string.join(map(lambda v: '${'+v+'}',
529                                                      self.varlist)))
530
531 class ListAction(ActionBase):
532     """Class for lists of other actions."""
533     def __init__(self, list, **kw):
534         if __debug__: logInstanceCreation(self)
535         apply(ActionBase.__init__, (self,), kw)
536         self.list = map(lambda x: Action(x), list)
537
538     def get_actions(self):
539         return self.list
540
541     def __str__(self):
542         s = []
543         for l in self.list:
544             s.append(str(l))
545         return string.join(s, "\n")
546
547     def strfunction(self, target, source, env):
548         s = []
549         for l in self.list:
550             if l.strfunction:
551                 x = l.strfunction(target, source, env)
552                 if not SCons.Util.is_List(x):
553                     x = [x]
554                 s.extend(x)
555         return string.join(s, "\n")
556
557     def execute(self, target, source, env):
558         for l in self.list:
559             r = l.execute(target, source, env)
560             if r:
561                 return r
562         return 0
563
564     def get_contents(self, target, source, env, dict=None):
565         """Return the signature contents of this action list.
566
567         Simple concatenation of the signatures of the elements.
568         """
569         dict = SCons.Util.subst_dict(target, source)
570         return string.join(map(lambda x, t=target, s=source, e=env, d=dict:
571                                       x.get_contents(t, s, e, d),
572                                self.list),
573                            "")
574
575 class ActionCaller:
576     """A class for delaying calling an Action function with specific
577     (positional and keyword) arguments until the Action is actually
578     executed.
579
580     This class looks to the rest of the world like a normal Action object,
581     but what it's really doing is hanging on to the arguments until we
582     have a target, source and env to use for the expansion.
583     """
584     def __init__(self, parent, args, kw):
585         self.parent = parent
586         self.args = args
587         self.kw = kw
588     def get_contents(self, target, source, env, dict=None):
589         actfunc = self.parent.actfunc
590         try:
591             # "self.actfunc" is a function.
592             contents = str(actfunc.func_code.co_code)
593         except AttributeError:
594             # "self.actfunc" is a callable object.
595             try:
596                 contents = str(actfunc.__call__.im_func.func_code.co_code)
597             except AttributeError:
598                 # No __call__() method, so it might be a builtin
599                 # or something like that.  Do the best we can.
600                 contents = str(actfunc)
601         return contents
602     def subst_args(self, target, source, env):
603         return map(lambda x, e=env, t=target, s=source:
604                           e.subst(x, 0, t, s),
605                    self.args)
606     def subst_kw(self, target, source, env):
607         kw = {}
608         for key in self.kw.keys():
609             kw[key] = env.subst(self.kw[key], 0, target, source)
610         return kw
611     def __call__(self, target, source, env):
612         args = self.subst_args(target, source, env)
613         kw = self.subst_kw(target, source, env)
614         return apply(self.parent.actfunc, args, kw)
615     def strfunction(self, target, source, env):
616         args = self.subst_args(target, source, env)
617         kw = self.subst_kw(target, source, env)
618         return apply(self.parent.strfunc, args, kw)
619
620 class ActionFactory:
621     """A factory class that will wrap up an arbitrary function
622     as an SCons-executable Action object.
623
624     The real heavy lifting here is done by the ActionCaller class.
625     We just collect the (positional and keyword) arguments that we're
626     called with and give them to the ActionCaller object we create,
627     so it can hang onto them until it needs them.
628     """
629     def __init__(self, actfunc, strfunc):
630         self.actfunc = actfunc
631         self.strfunc = strfunc
632     def __call__(self, *args, **kw):
633         ac = ActionCaller(self, args, kw)
634         return Action(ac, strfunction=ac.strfunction)