Strip $(-$) bracketed text from command lines.
[scons.git] / src / engine / SCons / Action.py
1 """engine.SCons.Action
2
3 XXX
4
5 """
6
7 #
8 # Copyright (c) 2001 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 os
33 import os.path
34 import re
35 import string
36 import sys
37
38 import SCons.Util
39
40 print_actions = 1;
41 execute_actions = 1;
42
43 exitvalmap = {
44     2 : 127,
45     13 : 126,
46 }
47
48 if os.name == 'posix':
49
50     def defaultSpawn(cmd, args, env):
51         pid = os.fork()
52         if not pid:
53             # Child process.
54             exitval = 127
55             args = [ 'sh', '-c' ] + \
56                    [ string.join(map(lambda x: string.replace(str(x),
57                                                               ' ',
58                                                               r'\ '),
59                                      args)) ]
60             try:
61                 os.execvpe('sh', args, env)
62             except OSError, e:
63                 exitval = exitvalmap[e[0]]
64                 sys.stderr.write("scons: %s: %s\n" % (cmd, e[1]))
65             os._exit(exitval)
66         else:
67             # Parent process.
68             pid, stat = os.waitpid(pid, 0)
69             ret = stat >> 8
70             return ret
71
72 elif os.name == 'nt':
73
74     def pathsearch(cmd, env):
75         # In order to deal with the fact that 1.5.2 doesn't have
76         # os.spawnvpe(), roll our own PATH search.
77         if os.path.isabs(cmd):
78             if not os.path.exists(cmd):
79                 exts = env['PATHEXT']
80                 if not SCons.Util.is_List(exts):
81                     exts = string.split(exts, os.pathsep)
82                 for e in exts:
83                     f = cmd + e
84                     if os.path.exists(f):
85                         return f
86             else:
87                 return cmd
88         else:
89             path = env['PATH']
90             if not SCons.Util.is_List(path):
91                 path = string.split(path, os.pathsep)
92             exts = env['PATHEXT']
93             if not SCons.Util.is_List(exts):
94                 exts = string.split(exts, os.pathsep)
95             pairs = []
96             for dir in path:
97                 for e in exts:
98                     pairs.append((dir, e))
99             for dir, ext in pairs:
100                 f = os.path.join(dir, cmd)
101                 if not ext is None:
102                     f = f + ext
103                 if os.path.exists(f):
104                     return f
105         return None
106
107     # Attempt to find cmd.exe (for WinNT/2k/XP) or
108     # command.com for Win9x
109
110     cmd_interp = ''
111     # First see if we can look in the registry...
112     if SCons.Util.can_read_reg:
113         try:
114             # Look for Windows NT system root
115             k=SCons.Util.RegOpenKeyEx(SCons.Util.hkey_mod.HKEY_LOCAL_MACHINE,
116                                           'Software\\Microsoft\\Windows NT\\CurrentVersion')
117             val, tok = SCons.Util.RegQueryValueEx(k, 'SystemRoot')
118             cmd_interp = os.path.join(val, 'System32\\cmd.exe')
119         except SCons.Util.RegError:
120             try:
121                 # Okay, try the Windows 9x system root
122                 k=SCons.Util.RegOpenKeyEx(SCons.Util.hkey_mod.HKEY_LOCAL_MACHINE,
123                                               'Software\\Microsoft\\Windows\\CurrentVersion')
124                 val, tok = SCons.Util.RegQueryValueEx(k, 'SystemRoot')
125                 cmd_interp = os.path.join(val, 'command.com')
126             except:
127                 pass
128     if not cmd_interp:
129         cmd_interp = pathsearch('cmd', os.environ)
130         if not cmd_interp:
131             cmd_interp = pathsearch('command', os.environ)
132
133     # The upshot of all this is that, if you are using Python 1.5.2,
134     # you had better have cmd or command.com in your PATH when you run
135     # scons.
136
137     def defaultSpawn(cmd, args, env):
138         if not cmd_interp:
139             sys.stderr.write("scons: Could not find command interpreter, is it in your PATH?\n")
140             return 127
141         else:
142             try:
143                 args = [ cmd_interp, '/C' ] + args
144                 ret = os.spawnve(os.P_WAIT, cmd_interp, args, env)
145             except OSError, e:
146                 ret = exitvalmap[e[0]]
147                 sys.stderr.write("scons: %s: %s\n" % (cmd, e[1]))
148             return ret
149 else:
150     def defaultSpawn(cmd, args, env):
151         sys.stderr.write("scons: Unknown os '%s', cannot spawn command interpreter.\n" % os.name)
152         sys.stderr.write("scons: Set your command handler with SetCommandHandler().\n")
153         return 127
154
155 spawn = defaultSpawn
156
157 def SetCommandHandler(func):
158     global spawn
159     spawn = func
160
161 def Action(act):
162     """A factory for action objects."""
163     if isinstance(act, ActionBase):
164         return act
165     elif callable(act):
166         return FunctionAction(act)
167     elif SCons.Util.is_String(act):
168         return CommandAction(act)
169     elif SCons.Util.is_List(act):
170         return ListAction(act)
171     else:
172         return None
173     
174 class ActionBase:
175     """Base class for actions that create output objects."""
176     def __cmp__(self, other):
177         return cmp(self.__dict__, other.__dict__)
178
179     def show(self, string):
180         print string
181
182     def subst_dict(self, **kw):
183         """Create a dictionary for substitution of construction
184         variables.
185
186         This translates the following special arguments:
187
188             env    - the construction environment itself,
189                      the values of which (CC, CCFLAGS, etc.)
190                      are copied straight into the dictionary
191
192             target - the target (object or array of objects),
193                      used to generate the TARGET and TARGETS
194                      construction variables
195
196             source - the source (object or array of objects),
197                      used to generate the SOURCES construction
198                      variable
199
200         Any other keyword arguments are copied into the
201         dictionary."""
202
203         dict = {}
204         if kw.has_key('env'):
205             dict.update(kw['env'])
206             del kw['env']
207
208         try:
209             cwd = kw['dir']
210         except:
211             cwd = None
212         else:
213             del kw['dir']
214
215         if kw.has_key('target'):
216             t = kw['target']
217             del kw['target']
218             if not SCons.Util.is_List(t):
219                 t = [t]
220             try:
221                 cwd = t[0].cwd
222             except AttributeError:
223                 pass
224             dict['TARGETS'] = SCons.Util.PathList(map(os.path.normpath, map(str, t)))
225             if dict['TARGETS']:
226                 dict['TARGET'] = dict['TARGETS'][0]
227
228         if kw.has_key('source'):
229             s = kw['source']
230             del kw['source']
231             if not SCons.Util.is_List(s):
232                 s = [s]
233             dict['SOURCES'] = SCons.Util.PathList(map(os.path.normpath, map(str, s)))
234
235         dict.update(kw)
236
237         # Autogenerate necessary construction variables.
238         SCons.Util.autogenerate(dict, dir = cwd)
239
240         return dict
241
242 _rm = re.compile(r'\$[()]')
243 _remove = re.compile(r'\$\(([^\$]|\$[^\(])*?\$\)')
244
245 class CommandAction(ActionBase):
246     """Class for command-execution actions."""
247     def __init__(self, string):
248         self.command = string
249
250     def execute(self, **kw):
251         import SCons.Util
252         dict = apply(self.subst_dict, (), kw)
253         cmd_list = SCons.Util.scons_subst_list(self.command, dict, {}, _rm)
254         for cmd_line in cmd_list:
255             if len(cmd_line):
256                 if print_actions:
257                     self.show(string.join(cmd_line))
258                 if execute_actions:
259                     try:
260                         ENV = kw['env']['ENV']
261                     except:
262                         import SCons.Defaults
263                         ENV = SCons.Defaults.ConstructionEnvironment['ENV']
264                     ret = spawn(cmd_line[0], cmd_line, ENV)
265                     if ret:
266                         return ret
267         return 0
268
269     def _sig_dict(self, kw):
270         """Supply a dictionary for use in computing signatures.
271
272         For signature purposes, it doesn't matter what targets or
273         sources we use, so long as we use the same ones every time
274         so the signature stays the same.  We supply an array of two
275         of each to allow for distinction between TARGET and TARGETS.
276         """
277         kw['target'] = ['__t1__', '__t2__']
278         kw['source'] = ['__s1__', '__s2__']
279         return apply(self.subst_dict, (), kw)
280
281     def get_raw_contents(self, **kw):
282         """Return the complete contents of this action's command line.
283         """
284         return SCons.Util.scons_subst(self.command, self._sig_dict(kw), {})
285
286     def get_contents(self, **kw):
287         """Return the signature contents of this action's command line.
288
289         This strips $(-$) and everything in between the string,
290         since those parts don't affect signatures.
291         """
292         return SCons.Util.scons_subst(self.command, self._sig_dict(kw), {}, _remove)
293
294 class FunctionAction(ActionBase):
295     """Class for Python function actions."""
296     def __init__(self, function):
297         self.function = function
298
299     def execute(self, **kw):
300         # if print_actions:
301         # XXX:  WHAT SHOULD WE PRINT HERE?
302         if execute_actions:
303             if kw.has_key('target'):
304                 if SCons.Util.is_List(kw['target']):
305                     kw['target'] = map(str, kw['target'])
306                 else:
307                     kw['target'] = str(kw['target'])
308             if kw.has_key('source'):
309                 kw['source'] = map(str, kw['source'])
310             return apply(self.function, (), kw)
311
312     def get_contents(self, **kw):
313         """Return the signature contents of this callable action.
314
315         By providing direct access to the code object of the
316         function, Python makes this extremely easy.  Hooray!
317         """
318         #XXX DOES NOT ACCOUNT FOR CHANGES IN ENVIRONMENT VARIABLES
319         #THE FUNCTION MAY USE
320         try:
321             # "self.function" is a function.
322             code = self.function.func_code.co_code
323         except:
324             # "self.function" is a callable object.
325             code = self.function.__call__.im_func.func_code.co_code
326         return str(code)
327
328 class ListAction(ActionBase):
329     """Class for lists of other actions."""
330     def __init__(self, list):
331         self.list = map(lambda x: Action(x), list)
332
333     def execute(self, **kw):
334         for l in self.list:
335             r = apply(l.execute, (), kw)
336             if r != 0:
337                 return r
338         return 0
339
340     def get_contents(self, **kw):
341         """Return the signature contents of this action list.
342
343         Simple concatenation of the signatures of the elements.
344         """
345
346         return reduce(lambda x, y: x + str(y.get_contents()), self.list, "")