8 # Copyright (c) 2001, 2002 Steven Knight
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:
18 # The above copyright notice and this permission notice shall be included
19 # in all copies or substantial portions of the Software.
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.
30 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
56 except AttributeError:
59 if os.name == 'posix':
61 def defaultEscape(arg):
62 "escape shell special characters"
66 arg = string.replace(arg, slash, slash+slash)
68 arg = string.replace(arg, c, slash+c)
70 return '"' + arg + '"'
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):
80 for key in env.keys():
81 s = s + '%s=%s '%(key, defaultEscape(env[key]))
83 s = s + defaultEscape(string.join(args))
87 return os.system(s) >> 8
89 def defaultSpawn(cmd, args, env):
94 args = ['sh', '-c', string.join(args)]
96 os.execvpe('sh', args, env)
98 exitval = exitvalmap[e[0]]
99 sys.stderr.write("scons: %s: %s\n" % (cmd, e[1]))
103 pid, stat = os.waitpid(pid, 0)
107 elif os.name == 'nt':
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)
119 if os.path.exists(f):
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)
133 pairs.append((dir, e))
134 for dir, ext in pairs:
135 f = os.path.join(dir, cmd)
138 if os.path.exists(f):
142 # Attempt to find cmd.exe (for WinNT/2k/XP) or
143 # command.com for Win9x
146 # First see if we can look in the registry...
147 if SCons.Util.can_read_reg:
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:
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')
164 cmd_interp = pathsearch('cmd', os.environ)
166 cmd_interp = pathsearch('command', os.environ)
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
172 def defaultSpawn(cmd, args, env):
174 sys.stderr.write("scons: Could not find command interpreter, is it in your PATH?\n")
178 args = [cmd_interp, '/C', quote(string.join(args)) ]
179 ret = os.spawnve(os.P_WAIT, cmd_interp, args, env)
181 ret = exitvalmap[e[0]]
182 sys.stderr.write("scons: %s: %s\n" % (cmd, e[1]))
185 # Windows does not allow special characters in file names
186 # anyway, so no need for an escape function, we will just quote
188 defaultEscape = lambda x: '"' + x + '"'
190 def defaultSpawn(cmd, args, env):
191 sys.stderr.write("scons: Unknown os '%s', cannot spawn command interpreter.\n" % os.name)
192 sys.stderr.write("scons: Set your command handler with SetCommandHandler().\n")
196 escape_cmd = defaultEscape
198 def SetCommandHandler(func, escape = lambda x: x):
199 """Sets the command handler and escape function for the
200 system. All command actions are passed through
201 the command handler, which should be a function that accepts
202 3 arguments: a string command, a list of arguments (the first
203 of which is the command itself), and a dictionary representing
204 the execution environment. The function should then pass
205 the string to a suitable command interpreter.
207 The escape function should take a string and return the same
208 string with all special characters escaped such that the command
209 interpreter will interpret the string literally."""
210 global spawn, escape_cmd
214 def GetCommandHandler():
218 def GetEscapeHandler():
222 class CommandGenerator:
224 Wraps a command generator function so the Action() factory
225 function can tell a generator function from a function action.
227 def __init__(self, generator):
228 self.generator = generator
230 def _do_create_action(act):
231 """This is the actual "implementation" for the
232 Action factory method, below. This handles the
233 fact that passing lists to Action() itself has
234 different semantics than passing lists as elements
237 The former will create a ListAction, the latter
238 will create a CommandAction by converting the inner
239 list elements to strings."""
241 if isinstance(act, ActionBase):
243 elif SCons.Util.is_List(act):
244 return CommandAction(act)
245 elif isinstance(act, CommandGenerator):
246 return CommandGeneratorAction(act.generator)
248 return FunctionAction(act)
249 elif SCons.Util.is_String(act):
250 var=SCons.Util.get_environment_var(act)
252 # This looks like a string that is purely an Environment
253 # variable reference, like "$FOO" or "${FOO}". We do
254 # something special here...we lazily evaluate the contents
255 # of that Environment variable, so a user could but something
256 # like a function or a CommandGenerator in that variable
257 # instead of a string.
258 return CommandGeneratorAction(LazyCmdGenerator(var))
259 listCmds = map(lambda x: CommandAction(string.split(x)),
260 string.split(act, '\n'))
261 if len(listCmds) == 1:
264 return ListAction(listCmds)
269 """A factory for action objects."""
270 if SCons.Util.is_List(act):
271 acts = filter(lambda x: not x is None,
272 map(_do_create_action, act))
276 return ListAction(acts)
278 return _do_create_action(act)
281 """Base class for actions that create output objects."""
282 def __cmp__(self, other):
283 return cmp(self.__dict__, other.__dict__)
285 def show(self, string):
288 def subst_dict(self, target, source, env):
289 """Create a dictionary for substitution of construction
292 This translates the following special arguments:
294 env - the construction environment itself,
295 the values of which (CC, CCFLAGS, etc.)
296 are copied straight into the dictionary
298 target - the target (object or array of objects),
299 used to generate the TARGET and TARGETS
300 construction variables
302 source - the source (object or array of objects),
303 used to generate the SOURCES and SOURCE
304 construction variables
309 for k,v in env.items(): dict[k] = v
311 if not SCons.Util.is_List(target):
314 dict['TARGETS'] = SCons.Util.PathList(map(os.path.normpath, map(str, target)))
316 dict['TARGET'] = dict['TARGETS'][0]
321 except AttributeError:
323 if not SCons.Util.is_List(source):
325 dict['SOURCES'] = SCons.Util.PathList(map(os.path.normpath, map(rstr, source)))
327 dict['SOURCE'] = dict['SOURCES'][0]
331 def _string_from_cmd_list(cmd_list):
332 """Takes a list of command line arguments and returns a pretty
333 representation for printing."""
335 for arg in map(str, cmd_list):
336 if ' ' in arg or '\t' in arg:
337 arg = '"' + arg + '"'
339 return string.join(cl)
341 _rm = re.compile(r'\$[()]')
342 _remove = re.compile(r'\$\(([^\$]|\$[^\(])*?\$\)')
344 class CommandAction(ActionBase):
345 """Class for command-execution actions."""
346 def __init__(self, cmd):
349 def execute(self, target, source, env):
350 dict = self.subst_dict(target, source, env)
352 cmd_list = SCons.Util.scons_subst_list(self.cmd_list, dict, {}, _rm)
353 for cmd_line in cmd_list:
356 self.show(_string_from_cmd_list(cmd_line))
363 import SCons.Environment
364 default_ENV = SCons.Environment.Environment()['ENV']
366 # Escape the command line for the command
367 # interpreter we are using
368 map(lambda x: x.escape(escape_cmd), cmd_line)
369 cmd_line = map(str, cmd_line)
370 ret = spawn(cmd_line[0], cmd_line, ENV)
375 def _sig_dict(self, target, source, env):
376 """Supply a dictionary for use in computing signatures.
378 For signature purposes, it doesn't matter what targets or
379 sources we use, so long as we use the same ones every time
380 so the signature stays the same. We supply an array of two
381 of each to allow for distinction between TARGET and TARGETS.
383 return self.subst_dict(['__t1__', '__t2__'], ['__s1__', '__s2__'], env)
385 def get_raw_contents(self, target, source, env):
386 """Return the complete contents of this action's command line.
388 return SCons.Util.scons_subst(string.join(self.cmd_list),
389 self._sig_dict(target, source, env), {})
391 def get_contents(self, target, source, env):
392 """Return the signature contents of this action's command line.
394 This strips $(-$) and everything in between the string,
395 since those parts don't affect signatures.
397 return SCons.Util.scons_subst(string.join(map(str, self.cmd_list)),
398 self._sig_dict(target, source, env), {}, _remove)
400 class CommandGeneratorAction(ActionBase):
401 """Class for command-generator actions."""
402 def __init__(self, generator):
403 self.generator = generator
405 def __generate(self, target, source, env, for_signature):
408 # ensure that target is a list, to make it easier to write
409 # generator functions:
410 if not SCons.Util.is_List(target):
413 ret = self.generator(target=target, source=source, env=env, for_signature=for_signature)
414 gen_cmd = Action(ret)
416 raise SCons.Errors.UserError("Object returned from command generator: %s cannot be used to create an Action." % repr(ret))
419 def execute(self, target, source, env):
420 if not SCons.Util.is_List(source):
422 rsources = map(rfile, source)
423 return self.__generate(target, source, env, 0).execute(target, rsources, env)
425 def get_contents(self, target, source, env):
426 """Return the signature contents of this action's command line.
428 This strips $(-$) and everything in between the string,
429 since those parts don't affect signatures.
431 return self.__generate(target, source, env, 1).get_contents(target, source, env)
433 class LazyCmdGenerator:
434 """This is a simple callable class that acts as a command generator.
435 It holds on to a key into an Environment dictionary, then waits
436 until execution time to see what type it is, then tries to
437 create an Action out of it."""
438 def __init__(self, var):
439 self.var = SCons.Util.to_String(var)
441 def __call__(self, target, source, env, for_signature):
442 if env.has_key(self.var):
445 # The variable reference substitutes to nothing.
448 class FunctionAction(ActionBase):
449 """Class for Python function actions."""
450 def __init__(self, function):
451 self.function = function
453 def execute(self, target, source, env):
455 # XXX: WHAT SHOULD WE PRINT HERE?
457 if not SCons.Util.is_List(target):
460 if not SCons.Util.is_List(source):
462 rsources = map(rfile, source)
464 return self.function(target=target, source=rsources, env=env)
466 def get_contents(self, target, source, env):
467 """Return the signature contents of this callable action.
469 By providing direct access to the code object of the
470 function, Python makes this extremely easy. Hooray!
472 #XXX DOES NOT ACCOUNT FOR CHANGES IN ENVIRONMENT VARIABLES
473 #THE FUNCTION MAY USE
475 # "self.function" is a function.
476 code = self.function.func_code.co_code
478 # "self.function" is a callable object.
479 code = self.function.__call__.im_func.func_code.co_code
482 class ListAction(ActionBase):
483 """Class for lists of other actions."""
484 def __init__(self, list):
485 self.list = map(lambda x: Action(x), list)
487 def execute(self, target, source, env):
489 r = l.execute(target, source, env)
494 def get_contents(self, target, source, env):
495 """Return the signature contents of this action list.
497 Simple concatenation of the signatures of the elements.
502 ret = ret + a.get_contents(target, source, env)