Add AddPreAction() and AddPostAction() methods. (Charles Crain)
[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 exitvalmap = {
49     2 : 127,
50     13 : 126,
51 }
52
53 default_ENV = None
54
55 def rfile(n):
56     try:
57         return n.rfile()
58     except AttributeError:
59         return n
60
61 def SetCommandHandler(func, escape = lambda x: x):
62     raise SCons.Errors.UserError("SetCommandHandler() is no longer supported, use the SPAWN and ESCAPE construction variables.")
63
64 def GetCommandHandler():
65     raise SCons.Errors.UserError("GetCommandHandler() is no longer supported, use the SPAWN construction variable.")
66
67 def _actionAppend(act1, act2):
68     # This function knows how to slap two actions together.
69     # Mainly, it handles ListActions by concatenating into
70     # a single ListAction.
71     a1 = Action(act1)
72     a2 = Action(act2)
73     if a1 is None or a2 is None:
74         raise TypeError, "Cannot append %s to %s" % (type(act1), type(act2))
75     if isinstance(a1, ListAction):
76         if isinstance(a2, ListAction):
77             return ListAction(a1.list + a2.list)
78         else:
79             return ListAction(a1.list + [ a2 ])
80     else:
81         if isinstance(a2, ListAction):
82             return ListAction([ a1 ] + a2.list)
83         else:
84             return ListAction([ a1, a2 ])
85
86 class CommandGenerator:
87     """
88     Wraps a command generator function so the Action() factory
89     function can tell a generator function from a function action.
90     """
91     def __init__(self, generator):
92         self.generator = generator
93
94     def __add__(self, other):
95         return _actionAppend(self, other)
96
97     def __radd__(self, other):
98         return _actionAppend(other, self)
99
100 def _do_create_action(act, strfunction=_null, varlist=[]):
101     """This is the actual "implementation" for the
102     Action factory method, below.  This handles the
103     fact that passing lists to Action() itself has
104     different semantics than passing lists as elements
105     of lists.
106
107     The former will create a ListAction, the latter
108     will create a CommandAction by converting the inner
109     list elements to strings."""
110
111     if isinstance(act, ActionBase):
112         return act
113     elif SCons.Util.is_List(act):
114         return CommandAction(act)
115     elif isinstance(act, CommandGenerator):
116         return CommandGeneratorAction(act.generator)
117     elif callable(act):
118         return FunctionAction(act, strfunction=strfunction, varlist=varlist)
119     elif SCons.Util.is_String(act):
120         var=SCons.Util.get_environment_var(act)
121         if var:
122             # This looks like a string that is purely an Environment
123             # variable reference, like "$FOO" or "${FOO}".  We do
124             # something special here...we lazily evaluate the contents
125             # of that Environment variable, so a user could but something
126             # like a function or a CommandGenerator in that variable
127             # instead of a string.
128             return CommandGeneratorAction(LazyCmdGenerator(var))
129         listCmds = map(lambda x: CommandAction(string.split(x)),
130                        string.split(act, '\n'))
131         if len(listCmds) == 1:
132             return listCmds[0]
133         else:
134             return ListAction(listCmds)
135     else:
136         return None
137
138 def Action(act, strfunction=_null, varlist=[]):
139     """A factory for action objects."""
140     if SCons.Util.is_List(act):
141         acts = map(lambda x, s=strfunction, v=varlist:
142                           _do_create_action(x, s, v),
143                    act)
144         acts = filter(lambda x: not x is None, acts)
145         if len(acts) == 1:
146             return acts[0]
147         else:
148             return ListAction(acts)
149     else:
150         return _do_create_action(act, strfunction=strfunction, varlist=varlist)
151
152 class ActionBase:
153     """Base class for actions that create output objects."""
154     def __cmp__(self, other):
155         return cmp(self.__dict__, other.__dict__)
156
157     def show(self, string):
158         print string
159
160     def get_actions(self):
161         return [self]
162
163     def subst_dict(self, target, source, env):
164         """Create a dictionary for substitution of construction
165         variables.
166
167         This translates the following special arguments:
168
169             env    - the construction environment itself,
170                      the values of which (CC, CCFLAGS, etc.)
171                      are copied straight into the dictionary
172
173             target - the target (object or array of objects),
174                      used to generate the TARGET and TARGETS
175                      construction variables
176
177             source - the source (object or array of objects),
178                      used to generate the SOURCES and SOURCE
179                      construction variables
180         """
181
182         dict = {}
183
184         for k,v in env.items(): dict[k] = v
185
186         if not SCons.Util.is_List(target):
187             target = [target]
188
189         dict['TARGETS'] = SCons.Util.PathList(map(os.path.normpath, map(str, target)))
190         if dict['TARGETS']:
191             dict['TARGET'] = dict['TARGETS'][0]
192
193         def rstr(x):
194             try:
195                 return x.rstr()
196             except AttributeError:
197                 return str(x)
198         if not SCons.Util.is_List(source):
199             source = [source]
200         dict['SOURCES'] = SCons.Util.PathList(map(os.path.normpath, map(rstr, source)))
201         if dict['SOURCES']:
202             dict['SOURCE'] = dict['SOURCES'][0]
203
204         return dict
205
206     def __add__(self, other):
207         return _actionAppend(self, other)
208
209     def __radd__(self, other):
210         return _actionAppend(other, self)
211
212 def _string_from_cmd_list(cmd_list):
213     """Takes a list of command line arguments and returns a pretty
214     representation for printing."""
215     cl = []
216     for arg in map(str, cmd_list):
217         if ' ' in arg or '\t' in arg:
218             arg = '"' + arg + '"'
219         cl.append(arg)
220     return string.join(cl)
221
222 _rm = re.compile(r'\$[()]')
223 _remove = re.compile(r'\$\(([^\$]|\$[^\(])*?\$\)')
224
225 class CommandAction(ActionBase):
226     """Class for command-execution actions."""
227     def __init__(self, cmd):
228         self.cmd_list = cmd
229
230     def __call__(self, target, source, env):
231         """Execute a command action.
232
233         This will handle lists of commands as well as individual commands,
234         because construction variable substitution may turn a single
235         "command" into a list.  This means that this class can actually
236         handle lists of commands, even though that's not how we use it
237         externally.
238         """
239         import SCons.Util
240
241         escape = env.get('ESCAPE', lambda x: x)
242
243         if env.has_key('SHELL'):
244             shell = env['SHELL']
245         else:
246             raise SCons.Errors.UserError('Missing SHELL construction variable.')
247
248         if env.has_key('SPAWN'):
249             spawn = env['SPAWN']
250         else:
251             raise SCons.Errors.UserError('Missing SPAWN construction variable.')
252
253         dict = self.subst_dict(target, source, env)
254         cmd_list = SCons.Util.scons_subst_list(self.cmd_list, dict, {}, _rm)
255         for cmd_line in cmd_list:
256             if len(cmd_line):
257                 if print_actions:
258                     self.show(_string_from_cmd_list(cmd_line))
259                 if execute_actions:
260                     try:
261                         ENV = dict['ENV']
262                     except KeyError:
263                         global default_ENV
264                         if not default_ENV:
265                             import SCons.Environment
266                             default_ENV = SCons.Environment.Environment()['ENV']
267                         ENV = default_ENV
268                     # Escape the command line for the command
269                     # interpreter we are using
270                     map(lambda x, e=escape: x.escape(e), cmd_line)
271                     cmd_line = map(str, cmd_line)
272                     ret = spawn(shell, escape, cmd_line[0], cmd_line, ENV)
273                     if ret:
274                         return ret
275         return 0
276
277     def _sig_dict(self, target, source, env):
278         """Supply a dictionary for use in computing signatures.
279
280         For signature purposes, it doesn't matter what targets or
281         sources we use, so long as we use the same ones every time
282         so the signature stays the same.  We supply an array of two
283         of each to allow for distinction between TARGET and TARGETS.
284         """
285         return self.subst_dict(['__t1__', '__t2__'], ['__s1__', '__s2__'], env)
286
287     def get_raw_contents(self, target, source, env):
288         """Return the complete contents of this action's command line.
289         """
290         return SCons.Util.scons_subst(string.join(self.cmd_list),
291                                       self._sig_dict(target, source, env), {})
292
293     def get_contents(self, target, source, env):
294         """Return the signature contents of this action's command line.
295
296         This strips $(-$) and everything in between the string,
297         since those parts don't affect signatures.
298         """
299         return SCons.Util.scons_subst(string.join(map(str, self.cmd_list)),
300                                       self._sig_dict(target, source, env), {}, _remove)
301
302 class CommandGeneratorAction(ActionBase):
303     """Class for command-generator actions."""
304     def __init__(self, generator):
305         self.generator = generator
306
307     def __generate(self, target, source, env, for_signature):
308         # ensure that target is a list, to make it easier to write
309         # generator functions:
310         if not SCons.Util.is_List(target):
311             target = [target]
312
313         ret = self.generator(target=target, source=source, env=env, for_signature=for_signature)
314         gen_cmd = Action(ret)
315         if not gen_cmd:
316             raise SCons.Errors.UserError("Object returned from command generator: %s cannot be used to create an Action." % repr(ret))
317         return gen_cmd
318
319     def __call__(self, target, source, env):
320         if not SCons.Util.is_List(source):
321             source = [source]
322         rsources = map(rfile, source)
323         act = self.__generate(target, source, env, 0)
324         return act(target, rsources, env)
325
326     def get_contents(self, target, source, env):
327         """Return the signature contents of this action's command line.
328
329         This strips $(-$) and everything in between the string,
330         since those parts don't affect signatures.
331         """
332         return self.__generate(target, source, env, 1).get_contents(target, source, env)
333
334 class LazyCmdGenerator:
335     """This is a simple callable class that acts as a command generator.
336     It holds on to a key into an Environment dictionary, then waits
337     until execution time to see what type it is, then tries to
338     create an Action out of it."""
339     def __init__(self, var):
340         self.var = SCons.Util.to_String(var)
341
342     def __call__(self, target, source, env, for_signature):
343         if env.has_key(self.var):
344             return env[self.var]
345         else:
346             # The variable reference substitutes to nothing.
347             return ''
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, execfunction=execfunction):
356                 def quote(s):
357                     return '"' + str(s) + '"'
358                 try:
359                     name = execfunction.__name__
360                 except AttributeError:
361                     try:
362                         name = execfunction.__class__.__name__
363                     except AttributeError:
364                         name = "unknown_python_function"
365                 if len(target) == 1:
366                     tstr = quote(target[0])
367                 else:
368                     tstr = str(map(lambda x, q=quote: q(x), target))
369                 if len(source) == 1:
370                     sstr = quote(source[0])
371                 else:
372                     sstr = str(map(lambda x, q=quote: q(x), source))
373                 return "%s(%s, %s)" % (name, tstr, sstr)
374         self.strfunction = strfunction
375         self.varlist = varlist
376
377     def __call__(self, target, source, env):
378         r = 0
379         if not SCons.Util.is_List(target):
380             target = [target]
381         if not SCons.Util.is_List(source):
382             source = [source]
383         if print_actions and self.strfunction:
384             s = self.strfunction(target, source)
385             if s:
386                 self.show(s)
387         if execute_actions:
388             rsources = map(rfile, source)
389             r = self.execfunction(target=target, source=rsources, env=env)
390         return r
391
392     def get_contents(self, target, source, env):
393         """Return the signature contents of this callable action.
394
395         By providing direct access to the code object of the
396         function, Python makes this extremely easy.  Hooray!
397         """
398         try:
399             # "self.execfunction" is a function.
400             code = self.execfunction.func_code.co_code
401         except:
402             # "self.execfunction" is a callable object.
403             code = self.execfunction.__call__.im_func.func_code.co_code
404         return str(code) + string.join(map(lambda v, e=env: str(e[v]),
405                                        self.varlist))
406
407 class ListAction(ActionBase):
408     """Class for lists of other actions."""
409     def __init__(self, list):
410         self.list = map(lambda x: Action(x), list)
411
412     def get_actions(self):
413         return self.list
414
415     def __call__(self, target, source, env):
416         for l in self.list:
417             r = l(target, source, env)
418             if r:
419                 return r
420         return 0
421
422     def get_contents(self, target, source, env):
423         """Return the signature contents of this action list.
424
425         Simple concatenation of the signatures of the elements.
426         """
427         return string.join(map(lambda x, t=target, s=source, e=env:
428                                       x.get_contents(t, s, e),
429                                self.list),
430                            "")