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