1 """SCons.Script.SConscript
3 This module defines the Python API provided to SConscript and SConstruct
11 # Permission is hereby granted, free of charge, to any person obtaining
12 # a copy of this software and associated documentation files (the
13 # "Software"), to deal in the Software without restriction, including
14 # without limitation the rights to use, copy, modify, merge, publish,
15 # distribute, sublicense, and/or sell copies of the Software, and to
16 # permit persons to whom the Software is furnished to do so, subject to
17 # the following conditions:
19 # The above copyright notice and this permission notice shall be included
20 # in all copies or substantial portions of the Software.
22 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
23 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
24 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
37 import SCons.Environment
40 import SCons.Node.Alias
42 import SCons.Node.Python
57 def do_nothing(text): pass
58 HelpFunction = do_nothing
61 launch_dir = os.path.abspath(os.curdir)
63 # global exports set by Export():
69 def SConscriptChdir(flag):
70 global sconscript_chdir
71 sconscript_chdir = flag
73 def _scons_add_args(alist):
76 a, b = string.split(arg, '=', 2)
79 def get_calling_namespaces():
80 """Return the locals and globals for the function that called
81 into this module in the current callstack."""
83 except: frame = sys.exc_info()[2].tb_frame
85 while frame.f_globals.get("__name__") == __name__: frame = frame.f_back
87 return frame.f_locals, frame.f_globals
90 def compute_exports(exports):
91 """Compute a dictionary of exports given one of the parameters
92 to the Export() function or the exports argument to SConscript()."""
94 exports = SCons.Util.Split(exports)
95 loc, glob = get_calling_namespaces()
99 for export in exports:
100 if SCons.Util.is_Dict(export):
101 retval.update(export)
104 retval[export] = loc[export]
106 retval[export] = glob[export]
108 raise SCons.Errors.UserError, "Export of non-existant variable '%s'"%x
114 """A frame on the SConstruct/SConscript call stack"""
115 def __init__(self, exports, sconscript):
116 self.globals = BuildDefaultGlobals()
118 self.prev_dir = SCons.Node.FS.default_fs.getcwd()
119 self.exports = compute_exports(exports) # exports from the calling SConscript
120 # make sure the sconscript attr is a Node.
121 if isinstance(sconscript, SCons.Node.Node):
122 self.sconscript = sconscript
124 self.sconscript = SCons.Node.FS.default_fs.File(str(sconscript))
126 # the SConstruct/SConscript call stack:
129 # For documentation on the methods in this file, see the scons man-page
135 for v in string.split(var):
136 retval.append(stack[-1].globals[v])
138 raise SCons.Errors.UserError, "Return of non-existant variable '%s'"%x
141 stack[-1].retval = retval[0]
143 stack[-1].retval = tuple(retval)
145 # This function is responsible for converting the parameters passed to
146 # SConscript() calls into a list of files and export variables. If the
147 # parameters are invalid, throws SCons.Errors.UserError. Returns a tuple
148 # (l, e) where l is a list of SConscript filenames and e is a list of
151 def GetSConscriptFilenames(ls, kw):
158 raise SCons.Errors.UserError, \
159 "Invalid SConscript usage - no parameters"
161 if not SCons.Util.is_List(dirs):
163 dirs = map(str, dirs)
165 name = kw.get('name', 'SConscript')
167 files = map(lambda n, name = name: os.path.join(n, name), dirs)
176 exports = SCons.Util.Split(ls[1])
180 raise SCons.Errors.UserError, \
181 "Invalid SConscript() usage - too many arguments"
183 if not SCons.Util.is_List(files):
186 if kw.get('exports'):
187 exports.extend(SCons.Util.Split(kw['exports']))
189 build_dir = kw.get('build_dir')
192 raise SCons.Errors.UserError, \
193 "Invalid SConscript() usage - can only specify one SConscript with a build_dir"
194 duplicate = kw.get('duplicate', 1)
195 src_dir = kw.get('src_dir')
197 src_dir, fname = os.path.split(str(files[0]))
199 if not isinstance(src_dir, SCons.Node.Node):
200 src_dir = SCons.Node.FS.default_fs.Dir(src_dir)
202 if not isinstance(fn, SCons.Node.Node):
203 fn = SCons.Node.FS.default_fs.File(fn)
204 if fn.is_under(src_dir):
205 # Get path relative to the source directory.
206 fname = fn.get_path(src_dir)
208 # Fast way to only get the terminal path component of a Node.
209 fname = fn.get_path(fn.dir)
210 SCons.Node.FS.default_fs.BuildDir(build_dir, src_dir, duplicate)
211 files = [os.path.join(str(build_dir), fname)]
213 return (files, exports)
215 def _SConscript(fs, *ls, **kw):
216 files, exports = GetSConscriptFilenames(ls, kw)
219 sd = fs.SConstruct_dir.rdir()
221 # evaluate each SConscript file
224 stack.append(Frame(exports,fn))
225 old_sys_path = sys.path
228 exec sys.stdin in stack[-1].globals
230 if isinstance(fn, SCons.Node.Node):
236 # Change directory to the top of the source
237 # tree to make sure the os's cwd and the cwd of
238 # fs match so we can open the SConscript.
239 fs.chdir(top, change_os_dir=1)
241 _file_ = open(f.rstr(), "r")
242 elif f.has_src_builder():
243 # The SConscript file apparently exists in a source
244 # code management system. Build it, but then clear
245 # the builder so that it doesn't get built *again*
246 # during the actual build phase.
250 if os.path.exists(s):
251 _file_ = open(s, "r")
253 # Chdir to the SConscript directory. Use a path
254 # name relative to the SConstruct file so that if
255 # we're using the -f option, we're essentially
256 # creating a parallel SConscript directory structure
257 # in our local directory tree.
259 # XXX This is broken for multiple-repository cases
260 # where the SConstruct and SConscript files might be
261 # in different Repositories. For now, cross that
262 # bridge when someone comes to it.
263 ldir = fs.Dir(f.dir.get_path(sd))
265 fs.chdir(ldir, change_os_dir=sconscript_chdir)
267 # There was no local directory, so we should be
268 # able to chdir to the Repository directory.
269 # Note that we do this directly, not through
270 # fs.chdir(), because we still need to
271 # interpret the stuff within the SConscript file
272 # relative to where we are logically.
273 fs.chdir(ldir, change_os_dir=0)
274 os.chdir(f.rfile().dir.get_abspath())
276 # Append the SConscript directory to the beginning
277 # of sys.path so Python modules in the SConscript
278 # directory can be easily imported.
279 sys.path = [ f.dir.get_abspath() ] + sys.path
281 # This is the magic line that actually reads up and
282 # executes the stuff in the SConscript file. We
283 # look for the "exec _file_ " from the beginning
284 # of this line to find the right stack frame (the
285 # next one) describing the SConscript file and line
286 # number that creates a node.
287 exec _file_ in stack[-1].globals
289 sys.stderr.write("Ignoring missing SConscript '%s'\n" %
293 sys.path = old_sys_path
296 fs.chdir(frame.prev_dir, change_os_dir=sconscript_chdir)
298 # There was no local directory, so chdir to the
299 # Repository directory. Like above, we do this
301 fs.chdir(frame.prev_dir, change_os_dir=0)
302 os.chdir(frame.prev_dir.rdir().get_abspath())
304 results.append(frame.retval)
306 # if we only have one script, don't return a tuple
307 if len(results) == 1:
310 return tuple(results)
312 def is_our_exec_statement(line):
313 return not line is None and line[:12] == "exec _file_ "
315 def SConscript_exception(file=sys.stderr):
316 """Print an exception stack trace just for the SConscript file(s).
317 This will show users who have Python errors where the problem is,
318 without cluttering the output with all of the internal calls leading
319 up to where we exec the SConscript."""
320 stack = traceback.extract_tb(sys.exc_traceback)
324 if is_our_exec_statement(last_text):
328 type = str(sys.exc_type)
329 if type[:11] == "exceptions.":
331 file.write('%s: %s:\n' % (type, sys.exc_value))
332 for fname, line, func, text in stack[i:]:
333 file.write(' File "%s", line %d:\n' % (fname, line))
334 file.write(' %s\n' % text)
337 """Annotate a node with the stack frame describing the
338 SConscript file and line number that created it."""
339 stack = traceback.extract_stack()
342 # If the script text of the previous frame begins with the
343 # magic "exec _file_ " string, then this frame describes the
344 # SConscript file and line number that caused this node to be
345 # created. Record the tuple and carry on.
346 if is_our_exec_statement(last_text):
351 # The following line would cause each Node to be annotated using the
352 # above function. Unfortunately, this is a *huge* performance hit, so
353 # leave this disabled until we find a more efficient mechanism.
354 #SCons.Node.Annotate = annotate
356 class SConsEnvironment(SCons.Environment.Base):
357 """An Environment subclass that contains all of the methods that
358 are particular to the wrapper SCons interface and which aren't
359 (or shouldn't be) part of the build engine itself.
363 # Private functions of an SConsEnvironment.
366 def _check_version(self, major, minor, version_string):
367 """Return 0 if 'major' and 'minor' are greater than the version
368 in 'version_string', and 1 otherwise."""
370 v_major, v_minor, v_micro, release, serial = sys.version_info
371 except AttributeError:
372 version = string.split(string.split(version_string, ' ')[0], '.')
373 v_major = int(version[0])
374 v_minor = int(re.match('\d+', version[1]).group())
375 if major > v_major or (major == v_major and minor > v_minor):
381 # Public functions of an SConsEnvironment. These get
382 # entry points in the global name space so they can be called
383 # as global functions.
386 def EnsureSConsVersion(self, major, minor):
387 """Exit abnormally if the SCons version is not late enough."""
388 if not self._check_version(major,minor,SCons.__version__):
389 print "SCons %d.%d or greater required, but you have SCons %s" %(major,minor,SCons.__version__)
392 def EnsurePythonVersion(self, major, minor):
393 """Exit abnormally if the Python version is not late enough."""
394 if not self._check_version(major,minor,sys.version):
395 v = string.split(sys.version, " ", 1)[0]
396 print "Python %d.%d or greater required, but you have Python %s" %(major,minor,v)
399 def Exit(self, value=0):
402 def Export(self, *vars):
404 global_exports.update(compute_exports(var))
406 def GetLaunchDir(self):
410 def GetOption(self, name):
411 name = self.subst(name)
412 return SCons.Script.ssoptions.get(name)
414 def Help(self, text):
415 text = self.subst(text, raw=1)
418 def Import(self, *vars):
421 var = SCons.Util.Split(var)
424 stack[-1].globals.update(global_exports)
425 stack[-1].globals.update(stack[-1].exports)
427 if stack[-1].exports.has_key(v):
428 stack[-1].globals[v] = stack[-1].exports[v]
430 stack[-1].globals[v] = global_exports[v]
432 raise SCons.Errors.UserError, "Import of non-existant variable '%s'"%x
434 def SConscript(self, *ls, **kw):
435 ls = map(lambda l, self=self: self.subst(l), ls)
437 for key, val in kw.items():
438 if SCons.Util.is_String(val):
439 val = self.subst(val)
441 return apply(_SConscript, [self.fs,] + ls, subst_kw)
443 def SetOption(self, name, value):
444 name = self.subst(name)
445 SCons.Script.ssoptions.set(name, value)
450 SCons.Environment.Environment = SConsEnvironment
452 def SetBuildSignatureType(type):
453 SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning,
454 "The SetBuildSignatureType() function has been deprecated;\n" +\
455 "\tuse the TargetSignatures() function instead.")
456 SCons.Defaults.DefaultEnvironment().TargetSignatures(type)
458 def SetContentSignatureType(type):
459 SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning,
460 "The SetContentSignatureType() function has been deprecated;\n" +\
461 "\tuse the SourceSignatures() function instead.")
462 SCons.Defaults.DefaultEnvironment().SourceSignatures(type)
464 class Options(SCons.Options.Options):
465 def __init__(self, files=None, args=arguments):
466 SCons.Options.Options.__init__(self, files, args)
469 SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning,
470 "The GetJobs() function has been deprecated;\n" +\
471 "\tuse GetOption('num_jobs') instead.")
473 return GetOption('num_jobs')
476 SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning,
477 "The SetJobs() function has been deprecated;\n" +\
478 "\tuse SetOption('num_jobs', num) instead.")
479 SetOption('num_jobs', num)
483 alias = SCons.Node.Alias.default_ans.lookup(name)
485 alias = SCons.Node.Alias.default_ans.Alias(name)
489 _DefaultEnvironmentProxy = None
491 def get_DefaultEnvironmentProxy():
492 global _DefaultEnvironmentProxy
493 if not _DefaultEnvironmentProxy:
494 class EnvironmentProxy(SCons.Environment.Environment):
495 """A proxy subclass for an environment instance that overrides
496 the subst() and subst_list() methods so they don't actually
497 actually perform construction variable substitution. This is
498 specifically intended to be the shim layer in between global
499 function calls (which don't want want construction variable
500 substitution) and the DefaultEnvironment() (which would
501 substitute variables if left to its own devices)."""
502 def __init__(self, subject):
503 self.__dict__['__subject'] = subject
504 def __getattr__(self, name):
505 return getattr(self.__dict__['__subject'], name)
506 def __setattr__(self, name, value):
507 return setattr(self.__dict__['__subject'], name, value)
508 def subst(self, string, raw=0, target=None, source=None):
510 def subst_list(self, string, raw=0, target=None, source=None):
512 default_env = SCons.Defaults.DefaultEnvironment()
513 _DefaultEnvironmentProxy = EnvironmentProxy(default_env)
514 return _DefaultEnvironmentProxy
516 def BuildDefaultGlobals():
518 Create a dictionary containing all the default globals for
519 SConstruct and SConscript files.
523 globals['Action'] = SCons.Action.Action
524 globals['Alias'] = Alias
525 globals['ARGUMENTS'] = arguments
526 globals['Builder'] = SCons.Builder.Builder
527 globals['Configure'] = SCons.SConf.SConf
528 globals['CScan'] = SCons.Defaults.CScan
529 globals['DefaultEnvironment'] = SCons.Defaults.DefaultEnvironment
530 globals['Environment'] = SCons.Environment.Environment
531 globals['GetCommandHandler'] = SCons.Action.GetCommandHandler
532 globals['Literal'] = SCons.Util.Literal
533 globals['Options'] = Options
534 globals['ParseConfig'] = SCons.Util.ParseConfig
535 globals['Platform'] = SCons.Platform.Platform
536 globals['Return'] = Return
537 globals['SConscriptChdir'] = SConscriptChdir
538 globals['Scanner'] = SCons.Scanner.Base
539 globals['SetCommandHandler'] = SCons.Action.SetCommandHandler
540 globals['Split'] = SCons.Util.Split
541 globals['Tool'] = SCons.Tool.Tool
542 globals['Value'] = SCons.Node.Python.Value
543 globals['WhereIs'] = SCons.Util.WhereIs
545 # Deprecated functions, leave this here for now.
546 globals['GetJobs'] = GetJobs
547 globals['SetBuildSignatureType'] = SetBuildSignatureType
548 globals['SetContentSignatureType'] = SetContentSignatureType
549 globals['SetJobs'] = SetJobs
551 class DefaultEnvironmentCall:
552 """A class that implements "global function" calls of
553 Environment methods by fetching the specified method from the
554 DefaultEnvironment's class. Note that this uses an intermediate
555 proxy class instead of calling the DefaultEnvironment method
556 directly so that the proxy can override the subst() method and
557 thereby prevent expansion of construction variables (since from
558 the user's point of view this was called as a global function,
559 with no associated construction environment)."""
560 def __init__(self, method_name):
561 self.method_name = method_name
562 def __call__(self, *args, **kw):
563 proxy = get_DefaultEnvironmentProxy()
564 method = getattr(proxy.__class__, self.method_name)
565 return apply(method, (proxy,) + args, kw)
567 EnvironmentMethods = [
594 SConsEnvironmentMethods = [
595 'EnsurePythonVersion',
596 'EnsureSConsVersion',
607 for name in EnvironmentMethods + SConsEnvironmentMethods:
608 globals[name] = DefaultEnvironmentCall(name)