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

Source Code for Module SCons.Script.Main

   1  """SCons.Script 
   2   
   3  This file implements the main() function used by the scons script. 
   4   
   5  Architecturally, this *is* the scons script, and will likely only be 
   6  called from the external "scons" wrapper.  Consequently, anything here 
   7  should not be, or be considered, part of the build engine.  If it's 
   8  something that we expect other software to want to use, it should go in 
   9  some other module.  If it's specific to the "scons" script invocation, 
  10  it goes here. 
  11  """ 
  12   
  13  unsupported_python_version = (2, 3, 0) 
  14  deprecated_python_version = (2, 4, 0) 
  15   
  16  # Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 The SCons Foundation 
  17  # 
  18  # Permission is hereby granted, free of charge, to any person obtaining 
  19  # a copy of this software and associated documentation files (the 
  20  # "Software"), to deal in the Software without restriction, including 
  21  # without limitation the rights to use, copy, modify, merge, publish, 
  22  # distribute, sublicense, and/or sell copies of the Software, and to 
  23  # permit persons to whom the Software is furnished to do so, subject to 
  24  # the following conditions: 
  25  # 
  26  # The above copyright notice and this permission notice shall be included 
  27  # in all copies or substantial portions of the Software. 
  28  # 
  29  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY 
  30  # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 
  31  # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
  32  # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 
  33  # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 
  34  # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 
  35  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
  36   
  37  __revision__ = "src/engine/SCons/Script/Main.py 5023 2010/06/14 22:05:46 scons" 
  38   
  39  import SCons.compat 
  40   
  41  import os 
  42  import sys 
  43  import time 
  44  import traceback 
  45   
  46  # Strip the script directory from sys.path() so on case-insensitive 
  47  # (Windows) systems Python doesn't think that the "scons" script is the 
  48  # "SCons" package.  Replace it with our own version directory so, if 
  49  # if they're there, we pick up the right version of the build engine 
  50  # modules. 
  51  #sys.path = [os.path.join(sys.prefix, 
  52  #                         'lib', 
  53  #                         'scons-%d' % SCons.__version__)] + sys.path[1:] 
  54   
  55  import SCons.CacheDir 
  56  import SCons.Debug 
  57  import SCons.Defaults 
  58  import SCons.Environment 
  59  import SCons.Errors 
  60  import SCons.Job 
  61  import SCons.Node 
  62  import SCons.Node.FS 
  63  import SCons.SConf 
  64  import SCons.Script 
  65  import SCons.Taskmaster 
  66  import SCons.Util 
  67  import SCons.Warnings 
  68   
  69  import SCons.Script.Interactive 
  70   
