Package SCons :: Package Script :: Module SConscript'
[hide private]
[frames] | no frames]

Source Code for Module SCons.Script.SConscript'

  1  """SCons.Script.SConscript 
  2   
  3  This module defines the Python API provided to SConscript and SConstruct 
  4  files. 
  5   
  6  """ 
  7   
  8  # 
  9  # Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008 The SCons Foundation 
 10  # 
 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: 
 18  # 
 19  # The above copyright notice and this permission notice shall be included 
 20  # in all copies or substantial portions of the Software. 
 21  # 
 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. 
 29  # 
 30   
 31  __revision__ = "src/engine/SCons/Script/SConscript.py 3266 2008/08/12 07:31:01 knight" 
 32   
 33  import SCons 
 34  import SCons.Action 
 35  import SCons.Builder 
 36  import SCons.Defaults 
 37  import SCons.Environment 
 38  import SCons.Errors 
 39  import SCons.Node 
 40  import SCons.Node.Alias 
 41  import SCons.Node.FS 
 42  import SCons.Platform 
 43  import SCons.SConf 
 44  import SCons.Script.Main 
 45  import SCons.Tool 
 46  import SCons.Util 
 47   
 48  import os 
 49  import os.path 
 50  import re 
 51  import string 
 52  import sys 
 53  import traceback 
 54  import types 
 55  import UserList 
 56   
 57  # The following variables used to live in this module.  Some 
 58  # SConscript files out there may have referred to them directly as 
 59  # SCons.Script.SConscript.*.  This is now supported by some special 
 60  # handling towards the bottom of the SConscript.__init__.py module. 
 61  #Arguments = {} 
 62  #ArgList = [] 
 63  #BuildTargets = TargetList() 
 64  #CommandLineTargets = [] 
 65  #DefaultTargets = [] 
 66   
