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__"
36 import SCons.Environment
40 import SCons.Node.Python
47 import SCons.Node.Alias
56 def do_nothing(text): pass
57 HelpFunction = do_nothing
60 launch_dir = os.path.abspath(os.curdir)
62 # global exports set by Export():
68 def SConscriptChdir(flag):
69 global sconscript_chdir
70 sconscript_chdir = flag
72 def _scons_add_args(alist):
75 a, b = string.split(arg, '=', 2)
78 def get_calling_namespaces():
79 """Return the locals and globals for the function that called
80 into this module in the current callstack."""
82 except: frame = sys.exc_info()[2].tb_frame
84 while frame.f_globals.get("__name__") == __name__: frame = frame.f_back
86 return frame.f_locals, frame.f_globals
89 def compute_exports(exports):
90 """Compute a dictionary of exports given one of the parameters
91 to the Export() function or the exports argument to SConscript()."""
93 exports = SCons.Util.Split(exports)
94 loc, glob = get_calling_namespaces()
98 for export in exports:
99 if SCons.Util.is_Dict(export):
100 retval.update(export)
103 retval[export] = loc[export]
105 retval[export] = glob[export]
107 raise SCons.Errors.UserError, "Export of non-existant variable '%s'"%x
113 """A frame on the SConstruct/SConscript call stack"""
114 def __init__(self, exports, sconscript):
115 self.globals = BuildDefaultGlobals()
117 self.prev_dir = SCons.Node.FS.default_fs.getcwd()
118 self.exports = compute_exports(exports) # exports from the calling SConscript
119 # make sure the sconscript attr is a Node.
120 if isinstance(sconscript, SCons.Node.Node):
121 self.sconscript = sconscript
123 self.sconscript = SCons.Node.FS.default_fs.File(str(sconscript))
125 # the SConstruct/SConscript call stack:
128 # For documentation on the methods in this file, see the scons man-page
134 for v in string.split(var):
135 retval.append(stack[-1].globals[v])
137 raise SCons.Errors.UserError, "Return of non-existant variable '%s'"%x
140 stack[-1].retval = retval[0]
142 stack[-1].retval = tuple(retval)
144 # This function is responsible for converting the parameters passed to
145 # SConscript() calls into a list of files and export variables. If the
146 # parameters are invalid, throws SCons.Errors.UserError. Returns a tuple
147 # (l, e) where l is a list of SConscript filenames and e is a list of
150 def GetSConscriptFilenames(ls, kw):
157 raise SCons.Errors.UserError, \
158 "Invalid SConscript usage - no parameters"
160 if not SCons.Util.is_List(dirs):
162 dirs = map(str, dirs)
164 name = kw.get('name', 'SConscript')
166 files = map(lambda n, name = name: os.path.join(n, name), dirs)
175 exports = SCons.Util.Split(ls[1])
179 raise SCons.Errors.UserError, \
180 "Invalid SConscript() usage - too many arguments"
182 if not SCons.Util.is_List(files):
185 if kw.get('exports'):
186 exports.extend(SCons.Util.Split(kw['exports']))
188 build_dir = kw.get('build_dir')
191 raise SCons.Errors.UserError, \
192 "Invalid SConscript() usage - can only specify one SConscript with a build_dir"
193 duplicate = kw.get('duplicate', 1)
194 src_dir = kw.get('src_dir')
196 src_dir, fname = os.path.split(str(files[0]))
198 if not isinstance(src_dir, SCons.Node.Node):
199 src_dir = SCons.Node.FS.default_fs.Dir(src_dir)
201 if not isinstance(fn, SCons.Node.Node):
202 fn = SCons.Node.FS.default_fs.File(fn)
203 if fn.is_under(src_dir):
204 # Get path relative to the source directory.
205 fname = fn.get_path(src_dir)
207 # Fast way to only get the terminal path component of a Node.
208 fname = fn.get_path(fn.dir)
209 BuildDir(build_dir, src_dir, duplicate)
210 files = [os.path.join(str(build_dir), fname)]
212 return (files, exports)
214 def SConscript(*ls, **kw):
215 files, exports = GetSConscriptFilenames(ls, kw)
217 default_fs = SCons.Node.FS.default_fs
219 sd = default_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):
233 f = default_fs.File(str(fn))
236 # Change directory to the top of the source
237 # tree to make sure the os's cwd and the cwd of
238 # SCons.Node.FS.default_fs match so we can open the
240 default_fs.chdir(top, change_os_dir=1)
242 _file_ = open(f.rstr(), "r")
243 elif f.has_src_builder():
244 # The SConscript file apparently exists in a source
245 # code management system. Build it, but then clear
246 # the builder so that it doesn't get built *again*
247 # during the actual build phase.
251 if os.path.exists(s):
252 _file_ = open(s, "r")
254 # Chdir to the SConscript directory. Use a path
255 # name relative to the SConstruct file so that if
256 # we're using the -f option, we're essentially
257 # creating a parallel SConscript directory structure
258 # in our local directory tree.
260 # XXX This is broken for multiple-repository cases
261 # where the SConstruct and SConscript files might be
262 # in different Repositories. For now, cross that
263 # bridge when someone comes to it.
264 ldir = default_fs.Dir(f.dir.get_path(sd))
266 default_fs.chdir(ldir, change_os_dir=sconscript_chdir)
268 # There was no local directory, so we should be
269 # able to chdir to the Repository directory.
270 # Note that we do this directly, not through
271 # default_fs.chdir(), because we still need to
272 # interpret the stuff within the SConscript file
273 # relative to where we are logically.
274 default_fs.chdir(ldir, change_os_dir=0)
275 os.chdir(f.rfile().dir.get_abspath())
277 # Append the SConscript directory to the beginning
278 # of sys.path so Python modules in the SConscript
279 # directory can be easily imported.
280 sys.path = [ f.dir.get_abspath() ] + sys.path
282 # This is the magic line that actually reads up and
283 # executes the stuff in the SConscript file. We
284 # look for the "exec _file_ " from the beginning
285 # of this line to find the right stack frame (the
286 # next one) describing the SConscript file and line
287 # number that creates a node.
288 exec _file_ in stack[-1].globals
290 sys.stderr.write("Ignoring missing SConscript '%s'\n" %
294 sys.path = old_sys_path
297 default_fs.chdir(frame.prev_dir, change_os_dir=sconscript_chdir)
299 # There was no local directory, so chdir to the
300 # Repository directory. Like above, we do this
302 default_fs.chdir(frame.prev_dir, change_os_dir=0)
303 os.chdir(frame.prev_dir.rdir().get_abspath())
305 results.append(frame.retval)
307 # if we only have one script, don't return a tuple
308 if len(results) == 1:
311 return tuple(results)
313 def is_our_exec_statement(line):
314 return not line is None and line[:12] == "exec _file_ "
316 def SConscript_exception(file=sys.stderr):
317 """Print an exception stack trace just for the SConscript file(s).
318 This will show users who have Python errors where the problem is,
319 without cluttering the output with all of the internal calls leading
320 up to where we exec the SConscript."""
321 stack = traceback.extract_tb(sys.exc_traceback)
325 if is_our_exec_statement(last_text):
329 type = str(sys.exc_type)
330 if type[:11] == "exceptions.":
332 file.write('%s: %s:\n' % (type, sys.exc_value))
333 for fname, line, func, text in stack[i:]:
334 file.write(' File "%s", line %d:\n' % (fname, line))
335 file.write(' %s\n' % text)
338 """Annotate a node with the stack frame describing the
339 SConscript file and line number that created it."""
340 stack = traceback.extract_stack()
343 # If the script text of the previous frame begins with the
344 # magic "exec _file_ " string, then this frame describes the
345 # SConscript file and line number that caused this node to be
346 # created. Record the tuple and carry on.
347 if is_our_exec_statement(last_text):
352 # The following line would cause each Node to be annotated using the
353 # above function. Unfortunately, this is a *huge* performance hit, so
354 # leave this disabled until we find a more efficient mechanism.
355 #SCons.Node.Annotate = annotate
360 def BuildDir(build_dir, src_dir, duplicate=1):
361 SCons.Node.FS.default_fs.BuildDir(build_dir, src_dir, duplicate)
363 def GetBuildPath(files):
364 nodes = SCons.Node.arg2nodes(files, SCons.Node.FS.default_fs.Entry)
365 ret = map(str, nodes)
372 global_exports.update(compute_exports(var))
377 var = SCons.Util.Split(var)
380 stack[-1].globals.update(global_exports)
381 stack[-1].globals.update(stack[-1].exports)
383 if stack[-1].exports.has_key(v):
384 stack[-1].globals[v] = stack[-1].exports[v]
386 stack[-1].globals[v] = global_exports[v]
388 raise SCons.Errors.UserError, "Import of non-existant variable '%s'"%x
393 def SetBuildSignatureType(type):
394 SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning,
395 "The SetBuildSignatureType() function has been deprecated;\n" +\
396 "\tuse the TargetSignatures() function instead.")
397 SCons.Defaults.DefaultEnvironment().TargetSignatures(type)
399 def SetContentSignatureType(type):
400 SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning,
401 "The SetContentSignatureType() function has been deprecated;\n" +\
402 "\tuse the SourceSignatures() function instead.")
403 SCons.Defaults.DefaultEnvironment().SourceSignatures(type)
405 class Options(SCons.Options.Options):
406 def __init__(self, files=None, args=arguments):
407 SCons.Options.Options.__init__(self, files, args)
409 def CheckVersion(major, minor, version_string):
410 """Return 0 if 'major' and 'minor' are greater than the version
411 in 'version_string', and 1 otherwise."""
413 v_major, v_minor, v_micro, release, serial = sys.version_info
414 except AttributeError:
415 version = string.split(string.split(version_string, ' ')[0], '.')
416 v_major = int(version[0])
417 v_minor = int(re.match('\d+', version[1]).group())
418 if major > v_major or (major == v_major and minor > v_minor):
423 def EnsureSConsVersion(major, minor):
424 """Exit abnormally if the SCons version is not late enough."""
425 if not CheckVersion(major,minor,SCons.__version__):
426 print "SCons %d.%d or greater required, but you have SCons %s" %(major,minor,SCons.__version__)
429 def EnsurePythonVersion(major, minor):
430 """Exit abnormally if the Python version is not late enough."""
431 if not CheckVersion(major,minor,sys.version):
432 v = string.split(sys.version, " ", 1)[0]
433 print "Python %d.%d or greater required, but you have Python %s" %(major,minor,v)
437 SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning,
438 "The GetJobs() function has been deprecated;\n" +\
439 "\tuse GetOption('num_jobs') instead.")
441 return GetOption('num_jobs')
444 SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning,
445 "The SetJobs() function has been deprecated;\n" +\
446 "\tuse SetOption('num_jobs', num) instead.")
447 SetOption('num_jobs', num)
454 alias = SCons.Node.Alias.default_ans.lookup(name)
456 alias = SCons.Node.Alias.default_ans.Alias(name)
459 def SetOption(name, value):
460 SCons.Script.ssoptions.set(name, value)
463 return SCons.Script.ssoptions.get(name)
465 def SConsignFile(name=".sconsign.dbm"):
467 if not os.path.isabs(name):
468 sd = str(SCons.Node.FS.default_fs.SConstruct_dir)
469 name = os.path.join(sd, name)
470 SCons.Sig.SConsignFile(name)
472 def BuildDefaultGlobals():
474 Create a dictionary containing all the default globals for
475 SConstruct and SConscript files.
479 globals['Action'] = SCons.Action.Action
480 globals['Alias'] = Alias
481 globals['ARGUMENTS'] = arguments
482 globals['BuildDir'] = BuildDir
483 globals['Builder'] = SCons.Builder.Builder
484 globals['CacheDir'] = SCons.Node.FS.default_fs.CacheDir
485 globals['Configure'] = SCons.SConf.SConf
486 globals['CScan'] = SCons.Defaults.CScan
487 globals['DefaultEnvironment'] = SCons.Defaults.DefaultEnvironment
488 globals['Dir'] = SCons.Node.FS.default_fs.Dir
489 globals['EnsurePythonVersion'] = EnsurePythonVersion
490 globals['EnsureSConsVersion'] = EnsureSConsVersion
491 globals['Environment'] = SCons.Environment.Environment
492 globals['Exit'] = Exit
493 globals['Export'] = Export
494 globals['File'] = SCons.Node.FS.default_fs.File
495 globals['GetBuildPath'] = GetBuildPath
496 globals['GetCommandHandler'] = SCons.Action.GetCommandHandler
497 globals['GetJobs'] = GetJobs
498 globals['GetLaunchDir'] = GetLaunchDir
499 globals['GetOption'] = GetOption
500 globals['Help'] = Help
501 globals['Import'] = Import
502 globals['Literal'] = SCons.Util.Literal
503 globals['Options'] = Options
504 globals['ParseConfig'] = SCons.Util.ParseConfig
505 globals['Platform'] = SCons.Platform.Platform
506 globals['Repository'] = SCons.Node.FS.default_fs.Repository
507 globals['Return'] = Return
508 globals['SConscript'] = SConscript
509 globals['SConscriptChdir'] = SConscriptChdir
510 globals['SConsignFile'] = SConsignFile
511 globals['Scanner'] = SCons.Scanner.Base
512 globals['SetBuildSignatureType'] = SetBuildSignatureType
513 globals['SetCommandHandler'] = SCons.Action.SetCommandHandler
514 globals['SetContentSignatureType'] = SetContentSignatureType
515 globals['SetJobs'] = SetJobs
516 globals['SetOption'] = SetOption
517 globals['Split'] = SCons.Util.Split
518 globals['Tool'] = SCons.Tool.Tool
519 globals['Value'] = SCons.Node.Python.Value
520 globals['WhereIs'] = SCons.Util.WhereIs
522 class DefaultEnvironmentCall:
524 def __init__(self, method_name):
525 self.method_name = method_name
526 def __call__(self, *args, **kw):
527 method = getattr(SCons.Defaults.DefaultEnvironment(),
529 return apply(method, args, kw)
531 EnvironmentMethods = [
551 for name in EnvironmentMethods:
552 globals[name] = DefaultEnvironmentCall(name)