Fix a problem when using --cache-show.
[scons.git] / src / engine / SCons / Action.py
1 """engine.SCons.Action
2
3 XXX
4
5 """
6
7 #
8 # __COPYRIGHT__
9 #
10 # Permission is hereby granted, free of charge, to any person obtaining
11 # a copy of this software and associated documentation files (the
12 # "Software"), to deal in the Software without restriction, including
13 # without limitation the rights to use, copy, modify, merge, publish,
14 # distribute, sublicense, and/or sell copies of the Software, and to
15 # permit persons to whom the Software is furnished to do so, subject to
16 # the following conditions:
17 #
18 # The above copyright notice and this permission notice shall be included
19 # in all copies or substantial portions of the Software.
20 #
21 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
22 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
23 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 #
29
30 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
31
32 import os
33 import os.path
34 import re
35 import string
36
37 import SCons.Errors
38 import SCons.Util
39
40 class _Null:
41     pass
42
43 _null = _Null
44
45 print_actions = 1;
46 execute_actions = 1;
47
48 default_ENV = None
49
50 def rfile(n):
51     try:
52         return n.rfile()
53     except AttributeError:
54         return n
55
56 def SetCommandHandler(func, escape = lambda x: x):
57     raise SCons.Errors.UserError("SetCommandHandler() is no longer supported, use the SPAWN and ESCAPE construction variables.")
58
59 def GetCommandHandler():
60     raise SCons.Errors.UserError("GetCommandHandler() is no longer supported, use the SPAWN construction variable.")
61
62 def _actionAppend(act1, act2):
63     # This function knows how to slap two actions together.
64     # Mainly, it handles ListActions by concatenating into
65     # a single ListAction.
66     a1 = Action(act1)
67     a2 = Action(act2)
68     if a1 is None or a2 is None:
69         raise TypeError, "Cannot append %s to %s" % (type(act1), type(act2))
70     if isinstance(a1, ListAction):
71         if isinstance(a2, ListAction):
72             return ListAction(a1.list + a2.list)
73         else:
74             return ListAction(a1.list + [ a2 ])
75     else:
76         if isinstance(a2, ListAction):
77             return ListAction([ a1 ] + a2.list)
78         else:
79             return ListAction([ a1, a2 ])
80
81 class CommandGenerator:
82     """
83     Wraps a command generator function so the Action() factory
84     function can tell a generator function from a function action.
85     """
86     def __init__(self, generator):
87         self.generator = generator
88
89     def __add__(self, other):
90         return _actionAppend(self, other)
91
92     def __radd__(self, other):
93         return _actionAppend(other, self)
94
95 def _do_create_action(act, strfunction=_null, varlist=[]):
96     """This is the actual "implementation" for the
97     Action factory method, below.  This handles the
98     fact that passing lists to Action() itself has
99     different semantics than passing lists as elements
100     of lists.
101
102     The former will create a ListAction, the latter
103     will create a CommandAction by converting the inner
104     list elements to strings."""
105
106     if isinstance(act, ActionBase):
107         return act
108     elif SCons.Util.is_List(act):
109         return CommandAction(act)
110     elif isinstance(act, CommandGenerator):
111         return CommandGeneratorAction(act.generator)
112     elif callable(act):
113         return FunctionAction(act, strfunction=strfunction, varlist=varlist)
114     elif SCons.Util.is_String(act):
115         var=SCons.Util.get_environment_var(act)
116         if var:
117             # This looks like a string that is purely an Environment
118             # variable reference, like "$FOO" or "${FOO}".  We do
119             # something special here...we lazily evaluate the contents
120             # of that Environment variable, so a user could but something
121             # like a function or a CommandGenerator in that variable
122             # instead of a string.
123             return CommandGeneratorAction(LazyCmdGenerator(var))
124         listCmds = map(lambda x: CommandAction(x), string.split(act, '\n'))
125         if len(listCmds) == 1:
126             return listCmds[0]
127         else:
128             return ListAction(listCmds)
129     else:
130         return None
131
132 def Action(act, strfunction=_null, varlist=[]):
133     """A factory for action objects."""
134     if SCons.Util.is_List(act):
135         acts = map(lambda x, s=strfunction, v=varlist:
136                           _do_create_action(x, s, v),
137                    act)
138         acts = filter(lambda x: not x is None, acts)
139         if len(acts) == 1:
140             return acts[0]
141         else:
142             return ListAction(acts)
143     else:
144         return _do_create_action(act, strfunction=strfunction, varlist=varlist)
145
146 class ActionBase:
147     """Base class for actions that create output objects."""
148     def __cmp__(self, other):
149         return cmp(self.__dict__, other.__dict__)
150
151     def show(self, string):
152         if print_actions:
153             print string
154
155     def get_actions(self):
156         return [self]
157
158     def __add__(self, other):
159         return _actionAppend(self, other)
160
161     def __radd__(self, other):
162         return _actionAppend(other, self)
163
164 def _string_from_cmd_list(cmd_list):
165     """Takes a list of command line arguments and returns a pretty
166     representation for printing."""
167     cl = []
168     for arg in map(str, cmd_list):
169         if ' ' in arg or '\t' in arg:
170             arg = '"' + arg + '"'
171         cl.append(arg)
172     return string.join(cl)
173
174 class CommandAction(ActionBase):
175     """Class for command-execution actions."""
176     def __init__(self, cmd):
177         # Cmd list can actually be a list or a single item...basically
178         # anything that we could pass in as the first arg to
179         # scons_subst_list().
180         self.cmd_list = cmd
181
182     def strfunction(self, target, source, env):
183         cmd_list = SCons.Util.scons_subst_list(self.cmd_list, env,
184                                                SCons.Util.SUBST_CMD,
185                                                target, source)
186         return map(_string_from_cmd_list, cmd_list)
187
188     def __call__(self, target, source, env):
189         """Execute a command action.
190
191         This will handle lists of commands as well as individual commands,
192         because construction variable substitution may turn a single
193         "command" into a list.  This means that this class can actually
194         handle lists of commands, even though that's not how we use it
195         externally.
196         """
197         import SCons.Util
198
199         escape = env.get('ESCAPE', lambda x: x)
200
201         if env.has_key('SHELL'):
202             shell = env['SHELL']
203         else:
204             raise SCons.Errors.UserError('Missing SHELL construction variable.')
205
206         # for SConf support (by now): check, if we want to pipe the command
207         # output to somewhere else
208         if env.has_key('PIPE_BUILD'):
209             pipe_build = 1
210             if env.has_key('PSPAWN'):
211                 pspawn = env['PSPAWN']
212             else:
213                 raise SCons.Errors.UserError('Missing PSPAWN construction variable.')
214             if env.has_key('PSTDOUT'):
215                 pstdout = env['PSTDOUT']
216             else:
217                 raise SCons.Errors.UserError('Missing PSTDOUT construction variable.')
218             if env.has_key('PSTDERR'):
219                 pstderr = env['PSTDERR']
220             else:
221                 raise SCons.Errors.UserError('Missing PSTDOUT construction variable.')
222         else:
223             pipe_build = 0
224             if env.has_key('SPAWN'):
225                 spawn = env['SPAWN']
226             else:
227                 raise SCons.Errors.UserError('Missing SPAWN construction variable.')
228
229         cmd_list = SCons.Util.scons_subst_list(self.cmd_list, env,
230                                                SCons.Util.SUBST_CMD,
231                                                target, source)
232         for cmd_line in cmd_list:
233             if len(cmd_line):
234                 if print_actions:
235                     self.show(_string_from_cmd_list(cmd_line))
236                 if execute_actions:
237                     try:
238                         ENV = env['ENV']
239                     except KeyError:
240                         global default_ENV
241                         if not default_ENV:
242                             import SCons.Environment
243                             default_ENV = SCons.Environment.Environment()['ENV']
244                         ENV = default_ENV
245                     # Escape the command line for the command
246                     # interpreter we are using
247                     map(lambda x, e=escape: x.escape(e), cmd_line)
248                     cmd_line = map(str, cmd_line)
249                     if pipe_build:
250                         ret = pspawn( shell, escape, cmd_line[0], cmd_line,
251                                       ENV, pstdout, pstderr )
252                     else:
253                         ret = spawn(shell, escape, cmd_line[0], cmd_line, ENV)
254                     if ret:
255                         return ret
256         return 0
257
258     def get_raw_contents(self, target, source, env):
259         """Return the complete contents of this action's command line.
260         """
261         cmd = self.cmd_list
262         if not SCons.Util.is_List(cmd):
263             cmd = [ cmd ]
264         return SCons.Util.scons_subst(string.join(map(str, cmd)),
265                                       env,
266                                       SCons.Util.SUBST_RAW,
267                                       SCons.Util.target_prep(target),
268                                       SCons.Util.source_prep(source))
269
270     def get_contents(self, target, source, env):
271         """Return the signature contents of this action's command line.
272
273         This strips $(-$) and everything in between the string,
274         since those parts don't affect signatures.
275         """
276         cmd = self.cmd_list
277         if not SCons.Util.is_List(cmd):
278             cmd = [ cmd ]
279         return SCons.Util.scons_subst(string.join(map(str, cmd)),
280                                       env,
281                                       SCons.Util.SUBST_SIG,
282                                       SCons.Util.target_prep(target),
283                                       SCons.Util.source_prep(source))
284
285 class CommandGeneratorAction(ActionBase):
286     """Class for command-generator actions."""
287     def __init__(self, generator):
288         self.generator = generator
289
290     def __generate(self, target, source, env, for_signature):
291         # ensure that target is a list, to make it easier to write
292         # generator functions:
293         if not SCons.Util.is_List(target):
294             target = [target]
295
296         ret = self.generator(target=target, source=source, env=env, for_signature=for_signature)
297         gen_cmd = Action(ret)
298         if not gen_cmd:
299             raise SCons.Errors.UserError("Object returned from command generator: %s cannot be used to create an Action." % repr(ret))
300         return gen_cmd
301
302     def strfunction(self, target, source, env):
303         if not SCons.Util.is_List(source):
304             source = [source]
305         rsources = map(rfile, source)
306         act = self.__generate(target, source, env, 0)
307         return act.strfunction(target, rsources, env)
308
309     def __call__(self, target, source, env):
310         if not SCons.Util.is_List(source):
311             source = [source]
312         rsources = map(rfile, source)
313         act = self.__generate(target, source, env, 0)
314         return act(target, rsources, env)
315
316     def get_contents(self, target, source, env):
317         """Return the signature contents of this action's command line.
318
319         This strips $(-$) and everything in between the string,
320         since those parts don't affect signatures.
321         """
322         return self.__generate(target, source, env, 1).get_contents(target, source, env)
323
324 class LazyCmdGenerator:
325     """This is a simple callable class that acts as a command generator.
326     It holds on to a key into an Environment dictionary, then waits
327     until execution time to see what type it is, then tries to
328     create an Action out of it."""
329     def __init__(self, var):
330         self.var = SCons.Util.to_String(var)
331
332     def strfunction(self, target, source, env):
333         try:
334             return env[self.var]
335         except KeyError:
336             # The variable reference substitutes to nothing.
337             return ''
338
339     def __call__(self, target, source, env, for_signature):
340         try:
341             return env[self.var]
342         except KeyError:
343             # The variable reference substitutes to nothing.
344             return ''
345
346     def __cmp__(self, other):
347         return cmp(self.__dict__, other.__dict__)
348
349 class FunctionAction(ActionBase):
350     """Class for Python function actions."""
351
352     def __init__(self, execfunction, strfunction=_null, varlist=[]):
353         self.execfunction = execfunction
354         if strfunction is _null:
355             def strfunction(target, source, env, execfunction=execfunction):
356                 def quote(s):
357                     return '"' + str(s) + '"'
358                 def array(a, q=quote):
359                     return '[' + string.join(map(lambda x, q=q: q(x), a), ", ") + ']'
360                 try:
361                     name = execfunction.__name__
362                 except AttributeError:
363                     try:
364                         name = execfunction.__class__.__name__
365                     except AttributeError:
366                         name = "unknown_python_function"
367                 tstr = len(target) == 1 and quote(target[0]) or array(target)
368                 sstr = len(source) == 1 and quote(source[0]) or array(source)
369                 return "%s(%s, %s)" % (name, tstr, sstr)
370         self.strfunction = strfunction
371         self.varlist = varlist
372
373     def __call__(self, target, source, env):
374         r = 0
375         if not SCons.Util.is_List(target):
376             target = [target]
377         if not SCons.Util.is_List(source):
378             source = [source]
379         if print_actions and self.strfunction:
380             s = self.strfunction(target, source, env)
381             if s:
382                 self.show(s)
383         if execute_actions:
384             rsources = map(rfile, source)
385             r = self.execfunction(target=target, source=rsources, env=env)
386         return r
387
388     def get_contents(self, target, source, env):
389         """Return the signature contents of this callable action.
390
391         By providing direct access to the code object of the
392         function, Python makes this extremely easy.  Hooray!
393         """
394         try:
395             # "self.execfunction" is a function.
396             code = self.execfunction.func_code.co_code
397         except:
398             # "self.execfunction" is a callable object.
399             code = self.execfunction.__call__.im_func.func_code.co_code
400         return str(code) + env.subst(string.join(map(lambda v: '${'+v+'}',
401                                                      self.varlist)))
402
403 class ListAction(ActionBase):
404     """Class for lists of other actions."""
405     def __init__(self, list):
406         self.list = map(lambda x: Action(x), list)
407
408     def get_actions(self):
409         return self.list
410
411     def strfunction(self, target, source, env):
412         s = []
413         for l in self.list:
414             if l.strfunction:
415                 x = l.strfunction(target, source, env)
416                 if not SCons.Util.is_List(x):
417                     x = [x]
418                 s.extend(x)
419         return string.join(s, "\n")
420
421     def __call__(self, target, source, env):
422         for l in self.list:
423             r = l(target, source, env)
424             if r:
425                 return r
426         return 0
427
428     def get_contents(self, target, source, env):
429         """Return the signature contents of this action list.
430
431         Simple concatenation of the signatures of the elements.
432         """
433         target = SCons.Util.target_prep(target)
434         source = SCons.Util.source_prep(source)
435         return string.join(map(lambda x, t=target, s=source, e=env:
436                                       x.get_contents(t, s, e),
437                                self.list),
438                            "")