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__"
51 if os.name == 'posix':
53 def defaultSpawn(cmd, args, env):
58 args = [ 'sh', '-c' ] + \
59 [ string.join(map(lambda x: string.replace(str(x),
64 os.execvpe('sh', args, env)
66 exitval = exitvalmap[e[0]]
67 sys.stderr.write("scons: %s: %s\n" % (cmd, e[1]))
71 pid, stat = os.waitpid(pid, 0)
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):
83 if not SCons.Util.is_List(exts):
84 exts = string.split(exts, os.pathsep)
93 if not SCons.Util.is_List(path):
94 path = string.split(path, os.pathsep)
96 if not SCons.Util.is_List(exts):
97 exts = string.split(exts, os.pathsep)
101 pairs.append((dir, e))
102 for dir, ext in pairs:
103 f = os.path.join(dir, cmd)
106 if os.path.exists(f):
110 # Attempt to find cmd.exe (for WinNT/2k/XP) or
111 # command.com for Win9x
114 # First see if we can look in the registry...
115 if SCons.Util.can_read_reg:
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:
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')
132 cmd_interp = pathsearch('cmd', os.environ)
134 cmd_interp = pathsearch('command', os.environ)
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
140 def defaultSpawn(cmd, args, env):
142 sys.stderr.write("scons: Could not find command interpreter, is it in your PATH?\n")
147 a = [ cmd_interp, '/C', args[0] ]
149 if ' ' in arg or '\t' in arg:
150 arg = '"' + arg + '"'
152 ret = os.spawnve(os.P_WAIT, cmd_interp, a, env)
154 ret = exitvalmap[e[0]]
155 sys.stderr.write("scons: %s: %s\n" % (cmd, e[1]))
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")
165 def SetCommandHandler(func):
169 def GetCommandHandler():
173 class CommandGenerator:
175 Wraps a command generator function so the Action() factory
176 function can tell a generator function from a function action.
178 def __init__(self, generator):
179 self.generator = generator
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
188 The former will create a ListAction, the latter
189 will create a CommandAction by converting the inner
190 list elements to strings."""
192 if isinstance(act, ActionBase):
194 elif SCons.Util.is_List(act):
195 return CommandAction(act)
196 elif isinstance(act, CommandGenerator):
197 return CommandGeneratorAction(act.generator)
199 return FunctionAction(act)
200 elif SCons.Util.is_String(act):
201 var=SCons.Util.get_environment_var(act)
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:
215 return ListAction(listCmds)
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))
227 return ListAction(acts)
229 return _do_create_action(act)
232 """Base class for actions that create output objects."""
233 def __cmp__(self, other):
234 return cmp(self.__dict__, other.__dict__)
236 def show(self, string):
239 def subst_dict(self, **kw):
240 """Create a dictionary for substitution of construction
243 This translates the following special arguments:
245 env - the construction environment itself,
246 the values of which (CC, CCFLAGS, etc.)
247 are copied straight into the dictionary
249 target - the target (object or array of objects),
250 used to generate the TARGET and TARGETS
251 construction variables
253 source - the source (object or array of objects),
254 used to generate the SOURCES and SOURCE
255 construction variables
257 Any other keyword arguments are copied into the
261 if kw.has_key('env'):
262 dict.update(kw['env'])
272 if kw.has_key('target'):
275 if not SCons.Util.is_List(t):
279 except (IndexError, AttributeError):
281 dict['TARGETS'] = SCons.Util.PathList(map(os.path.normpath, map(str, t)))
283 dict['TARGET'] = dict['TARGETS'][0]
285 if kw.has_key('source'):
288 if not SCons.Util.is_List(s):
290 dict['SOURCES'] = SCons.Util.PathList(map(os.path.normpath, map(str, s)))
292 dict['SOURCE'] = dict['SOURCES'][0]
298 def _string_from_cmd_list(cmd_list):
299 """Takes a list of command line arguments and returns a pretty
300 representation for printing."""
303 if ' ' in arg or '\t' in arg:
304 arg = '"' + arg + '"'
306 return string.join(cl)
308 _rm = re.compile(r'\$[()]')
309 _remove = re.compile(r'\$\(([^\$]|\$[^\(])*?\$\)')
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.
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."""
323 def __init__(self, env):
324 UserDict.UserDict.__init__(self, env)
326 def subst(self, string, raw=0):
331 return SCons.Util.scons_subst(string, self.data, {}, regex_remove)
333 def subst_list(self, string, raw=0):
338 return SCons.Util.scons_subst_list(string, self.data, {}, regex_remove)
340 class CommandAction(ActionBase):
341 """Class for command-execution actions."""
342 def __init__(self, cmd):
345 self.cmd_list = map(SCons.Util.to_String, cmd)
347 def execute(self, **kw):
348 dict = apply(self.subst_dict, (), kw)
350 cmd_list = SCons.Util.scons_subst_list(self.cmd_list, dict, {}, _rm)
351 for cmd_line in cmd_list:
354 self.show(_string_from_cmd_list(cmd_line))
357 ENV = kw['env']['ENV']
359 import SCons.Defaults
360 ENV = SCons.Defaults.ConstructionEnvironment['ENV']
361 ret = spawn(cmd_line[0], cmd_line, ENV)
366 def _sig_dict(self, kw):
367 """Supply a dictionary for use in computing signatures.
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.
374 kw['target'] = ['__t1__', '__t2__']
375 kw['source'] = ['__s1__', '__s2__']
376 return apply(self.subst_dict, (), kw)
378 def get_raw_contents(self, **kw):
379 """Return the complete contents of this action's command line.
381 return SCons.Util.scons_subst(string.join(self.cmd_list),
382 self._sig_dict(kw), {})
384 def get_contents(self, **kw):
385 """Return the signature contents of this action's command line.
387 This strips $(-$) and everything in between the string,
388 since those parts don't affect signatures.
390 return SCons.Util.scons_subst(string.join(self.cmd_list),
391 self._sig_dict(kw), {}, _remove)
393 class CommandGeneratorAction(ActionBase):
394 """Class for command-generator actions."""
395 def __init__(self, generator):
396 self.generator = generator
398 def __generate(self, kw, for_signature):
401 # Wrap the environment dictionary in an EnvDictProxy
402 # object to make variable interpolation easier for the
405 args['for_signature'] = for_signature
406 if args.has_key("env") and not isinstance(args["env"], EnvDictProxy):
407 args["env"] = EnvDictProxy(args["env"])
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"]]
414 ret = apply(self.generator, (), args)
415 gen_cmd = Action(ret)
417 raise SCons.Errors.UserError("Object returned from command generator: %s cannot be used to create an Action." % repr(ret))
420 def execute(self, **kw):
421 return apply(self.__generate(kw, 0).execute, (), kw)
423 def get_contents(self, **kw):
424 """Return the signature contents of this action's command line.
426 This strips $(-$) and everything in between the string,
427 since those parts don't affect signatures.
429 return apply(self.__generate(kw, 1).get_contents, (), kw)
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)
439 def __call__(self, env, **kw):
440 if env.has_key(self.var):
443 # The variable reference substitutes to nothing.
446 class FunctionAction(ActionBase):
447 """Class for Python function actions."""
448 def __init__(self, function):
449 self.function = function
451 def execute(self, **kw):
453 # XXX: WHAT SHOULD WE PRINT HERE?
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)
465 def get_contents(self, **kw):
466 """Return the signature contents of this callable action.
468 By providing direct access to the code object of the
469 function, Python makes this extremely easy. Hooray!
471 #XXX DOES NOT ACCOUNT FOR CHANGES IN ENVIRONMENT VARIABLES
472 #THE FUNCTION MAY USE
474 # "self.function" is a function.
475 code = self.function.func_code.co_code
477 # "self.function" is a callable object.
478 code = self.function.__call__.im_func.func_code.co_code
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)
486 def execute(self, **kw):
488 r = apply(l.execute, (), kw)
493 def get_contents(self, **kw):
494 """Return the signature contents of this action list.
496 Simple concatenation of the signatures of the elements.
499 return reduce(lambda x, y: x + str(y.get_contents()), self.list, "")