Multiple directory .h includes in Repositories.
[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 KeyError:
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             def rstr(x):
289                 try:
290                     return x.rstr()
291                 except AttributeError:
292                     return str(x)
293             s = kw['source']
294             del kw['source']
295             if not SCons.Util.is_List(s):
296                 s = [s]
297             dict['SOURCES'] = SCons.Util.PathList(map(os.path.normpath, map(rstr, s)))
298             if dict['SOURCES']:
299                 dict['SOURCE'] = dict['SOURCES'][0]
300
301         dict.update(kw)
302
303         return dict
304
305 def _string_from_cmd_list(cmd_list):
306     """Takes a list of command line arguments and returns a pretty
307     representation for printing."""
308     cl = []
309     for arg in cmd_list:
310         if ' ' in arg or '\t' in arg:
311             arg = '"' + arg + '"'
312         cl.append(arg)
313     return string.join(cl)
314
315 _rm = re.compile(r'\$[()]')
316 _remove = re.compile(r'\$\(([^\$]|\$[^\(])*?\$\)')
317
318 class EnvDictProxy(UserDict.UserDict):
319     """This is a dictionary-like class that contains the
320     Environment dictionary we pass to FunctionActions
321     and CommandGeneratorActions.
322
323     In addition to providing
324     normal dictionary-like access to the variables in the
325     Environment, it also exposes the functions subst()
326     and subst_list(), allowing users to easily do variable
327     interpolation when writing their FunctionActions
328     and CommandGeneratorActions."""
329
330     def __init__(self, env):
331         UserDict.UserDict.__init__(self, env)
332
333     def subst(self, string, raw=0):
334         if raw:
335             regex_remove = None
336         else:
337             regex_remove = _rm
338         return SCons.Util.scons_subst(string, self.data, {}, regex_remove)
339
340     def subst_list(self, string, raw=0):
341         if raw:
342             regex_remove = None
343         else:
344             regex_remove = _rm
345         return SCons.Util.scons_subst_list(string, self.data, {}, regex_remove)
346
347 class CommandAction(ActionBase):
348     """Class for command-execution actions."""
349     def __init__(self, cmd):
350         import SCons.Util
351         
352         self.cmd_list = map(SCons.Util.to_String, cmd)
353
354     def execute(self, **kw):
355         dict = apply(self.subst_dict, (), kw)
356         import SCons.Util
357         cmd_list = SCons.Util.scons_subst_list(self.cmd_list, dict, {}, _rm)
358         for cmd_line in cmd_list:
359             if len(cmd_line):
360                 if print_actions:
361                     self.show(_string_from_cmd_list(cmd_line))
362                 if execute_actions:
363                     try:
364                         ENV = kw['env']['ENV']
365                     except KeyError:
366                         global default_ENV
367                         if not default_ENV:
368                             import SCons.Environment
369                             default_ENV = SCons.Environment.Environment()['ENV']
370                         ENV = default_ENV
371                     ret = spawn(cmd_line[0], cmd_line, ENV)
372                     if ret:
373                         return ret
374         return 0
375
376     def _sig_dict(self, kw):
377         """Supply a dictionary for use in computing signatures.
378
379         For signature purposes, it doesn't matter what targets or
380         sources we use, so long as we use the same ones every time
381         so the signature stays the same.  We supply an array of two
382         of each to allow for distinction between TARGET and TARGETS.
383         """
384         kw['target'] = ['__t1__', '__t2__']
385         kw['source'] = ['__s1__', '__s2__']
386         return apply(self.subst_dict, (), kw)
387
388     def get_raw_contents(self, **kw):
389         """Return the complete contents of this action's command line.
390         """
391         return SCons.Util.scons_subst(string.join(self.cmd_list),
392                                       self._sig_dict(kw), {})
393
394     def get_contents(self, **kw):
395         """Return the signature contents of this action's command line.
396
397         This strips $(-$) and everything in between the string,
398         since those parts don't affect signatures.
399         """
400         return SCons.Util.scons_subst(string.join(self.cmd_list),
401                                       self._sig_dict(kw), {}, _remove)
402
403 class CommandGeneratorAction(ActionBase):
404     """Class for command-generator actions."""
405     def __init__(self, generator):
406         self.generator = generator
407
408     def __generate(self, kw, for_signature):
409         import SCons.Util
410
411         # Wrap the environment dictionary in an EnvDictProxy
412         # object to make variable interpolation easier for the
413         # client.
414         args = copy.copy(kw)
415         args['for_signature'] = for_signature
416         if args.has_key("env") and not isinstance(args["env"], EnvDictProxy):
417             args["env"] = EnvDictProxy(args["env"])
418
419         # ensure that target is a list, to make it easier to write
420         # generator functions:
421         if args.has_key("target") and not SCons.Util.is_List(args["target"]):
422             args["target"] = [args["target"]]
423
424         ret = apply(self.generator, (), args)
425         gen_cmd = Action(ret)
426         if not gen_cmd:
427             raise SCons.Errors.UserError("Object returned from command generator: %s cannot be used to create an Action." % repr(ret))
428         return gen_cmd
429
430     def execute(self, **kw):
431         return apply(self.__generate(kw, 0).execute, (), kw)
432
433     def get_contents(self, **kw):
434         """Return the signature contents of this action's command line.
435
436         This strips $(-$) and everything in between the string,
437         since those parts don't affect signatures.
438         """
439         return apply(self.__generate(kw, 1).get_contents, (), kw)
440
441 class LazyCmdGenerator:
442     """This is a simple callable class that acts as a command generator.
443     It holds on to a key into an Environment dictionary, then waits
444     until execution time to see what type it is, then tries to
445     create an Action out of it."""
446     def __init__(self, var):
447         self.var = SCons.Util.to_String(var)
448
449     def __call__(self, env, **kw):
450         if env.has_key(self.var):
451             return env[self.var]
452         else:
453             # The variable reference substitutes to nothing.
454             return ''
455
456 class FunctionAction(ActionBase):
457     """Class for Python function actions."""
458     def __init__(self, function):
459         self.function = function
460
461     def execute(self, **kw):
462         # if print_actions:
463         # XXX:  WHAT SHOULD WE PRINT HERE?
464         if execute_actions:
465             if kw.has_key('target') and not \
466                SCons.Util.is_List(kw['target']):
467                 kw['target'] = [ kw['target'] ]
468             if kw.has_key('source') and not \
469                SCons.Util.is_List(kw['source']):
470                 kw['source'] = [ kw['source'] ]
471             if kw.has_key("env") and not isinstance(kw["env"], EnvDictProxy):
472                 kw["env"] = EnvDictProxy(kw["env"])
473             return apply(self.function, (), kw)
474
475     def get_contents(self, **kw):
476         """Return the signature contents of this callable action.
477
478         By providing direct access to the code object of the
479         function, Python makes this extremely easy.  Hooray!
480         """
481         #XXX DOES NOT ACCOUNT FOR CHANGES IN ENVIRONMENT VARIABLES
482         #THE FUNCTION MAY USE
483         try:
484             # "self.function" is a function.
485             code = self.function.func_code.co_code
486         except:
487             # "self.function" is a callable object.
488             code = self.function.__call__.im_func.func_code.co_code
489         return str(code)
490
491 class ListAction(ActionBase):
492     """Class for lists of other actions."""
493     def __init__(self, list):
494         self.list = map(lambda x: Action(x), list)
495
496     def execute(self, **kw):
497         for l in self.list:
498             r = apply(l.execute, (), kw)
499             if r:
500                 return r
501         return 0
502
503     def get_contents(self, **kw):
504         """Return the signature contents of this action list.
505
506         Simple concatenation of the signatures of the elements.
507         """
508
509         return reduce(lambda x, y, kw=kw: x + str(apply(y.get_contents, (), kw)), self.list, "")