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