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
57 def do_nothing(text): pass
58 HelpFunction = do_nothing
62 launch_dir = os.path.abspath(os.curdir)
64 # global exports set by Export():
70 # will be set to 1, if we are reading a SConscript
71 sconscript_reading = 0
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 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 def _SConscript(fs, *files, **kw):
145 global sconscript_reading
146 sconscript_reading = 1
148 sd = fs.SConstruct_dir.rdir()
149 exports = kw.get('exports', [])
151 # evaluate each SConscript file
154 stack.append(Frame(exports,fn))
155 old_sys_path = sys.path
158 exec sys.stdin in stack[-1].globals
160 if isinstance(fn, SCons.Node.Node):
166 # Change directory to the top of the source
167 # tree to make sure the os's cwd and the cwd of
168 # fs match so we can open the SConscript.
169 fs.chdir(top, change_os_dir=1)
171 _file_ = open(f.rstr(), "r")
172 elif f.has_src_builder():
173 # The SConscript file apparently exists in a source
174 # code management system. Build it, but then clear
175 # the builder so that it doesn't get built *again*
176 # during the actual build phase.
180 if os.path.exists(s):
181 _file_ = open(s, "r")
183 # Chdir to the SConscript directory. Use a path
184 # name relative to the SConstruct file so that if
185 # we're using the -f option, we're essentially
186 # creating a parallel SConscript directory structure
187 # in our local directory tree.
189 # XXX This is broken for multiple-repository cases
190 # where the SConstruct and SConscript files might be
191 # in different Repositories. For now, cross that
192 # bridge when someone comes to it.
193 ldir = fs.Dir(f.dir.get_path(sd))
195 fs.chdir(ldir, change_os_dir=sconscript_chdir)
197 # There was no local directory, so we should be
198 # able to chdir to the Repository directory.
199 # Note that we do this directly, not through
200 # fs.chdir(), because we still need to
201 # interpret the stuff within the SConscript file
202 # relative to where we are logically.
203 fs.chdir(ldir, change_os_dir=0)
204 os.chdir(f.rfile().dir.get_abspath())
206 # Append the SConscript directory to the beginning
207 # of sys.path so Python modules in the SConscript
208 # directory can be easily imported.
209 sys.path = [ f.dir.get_abspath() ] + sys.path
211 # This is the magic line that actually reads up and
212 # executes the stuff in the SConscript file. We
213 # look for the "exec _file_ " from the beginning
214 # of this line to find the right stack frame (the
215 # next one) describing the SConscript file and line
216 # number that creates a node.
217 exec _file_ in stack[-1].globals
219 sys.stderr.write("Ignoring missing SConscript '%s'\n" %
223 sconscript_reading = 0
224 sys.path = old_sys_path
227 fs.chdir(frame.prev_dir, change_os_dir=sconscript_chdir)
229 # There was no local directory, so chdir to the
230 # Repository directory. Like above, we do this
232 fs.chdir(frame.prev_dir, change_os_dir=0)
233 os.chdir(frame.prev_dir.rdir().get_abspath())
235 results.append(frame.retval)
237 # if we only have one script, don't return a tuple
238 if len(results) == 1:
241 return tuple(results)
243 def is_our_exec_statement(line):
244 return not line is None and line[:12] == "exec _file_ "
246 def SConscript_exception(file=sys.stderr):
247 """Print an exception stack trace just for the SConscript file(s).
248 This will show users who have Python errors where the problem is,
249 without cluttering the output with all of the internal calls leading
250 up to where we exec the SConscript."""
251 stack = traceback.extract_tb(sys.exc_traceback)
255 if is_our_exec_statement(last_text):
259 type = str(sys.exc_type)
260 if type[:11] == "exceptions.":
262 file.write('%s: %s:\n' % (type, sys.exc_value))
263 for fname, line, func, text in stack[i:]:
264 file.write(' File "%s", line %d:\n' % (fname, line))
265 file.write(' %s\n' % text)
268 """Annotate a node with the stack frame describing the
269 SConscript file and line number that created it."""
270 stack = traceback.extract_stack()
273 # If the script text of the previous frame begins with the
274 # magic "exec _file_ " string, then this frame describes the
275 # SConscript file and line number that caused this node to be
276 # created. Record the tuple and carry on.
277 if is_our_exec_statement(last_text):
282 # The following line would cause each Node to be annotated using the
283 # above function. Unfortunately, this is a *huge* performance hit, so
284 # leave this disabled until we find a more efficient mechanism.
285 #SCons.Node.Annotate = annotate
287 class SConsEnvironment(SCons.Environment.Base):
288 """An Environment subclass that contains all of the methods that
289 are particular to the wrapper SCons interface and which aren't
290 (or shouldn't be) part of the build engine itself.
292 Note that not all of the methods of this class have corresponding
293 global functions, there are some private methods.
297 # Private methods of an SConsEnvironment.
300 def _check_version(self, major, minor, version_string):
301 """Return 0 if 'major' and 'minor' are greater than the version
302 in 'version_string', and 1 otherwise."""
304 v_major, v_minor, v_micro, release, serial = sys.version_info
305 except AttributeError:
306 version = string.split(string.split(version_string, ' ')[0], '.')
307 v_major = int(version[0])
308 v_minor = int(re.match('\d+', version[1]).group())
309 if major > v_major or (major == v_major and minor > v_minor):
314 def _get_SConscript_filenames(self, ls, kw):
316 Convert the parameters passed to # SConscript() calls into a list
317 of files and export variables. If the parameters are invalid,
318 throws SCons.Errors.UserError. Returns a tuple (l, e) where l
319 is a list of SConscript filenames and e is a list of exports.
327 raise SCons.Errors.UserError, \
328 "Invalid SConscript usage - no parameters"
330 if not SCons.Util.is_List(dirs):
332 dirs = map(str, dirs)
334 name = kw.get('name', 'SConscript')
336 files = map(lambda n, name = name: os.path.join(n, name), dirs)
345 exports = self.Split(ls[1])
349 raise SCons.Errors.UserError, \
350 "Invalid SConscript() usage - too many arguments"
352 if not SCons.Util.is_List(files):
355 if kw.get('exports'):
356 exports.extend(self.Split(kw['exports']))
358 build_dir = kw.get('build_dir')
361 raise SCons.Errors.UserError, \
362 "Invalid SConscript() usage - can only specify one SConscript with a build_dir"
363 duplicate = kw.get('duplicate', 1)
364 src_dir = kw.get('src_dir')
366 src_dir, fname = os.path.split(str(files[0]))
368 if not isinstance(src_dir, SCons.Node.Node):
369 src_dir = self.fs.Dir(src_dir)
371 if not isinstance(fn, SCons.Node.Node):
372 fn = self.fs.File(fn)
373 if fn.is_under(src_dir):
374 # Get path relative to the source directory.
375 fname = fn.get_path(src_dir)
377 # Fast way to only get the terminal path component of a Node.
378 fname = fn.get_path(fn.dir)
379 self.fs.BuildDir(build_dir, src_dir, duplicate)
380 files = [os.path.join(str(build_dir), fname)]
382 return (files, exports)
385 # Public methods of an SConsEnvironment. These get
386 # entry points in the global name space so they can be called
387 # as global functions.
390 def EnsureSConsVersion(self, major, minor):
391 """Exit abnormally if the SCons version is not late enough."""
392 if not self._check_version(major,minor,SCons.__version__):
393 print "SCons %d.%d or greater required, but you have SCons %s" %(major,minor,SCons.__version__)
396 def EnsurePythonVersion(self, major, minor):
397 """Exit abnormally if the Python version is not late enough."""
398 if not self._check_version(major,minor,sys.version):
399 v = string.split(sys.version, " ", 1)[0]
400 print "Python %d.%d or greater required, but you have Python %s" %(major,minor,v)
403 def Exit(self, value=0):
406 def Export(self, *vars):
408 global_exports.update(compute_exports(self.Split(var)))
410 def GetLaunchDir(self):
414 def GetOption(self, name):
415 name = self.subst(name)
416 return SCons.Script.ssoptions.get(name)
418 def Help(self, text):
419 text = self.subst(text, raw=1)
422 def Import(self, *vars):
425 var = self.Split(var)
428 stack[-1].globals.update(global_exports)
429 stack[-1].globals.update(stack[-1].exports)
431 if stack[-1].exports.has_key(v):
432 stack[-1].globals[v] = stack[-1].exports[v]
434 stack[-1].globals[v] = global_exports[v]
436 raise SCons.Errors.UserError, "Import of non-existant variable '%s'"%x
438 def SConscript(self, *ls, **kw):
439 ls = map(lambda l, self=self: self.subst(l), ls)
441 for key, val in kw.items():
442 if SCons.Util.is_String(val):
443 val = self.subst(val)
446 files, exports = self._get_SConscript_filenames(ls, subst_kw)
448 return apply(_SConscript, [self.fs,] + files, {'exports' : exports})
450 def SConscriptChdir(self, flag):
451 global sconscript_chdir
452 sconscript_chdir = flag
454 def SetOption(self, name, value):
455 name = self.subst(name)
456 SCons.Script.ssoptions.set(name, value)
461 SCons.Environment.Environment = SConsEnvironment
463 def Options(files=None, args=arguments):
464 return SCons.Options.Options(files, args)
466 def SetBuildSignatureType(type):
467 SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning,
468 "The SetBuildSignatureType() function has been deprecated;\n" +\
469 "\tuse the TargetSignatures() function instead.")
470 SCons.Defaults.DefaultEnvironment().TargetSignatures(type)
472 def SetContentSignatureType(type):
473 SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning,
474 "The SetContentSignatureType() function has been deprecated;\n" +\
475 "\tuse the SourceSignatures() function instead.")
476 SCons.Defaults.DefaultEnvironment().SourceSignatures(type)
479 SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning,
480 "The GetJobs() function has been deprecated;\n" +\
481 "\tuse GetOption('num_jobs') instead.")
483 return GetOption('num_jobs')
486 SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning,
487 "The SetJobs() function has been deprecated;\n" +\
488 "\tuse SetOption('num_jobs', num) instead.")
489 SetOption('num_jobs', num)
491 def ParseConfig(env, command, function=None):
492 SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning,
493 "The ParseConfig() function has been deprecated;\n" +\
494 "\tuse the env.ParseConfig() method instead.")
495 return env.ParseConfig(command, function)
498 _DefaultEnvironmentProxy = None
500 def get_DefaultEnvironmentProxy():
501 global _DefaultEnvironmentProxy
502 if not _DefaultEnvironmentProxy:
503 class EnvironmentProxy(SCons.Environment.Environment):
504 """A proxy subclass for an environment instance that overrides
505 the subst() and subst_list() methods so they don't actually
506 actually perform construction variable substitution. This is
507 specifically intended to be the shim layer in between global
508 function calls (which don't want want construction variable
509 substitution) and the DefaultEnvironment() (which would
510 substitute variables if left to its own devices)."""
511 def __init__(self, subject):
512 self.__dict__['__subject'] = subject
513 def __getattr__(self, name):
514 return getattr(self.__dict__['__subject'], name)
515 def __setattr__(self, name, value):
516 return setattr(self.__dict__['__subject'], name, value)
517 def subst(self, string, raw=0, target=None, source=None):
519 def subst_kw(self, kw, raw=0, target=None, source=None):
521 def subst_list(self, string, raw=0, target=None, source=None):
523 default_env = SCons.Defaults.DefaultEnvironment()
524 _DefaultEnvironmentProxy = EnvironmentProxy(default_env)
525 return _DefaultEnvironmentProxy
527 class DefaultEnvironmentCall:
528 """A class that implements "global function" calls of
529 Environment methods by fetching the specified method from the
530 DefaultEnvironment's class. Note that this uses an intermediate
531 proxy class instead of calling the DefaultEnvironment method
532 directly so that the proxy can override the subst() method and
533 thereby prevent expansion of construction variables (since from
534 the user's point of view this was called as a global function,
535 with no associated construction environment)."""
536 def __init__(self, method_name):
537 self.method_name = method_name
538 def __call__(self, *args, **kw):
539 proxy = get_DefaultEnvironmentProxy()
540 method = getattr(proxy, self.method_name)
541 return apply(method, args, kw)
543 # The list of global functions to add to the SConscript name space
544 # that end up calling corresponding methods or Builders in the
545 # DefaultEnvironment().
546 GlobalDefaultEnvironmentFunctions = [
547 # Methods from the SConsEnvironment class, above.
548 'EnsurePythonVersion',
549 'EnsureSConsVersion',
560 # Methods from the Environment.Base class.
595 GlobalDefaultBuilders = [
596 # Supported builders.
622 for name in GlobalDefaultEnvironmentFunctions + GlobalDefaultBuilders:
623 GlobalDict[name] = DefaultEnvironmentCall(name)
625 def BuildDefaultGlobals():
627 Create a dictionary containing all the default globals for
628 SConstruct and SConscript files.
632 globals['ARGUMENTS'] = arguments
633 globals['Configure'] = SCons.SConf.SConf
634 globals['Options'] = Options
635 globals['Platform'] = SCons.Platform.Platform
636 globals['Return'] = Return
637 globals['Tool'] = SCons.Tool.Tool
638 globals['WhereIs'] = SCons.Util.WhereIs
640 # Functions we're in the process of converting to Environment methods.
641 globals['CScan'] = SCons.Defaults.CScan
642 globals['DefaultEnvironment'] = SCons.Defaults.DefaultEnvironment
644 # Deprecated functions, leave these here for now.
645 globals['GetJobs'] = GetJobs
646 globals['ParseConfig'] = ParseConfig
647 globals['SetBuildSignatureType'] = SetBuildSignatureType
648 globals['SetContentSignatureType'] = SetContentSignatureType
649 globals['SetJobs'] = SetJobs
651 globals.update(GlobalDict)