67 -class SConscriptReturn(Exception):
68 pass
69 70 launch_dir = os.path.abspath(os.curdir) 71 72 GlobalDict = None 73 74 # global exports set by Export(): 75 global_exports = {} 76 77 # chdir flag 78 sconscript_chdir = 1 79
80 -def get_calling_namespaces():
81 """Return the locals and globals for the function that called 82 into this module in the current call stack.""" 83 try: 1/0 84 except ZeroDivisionError: frame = sys.exc_info()[2].tb_frame 85 86 # Find the first frame that *isn't* from this file. This means 87 # that we expect all of the SCons frames that implement an Export() 88 # or SConscript() call to be in this file, so that we can identify 89 # the first non-Script.SConscript frame as the user's local calling 90 # environment, and the locals and globals dictionaries from that 91 # frame as the calling namespaces. See the comment below preceding 92 # the DefaultEnvironmentCall block for even more explanation. 93 while frame.f_globals.get("__name__") == __name__: 94 frame = frame.f_back 95 96 return frame.f_locals, frame.f_globals
97 98
99 -def compute_exports(exports):
100 """Compute a dictionary of exports given one of the parameters 101 to the Export() function or the exports argument to SConscript().""" 102 103 loc, glob = get_calling_namespaces() 104 105 retval = {} 106 try: 107 for export in exports: 108 if SCons.Util.is_Dict(export): 109 retval.update(export) 110 else: 111 try: 112 retval[export] = loc[export] 113 except KeyError: 114 retval[export] = glob[export] 115 except KeyError, x: 116 raise SCons.Errors.UserError, "Export of non-existent variable '%s'"%x 117 118 return retval
119
120 -class Frame:
121 """A frame on the SConstruct/SConscript call stack"""
122 - def __init__(self, fs, exports, sconscript):
123 self.globals = BuildDefaultGlobals() 124 self.retval = None 125 self.prev_dir = fs.getcwd() 126 self.exports = compute_exports(exports) # exports from the calling SConscript 127 # make sure the sconscript attr is a Node. 128 if isinstance(sconscript, SCons.Node.Node): 129 self.sconscript = sconscript 130 elif sconscript == '-': 131 self.sconscript = None 132 else: 133 self.sconscript = fs.File(str(sconscript))
134 135 # the SConstruct/SConscript call stack: 136 call_stack = [] 137 138 # For documentation on the methods in this file, see the scons man-page 139
140 -def Return(*vars, **kw):
141 retval = [] 142 try: 143 fvars = SCons.Util.flatten(vars) 144 for var in fvars: 145 for v in string.split(var): 146 retval.append(call_stack[-1].globals[v]) 147 except KeyError, x: 148 raise SCons.Errors.UserError, "Return of non-existent variable '%s'"%x 149 150 if len(retval) == 1: 151 call_stack[-1].retval = retval[0] 152 else: 153 call_stack[-1].retval = tuple(retval) 154 155 stop = kw.get('stop', True) 156 157 if stop: 158 raise SConscriptReturn
159 160 161 stack_bottom = '% Stack boTTom %' # hard to define a variable w/this name :) 162
163 -def _SConscript(fs, *files, **kw):
164 top = fs.Top 165 sd = fs.SConstruct_dir.rdir() 166 exports = kw.get('exports', []) 167 168 # evaluate each SConscript file 169 results = [] 170 for fn in files: 171 call_stack.append(Frame(fs, exports, fn)) 172 old_sys_path = sys.path 173 try: 174 SCons.Script.sconscript_reading = SCons.Script.sconscript_reading + 1 175 if fn == "-": 176 exec sys.stdin in call_stack[-1].globals 177 else: 178 if isinstance(fn, SCons.Node.Node): 179 f = fn 180 else: 181 f = fs.File(str(fn)) 182 _file_ = None 183 184 # Change directory to the top of the source 185 # tree to make sure the os's cwd and the cwd of 186 # fs match so we can open the SConscript. 187 fs.chdir(top, change_os_dir=1) 188 if f.rexists(): 189 _file_ = open(f.rfile().get_abspath(), "r") 190 elif f.has_src_builder(): 191 # The SConscript file apparently exists in a source 192 # code management system. Build it, but then clear 193 # the builder so that it doesn't get built *again* 194 # during the actual build phase. 195 f.build() 196 f.built() 197 f.builder_set(None) 198 if f.exists(): 199 _file_ = open(f.get_abspath(), "r") 200 if _file_: 201 # Chdir to the SConscript directory. Use a path 202 # name relative to the SConstruct file so that if 203 # we're using the -f option, we're essentially 204 # creating a parallel SConscript directory structure 205 # in our local directory tree. 206 # 207 # XXX This is broken for multiple-repository cases 208 # where the SConstruct and SConscript files might be 209 # in different Repositories. For now, cross that 210 # bridge when someone comes to it. 211 try: 212 src_dir = kw['src_dir'] 213 except KeyError: 214 ldir = fs.Dir(f.dir.get_path(sd)) 215 else: 216 ldir = fs.Dir(src_dir) 217 if not ldir.is_under(f.dir): 218 # They specified a source directory, but 219 # it's above the SConscript directory. 220 # Do the sensible thing and just use the 221 # SConcript directory. 222 ldir = fs.Dir(f.dir.get_path(sd)) 223 try: 224 fs.chdir(ldir, change_os_dir=sconscript_chdir) 225 except OSError: 226 # There was no local directory, so we should be 227 # able to chdir to the Repository directory. 228 # Note that we do this directly, not through 229 # fs.chdir(), because we still need to 230 # interpret the stuff within the SConscript file 231 # relative to where we are logically. 232 fs.chdir(ldir, change_os_dir=0) 233 # TODO Not sure how to handle src_dir here 234 os.chdir(f.rfile().dir.get_abspath()) 235 236 # Append the SConscript directory to the beginning 237 # of sys.path so Python modules in the SConscript 238 # directory can be easily imported. 239 sys.path = [ f.dir.get_abspath() ] + sys.path 240 241 # This is the magic line that actually reads up 242 # and executes the stuff in the SConscript file. 243 # The locals for this frame contain the special 244 # bottom-of-the-stack marker so that any 245 # exceptions that occur when processing this 246 # SConscript can base the printed frames at this 247 # level and not show SCons internals as well. 248 call_stack[-1].globals.update({stack_bottom:1}) 249 old_file = call_stack[-1].globals.get('__file__') 250 try: 251 del call_stack[-1].globals['__file__'] 252 except KeyError: 253 pass 254 try: 255 try: 256 exec _file_ in call_stack[-1].globals 257 except SConscriptReturn: 258 pass 259 finally: 260 if old_file is not None: 261 call_stack[-1].globals.update({__file__:old_file}) 262 else: 263 SCons.Warnings.warn(SCons.Warnings.MissingSConscriptWarning, 264 "Ignoring missing SConscript '%s'" % f.path) 265 266 finally: 267 SCons.Script.sconscript_reading = SCons.Script.sconscript_reading - 1 268 sys.path = old_sys_path 269 frame = call_stack.pop() 270 try: 271 fs.chdir(frame.prev_dir, change_os_dir=sconscript_chdir) 272 except OSError: 273 # There was no local directory, so chdir to the 274 # Repository directory. Like above, we do this 275 # directly. 276 fs.chdir(frame.prev_dir, change_os_dir=0) 277 rdir = frame.prev_dir.rdir() 278 rdir._create() # Make sure there's a directory there. 279 os.chdir(rdir.get_abspath()) 280 281 results.append(frame.retval) 282 283 # if we only have one script, don't return a tuple 284 if len(results) == 1: 285 return results[0] 286 else: 287 return tuple(results)
288
289 -def SConscript_exception(file=sys.stderr):
290 """Print an exception stack trace just for the SConscript file(s). 291 This will show users who have Python errors where the problem is, 292 without cluttering the output with all of the internal calls leading 293 up to where we exec the SConscript.""" 294 exc_type, exc_value, exc_tb = sys.exc_info() 295 tb = exc_tb 296 while tb and not tb.tb_frame.f_locals.has_key(stack_bottom): 297 tb = tb.tb_next 298 if not tb: 299 # We did not find our exec statement, so this was actually a bug 300 # in SCons itself. Show the whole stack. 301 tb = exc_tb 302 stack = traceback.extract_tb(tb) 303 try: 304 type = exc_type.__name__ 305 except AttributeError: 306 type = str(exc_type) 307 if type[:11] == "exceptions.": 308 type = type[11:] 309 file.write('%s: %s:\n' % (type, exc_value)) 310 for fname, line, func, text in stack: 311 file.write(' File "%s", line %d:\n' % (fname, line)) 312 file.write(' %s\n' % text)
313
314 -def annotate(node):
315 """Annotate a node with the stack frame describing the 316 SConscript file and line number that created it.""" 317 tb = sys.exc_info()[2] 318 while tb and not tb.tb_frame.f_locals.has_key(stack_bottom): 319 tb = tb.tb_next 320 if not tb: 321 # We did not find any exec of an SConscript file: what?! 322 raise SCons.Errors.InternalError, "could not find SConscript stack frame" 323 node.creator = traceback.extract_stack(tb)[0]
324 325 # The following line would cause each Node to be annotated using the 326 # above function. Unfortunately, this is a *huge* performance hit, so 327 # leave this disabled until we find a more efficient mechanism. 328 #SCons.Node.Annotate = annotate 329
330 -class SConsEnvironment(SCons.Environment.Base):
331 """An Environment subclass that contains all of the methods that 332 are particular to the wrapper SCons interface and which aren't 333 (or shouldn't be) part of the build engine itself. 334 335 Note that not all of the methods of this class have corresponding 336 global functions, there are some private methods. 337 """ 338 339 # 340 # Private methods of an SConsEnvironment. 341 #
342 - def _exceeds_version(self, major, minor, v_major, v_minor):
343 """Return 1 if 'major' and 'minor' are greater than the version 344 in 'v_major' and 'v_minor', and 0 otherwise.""" 345 return (major > v_major or (major == v_major and minor > v_minor))
346
347 - def _get_major_minor_revision(self, version_string):
348 """Split a version string into major, minor and (optionally) 349 revision parts. 350 351 This is complicated by the fact that a version string can be 352 something like 3.2b1.""" 353 version = string.split(string.split(version_string, ' ')[0], '.') 354 v_major = int(version[0]) 355 v_minor = int(re.match('\d+', version[1]).group()) 356 if len(version) >= 3: 357 v_revision = int(re.match('\d+', version[2]).group()) 358 else: 359 v_revision = 0 360 return v_major, v_minor, v_revision
361
362 - def _get_SConscript_filenames(self, ls, kw):
363 """ 364 Convert the parameters passed to # SConscript() calls into a list 365 of files and export variables. If the parameters are invalid, 366 throws SCons.Errors.UserError. Returns a tuple (l, e) where l 367 is a list of SConscript filenames and e is a list of exports. 368 """ 369 exports = [] 370 371 if len(ls) == 0: 372 try: 373 dirs = kw["dirs"] 374 except KeyError: 375 raise SCons.Errors.UserError, \ 376 "Invalid SConscript usage - no parameters" 377 378 if not SCons.Util.is_List(dirs): 379 dirs = [ dirs ] 380 dirs = map(str, dirs) 381 382 name = kw.get('name', 'SConscript') 383 384 files = map(lambda n, name = name: os.path.join(n, name), dirs) 385 386 elif len(ls) == 1: 387 388 files = ls[0] 389 390 elif len(ls) == 2: 391 392 files = ls[0] 393 exports = self.Split(ls[1]) 394 395 else: 396 397 raise SCons.Errors.UserError, \ 398 "Invalid SConscript() usage - too many arguments" 399 400 if not SCons.Util.is_List(files): 401 files = [ files ] 402 403 if kw.get('exports'): 404 exports.extend(self.Split(kw['exports'])) 405 406 variant_dir = kw.get('variant_dir') or kw.get('build_dir') 407 if variant_dir: 408 if len(files) != 1: 409 raise SCons.Errors.UserError, \ 410 "Invalid SConscript() usage - can only specify one SConscript with a variant_dir" 411 duplicate = kw.get('duplicate', 1) 412 src_dir = kw.get('src_dir') 413 if not src_dir: 414 src_dir, fname = os.path.split(str(files[0])) 415 files = [os.path.join(str(variant_dir), fname)] 416 else: 417 if not isinstance(src_dir, SCons.Node.Node): 418 src_dir = self.fs.Dir(src_dir) 419 fn = files[0] 420 if not isinstance(fn, SCons.Node.Node): 421 fn = self.fs.File(fn) 422 if fn.is_under(src_dir): 423 # Get path relative to the source directory. 424 fname = fn.get_path(src_dir) 425 files = [os.path.join(str(variant_dir), fname)] 426 else: 427 files = [fn.abspath] 428 kw['src_dir'] = variant_dir 429 self.fs.VariantDir(variant_dir, src_dir, duplicate) 430 431 return (files, exports)
432 433 # 434 # Public methods of an SConsEnvironment. These get 435 # entry points in the global name space so they can be called 436 # as global functions. 437 # 438
439 - def Configure(self, *args, **kw):
440 if not SCons.Script.sconscript_reading: 441 raise SCons.Errors.UserError, "Calling Configure from Builders is not supported." 442 kw['_depth'] = kw.get('_depth', 0) + 1 443 return apply(SCons.Environment.Base.Configure, (self,)+args, kw)
444
445 - def Default(self, *targets):
446 SCons.Script._Set_Default_Targets(self, targets)
447
448 - def EnsureSConsVersion(self, major, minor, revision=0):
449 """Exit abnormally if the SCons version is not late enough.""" 450 scons_ver = self._get_major_minor_revision(SCons.__version__) 451 if scons_ver < (major, minor, revision): 452 if revision: 453 scons_ver_string = '%d.%d.%d' % (major, minor, revision) 454 else: 455 scons_ver_string = '%d.%d' % (major, minor) 456 print "SCons %s or greater required, but you have SCons %s" % \ 457 (scons_ver_string, SCons.__version__) 458 sys.exit(2)
459
460 - def EnsurePythonVersion(self, major, minor):
461 """Exit abnormally if the Python version is not late enough.""" 462 try: 463 v_major, v_minor, v_micro, release, serial = sys.version_info 464 python_ver = (v_major, v_minor) 465 except AttributeError: 466 python_ver = self._get_major_minor_revision(sys.version)[:2] 467 if python_ver < (major, minor): 468 v = string.split(sys.version, " ", 1)[0] 469 print "Python %d.%d or greater required, but you have Python %s" %(major,minor,v) 470 sys.exit(2)
471
472 - def Exit(self, value=0):
473 sys.exit(value)
474
475 - def Export(self, *vars):
476 for var in vars: 477 global_exports.update(compute_exports(self.Split(var)))
478
479 - def GetLaunchDir(self):
480 global launch_dir 481 return launch_dir
482
483 - def GetOption(self, name):
484 name = self.subst(name) 485 return SCons.Script.Main.GetOption(name)
486
487 - def Help(self, text):
488 text = self.subst(text, raw=1) 489 SCons.Script.HelpFunction(text)
490
491 - def Import(self, *vars):
492 try: 493 frame = call_stack[-1] 494 globals = frame.globals 495 exports = frame.exports 496 for var in vars: 497 var = self.Split(var) 498 for v in var: 499 if v == '*': 500 globals.update(global_exports) 501 globals.update(exports) 502 else: 503 if exports.has_key(v): 504 globals[v] = exports[v] 505 else: 506 globals[v] = global_exports[v] 507 except KeyError,x: 508 raise SCons.Errors.UserError, "Import of non-existent variable '%s'"%x
509
510 - def SConscript(self, *ls, **kw):
511 def subst_element(x, subst=self.subst): 512 if SCons.Util.is_List(x): 513 x = map(subst, x) 514 else: 515 x = subst(x) 516 return x
517 ls = map(subst_element, ls) 518 subst_kw = {} 519 for key, val in kw.items(): 520 if SCons.Util.is_String(val): 521 val = self.subst(val) 522 elif SCons.Util.is_List(val): 523 result = [] 524 for v in val: 525 if SCons.Util.is_String(v): 526 v = self.subst(v) 527 result.append(v) 528 val = result 529 subst_kw[key] = val 530 531 files, exports = self._get_SConscript_filenames(ls, subst_kw) 532 subst_kw['exports'] = exports 533 return apply(_SConscript, [self.fs,] + files, subst_kw)
534
535 - def SConscriptChdir(self, flag):
536 global sconscript_chdir 537 sconscript_chdir = flag
538
539 - def SetOption(self, name, value):
540 name = self.subst(name) 541 SCons.Script.Main.SetOption(name, value)
542 543 # 544 # 545 # 546 SCons.Environment.Environment = SConsEnvironment 547
548 -def Configure(*args, **kw):
549 if not SCons.Script.sconscript_reading: 550 raise SCons.Errors.UserError, "Calling Configure from Builders is not supported." 551 kw['_depth'] = 1 552 return apply(SCons.SConf.SConf, args, kw)
553 554 # It's very important that the DefaultEnvironmentCall() class stay in this 555 # file, with the get_calling_namespaces() function, the compute_exports() 556 # function, the Frame class and the SConsEnvironment.Export() method. 557 # These things make up the calling stack leading up to the actual global 558 # Export() or SConscript() call that the user issued. We want to allow 559 # users to export local variables that they define, like so: 560 # 561 # def func(): 562 # x = 1 563 # Export('x') 564 # 565 # To support this, the get_calling_namespaces() function assumes that 566 # the *first* stack frame that's not from this file is the local frame 567 # for the Export() or SConscript() call. 568 569 _DefaultEnvironmentProxy = None 570
571 -def get_DefaultEnvironmentProxy():
572 global _DefaultEnvironmentProxy 573 if not _DefaultEnvironmentProxy: 574 default_env = SCons.Defaults.DefaultEnvironment() 575 _DefaultEnvironmentProxy = SCons.Environment.NoSubstitutionProxy(default_env) 576 return _DefaultEnvironmentProxy
577
578 -class DefaultEnvironmentCall:
579 """A class that implements "global function" calls of 580 Environment methods by fetching the specified method from the 581 DefaultEnvironment's class. Note that this uses an intermediate 582 proxy class instead of calling the DefaultEnvironment method 583 directly so that the proxy can override the subst() method and 584 thereby prevent expansion of construction variables (since from 585 the user's point of view this was called as a global function, 586 with no associated construction environment)."""
587 - def __init__(self, method_name, subst=0):
588 self.method_name = method_name 589 if subst: 590 self.factory = SCons.Defaults.DefaultEnvironment 591 else: 592 self.factory = get_DefaultEnvironmentProxy
593 - def __call__(self, *args, **kw):
594 env = self.factory() 595 method = getattr(env, self.method_name) 596 return apply(method, args, kw)
597 598
599 -def BuildDefaultGlobals():
600 """ 601 Create a dictionary containing all the default globals for 602 SConstruct and SConscript files. 603 """ 604 605 global GlobalDict 606 if GlobalDict is None: 607 GlobalDict = {} 608 609 import SCons.Script 610 d = SCons.Script.__dict__ 611 def not_a_module(m, d=d, mtype=type(SCons.Script)): 612 return type(d[m]) != mtype
613 for m in filter(not_a_module, dir(SCons.Script)): 614 GlobalDict[m] = d[m] 615 616 return GlobalDict.copy() 617