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