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