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