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
58 launch_dir = os.path.abspath(os.curdir)
62 def HelpFunction(text):
67 help_text = help_text + text
71 CommandLineTargets = []
76 class TargetList(UserList.UserList):
77 def _do_nothing(self, *args, **kw):
79 def _add_Default(self, list):
83 BuildTargets = TargetList()
85 # global exports set by Export():
91 # will be set to 1, if we are reading a SConscript
92 sconscript_reading = 0
94 def _scons_add_args(alist):
96 a, b = string.split(arg, '=', 1)
98 ArgList.append((a, b))
100 def _scons_add_targets(tlist):
102 CommandLineTargets.extend(tlist)
103 BuildTargets.extend(tlist)
104 BuildTargets._add_Default = BuildTargets._do_nothing
105 BuildTargets._clear = BuildTargets._do_nothing
107 def get_calling_namespaces():
108 """Return the locals and globals for the function that called
109 into this module in the current callstack."""
111 except ZeroDivisionError: frame = sys.exc_info()[2].tb_frame
113 while frame.f_globals.get("__name__") == __name__: frame = frame.f_back
115 return frame.f_locals, frame.f_globals
118 def compute_exports(exports):
119 """Compute a dictionary of exports given one of the parameters
120 to the Export() function or the exports argument to SConscript()."""
122 loc, glob = get_calling_namespaces()
126 for export in exports:
127 if SCons.Util.is_Dict(export):
128 retval.update(export)
131 retval[export] = loc[export]
133 retval[export] = glob[export]
135 raise SCons.Errors.UserError, "Export of non-existent variable '%s'"%x
141 """A frame on the SConstruct/SConscript call stack"""
142 def __init__(self, exports, sconscript):
143 self.globals = BuildDefaultGlobals()
145 self.prev_dir = SCons.Node.FS.default_fs.getcwd()
146 self.exports = compute_exports(exports) # exports from the calling SConscript
147 # make sure the sconscript attr is a Node.
148 if isinstance(sconscript, SCons.Node.Node):
149 self.sconscript = sconscript
151 self.sconscript = SCons.Node.FS.default_fs.File(str(sconscript))
153 # the SConstruct/SConscript call stack:
156 # For documentation on the methods in this file, see the scons man-page
162 for v in string.split(var):
163 retval.append(stack[-1].globals[v])
165 raise SCons.Errors.UserError, "Return of non-existent variable '%s'"%x
168 stack[-1].retval = retval[0]
170 stack[-1].retval = tuple(retval)
172 def _SConscript(fs, *files, **kw):
174 sd = fs.SConstruct_dir.rdir()
175 exports = kw.get('exports', [])
177 # evaluate each SConscript file
180 stack.append(Frame(exports,fn))
181 old_sys_path = sys.path
183 global sconscript_reading
184 sconscript_reading = 1
186 exec sys.stdin in stack[-1].globals
188 if isinstance(fn, SCons.Node.Node):
194 # Change directory to the top of the source
195 # tree to make sure the os's cwd and the cwd of
196 # fs match so we can open the SConscript.
197 fs.chdir(top, change_os_dir=1)
199 _file_ = open(f.rstr(), "r")
200 elif f.has_src_builder():
201 # The SConscript file apparently exists in a source
202 # code management system. Build it, but then clear
203 # the builder so that it doesn't get built *again*
204 # during the actual build phase.
208 if os.path.exists(s):
209 _file_ = open(s, "r")
211 # Chdir to the SConscript directory. Use a path
212 # name relative to the SConstruct file so that if
213 # we're using the -f option, we're essentially
214 # creating a parallel SConscript directory structure
215 # in our local directory tree.
217 # XXX This is broken for multiple-repository cases
218 # where the SConstruct and SConscript files might be
219 # in different Repositories. For now, cross that
220 # bridge when someone comes to it.
221 ldir = fs.Dir(f.dir.get_path(sd))
223 fs.chdir(ldir, change_os_dir=sconscript_chdir)
225 # There was no local directory, so we should be
226 # able to chdir to the Repository directory.
227 # Note that we do this directly, not through
228 # fs.chdir(), because we still need to
229 # interpret the stuff within the SConscript file
230 # relative to where we are logically.
231 fs.chdir(ldir, change_os_dir=0)
232 os.chdir(f.rfile().dir.get_abspath())
234 # Append the SConscript directory to the beginning
235 # of sys.path so Python modules in the SConscript
236 # directory can be easily imported.
237 sys.path = [ f.dir.get_abspath() ] + sys.path
239 # This is the magic line that actually reads up and
240 # executes the stuff in the SConscript file. We
241 # look for the "exec _file_ " from the beginning
242 # of this line to find the right stack frame (the
243 # next one) describing the SConscript file and line
244 # number that creates a node.
245 exec _file_ in stack[-1].globals
247 SCons.Warnings.warn(SCons.Warnings.MissingSConscriptWarning,
248 "Ignoring missing SConscript '%s'" % f.path)
251 sconscript_reading = 0
252 sys.path = old_sys_path
255 fs.chdir(frame.prev_dir, change_os_dir=sconscript_chdir)
257 # There was no local directory, so chdir to the
258 # Repository directory. Like above, we do this
260 fs.chdir(frame.prev_dir, change_os_dir=0)
261 os.chdir(frame.prev_dir.rdir().get_abspath())
263 results.append(frame.retval)
265 # if we only have one script, don't return a tuple
266 if len(results) == 1:
269 return tuple(results)
271 def is_our_exec_statement(line):
272 return not line is None and line[:12] == "exec _file_ "
274 def SConscript_exception(file=sys.stderr):
275 """Print an exception stack trace just for the SConscript file(s).
276 This will show users who have Python errors where the problem is,
277 without cluttering the output with all of the internal calls leading
278 up to where we exec the SConscript."""
279 exc_type, exc_value, exc_tb = sys.exc_info()
280 stack = traceback.extract_tb(exc_tb)
285 if is_our_exec_statement(last_text):
291 # We did not find our exec statement, so this was actually a bug
292 # in SCons itself. Show the whole stack.
295 if type[:11] == "exceptions.":
297 file.write('%s: %s:\n' % (type, exc_value))
298 for fname, line, func, text in stack[i:]:
299 file.write(' File "%s", line %d:\n' % (fname, line))
300 file.write(' %s\n' % text)
303 """Annotate a node with the stack frame describing the
304 SConscript file and line number that created it."""
305 stack = traceback.extract_stack()
308 # If the script text of the previous frame begins with the
309 # magic "exec _file_ " string, then this frame describes the
310 # SConscript file and line number that caused this node to be
311 # created. Record the tuple and carry on.
312 if is_our_exec_statement(last_text):
317 # The following line would cause each Node to be annotated using the
318 # above function. Unfortunately, this is a *huge* performance hit, so
319 # leave this disabled until we find a more efficient mechanism.
320 #SCons.Node.Annotate = annotate
322 class SConsEnvironment(SCons.Environment.Base):
323 """An Environment subclass that contains all of the methods that
324 are particular to the wrapper SCons interface and which aren't
325 (or shouldn't be) part of the build engine itself.
327 Note that not all of the methods of this class have corresponding
328 global functions, there are some private methods.
332 # Private methods of an SConsEnvironment.
334 def _exceeds_version(self, major, minor, v_major, v_minor):
335 """Return 1 if 'major' and 'minor' are greater than the version
336 in 'v_major' and 'v_minor', and 0 otherwise."""
337 return (major > v_major or (major == v_major and minor > v_minor))
339 def _get_major_minor(self, version_string):
340 """Split a version string into major and minor parts. This
341 is complicated by the fact that a version string can be something
343 version = string.split(string.split(version_string, ' ')[0], '.')
344 v_major = int(version[0])
345 v_minor = int(re.match('\d+', version[1]).group())
346 return v_major, v_minor
348 def _get_SConscript_filenames(self, ls, kw):
350 Convert the parameters passed to # SConscript() calls into a list
351 of files and export variables. If the parameters are invalid,
352 throws SCons.Errors.UserError. Returns a tuple (l, e) where l
353 is a list of SConscript filenames and e is a list of exports.
361 raise SCons.Errors.UserError, \
362 "Invalid SConscript usage - no parameters"
364 if not SCons.Util.is_List(dirs):
366 dirs = map(str, dirs)
368 name = kw.get('name', 'SConscript')
370 files = map(lambda n, name = name: os.path.join(n, name), dirs)
379 exports = self.Split(ls[1])
383 raise SCons.Errors.UserError, \
384 "Invalid SConscript() usage - too many arguments"
386 if not SCons.Util.is_List(files):
389 if kw.get('exports'):
390 exports.extend(self.Split(kw['exports']))
392 build_dir = kw.get('build_dir')
395 raise SCons.Errors.UserError, \
396 "Invalid SConscript() usage - can only specify one SConscript with a build_dir"
397 duplicate = kw.get('duplicate', 1)
398 src_dir = kw.get('src_dir')
400 src_dir, fname = os.path.split(str(files[0]))
402 if not isinstance(src_dir, SCons.Node.Node):
403 src_dir = self.fs.Dir(src_dir)
405 if not isinstance(fn, SCons.Node.Node):
406 fn = self.fs.File(fn)
407 if fn.is_under(src_dir):
408 # Get path relative to the source directory.
409 fname = fn.get_path(src_dir)
411 # Fast way to only get the terminal path component of a Node.
412 fname = fn.get_path(fn.dir)
413 self.fs.BuildDir(build_dir, src_dir, duplicate)
414 files = [os.path.join(str(build_dir), fname)]
416 return (files, exports)
419 # Public methods of an SConsEnvironment. These get
420 # entry points in the global name space so they can be called
421 # as global functions.
424 def Default(self, *targets):
426 global DefaultTargets
430 # Delete the elements from the list in-place, don't
431 # reassign an empty list to DefaultTargets, so that the
432 # DEFAULT_TARGETS variable will still point to the
433 # same object we point to.
434 del DefaultTargets[:]
435 BuildTargets._clear()
436 elif isinstance(t, SCons.Node.Node):
437 DefaultTargets.append(t)
438 BuildTargets._add_Default([t])
440 nodes = self.arg2nodes(t, self.fs.Entry)
441 DefaultTargets.extend(nodes)
442 BuildTargets._add_Default(nodes)
444 def EnsureSConsVersion(self, major, minor):
445 """Exit abnormally if the SCons version is not late enough."""
446 v_major, v_minor = self._get_major_minor(SCons.__version__)
447 if self._exceeds_version(major, minor, v_major, v_minor):
448 print "SCons %d.%d or greater required, but you have SCons %s" %(major,minor,SCons.__version__)
451 def EnsurePythonVersion(self, major, minor):
452 """Exit abnormally if the Python version is not late enough."""
454 v_major, v_minor, v_micro, release, serial = sys.version_info
455 except AttributeError:
456 v_major, v_minor = self._get_major_minor(sys.version)
457 if self._exceeds_version(major, minor, v_major, v_minor):
458 v = string.split(sys.version, " ", 1)[0]
459 print "Python %d.%d or greater required, but you have Python %s" %(major,minor,v)
462 def Exit(self, value=0):
465 def Export(self, *vars):
467 global_exports.update(compute_exports(self.Split(var)))
469 def GetLaunchDir(self):
473 def GetOption(self, name):
474 name = self.subst(name)
475 return SCons.Script.ssoptions.get(name)
477 def Help(self, text):
478 text = self.subst(text, raw=1)
481 def Import(self, *vars):
484 var = self.Split(var)
487 stack[-1].globals.update(global_exports)
488 stack[-1].globals.update(stack[-1].exports)
490 if stack[-1].exports.has_key(v):
491 stack[-1].globals[v] = stack[-1].exports[v]
493 stack[-1].globals[v] = global_exports[v]
495 raise SCons.Errors.UserError, "Import of non-existent variable '%s'"%x
497 def SConscript(self, *ls, **kw):
498 ls = map(lambda l, self=self: self.subst(l), ls)
500 for key, val in kw.items():
501 if SCons.Util.is_String(val):
502 val = self.subst(val)
503 elif SCons.Util.is_List(val):
506 if SCons.Util.is_String(v):
512 files, exports = self._get_SConscript_filenames(ls, subst_kw)
514 return apply(_SConscript, [self.fs,] + files, {'exports' : exports})
516 def SConscriptChdir(self, flag):
517 global sconscript_chdir
518 sconscript_chdir = flag
520 def SetOption(self, name, value):
521 name = self.subst(name)
522 SCons.Script.ssoptions.set(name, value)
527 SCons.Environment.Environment = SConsEnvironment
529 def Options(files=None, args=Arguments):
530 return SCons.Options.Options(files, args)
532 def SetBuildSignatureType(type):
533 SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning,
534 "The SetBuildSignatureType() function has been deprecated;\n" +\
535 "\tuse the TargetSignatures() function instead.")
536 SCons.Defaults.DefaultEnvironment().TargetSignatures(type)
538 def SetContentSignatureType(type):
539 SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning,
540 "The SetContentSignatureType() function has been deprecated;\n" +\
541 "\tuse the SourceSignatures() function instead.")
542 SCons.Defaults.DefaultEnvironment().SourceSignatures(type)
545 SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning,
546 "The GetJobs() function has been deprecated;\n" +\
547 "\tuse GetOption('num_jobs') instead.")
549 return GetOption('num_jobs')
552 SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning,
553 "The SetJobs() function has been deprecated;\n" +\
554 "\tuse SetOption('num_jobs', num) instead.")
555 SetOption('num_jobs', num)
557 def ParseConfig(env, command, function=None):
558 SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning,
559 "The ParseConfig() function has been deprecated;\n" +\
560 "\tuse the env.ParseConfig() method instead.")
561 return env.ParseConfig(command, function)
564 _DefaultEnvironmentProxy = None
566 def get_DefaultEnvironmentProxy():
567 global _DefaultEnvironmentProxy
568 if not _DefaultEnvironmentProxy:
569 default_env = SCons.Defaults.DefaultEnvironment()
570 _DefaultEnvironmentProxy = SCons.Environment.NoSubstitutionProxy(default_env)
571 return _DefaultEnvironmentProxy
573 class DefaultEnvironmentCall:
574 """A class that implements "global function" calls of
575 Environment methods by fetching the specified method from the
576 DefaultEnvironment's class. Note that this uses an intermediate
577 proxy class instead of calling the DefaultEnvironment method
578 directly so that the proxy can override the subst() method and
579 thereby prevent expansion of construction variables (since from
580 the user's point of view this was called as a global function,
581 with no associated construction environment)."""
582 def __init__(self, method_name):
583 self.method_name = method_name
584 def __call__(self, *args, **kw):
585 proxy = get_DefaultEnvironmentProxy()
586 method = getattr(proxy, self.method_name)
587 return apply(method, args, kw)
589 # The list of global functions to add to the SConscript name space
590 # that end up calling corresponding methods or Builders in the
591 # DefaultEnvironment().
592 GlobalDefaultEnvironmentFunctions = [
593 # Methods from the SConsEnvironment class, above.
595 'EnsurePythonVersion',
596 'EnsureSConsVersion',
607 # Methods from the Environment.Base class.
639 GlobalDefaultBuilders = [
640 # Supported builders.
666 for name in GlobalDefaultEnvironmentFunctions + GlobalDefaultBuilders:
667 GlobalDict[name] = DefaultEnvironmentCall(name)
669 def BuildDefaultGlobals():
671 Create a dictionary containing all the default globals for
672 SConstruct and SConscript files.
676 # Global functions that don't get executed through the
677 # default Environment.
678 'Action' : SCons.Action.Action,
679 'BoolOption' : SCons.Options.BoolOption,
680 'Builder' : SCons.Builder.Builder,
681 'Configure' : SCons.SConf.SConf,
682 'EnumOption' : SCons.Options.EnumOption,
683 'Environment' : SCons.Environment.Environment,
684 'ListOption' : SCons.Options.ListOption,
686 'PackageOption' : SCons.Options.PackageOption,
687 'PathOption' : SCons.Options.PathOption,
688 'Platform' : SCons.Platform.Platform,
690 'Scanner' : SCons.Scanner.Base,
691 'Tool' : SCons.Tool.Tool,
692 'WhereIs' : SCons.Util.WhereIs,
695 'Chmod' : SCons.Defaults.Chmod,
696 'Copy' : SCons.Defaults.Copy,
697 'Delete' : SCons.Defaults.Delete,
698 'Mkdir' : SCons.Defaults.Mkdir,
699 'Move' : SCons.Defaults.Move,
700 'Touch' : SCons.Defaults.Touch,
702 # Other variables we provide.
703 'ARGUMENTS' : Arguments,
705 'BUILD_TARGETS' : BuildTargets,
706 'COMMAND_LINE_TARGETS' : CommandLineTargets,
707 'DEFAULT_TARGETS' : DefaultTargets,
710 # Functions we might still convert to Environment methods.
711 globals['CScan'] = SCons.Defaults.CScan
712 globals['DefaultEnvironment'] = SCons.Defaults.DefaultEnvironment
714 # Deprecated functions, leave these here for now.
715 globals['GetJobs'] = GetJobs
716 globals['ParseConfig'] = ParseConfig
717 globals['SetBuildSignatureType'] = SetBuildSignatureType
718 globals['SetContentSignatureType'] = SetContentSignatureType
719 globals['SetJobs'] = SetJobs
721 globals.update(GlobalDict)