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