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