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