71 -def fetch_win32_parallel_msg():
72 # A subsidiary function that exists solely to isolate this import 73 # so we don't have to pull it in on all platforms, and so that an 74 # in-line "import" statement in the _main() function below doesn't 75 # cause warnings about local names shadowing use of the 'SCons' 76 # globl in nest scopes and UnboundLocalErrors and the like in some 77 # versions (2.1) of Python. 78 import SCons.Platform.win32 79 return SCons.Platform.win32.parallel_msg
80 81 # 82
83 -class SConsPrintHelpException(Exception):
84 pass
85 86 display = SCons.Util.display 87 progress_display = SCons.Util.DisplayEngine() 88 89 first_command_start = None 90 last_command_end = None 91
92 -class Progressor(object):
93 prev = '' 94 count = 0 95 target_string = '$TARGET' 96
97 - def __init__(self, obj, interval=1, file=None, overwrite=False):
98 if file is None: 99 file = sys.stdout 100 101 self.obj = obj 102 self.file = file 103 self.interval = interval 104 self.overwrite = overwrite 105 106 if callable(obj): 107 self.func = obj 108 elif SCons.Util.is_List(obj): 109 self.func = self.spinner 110 elif obj.find(self.target_string) != -1: 111 self.func = self.replace_string 112 else: 113 self.func = self.string
114
115 - def write(self, s):
116 self.file.write(s) 117 self.file.flush() 118 self.prev = s
119
120 - def erase_previous(self):
121 if self.prev: 122 length = len(self.prev) 123 if self.prev[-1] in ('\n', '\r'): 124 length = length - 1 125 self.write(' ' * length + '\r') 126 self.prev = ''
127
128 - def spinner(self, node):
129 self.write(self.obj[self.count % len(self.obj)])
130
131 - def string(self, node):
132 self.write(self.obj)
133
134 - def replace_string(self, node):
135 self.write(self.obj.replace(self.target_string, str(node)))
136
137 - def __call__(self, node):
138 self.count = self.count + 1 139 if (self.count % self.interval) == 0: 140 if self.overwrite: 141 self.erase_previous() 142 self.func(node)
143 144 ProgressObject = SCons.Util.Null() 145
146 -def Progress(*args, **kw):
147 global ProgressObject 148 ProgressObject = Progressor(*args, **kw)
149 150 # Task control. 151 # 152 153 _BuildFailures = [] 154
155 -def GetBuildFailures():
156 return _BuildFailures
157
158 -class BuildTask(SCons.Taskmaster.OutOfDateTask):
159 """An SCons build task.""" 160 progress = ProgressObject 161
162 - def display(self, message):
163 display('scons: ' + message)
164
165 - def prepare(self):
166 self.progress(self.targets[0]) 167 return SCons.Taskmaster.OutOfDateTask.prepare(self)
168
169 - def needs_execute(self):
170 if SCons.Taskmaster.OutOfDateTask.needs_execute(self): 171 return True 172 if self.top and self.targets[0].has_builder(): 173 display("scons: `%s' is up to date." % str(self.node)) 174 return False
175
176 - def execute(self):
177 if print_time: 178 start_time = time.time() 179 global first_command_start 180 if first_command_start is None: 181 first_command_start = start_time 182 SCons.Taskmaster.OutOfDateTask.execute(self) 183 if print_time: 184 global cumulative_command_time 185 global last_command_end 186 finish_time = time.time() 187 last_command_end = finish_time 188 cumulative_command_time = cumulative_command_time+finish_time-start_time 189 sys.stdout.write("Command execution time: %f seconds\n"%(finish_time-start_time))
190
191 - def do_failed(self, status=2):
192 _BuildFailures.append(self.exception[1]) 193 global exit_status 194 global this_build_status 195 if self.options.ignore_errors: 196 SCons.Taskmaster.OutOfDateTask.executed(self) 197 elif self.options.keep_going: 198 SCons.Taskmaster.OutOfDateTask.fail_continue(self) 199 exit_status = status 200 this_build_status = status 201 else: 202 SCons.Taskmaster.OutOfDateTask.fail_stop(self) 203 exit_status = status 204 this_build_status = status
205
206 - def executed(self):
207 t = self.targets[0] 208 if self.top and not t.has_builder() and not t.side_effect: 209 if not t.exists(): 210 if t.__class__.__name__ in ('File', 'Dir', 'Entry'): 211 errstr="Do not know how to make %s target `%s' (%s)." % (t.__class__.__name__, t, t.abspath) 212 else: # Alias or Python or ... 213 errstr="Do not know how to make %s target `%s'." % (t.__class__.__name__, t) 214 sys.stderr.write("scons: *** " + errstr) 215 if not self.options.keep_going: 216 sys.stderr.write(" Stop.") 217 sys.stderr.write("\n") 218 try: 219 raise SCons.Errors.BuildError(t, errstr) 220 except KeyboardInterrupt: 221 raise 222 except: 223 self.exception_set() 224 self.do_failed() 225 else: 226 print "scons: Nothing to be done for `%s'." % t 227 SCons.Taskmaster.OutOfDateTask.executed(self) 228 else: 229 SCons.Taskmaster.OutOfDateTask.executed(self)
230
231 - def failed(self):
232 # Handle the failure of a build task. The primary purpose here 233 # is to display the various types of Errors and Exceptions 234 # appropriately. 235 exc_info = self.exc_info() 236 try: 237 t, e, tb = exc_info 238 except ValueError: 239 t, e = exc_info 240 tb = None 241 242 if t is None: 243 # The Taskmaster didn't record an exception for this Task; 244 # see if the sys module has one. 245 try: 246 t, e, tb = sys.exc_info()[:] 247 except ValueError: 248 t, e = exc_info 249 tb = None 250 251 # Deprecated string exceptions will have their string stored 252 # in the first entry of the tuple. 253 if e is None: 254 e = t 255 256 buildError = SCons.Errors.convert_to_BuildError(e) 257 if not buildError.node: 258 buildError.node = self.node 259 260 node = buildError.node 261 if not SCons.Util.is_List(node): 262 node = [ node ] 263 nodename = ', '.join(map(str, node)) 264 265 errfmt = "scons: *** [%s] %s\n" 266 sys.stderr.write(errfmt % (nodename, buildError)) 267 268 if (buildError.exc_info[2] and buildError.exc_info[1] and 269 not isinstance( 270 buildError.exc_info[1], 271 (EnvironmentError, SCons.Errors.StopError, 272 SCons.Errors.UserError))): 273 type, value, trace = buildError.exc_info 274 traceback.print_exception(type, value, trace) 275 elif tb and print_stacktrace: 276 sys.stderr.write("scons: internal stack trace:\n") 277 traceback.print_tb(tb, file=sys.stderr) 278 279 self.exception = (e, buildError, tb) # type, value, traceback 280 self.do_failed(buildError.exitstatus) 281 282 self.exc_clear()
283
284 - def postprocess(self):
285 if self.top: 286 t = self.targets[0] 287 for tp in self.options.tree_printers: 288 tp.display(t) 289 if self.options.debug_includes: 290 tree = t.render_include_tree() 291 if tree: 292 print 293 print tree 294 SCons.Taskmaster.OutOfDateTask.postprocess(self)
295
296 - def make_ready(self):
297 """Make a task ready for execution""" 298 SCons.Taskmaster.OutOfDateTask.make_ready(self) 299 if self.out_of_date and self.options.debug_explain: 300 explanation = self.out_of_date[0].explain() 301 if explanation: 302 sys.stdout.write("scons: " + explanation)
303
304 -class CleanTask(SCons.Taskmaster.AlwaysTask):
305 """An SCons clean task."""
306 - def fs_delete(self, path, pathstr, remove=1):
307 try: 308 if os.path.lexists(path): 309 if os.path.isfile(path) or os.path.islink(path): 310 if remove: os.unlink(path) 311 display("Removed " + pathstr) 312 elif os.path.isdir(path) and not os.path.islink(path): 313 # delete everything in the dir 314 for e in sorted(os.listdir(path)): 315 p = os.path.join(path, e) 316 s = os.path.join(pathstr, e) 317 if os.path.isfile(p): 318 if remove: os.unlink(p) 319 display("Removed " + s) 320 else: 321 self.fs_delete(p, s, remove) 322 # then delete dir itself 323 if remove: os.rmdir(path) 324 display("Removed directory " + pathstr) 325 else: 326 errstr = "Path '%s' exists but isn't a file or directory." 327 raise SCons.Errors.UserError(errstr % (pathstr)) 328 except SCons.Errors.UserError, e: 329 print e 330 except (IOError, OSError), e: 331 print "scons: Could not remove '%s':" % pathstr, e.strerror
332
333 - def show(self):
334 target = self.targets[0] 335 if (target.has_builder() or target.side_effect) and not target.noclean: 336 for t in self.targets: 337 if not t.isdir(): 338 display("Removed " + str(t)) 339 if target in SCons.Environment.CleanTargets: 340 files = SCons.Environment.CleanTargets[target] 341 for f in files: 342 self.fs_delete(f.abspath, str(f), 0)
343
344 - def remove(self):
345 target = self.targets[0] 346 if (target.has_builder() or target.side_effect) and not target.noclean: 347 for t in self.targets: 348 try: 349 removed = t.remove() 350 except OSError, e: 351 # An OSError may indicate something like a permissions 352 # issue, an IOError would indicate something like 353 # the file not existing. In either case, print a 354 # message and keep going to try to remove as many 355 # targets aa possible. 356 print "scons: Could not remove '%s':" % str(t), e.strerror 357 else: 358 if removed: 359 display("Removed " + str(t)) 360 if target in SCons.Environment.CleanTargets: 361 files = SCons.Environment.CleanTargets[target] 362 for f in files: 363 self.fs_delete(f.abspath, str(f))
364 365 execute = remove 366 367 # We want the Taskmaster to update the Node states (and therefore 368 # handle reference counts, etc.), but we don't want to call 369 # back to the Node's post-build methods, which would do things 370 # we don't want, like store .sconsign information. 371 executed = SCons.Taskmaster.Task.executed_without_callbacks 372 373 # Have the taskmaster arrange to "execute" all of the targets, because 374 # we'll figure out ourselves (in remove() or show() above) whether 375 # anything really needs to be done. 376 make_ready = SCons.Taskmaster.Task.make_ready_all 377
378 - def prepare(self):
379 pass
380
381 -class QuestionTask(SCons.Taskmaster.AlwaysTask):
382 """An SCons task for the -q (question) option."""
383 - def prepare(self):
384 pass
385
386 - def execute(self):
387 if self.targets[0].get_state() != SCons.Node.up_to_date or \ 388 (self.top and not self.targets[0].exists()): 389 global exit_status 390 global this_build_status 391 exit_status = 1 392 this_build_status = 1 393 self.tm.stop()
394
395 - def executed(self):
396 pass
397 398
399 -class TreePrinter(object):
400 - def __init__(self, derived=False, prune=False, status=False):
401 self.derived = derived 402 self.prune = prune 403 self.status = status
404 - def get_all_children(self, node):
405 return node.all_children()
406 - def get_derived_children(self, node):
407 children = node.all_children(None) 408 return [x for x in children if x.has_builder()]
409 - def display(self, t):
410 if self.derived: 411 func = self.get_derived_children 412 else: 413 func = self.get_all_children 414 s = self.status and 2 or 0 415 SCons.Util.print_tree(t, func, prune=self.prune, showtags=s)
416 417
418 -def python_version_string():
419 return sys.version.split()[0]
420
421 -def python_version_unsupported(version=sys.version_info):
422 return version < unsupported_python_version
423
424 -def python_version_deprecated(version=sys.version_info):
425 return version < deprecated_python_version
426 427 428 # Global variables 429 430 print_objects = 0 431 print_memoizer = 0 432 print_stacktrace = 0 433 print_time = 0 434 sconscript_time = 0 435 cumulative_command_time = 0 436 exit_status = 0 # final exit status, assume success by default 437 this_build_status = 0 # "exit status" of an individual build 438 num_jobs = None 439 delayed_warnings = [] 440
441 -class FakeOptionParser(object):
442 """ 443 A do-nothing option parser, used for the initial OptionsParser variable. 444 445 During normal SCons operation, the OptionsParser is created right 446 away by the main() function. Certain tests scripts however, can 447 introspect on different Tool modules, the initialization of which 448 can try to add a new, local option to an otherwise uninitialized 449 OptionsParser object. This allows that introspection to happen 450 without blowing up. 451 452 """
453 - class FakeOptionValues(object):
454 - def __getattr__(self, attr):
455 return None
456 values = FakeOptionValues()
457 - def add_local_option(self, *args, **kw):
458 pass
459 460 OptionsParser = FakeOptionParser() 461
462 -def AddOption(*args, **kw):
463 if 'default' not in kw: 464 kw['default'] = None 465 result = OptionsParser.add_local_option(*args, **kw) 466 return result
467
468 -def GetOption(name):
469 return getattr(OptionsParser.values, name)
470
471 -def SetOption(name, value):
472 return OptionsParser.values.set_option(name, value)
473 474 #
475 -class Stats(object):
476 - def __init__(self):
477 self.stats = [] 478 self.labels = [] 479 self.append = self.do_nothing 480 self.print_stats = self.do_nothing
481 - def enable(self, outfp):
482 self.outfp = outfp 483 self.append = self.do_append 484 self.print_stats = self.do_print
485 - def do_nothing(self, *args, **kw):
486 pass
487
488 -class CountStats(Stats):
489 - def do_append(self, label):
490 self.labels.append(label) 491 self.stats.append(SCons.Debug.fetchLoggedInstances())
492 - def do_print(self):
493 stats_table = {} 494 for s in self.stats: 495 for n in [t[0] for t in s]: 496 stats_table[n] = [0, 0, 0, 0] 497 i = 0 498 for s in self.stats: 499 for n, c in s: 500 stats_table[n][i] = c 501 i = i + 1 502 self.outfp.write("Object counts:\n") 503 pre = [" "] 504 post = [" %s\n"] 505 l = len(self.stats) 506 fmt1 = ''.join(pre + [' %7s']*l + post) 507 fmt2 = ''.join(pre + [' %7d']*l + post) 508 labels = self.labels[:l] 509 labels.append(("", "Class")) 510 self.outfp.write(fmt1 % tuple([x[0] for x in labels])) 511 self.outfp.write(fmt1 % tuple([x[1] for x in labels])) 512 for k in sorted(stats_table.keys()): 513 r = stats_table[k][:l] + [k] 514 self.outfp.write(fmt2 % tuple(r))
515 516 count_stats = CountStats() 517
518 -class MemStats(Stats):
519 - def do_append(self, label):
520 self.labels.append(label) 521 self.stats.append(SCons.Debug.memory())
522 - def do_print(self):
523 fmt = 'Memory %-32s %12d\n' 524 for label, stats in zip(self.labels, self.stats): 525 self.outfp.write(fmt % (label, stats))
526 527 memory_stats = MemStats() 528 529 # utility functions 530
531 -def _scons_syntax_error(e):
532 """Handle syntax errors. Print out a message and show where the error 533 occurred. 534 """ 535 etype, value, tb = sys.exc_info() 536 lines = traceback.format_exception_only(etype, value) 537 for line in lines: 538 sys.stderr.write(line+'\n') 539 sys.exit(2)
540
541 -def find_deepest_user_frame(tb):
542 """ 543 Find the deepest stack frame that is not part of SCons. 544 545 Input is a "pre-processed" stack trace in the form 546 returned by traceback.extract_tb() or traceback.extract_stack() 547 """ 548 549 tb.reverse() 550 551 # find the deepest traceback frame that is not part 552 # of SCons: 553 for frame in tb: 554 filename = frame[0] 555 if filename.find(os.sep+'SCons'+os.sep) == -1: 556 return frame 557 return tb[0]
558
559 -def _scons_user_error(e):
560 """Handle user errors. Print out a message and a description of the 561 error, along with the line number and routine where it occured. 562 The file and line number will be the deepest stack frame that is 563 not part of SCons itself. 564 """ 565 global print_stacktrace 566 etype, value, tb = sys.exc_info() 567 if print_stacktrace: 568 traceback.print_exception(etype, value, tb) 569 filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_tb(tb)) 570 sys.stderr.write("\nscons: *** %s\n" % value) 571 sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine)) 572 sys.exit(2)
573
574 -def _scons_user_warning(e):
575 """Handle user warnings. Print out a message and a description of 576 the warning, along with the line number and routine where it occured. 577 The file and line number will be the deepest stack frame that is 578 not part of SCons itself. 579 """ 580 etype, value, tb = sys.exc_info() 581 filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_tb(tb)) 582 sys.stderr.write("\nscons: warning: %s\n" % e) 583 sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
584
585 -def _scons_internal_warning(e):
586 """Slightly different from _scons_user_warning in that we use the 587 *current call stack* rather than sys.exc_info() to get our stack trace. 588 This is used by the warnings framework to print warnings.""" 589 filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_stack()) 590 sys.stderr.write("\nscons: warning: %s\n" % e.args[0]) 591 sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
592
593 -def _scons_internal_error():
594 """Handle all errors but user errors. Print out a message telling 595 the user what to do in this case and print a normal trace. 596 """ 597 print 'internal error' 598 traceback.print_exc() 599 sys.exit(2)
600
601 -def _SConstruct_exists(dirname='', repositories=[], filelist=None):
602 """This function checks that an SConstruct file exists in a directory. 603 If so, it returns the path of the file. By default, it checks the 604 current directory. 605 """ 606 if not filelist: 607 filelist = ['SConstruct', 'Sconstruct', 'sconstruct'] 608 for file in filelist: 609 sfile = os.path.join(dirname, file) 610 if os.path.isfile(sfile): 611 return sfile 612 if not os.path.isabs(sfile): 613 for rep in repositories: 614 if os.path.isfile(os.path.join(rep, sfile)): 615 return sfile 616 return None
617
618 -def _set_debug_values(options):
619 global print_memoizer, print_objects, print_stacktrace, print_time 620 621 debug_values = options.debug 622 623 if "count" in debug_values: 624 # All of the object counts are within "if __debug__:" blocks, 625 # which get stripped when running optimized (with python -O or 626 # from compiled *.pyo files). Provide a warning if __debug__ is 627 # stripped, so it doesn't just look like --debug=count is broken. 628 enable_count = False 629 if __debug__: enable_count = True 630 if enable_count: 631 count_stats.enable(sys.stdout) 632 else: 633 msg = "--debug=count is not supported when running SCons\n" + \ 634 "\twith the python -O option or optimized (.pyo) modules." 635 SCons.Warnings.warn(SCons.Warnings.NoObjectCountWarning, msg) 636 if "dtree" in debug_values: 637 options.tree_printers.append(TreePrinter(derived=True)) 638 options.debug_explain = ("explain" in debug_values) 639 if "findlibs" in debug_values: 640 SCons.Scanner.Prog.print_find_libs = "findlibs" 641 options.debug_includes = ("includes" in debug_values) 642 print_memoizer = ("memoizer" in debug_values) 643 if "memory" in debug_values: 644 memory_stats.enable(sys.stdout) 645 print_objects = ("objects" in debug_values) 646 if "presub" in debug_values: 647 SCons.Action.print_actions_presub = 1 648 if "stacktrace" in debug_values: 649 print_stacktrace = 1 650 if "stree" in debug_values: 651 options.tree_printers.append(TreePrinter(status=True)) 652 if "time" in debug_values: 653 print_time = 1 654 if "tree" in debug_values: 655 options.tree_printers.append(TreePrinter())
656
657 -def _create_path(plist):
658 path = '.' 659 for d in plist: 660 if os.path.isabs(d): 661 path = d 662 else: 663 path = path + '/' + d 664 return path
665
666 -def _load_site_scons_dir(topdir, site_dir_name=None):
667 """Load the site_scons dir under topdir. 668 Adds site_scons to sys.path, imports site_scons/site_init.py, 669 and adds site_scons/site_tools to default toolpath.""" 670 if site_dir_name: 671 err_if_not_found = True # user specified: err if missing 672 else: 673 site_dir_name = "site_scons" 674 err_if_not_found = False 675 676 site_dir = os.path.join(topdir.path, site_dir_name) 677 if not os.path.exists(site_dir): 678 if err_if_not_found: 679 raise SCons.Errors.UserError("site dir %s not found."%site_dir) 680 return 681 682 site_init_filename = "site_init.py" 683 site_init_modname = "site_init" 684 site_tools_dirname = "site_tools" 685 sys.path = [os.path.abspath(site_dir)] + sys.path 686 site_init_file = os.path.join(site_dir, site_init_filename) 687 site_tools_dir = os.path.join(site_dir, site_tools_dirname) 688 if os.path.exists(site_init_file): 689 import imp 690 # TODO(2.4): turn this into try:-except:-finally: 691 try: 692 try: 693 fp, pathname, description = imp.find_module(site_init_modname, 694 [site_dir]) 695 # Load the file into SCons.Script namespace. This is 696 # opaque and clever; m is the module object for the 697 # SCons.Script module, and the exec ... in call executes a 698 # file (or string containing code) in the context of the 699 # module's dictionary, so anything that code defines ends 700 # up adding to that module. This is really short, but all 701 # the error checking makes it longer. 702 try: 703 m = sys.modules['SCons.Script'] 704 except Exception, e: 705 fmt = 'cannot import site_init.py: missing SCons.Script module %s' 706 raise SCons.Errors.InternalError(fmt % repr(e)) 707 try: 708 # This is the magic. 709 exec fp in m.__dict__ 710 except KeyboardInterrupt: 711 raise 712 except Exception, e: 713 fmt = '*** Error loading site_init file %s:\n' 714 sys.stderr.write(fmt % repr(site_init_file)) 715 raise 716 except KeyboardInterrupt: 717 raise 718 except ImportError, e: 719 fmt = '*** cannot import site init file %s:\n' 720 sys.stderr.write(fmt % repr(site_init_file)) 721 raise 722 finally: 723 if fp: 724 fp.close() 725 if os.path.exists(site_tools_dir): 726 SCons.Tool.DefaultToolpath.append(os.path.abspath(site_tools_dir))
727
728 -def version_string(label, module):
729 version = module.__version__ 730 build = module.__build__ 731 if build: 732 if build[0] != '.': 733 build = '.' + build 734 version = version + build 735 fmt = "\t%s: v%s, %s, by %s on %s\n" 736 return fmt % (label, 737 version, 738 module.__date__, 739 module.__developer__, 740 module.__buildsys__)
741
742 -def _main(parser):
743 global exit_status 744 global this_build_status 745 746 options = parser.values 747 748 # Here's where everything really happens. 749 750 # First order of business: set up default warnings and then 751 # handle the user's warning options, so that we can issue (or 752 # suppress) appropriate warnings about anything that might happen, 753 # as configured by the user. 754 755 default_warnings = [ SCons.Warnings.WarningOnByDefault, 756 SCons.Warnings.DeprecatedWarning, 757 ] 758 759 for warning in default_warnings: 760 SCons.Warnings.enableWarningClass(warning) 761 SCons.Warnings._warningOut = _scons_internal_warning 762 SCons.Warnings.process_warn_strings(options.warn) 763 764 # Now that we have the warnings configuration set up, we can actually 765 # issue (or suppress) any warnings about warning-worthy things that 766 # occurred while the command-line options were getting parsed. 767 try: 768 dw = options.delayed_warnings 769 except AttributeError: 770 pass 771 else: 772 delayed_warnings.extend(dw) 773 for warning_type, message in delayed_warnings: 774 SCons.Warnings.warn(warning_type, message) 775 776 if options.diskcheck: 777 SCons.Node.FS.set_diskcheck(options.diskcheck) 778 779 # Next, we want to create the FS object that represents the outside 780 # world's file system, as that's central to a lot of initialization. 781 # To do this, however, we need to be in the directory from which we 782 # want to start everything, which means first handling any relevant 783 # options that might cause us to chdir somewhere (-C, -D, -U, -u). 784 if options.directory: 785 script_dir = os.path.abspath(_create_path(options.directory)) 786 else: 787 script_dir = os.getcwd() 788 789 target_top = None 790 if options.climb_up: 791 target_top = '.' # directory to prepend to targets 792 while script_dir and not _SConstruct_exists(script_dir, 793 options.repository, 794 options.file): 795 script_dir, last_part = os.path.split(script_dir) 796 if last_part: 797 target_top = os.path.join(last_part, target_top) 798 else: 799 script_dir = '' 800 801 if script_dir and script_dir != os.getcwd(): 802 display("scons: Entering directory `%s'" % script_dir) 803 try: 804 os.chdir(script_dir) 805 except OSError: 806 sys.stderr.write("Could not change directory to %s\n" % script_dir) 807 808 # Now that we're in the top-level SConstruct directory, go ahead 809 # and initialize the FS object that represents the file system, 810 # and make it the build engine default. 811 fs = SCons.Node.FS.get_default_fs() 812 813 for rep in options.repository: 814 fs.Repository(rep) 815 816 # Now that we have the FS object, the next order of business is to 817 # check for an SConstruct file (or other specified config file). 818 # If there isn't one, we can bail before doing any more work. 819 scripts = [] 820 if options.file: 821 scripts.extend(options.file) 822 if not scripts: 823 sfile = _SConstruct_exists(repositories=options.repository, 824 filelist=options.file) 825 if sfile: 826 scripts.append(sfile) 827 828 if not scripts: 829 if options.help: 830 # There's no SConstruct, but they specified -h. 831 # Give them the options usage now, before we fail 832 # trying to read a non-existent SConstruct file. 833 raise SConsPrintHelpException 834 raise SCons.Errors.UserError("No SConstruct file found.") 835 836 if scripts[0] == "-": 837 d = fs.getcwd() 838 else: 839 d = fs.File(scripts[0]).dir 840 fs.set_SConstruct_dir(d) 841 842 _set_debug_values(options) 843 SCons.Node.implicit_cache = options.implicit_cache 844 SCons.Node.implicit_deps_changed = options.implicit_deps_changed 845 SCons.Node.implicit_deps_unchanged = options.implicit_deps_unchanged 846 847 if options.no_exec: 848 SCons.SConf.dryrun = 1 849 SCons.Action.execute_actions = None 850 if options.question: 851 SCons.SConf.dryrun = 1 852 if options.clean: 853 SCons.SConf.SetBuildType('clean') 854 if options.help: 855 SCons.SConf.SetBuildType('help') 856 SCons.SConf.SetCacheMode(options.config) 857 SCons.SConf.SetProgressDisplay(progress_display) 858 859 if options.no_progress or options.silent: 860 progress_display.set_mode(0) 861 862 if options.site_dir: 863 _load_site_scons_dir(d, options.site_dir) 864 elif not options.no_site_dir: 865 _load_site_scons_dir(d) 866 867 if options.include_dir: 868 sys.path = options.include_dir + sys.path 869 870 # That should cover (most of) the options. Next, set up the variables 871 # that hold command-line arguments, so the SConscript files that we 872 # read and execute have access to them. 873 targets = [] 874 xmit_args = [] 875 for a in parser.largs: 876 if a[:1] == '-': 877 continue 878 if '=' in a: 879 xmit_args.append(a) 880 else: 881 targets.append(a) 882 SCons.Script._Add_Targets(targets + parser.rargs) 883 SCons.Script._Add_Arguments(xmit_args) 884 885 # If stdout is not a tty, replace it with a wrapper object to call flush 886 # after every write. 887 # 888 # Tty devices automatically flush after every newline, so the replacement 889 # isn't necessary. Furthermore, if we replace sys.stdout, the readline 890 # module will no longer work. This affects the behavior during 891 # --interactive mode. --interactive should only be used when stdin and 892 # stdout refer to a tty. 893 if not hasattr(sys.stdout, 'isatty') or not sys.stdout.isatty(): 894 sys.stdout = SCons.Util.Unbuffered(sys.stdout) 895 if not hasattr(sys.stderr, 'isatty') or not sys.stderr.isatty(): 896 sys.stderr = SCons.Util.Unbuffered(sys.stderr) 897 898 memory_stats.append('before reading SConscript files:') 899 count_stats.append(('pre-', 'read')) 900 901 # And here's where we (finally) read the SConscript files. 902 903 progress_display("scons: Reading SConscript files ...") 904 905 start_time = time.time() 906 try: 907 for script in scripts: 908 SCons.Script._SConscript._SConscript(fs, script) 909 except SCons.Errors.StopError, e: 910 # We had problems reading an SConscript file, such as it 911 # couldn't be copied in to the VariantDir. Since we're just 912 # reading SConscript files and haven't started building 913 # things yet, stop regardless of whether they used -i or -k 914 # or anything else. 915 sys.stderr.write("scons: *** %s Stop.\n" % e) 916 exit_status = 2 917 sys.exit(exit_status) 918 global sconscript_time 919 sconscript_time = time.time() - start_time 920 921 progress_display("scons: done reading SConscript files.") 922 923 memory_stats.append('after reading SConscript files:') 924 count_stats.append(('post-', 'read')) 925 926 # Re-{enable,disable} warnings in case they disabled some in 927 # the SConscript file. 928 # 929 # We delay enabling the PythonVersionWarning class until here so that, 930 # if they explicity disabled it in either in the command line or in 931 # $SCONSFLAGS, or in the SConscript file, then the search through 932 # the list of deprecated warning classes will find that disabling 933 # first and not issue the warning. 934 #SCons.Warnings.enableWarningClass(SCons.Warnings.PythonVersionWarning) 935 SCons.Warnings.process_warn_strings(options.warn) 936 937 # Now that we've read the SConscript files, we can check for the 938 # warning about deprecated Python versions--delayed until here 939 # in case they disabled the warning in the SConscript files. 940 if python_version_deprecated(): 941 msg = "Support for pre-2.4 Python (%s) is deprecated.\n" + \ 942 " If this will cause hardship, contact dev@scons.tigris.org." 943 SCons.Warnings.warn(SCons.Warnings.PythonVersionWarning, 944 msg % python_version_string()) 945 946 if not options.help: 947 SCons.SConf.CreateConfigHBuilder(SCons.Defaults.DefaultEnvironment()) 948 949 # Now re-parse the command-line options (any to the left of a '--' 950 # argument, that is) with any user-defined command-line options that 951 # the SConscript files may have added to the parser object. This will 952 # emit the appropriate error message and exit if any unknown option 953 # was specified on the command line. 954 955 parser.preserve_unknown_options = False 956 parser.parse_args(parser.largs, options) 957 958 if options.help: 959 help_text = SCons.Script.help_text 960 if help_text is None: 961 # They specified -h, but there was no Help() inside the 962 # SConscript files. Give them the options usage. 963 raise SConsPrintHelpException 964 else: 965 print help_text 966 print "Use scons -H for help about command-line options." 967 exit_status = 0 968 return 969 970 # Change directory to the top-level SConstruct directory, then tell 971 # the Node.FS subsystem that we're all done reading the SConscript 972 # files and calling Repository() and VariantDir() and changing 973 # directories and the like, so it can go ahead and start memoizing 974 # the string values of file system nodes. 975 976 fs.chdir(fs.Top) 977 978 SCons.Node.FS.save_strings(1) 979 980 # Now that we've read the SConscripts we can set the options 981 # that are SConscript settable: 982 SCons.Node.implicit_cache = options.implicit_cache 983 SCons.Node.FS.set_duplicate(options.duplicate) 984 fs.set_max_drift(options.max_drift) 985 986 SCons.Job.explicit_stack_size = options.stack_size 987 988 if options.md5_chunksize: 989 SCons.Node.FS.File.md5_chunksize = options.md5_chunksize 990 991 platform = SCons.Platform.platform_module() 992 993 if options.interactive: 994 SCons.Script.Interactive.interact(fs, OptionsParser, options, 995 targets, target_top) 996 997 else: 998 999 # Build the targets 1000 nodes = _build_targets(fs, options, targets, target_top) 1001 if not nodes: 1002 exit_status = 2
1003
1004 -def _build_targets(fs, options, targets, target_top):
1005 1006 global this_build_status 1007 this_build_status = 0 1008 1009 progress_display.set_mode(not (options.no_progress or options.silent)) 1010 display.set_mode(not options.silent) 1011 SCons.Action.print_actions = not options.silent 1012 SCons.Action.execute_actions = not options.no_exec 1013 SCons.Node.FS.do_store_info = not options.no_exec 1014 SCons.SConf.dryrun = options.no_exec 1015 1016 if options.diskcheck: 1017 SCons.Node.FS.set_diskcheck(options.diskcheck) 1018 1019 SCons.CacheDir.cache_enabled = not options.cache_disable 1020 SCons.CacheDir.cache_debug = options.cache_debug 1021 SCons.CacheDir.cache_force = options.cache_force 1022 SCons.CacheDir.cache_show = options.cache_show 1023 1024 if options.no_exec: 1025 CleanTask.execute = CleanTask.show 1026 else: 1027 CleanTask.execute = CleanTask.remove 1028 1029 lookup_top = None 1030 if targets or SCons.Script.BUILD_TARGETS != SCons.Script._build_plus_default: 1031 # They specified targets on the command line or modified 1032 # BUILD_TARGETS in the SConscript file(s), so if they used -u, 1033 # -U or -D, we have to look up targets relative to the top, 1034 # but we build whatever they specified. 1035 if target_top: 1036 lookup_top = fs.Dir(target_top) 1037 target_top = None 1038 1039 targets = SCons.Script.BUILD_TARGETS 1040 else: 1041 # There are no targets specified on the command line, 1042 # so if they used -u, -U or -D, we may have to restrict 1043 # what actually gets built. 1044 d = None 1045 if target_top: 1046 if options.climb_up == 1: 1047 # -u, local directory and below 1048 target_top = fs.Dir(target_top) 1049 lookup_top = target_top 1050 elif options.climb_up == 2: 1051 # -D, all Default() targets 1052 target_top = None 1053 lookup_top = None 1054 elif options.climb_up == 3: 1055 # -U, local SConscript Default() targets 1056 target_top = fs.Dir(target_top) 1057 def check_dir(x, target_top=target_top): 1058 if hasattr(x, 'cwd') and not x.cwd is None: 1059 cwd = x.cwd.srcnode() 1060 return cwd == target_top 1061 else: 1062 # x doesn't have a cwd, so it's either not a target, 1063 # or not a file, so go ahead and keep it as a default 1064 # target and let the engine sort it out: 1065 return 1
1066 d = list(filter(check_dir, SCons.Script.DEFAULT_TARGETS)) 1067 SCons.Script.DEFAULT_TARGETS[:] = d 1068 target_top = None 1069 lookup_top = None 1070 1071 targets = SCons.Script._Get_Default_Targets(d, fs) 1072 1073 if not targets: 1074 sys.stderr.write("scons: *** No targets specified and no Default() targets found. Stop.\n") 1075 return None 1076 1077 def Entry(x, ltop=lookup_top, ttop=target_top, fs=fs): 1078 if isinstance(x, SCons.Node.Node): 1079 node = x 1080 else: 1081 node = None 1082 # Why would ltop be None? Unfortunately this happens. 1083 if ltop is None: ltop = '' 1084 # Curdir becomes important when SCons is called with -u, -C, 1085 # or similar option that changes directory, and so the paths 1086 # of targets given on the command line need to be adjusted. 1087 curdir = os.path.join(os.getcwd(), str(ltop)) 1088 for lookup in SCons.Node.arg2nodes_lookups: 1089 node = lookup(x, curdir=curdir) 1090 if node is not None: 1091 break 1092 if node is None: 1093 node = fs.Entry(x, directory=ltop, create=1) 1094 if ttop and not node.is_under(ttop): 1095 if isinstance(node, SCons.Node.FS.Dir) and ttop.is_under(node): 1096 node = ttop 1097 else: 1098 node = None 1099 return node 1100 1101 nodes = [_f for _f in map(Entry, targets) if _f] 1102 1103 task_class = BuildTask # default action is to build targets 1104 opening_message = "Building targets ..." 1105 closing_message = "done building targets." 1106 if options.keep_going: 1107 failure_message = "done building targets (errors occurred during build)." 1108 else: 1109 failure_message = "building terminated because of errors." 1110 if options.question: 1111 task_class = QuestionTask 1112 try: 1113 if options.clean: 1114 task_class = CleanTask 1115 opening_message = "Cleaning targets ..." 1116 closing_message = "done cleaning targets." 1117 if options.keep_going: 1118 failure_message = "done cleaning targets (errors occurred during clean)." 1119 else: 1120 failure_message = "cleaning terminated because of errors." 1121 except AttributeError: 1122 pass 1123 1124 task_class.progress = ProgressObject 1125 1126 if options.random: 1127 def order(dependencies): 1128 """Randomize the dependencies.""" 1129 import random 1130 # This is cribbed from the implementation of 1131 # random.shuffle() in Python 2.X. 1132 d = dependencies 1133 for i in range(len(d)-1, 0, -1): 1134 j = int(random.random() * (i+1)) 1135 d[i], d[j] = d[j], d[i] 1136 return d 1137 else: 1138 def order(dependencies): 1139 """Leave the order of dependencies alone.""" 1140 return dependencies 1141 1142 if options.taskmastertrace_file == '-': 1143 tmtrace = sys.stdout 1144 elif options.taskmastertrace_file: 1145 tmtrace = open(options.taskmastertrace_file, 'wb') 1146 else: 1147 tmtrace = None 1148 taskmaster = SCons.Taskmaster.Taskmaster(nodes, task_class, order, tmtrace) 1149 1150 # Let the BuildTask objects get at the options to respond to the 1151 # various print_* settings, tree_printer list, etc. 1152 BuildTask.options = options 1153 1154 global num_jobs 1155 num_jobs = options.num_jobs 1156 jobs = SCons.Job.Jobs(num_jobs, taskmaster) 1157 if num_jobs > 1: 1158 msg = None 1159 if jobs.num_jobs == 1: 1160 msg = "parallel builds are unsupported by this version of Python;\n" + \ 1161 "\tignoring -j or num_jobs option.\n" 1162 elif sys.platform == 'win32': 1163 msg = fetch_win32_parallel_msg() 1164 if msg: 1165 SCons.Warnings.warn(SCons.Warnings.NoParallelSupportWarning, msg) 1166 1167 memory_stats.append('before building targets:') 1168 count_stats.append(('pre-', 'build')) 1169 1170 def jobs_postfunc( 1171 jobs=jobs, 1172 options=options, 1173 closing_message=closing_message, 1174 failure_message=failure_message 1175 ): 1176 if jobs.were_interrupted(): 1177 if not options.no_progress and not options.silent: 1178 sys.stderr.write("scons: Build interrupted.\n") 1179 global exit_status 1180 global this_build_status 1181 exit_status = 2 1182 this_build_status = 2 1183 1184 if this_build_status: 1185 progress_display("scons: " + failure_message) 1186 else: 1187 progress_display("scons: " + closing_message) 1188 if not options.no_exec: 1189 if jobs.were_interrupted(): 1190 progress_display("scons: writing .sconsign file.") 1191 SCons.SConsign.write() 1192 1193 progress_display("scons: " + opening_message) 1194 jobs.run(postfunc = jobs_postfunc) 1195 1196 memory_stats.append('after building targets:') 1197 count_stats.append(('post-', 'build')) 1198 1199 return nodes 1200
1201 -def _exec_main(parser, values):
1202 sconsflags = os.environ.get('SCONSFLAGS', '') 1203 all_args = sconsflags.split() + sys.argv[1:] 1204 1205 options, args = parser.parse_args(all_args, values) 1206 1207 if isinstance(options.debug, list) and "pdb" in options.debug: 1208 import pdb 1209 pdb.Pdb().runcall(_main, parser) 1210 elif options.profile_file: 1211 # compat layer imports "cProfile" for us if it's available. 1212 from profile import Profile 1213 1214 # Some versions of Python 2.4 shipped a profiler that had the 1215 # wrong 'c_exception' entry in its dispatch table. Make sure 1216 # we have the right one. (This may put an unnecessary entry 1217 # in the table in earlier versions of Python, but its presence 1218 # shouldn't hurt anything). 1219 try: 1220 dispatch = Profile.dispatch 1221 except AttributeError: 1222 pass 1223 else: 1224 dispatch['c_exception'] = Profile.trace_dispatch_return 1225 1226 prof = Profile() 1227 try: 1228 prof.runcall(_main, parser) 1229 except SConsPrintHelpException, e: 1230 prof.dump_stats(options.profile_file) 1231 raise e 1232 except SystemExit: 1233 pass 1234 prof.dump_stats(options.profile_file) 1235 else: 1236 _main(parser)
1237
1238 -def main():
1239 global OptionsParser 1240 global exit_status 1241 global first_command_start 1242 1243 # Check up front for a Python version we do not support. We 1244 # delay the check for deprecated Python versions until later, 1245 # after the SConscript files have been read, in case they 1246 # disable that warning. 1247 if python_version_unsupported(): 1248 msg = "scons: *** SCons version %s does not run under Python version %s.\n" 1249 sys.stderr.write(msg % (SCons.__version__, python_version_string())) 1250 sys.exit(1) 1251 1252 parts = ["SCons by Steven Knight et al.:\n"] 1253 try: 1254 import __main__ 1255 parts.append(version_string("script", __main__)) 1256 except (ImportError, AttributeError): 1257 # On Windows there is no scons.py, so there is no 1258 # __main__.__version__, hence there is no script version. 1259 pass 1260 parts.append(version_string("engine", SCons)) 1261 parts.append("Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 The SCons Foundation") 1262 version = ''.join(parts) 1263 1264 import SConsOptions 1265 parser = SConsOptions.Parser(version) 1266 values = SConsOptions.SConsValues(parser.get_default_values()) 1267 1268 OptionsParser = parser 1269 1270 try: 1271 _exec_main(parser, values) 1272 except SystemExit, s: 1273 if s: 1274 exit_status = s 1275 except KeyboardInterrupt: 1276 print("scons: Build interrupted.") 1277 sys.exit(2) 1278 except SyntaxError, e: 1279 _scons_syntax_error(e) 1280 except SCons.Errors.InternalError: 1281 _scons_internal_error() 1282 except SCons.Errors.UserError, e: 1283 _scons_user_error(e) 1284 except SConsPrintHelpException: 1285 parser.print_help() 1286 exit_status = 0 1287 except SCons.Errors.BuildError, e: 1288 exit_status = e.exitstatus 1289 except: 1290 # An exception here is likely a builtin Python exception Python 1291 # code in an SConscript file. Show them precisely what the 1292 # problem was and where it happened. 1293 SCons.Script._SConscript.SConscript_exception() 1294 sys.exit(2) 1295 1296 memory_stats.print_stats() 1297 count_stats.print_stats() 1298 1299 if print_objects: 1300 SCons.Debug.listLoggedInstances('*') 1301 #SCons.Debug.dumpLoggedInstances('*') 1302 1303 if print_memoizer: 1304 SCons.Memoize.Dump("Memoizer (memory cache) hits and misses:") 1305 1306 # Dump any development debug info that may have been enabled. 1307 # These are purely for internal debugging during development, so 1308 # there's no need to control them with --debug= options; they're 1309 # controlled by changing the source code. 1310 SCons.Debug.dump_caller_counts() 1311 SCons.Taskmaster.dump_stats() 1312 1313 if print_time: 1314 total_time = time.time() - SCons.Script.start_time 1315 if num_jobs == 1: 1316 ct = cumulative_command_time 1317 else: 1318 if last_command_end is None or first_command_start is None: 1319 ct = 0.0 1320 else: 1321 ct = last_command_end - first_command_start 1322 scons_time = total_time - sconscript_time - ct 1323 print "Total build time: %f seconds"%total_time 1324 print "Total SConscript file execution time: %f seconds"%sconscript_time 1325 print "Total SCons execution time: %f seconds"%scons_time 1326 print "Total command execution time: %f seconds"%ct 1327 1328 sys.exit(exit_status)
1329 1330 # Local Variables: 1331 # tab-width:4 1332 # indent-tabs-mode:nil 1333 # End: 1334 # vim: set expandtab tabstop=4 shiftwidth=4: 1335