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