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