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