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