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