Add a Platform() method.
[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.Util
41 import SCons.Errors
42
43 print_actions = 1;
44 execute_actions = 1;
45
46 exitvalmap = {
47     2 : 127,
48     13 : 126,
49 }
50
51 default_ENV = None
52
53 if os.name == 'posix':
54
55     def defaultSpawn(cmd, args, env):
56         pid = os.fork()
57         if not pid:
58             # Child process.
59             exitval = 127
60             args = [ 'sh', '-c' ] + \
61                    [ string.join(map(lambda x: string.replace(str(x),
62                                                               ' ',
63                                                               r'\ '),
64                                      args)) ]
65             try:
66                 os.execvpe('sh', args, env)
67             except OSError, e:
68                 exitval = exitvalmap[e[0]]
69                 sys.stderr.write("scons: %s: %s\n" % (cmd, e[1]))
70             os._exit(exitval)
71         else:
72             # Parent process.
73             pid, stat = os.waitpid(pid, 0)
74             ret = stat >> 8
75             return ret
76
77 elif os.name == 'nt':
78
79     def pathsearch(cmd, env):
80         # In order to deal with the fact that 1.5.2 doesn't have
81         # os.spawnvpe(), roll our own PATH search.
82         if os.path.isabs(cmd):
83             if not os.path.exists(cmd):
84                 exts = env['PATHEXT']
85                 if not SCons.Util.is_List(exts):
86                     exts = string.split(exts, os.pathsep)
87                 for e in exts:
88                     f = cmd + e
89                     if os.path.exists(f):
90                         return f
91             else:
92                 return cmd
93         else:
94             path = env['PATH']
95             if not SCons.Util.is_List(path):
96                 path = string.split(path, os.pathsep)
97             exts = env['PATHEXT']
98             if not SCons.Util.is_List(exts):
99                 exts = string.split(exts, os.pathsep)
100             pairs = []
101             for dir in path:
102                 for e in exts:
103                     pairs.append((dir, e))
104             for dir, ext in pairs:
105                 f = os.path.join(dir, cmd)
106                 if not ext is None:
107                     f = f + ext
108                 if os.path.exists(f):
109                     return f
110         return None
111
112     # Attempt to find cmd.exe (for WinNT/2k/XP) or
113     # command.com for Win9x
114
115     cmd_interp = ''
116     # First see if we can look in the registry...
117     if SCons.Util.can_read_reg:
118         try:
119             # Look for Windows NT system root
120             k=SCons.Util.RegOpenKeyEx(SCons.Util.hkey_mod.HKEY_LOCAL_MACHINE,
121                                           'Software\\Microsoft\\Windows NT\\CurrentVersion')
122             val, tok = SCons.Util.RegQueryValueEx(k, 'SystemRoot')
123             cmd_interp = os.path.join(val, 'System32\\cmd.exe')
124         except SCons.Util.RegError:
125             try:
126                 # Okay, try the Windows 9x system root
127                 k=SCons.Util.RegOpenKeyEx(SCons.Util.hkey_mod.HKEY_LOCAL_MACHINE,
128                                               'Software\\Microsoft\\Windows\\CurrentVersion')
129                 val, tok = SCons.Util.RegQueryValueEx(k, 'SystemRoot')
130                 cmd_interp = os.path.join(val, 'command.com')
131             except:
132                 pass
133     if not cmd_interp:
134         cmd_interp = pathsearch('cmd', os.environ)
135         if not cmd_interp:
136             cmd_interp = pathsearch('command', os.environ)
137
138     # The upshot of all this is that, if you are using Python 1.5.2,
139     # you had better have cmd or command.com in your PATH when you run
140     # scons.
141
142     def defaultSpawn(cmd, args, env):
143         if not cmd_interp:
144             sys.stderr.write("scons: Could not find command interpreter, is it in your PATH?\n")
145             return 127
146         else:
147             try:
148
149                 a = [ cmd_interp, '/C', args[0] ]
150                 for arg in args[1:]:
151                     if ' ' in arg or '\t' in arg:
152                         arg = '"' + arg + '"'
153                     a.append(arg)
154                 ret = os.spawnve(os.P_WAIT, cmd_interp, a, env)
155             except OSError, e:
156                 ret = exitvalmap[e[0]]
157                 sys.stderr.write("scons: %s: %s\n" % (cmd, e[1]))
158             return ret
159 else:
160     def defaultSpawn(cmd, args, env):
161         sys.stderr.write("scons: Unknown os '%s', cannot spawn command interpreter.\n" % os.name)
162         sys.stderr.write("scons: Set your command handler with SetCommandHandler().\n")
163         return 127
164
165 spawn = defaultSpawn
166
167 def SetCommandHandler(func):
168     global spawn
169     spawn = func
170
171 def GetCommandHandler():
172     global spawn
173     return spawn
174
175 class CommandGenerator:
176     """
177     Wraps a command generator function so the Action() factory
178     function can tell a generator function from a function action.
179     """
180     def __init__(self, generator):
181         self.generator = generator
182
183 def _do_create_action(act):
184     """This is the actual "implementation" for the
185     Action factory method, below.  This handles the
186     fact that passing lists to Action() itself has
187     different semantics than passing lists as elements
188     of lists.
189
190     The former will create a ListAction, the latter
191     will create a CommandAction by converting the inner
192     list elements to strings."""
193
194     if isinstance(act, ActionBase):
195         return act
196     elif SCons.Util.is_List(act):
197         return CommandAction(act)
198     elif isinstance(act, CommandGenerator):
199         return CommandGeneratorAction(act.generator)
200     elif callable(act):
201         return FunctionAction(act)
202     elif SCons.Util.is_String(act):
203         var=SCons.Util.get_environment_var(act)
204         if var:
205             # This looks like a string that is purely an Environment
206             # variable reference, like "$FOO" or "${FOO}".  We do
207             # something special here...we lazily evaluate the contents
208             # of that Environment variable, so a user could but something
209             # like a function or a CommandGenerator in that variable
210             # instead of a string.
211             return CommandGeneratorAction(LazyCmdGenerator(var))
212         listCmds = map(lambda x: CommandAction(string.split(x)),
213                        string.split(act, '\n'))
214         if len(listCmds) == 1:
215             return listCmds[0]
216         else:
217             return ListAction(listCmds)
218     else:
219         return None
220
221 def Action(act):
222     """A factory for action objects."""
223     if SCons.Util.is_List(act):
224         acts = filter(lambda x: not x is None,
225                       map(_do_create_action, act))
226         if len(acts) == 1:
227             return acts[0]
228         else:
229             return ListAction(acts)
230     else:
231         return _do_create_action(act)
232
233 class ActionBase:
234     """Base class for actions that create output objects."""
235     def __cmp__(self, other):
236         return cmp(self.__dict__, other.__dict__)
237
238     def show(self, string):
239         print string
240
241     def subst_dict(self, **kw):
242         """Create a dictionary for substitution of construction
243         variables.
244
245         This translates the following special arguments:
246
247             env    - the construction environment itself,
248                      the values of which (CC, CCFLAGS, etc.)
249                      are copied straight into the dictionary
250
251             target - the target (object or array of objects),
252                      used to generate the TARGET and TARGETS
253                      construction variables
254
255             source - the source (object or array of objects),
256                      used to generate the SOURCES and SOURCE 
257                      construction variables
258
259         Any other keyword arguments are copied into the
260         dictionary."""
261
262         dict = {}
263         if kw.has_key('env'):
264             dict.update(kw['env'])
265             del kw['env']
266
267         try:
268             cwd = kw['dir']
269         except:
270             cwd = None
271         else:
272             del kw['dir']
273
274         if kw.has_key('target'):
275             t = kw['target']
276             del kw['target']
277             if not SCons.Util.is_List(t):
278                 t = [t]
279             try:
280                 cwd = t[0].cwd
281             except (IndexError, AttributeError):
282                 pass
283             dict['TARGETS'] = SCons.Util.PathList(map(os.path.normpath, map(str, t)))
284             if dict['TARGETS']:
285                 dict['TARGET'] = dict['TARGETS'][0]
286
287         if kw.has_key('source'):
288             s = kw['source']
289             del kw['source']
290             if not SCons.Util.is_List(s):
291                 s = [s]
292             dict['SOURCES'] = SCons.Util.PathList(map(os.path.normpath, map(str, s)))
293             if dict['SOURCES']:
294                 dict['SOURCE'] = dict['SOURCES'][0]
295
296         dict.update(kw)
297
298         return dict
299
300 def _string_from_cmd_list(cmd_list):
301     """Takes a list of command line arguments and returns a pretty
302     representation for printing."""
303     cl = []
304     for arg in cmd_list:
305         if ' ' in arg or '\t' in arg:
306             arg = '"' + arg + '"'
307         cl.append(arg)
308     return string.join(cl)
309
310 _rm = re.compile(r'\$[()]')
311 _remove = re.compile(r'\$\(([^\$]|\$[^\(])*?\$\)')
312
313 class EnvDictProxy(UserDict.UserDict):
314     """This is a dictionary-like class that contains the
315     Environment dictionary we pass to FunctionActions
316     and CommandGeneratorActions.
317
318     In addition to providing
319     normal dictionary-like access to the variables in the
320     Environment, it also exposes the functions subst()
321     and subst_list(), allowing users to easily do variable
322     interpolation when writing their FunctionActions
323     and CommandGeneratorActions."""
324
325     def __init__(self, env):
326         UserDict.UserDict.__init__(self, env)
327
328     def subst(self, string, raw=0):
329         if raw:
330             regex_remove = None
331         else:
332             regex_remove = _rm
333         return SCons.Util.scons_subst(string, self.data, {}, regex_remove)
334
335     def subst_list(self, string, raw=0):
336         if raw:
337             regex_remove = None
338         else:
339             regex_remove = _rm
340         return SCons.Util.scons_subst_list(string, self.data, {}, regex_remove)
341
342 class CommandAction(ActionBase):
343     """Class for command-execution actions."""
344     def __init__(self, cmd):
345         import SCons.Util
346         
347         self.cmd_list = map(SCons.Util.to_String, cmd)
348
349     def execute(self, **kw):
350         dict = apply(self.subst_dict, (), kw)
351         import SCons.Util
352         cmd_list = SCons.Util.scons_subst_list(self.cmd_list, dict, {}, _rm)
353         for cmd_line in cmd_list:
354             if len(cmd_line):
355                 if print_actions:
356                     self.show(_string_from_cmd_list(cmd_line))
357                 if execute_actions:
358                     try:
359                         ENV = kw['env']['ENV']
360                     except:
361                         global default_ENV
362                         if not default_ENV:
363                             import SCons.Environment
364                             default_ENV = SCons.Environment.Environment()['ENV']
365                         ENV = default_ENV
366                     ret = spawn(cmd_line[0], cmd_line, ENV)
367                     if ret:
368                         return ret
369         return 0
370
371     def _sig_dict(self, kw):
372         """Supply a dictionary for use in computing signatures.
373
374         For signature purposes, it doesn't matter what targets or
375         sources we use, so long as we use the same ones every time
376         so the signature stays the same.  We supply an array of two
377         of each to allow for distinction between TARGET and TARGETS.
378         """
379         kw['target'] = ['__t1__', '__t2__']
380         kw['source'] = ['__s1__', '__s2__']
381         return apply(self.subst_dict, (), kw)
382
383     def get_raw_contents(self, **kw):
384         """Return the complete contents of this action's command line.
385         """
386         return SCons.Util.scons_subst(string.join(self.cmd_list),
387                                       self._sig_dict(kw), {})
388
389     def get_contents(self, **kw):
390         """Return the signature contents of this action's command line.
391
392         This strips $(-$) and everything in between the string,
393         since those parts don't affect signatures.
394         """
395         return SCons.Util.scons_subst(string.join(self.cmd_list),
396                                       self._sig_dict(kw), {}, _remove)
397
398 class CommandGeneratorAction(ActionBase):
399     """Class for command-generator actions."""
400     def __init__(self, generator):
401         self.generator = generator
402
403     def __generate(self, kw, for_signature):
404         import SCons.Util
405
406         # Wrap the environment dictionary in an EnvDictProxy
407         # object to make variable interpolation easier for the
408         # client.
409         args = copy.copy(kw)
410         args['for_signature'] = for_signature
411         if args.has_key("env") and not isinstance(args["env"], EnvDictProxy):
412             args["env"] = EnvDictProxy(args["env"])
413
414         # ensure that target is a list, to make it easier to write
415         # generator functions:
416         if args.has_key("target") and not SCons.Util.is_List(args["target"]):
417             args["target"] = [args["target"]]
418
419         ret = apply(self.generator, (), args)
420         gen_cmd = Action(ret)
421         if not gen_cmd:
422             raise SCons.Errors.UserError("Object returned from command generator: %s cannot be used to create an Action." % repr(ret))
423         return gen_cmd
424
425     def execute(self, **kw):
426         return apply(self.__generate(kw, 0).execute, (), kw)
427
428     def get_contents(self, **kw):
429         """Return the signature contents of this action's command line.
430
431         This strips $(-$) and everything in between the string,
432         since those parts don't affect signatures.
433         """
434         return apply(self.__generate(kw, 1).get_contents, (), kw)
435
436 class LazyCmdGenerator:
437     """This is a simple callable class that acts as a command generator.
438     It holds on to a key into an Environment dictionary, then waits
439     until execution time to see what type it is, then tries to
440     create an Action out of it."""
441     def __init__(self, var):
442         self.var = SCons.Util.to_String(var)
443
444     def __call__(self, env, **kw):
445         if env.has_key(self.var):
446             return env[self.var]
447         else:
448             # The variable reference substitutes to nothing.
449             return ''
450
451 class FunctionAction(ActionBase):
452     """Class for Python function actions."""
453     def __init__(self, function):
454         self.function = function
455
456     def execute(self, **kw):
457         # if print_actions:
458         # XXX:  WHAT SHOULD WE PRINT HERE?
459         if execute_actions:
460             if kw.has_key('target') and not \
461                SCons.Util.is_List(kw['target']):
462                 kw['target'] = [ kw['target'] ]
463             if kw.has_key('source') and not \
464                SCons.Util.is_List(kw['source']):
465                 kw['source'] = [ kw['source'] ]
466             if kw.has_key("env") and not isinstance(kw["env"], EnvDictProxy):
467                 kw["env"] = EnvDictProxy(kw["env"])
468             return apply(self.function, (), kw)
469
470     def get_contents(self, **kw):
471         """Return the signature contents of this callable action.
472
473         By providing direct access to the code object of the
474         function, Python makes this extremely easy.  Hooray!
475         """
476         #XXX DOES NOT ACCOUNT FOR CHANGES IN ENVIRONMENT VARIABLES
477         #THE FUNCTION MAY USE
478         try:
479             # "self.function" is a function.
480             code = self.function.func_code.co_code
481         except:
482             # "self.function" is a callable object.
483             code = self.function.__call__.im_func.func_code.co_code
484         return str(code)
485
486 class ListAction(ActionBase):
487     """Class for lists of other actions."""
488     def __init__(self, list):
489         self.list = map(lambda x: Action(x), list)
490
491     def execute(self, **kw):
492         for l in self.list:
493             r = apply(l.execute, (), kw)
494             if r != 0:
495                 return r
496         return 0
497
498     def get_contents(self, **kw):
499         """Return the signature contents of this action list.
500
501         Simple concatenation of the signatures of the elements.
502         """
503
504         return reduce(lambda x, y: x + str(y.get_contents()), self.list, "")