3 This file implements the main() function used by the scons script.
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,
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:
25 # The above copyright notice and this permission notice shall be included
26 # in all copies or substantial portions of the Software.
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.
37 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
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
53 #sys.path = [os.path.join(sys.prefix,
55 # 'scons-%d' % SCons.__version__)] + sys.path[1:]
60 import SCons.Environment
67 import SCons.Taskmaster
71 import SCons.Script.Interactive
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
85 class SConsPrintHelpException(Exception):
88 display = SCons.Util.display
89 progress_display = SCons.Util.DisplayEngine()
91 first_command_start = None
92 last_command_end = None
97 target_string = '$TARGET'
99 def __init__(self, obj, interval=1, file=None, overwrite=False):
105 self.interval = interval
106 self.overwrite = overwrite
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
115 self.func = self.string
122 def erase_previous(self):
124 length = len(self.prev)
125 if self.prev[-1] in ('\n', '\r'):
127 self.write(' ' * length + '\r')
130 def spinner(self, node):
131 self.write(self.obj[self.count % len(self.obj)])
133 def string(self, node):
136 def replace_string(self, node):
137 self.write(string.replace(self.obj, self.target_string, str(node)))
139 def __call__(self, node):
140 self.count = self.count + 1
141 if (self.count % self.interval) == 0:
143 self.erase_previous()
146 ProgressObject = SCons.Util.Null()
148 def Progress(*args, **kw):
149 global ProgressObject
150 ProgressObject = apply(Progressor, args, kw)
157 def GetBuildFailures():
158 return _BuildFailures
160 class BuildTask(SCons.Taskmaster.Task):
161 """An SCons build task."""
162 progress = ProgressObject
164 def display(self, message):
165 display('scons: ' + message)
168 self.progress(self.targets[0])
169 return SCons.Taskmaster.Task.prepare(self)
172 for target in self.targets:
173 if target.get_state() == SCons.Node.up_to_date:
175 if target.has_builder() and not hasattr(target.builder, 'status'):
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)
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))
191 if self.top and target.has_builder():
192 display("scons: `%s' is up to date." % str(self.node))
194 def do_failed(self, status=2):
195 _BuildFailures.append(self.exception[1])
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)
203 SCons.Taskmaster.Task.fail_stop(self)
208 if self.top and not t.has_builder() and not t.side_effect:
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")
216 print "scons: Nothing to be done for `%s'." % t
217 SCons.Taskmaster.Task.executed(self)
219 SCons.Taskmaster.Task.executed(self)
222 # Handle the failure of a build task. The primary purpose here
223 # is to display the various types of Errors and Exceptions
226 exc_info = self.exc_info()
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]
238 if not SCons.Util.is_List(n):
240 return string.join(map(str, n), ', ')
242 errfmt = "scons: *** [%s] %s\n"
244 if t == SCons.Errors.BuildError:
245 tname = nodestring(e.node)
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:
257 tname = nodestring(e.node)
258 errstr = 'Explicit exit, status %s' % status
259 sys.stderr.write(errfmt % (tname, errstr))
264 if t == SCons.Errors.StopError and not self.options.keep_going:
266 sys.stderr.write("scons: *** %s\n" % s)
268 if tb and print_stacktrace:
269 sys.stderr.write("scons: internal stack trace:\n")
270 traceback.print_tb(tb, file=sys.stderr)
272 self.do_failed(status)
276 def postprocess(self):
279 for tp in self.options.tree_printers:
281 if self.options.debug_includes:
282 tree = t.render_include_tree()
286 SCons.Taskmaster.Task.postprocess(self)
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()
294 sys.stdout.write("scons: " + explanation)
296 class CleanTask(SCons.Taskmaster.Task):
297 """An SCons clean task."""
298 def fs_delete(self, path, pathstr, remove=1):
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).
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)
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
325 target = self.targets[0]
326 if (target.has_builder() or target.side_effect) and not target.noclean:
327 for t in self.targets:
329 display("Removed " + str(t))
330 if SCons.Environment.CleanTargets.has_key(target):
331 files = SCons.Environment.CleanTargets[target]
333 self.fs_delete(f.abspath, str(f), 0)
336 target = self.targets[0]
337 if (target.has_builder() or target.side_effect) and not target.noclean:
338 for t in self.targets:
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
350 display("Removed " + str(t))
351 if SCons.Environment.CleanTargets.has_key(target):
352 files = SCons.Environment.CleanTargets[target]
354 self.fs_delete(f.abspath, str(f))
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
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
372 class QuestionTask(SCons.Taskmaster.Task):
373 """An SCons task for the -q (question) option."""
378 if self.targets[0].get_state() != SCons.Node.up_to_date or \
379 (self.top and not self.targets[0].exists()):
389 def __init__(self, derived=False, prune=False, status=False):
390 self.derived = derived
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):
400 func = self.get_derived_children
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)
407 def python_version_string():
408 return string.split(sys.version)[0]
410 def python_version_unsupported(version=sys.version_info):
411 return version < (1, 5, 2)
413 def python_version_deprecated(version=sys.version_info):
414 return version < (2, 2, 0)
424 cumulative_command_time = 0
425 exit_status = 0 # exit status, assume success by default
427 delayed_warnings = []
429 class FakeOptionParser:
431 A do-nothing option parser, used for the initial OptionsParser variable.
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
441 class FakeOptionValues:
442 def __getattr__(self, attr):
444 values = FakeOptionValues()
445 def add_local_option(self, *args, **kw):
448 OptionsParser = FakeOptionParser()
450 def AddOption(*args, **kw):
451 if not kw.has_key('default'):
453 result = apply(OptionsParser.add_local_option, args, kw)
457 return getattr(OptionsParser.values, name)
459 def SetOption(name, value):
460 return OptionsParser.values.set_option(name, value)
467 self.append = self.do_nothing
468 self.print_stats = self.do_nothing
469 def enable(self, outfp):
471 self.append = self.do_append
472 self.print_stats = self.do_print
473 def do_nothing(self, *args, **kw):
476 class CountStats(Stats):
477 def do_append(self, label):
478 self.labels.append(label)
479 self.stats.append(SCons.Debug.fetchLoggedInstances())
483 for n in map(lambda t: t[0], s):
484 stats_table[n] = [0, 0, 0, 0]
488 stats_table[n][i] = c
490 keys = stats_table.keys()
492 self.outfp.write("Object counts:\n")
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)))
503 r = stats_table[k][:l] + [k]
504 self.outfp.write(fmt2 % tuple(r))
506 count_stats = CountStats()
508 class MemStats(Stats):
509 def do_append(self, label):
510 self.labels.append(label)
511 self.stats.append(SCons.Debug.memory())
513 fmt = 'Memory %-32s %12d\n'
514 for label, stats in map(None, self.labels, self.stats):
515 self.outfp.write(fmt % (label, stats))
517 memory_stats = MemStats()
521 def _scons_syntax_error(e):
522 """Handle syntax errors. Print out a message and show where the error
525 etype, value, tb = sys.exc_info()
526 lines = traceback.format_exception_only(etype, value)
528 sys.stderr.write(line+'\n')
531 def find_deepest_user_frame(tb):
533 Find the deepest stack frame that is not part of SCons.
535 Input is a "pre-processed" stack trace in the form
536 returned by traceback.extract_tb() or traceback.extract_stack()
541 # find the deepest traceback frame that is not part
545 if string.find(filename, os.sep+'SCons'+os.sep) == -1:
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.
555 global print_stacktrace
556 etype, value, tb = sys.exc_info()
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))
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.
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))
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))
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.
587 print 'internal error'
588 traceback.print_exc()
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
596 for file in ['SConstruct', 'Sconstruct', 'sconstruct']:
597 sfile = os.path.join(dirname, file)
598 if os.path.isfile(sfile):
600 if not os.path.isabs(sfile):
601 for rep in repositories:
602 if os.path.isfile(os.path.join(rep, sfile)):
606 def _set_debug_values(options):
607 global print_memoizer, print_objects, print_stacktrace, print_time
609 debug_values = options.debug
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.
617 if __debug__: enable_count = True
619 count_stats.enable(sys.stdout)
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:
638 if "stree" in debug_values:
639 options.tree_printers.append(TreePrinter(status=True))
640 if "time" in debug_values:
642 if "tree" in debug_values:
643 options.tree_printers.append(TreePrinter())
645 def _create_path(plist):
651 path = path + '/' + d
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."""
659 err_if_not_found = True # user specified: err if missing
661 site_dir_name = "site_scons"
662 err_if_not_found = False
664 site_dir = os.path.join(topdir.path, site_dir_name)
665 if not os.path.exists(site_dir):
667 raise SCons.Errors.UserError, "site dir %s not found."%site_dir
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):
679 fp, pathname, description = imp.find_module(site_init_modname,
682 imp.load_module(site_init_modname, fp, pathname, description)
686 except ImportError, e:
687 sys.stderr.write("Can't import site init file '%s': %s\n"%(site_init_file, e))
690 sys.stderr.write("Site init file '%s' raised exception: %s\n"%(site_init_file, e))
692 if os.path.exists(site_tools_dir):
693 SCons.Tool.DefaultToolpath.append(os.path.abspath(site_tools_dir))
695 def version_string(label, module):
696 version = module.__version__
697 build = module.__build__
701 version = version + build
702 fmt = "\t%s: v%s, %s, by %s on %s\n"
706 module.__developer__,
712 options = parser.values
714 # Here's where everything really happens.
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.
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)
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.
740 dw = options.delayed_warnings
741 except AttributeError:
744 delayed_warnings.extend(dw)
745 for warning_type, message in delayed_warnings:
746 SCons.Warnings.warn(warning_type, message)
748 if options.diskcheck:
749 SCons.Node.FS.set_diskcheck(options.diskcheck)
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)
761 sys.stderr.write("Could not change directory to %s\n" % cdir)
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)
770 target_top = os.path.join(last_part, target_top)
774 display("scons: Entering directory `%s'" % script_dir)
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()
782 for rep in options.repository:
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.
790 scripts.extend(options.file)
792 sfile = _SConstruct_exists(repositories=options.repository)
794 scripts.append(sfile)
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."
804 if scripts[0] == "-":
807 d = fs.File(scripts[0]).dir
808 fs.set_SConstruct_dir(d)
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
816 SCons.SConf.dryrun = 1
817 SCons.Action.execute_actions = None
819 SCons.SConf.dryrun = 1
821 SCons.SConf.SetBuildType('clean')
823 SCons.SConf.SetBuildType('help')
824 SCons.SConf.SetCacheMode(options.config)
825 SCons.SConf.SetProgressDisplay(progress_display)
827 if options.no_progress or options.silent:
828 progress_display.set_mode(0)
831 _load_site_scons_dir(d, options.site_dir)
832 elif not options.no_site_dir:
833 _load_site_scons_dir(d)
835 if options.include_dir:
836 sys.path = options.include_dir + sys.path
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.
843 for a in parser.largs:
850 SCons.Script._Add_Targets(targets + parser.rargs)
851 SCons.Script._Add_Arguments(xmit_args)
853 # If stdout is not a tty, replace it with a wrapper object to call flush
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)
866 memory_stats.append('before reading SConscript files:')
867 count_stats.append(('pre-', 'read'))
869 # And here's where we (finally) read the SConscript files.
871 progress_display("scons: Reading SConscript files ...")
873 start_time = time.time()
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
883 sys.stderr.write("scons: *** %s Stop.\n" % e)
885 sys.exit(exit_status)
886 global sconscript_time
887 sconscript_time = time.time() - start_time
889 progress_display("scons: done reading SConscript files.")
891 memory_stats.append('after reading SConscript files:')
892 count_stats.append(('post-', 'read'))
894 # Re-{enable,disable} warnings in case they disabled some in
895 # the SConscript file.
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)
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())
915 SCons.SConf.CreateConfigHBuilder(SCons.Defaults.DefaultEnvironment())
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.
923 parser.preserve_unknown_options = False
924 parser.parse_args(parser.largs, options)
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
934 print "Use scons -H for help about command-line options."
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.
946 SCons.Node.FS.save_strings(1)
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
956 platform = SCons.Platform.platform_module()
958 if options.interactive:
959 SCons.Script.Interactive.interact(fs, OptionsParser, options,
965 nodes = _build_targets(fs, options, targets, target_top)
969 def _build_targets(fs, options, targets, target_top):
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
977 if options.diskcheck:
978 SCons.Node.FS.set_diskcheck(options.diskcheck)
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
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
991 CleanTask.execute = CleanTask.show
993 CleanTask.execute = CleanTask.remove
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.
1002 lookup_top = fs.Dir(target_top)
1005 targets = SCons.Script.BUILD_TARGETS
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.
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
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
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:
1032 d = filter(check_dir, SCons.Script.DEFAULT_TARGETS)
1033 SCons.Script.DEFAULT_TARGETS[:] = d
1037 targets = SCons.Script._Get_Default_Targets(d, fs)
1040 sys.stderr.write("scons: *** No targets specified and no Default() targets found. Stop.\n")
1043 def Entry(x, ltop=lookup_top, ttop=target_top, fs=fs):
1044 if isinstance(x, SCons.Node.Node):
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)
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):
1067 nodes = filter(None, map(Entry, targets))
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)."
1075 failure_message = "building terminated because of errors."
1076 if options.question:
1077 task_class = QuestionTask
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)."
1086 failure_message = "cleaning terminated because of errors."
1087 except AttributeError:
1090 task_class.progress = ProgressObject
1093 def order(dependencies):
1094 """Randomize the dependencies."""
1096 # This is cribbed from the implementation of
1097 # random.shuffle() in Python 2.X.
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]
1104 def order(dependencies):
1105 """Leave the order of dependencies alone."""
1108 if options.taskmastertrace_file == '-':
1109 tmtrace = sys.stdout
1110 elif options.taskmastertrace_file:
1111 tmtrace = open(options.taskmastertrace_file, 'wb')
1114 taskmaster = SCons.Taskmaster.Taskmaster(nodes, task_class, order, tmtrace)
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
1121 num_jobs = options.num_jobs
1122 jobs = SCons.Job.Jobs(num_jobs, taskmaster)
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()
1131 SCons.Warnings.warn(SCons.Warnings.NoParallelSupportWarning, msg)
1133 memory_stats.append('before building targets:')
1134 count_stats.append(('pre-', 'build'))
1137 progress_display("scons: " + opening_message)
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
1150 progress_display("scons: " + failure_message)
1152 progress_display("scons: " + closing_message)
1153 if not options.no_exec:
1154 SCons.SConsign.write()
1156 memory_stats.append('after building targets:')
1157 count_stats.append(('post-', 'build'))
1161 def _exec_main(parser, values):
1162 sconsflags = os.environ.get('SCONSFLAGS', '')
1163 all_args = string.split(sconsflags) + sys.argv[1:]
1165 options, args = parser.parse_args(all_args, values)
1167 if type(options.debug) == type([]) and "pdb" in options.debug:
1169 pdb.Pdb().runcall(_main, parser)
1170 elif options.profile_file:
1171 from profile import Profile
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).
1179 dispatch = Profile.dispatch
1180 except AttributeError:
1183 dispatch['c_exception'] = Profile.trace_dispatch_return
1187 prof.runcall(_main, parser)
1188 except SConsPrintHelpException, e:
1189 prof.dump_stats(options.profile_file)
1193 prof.dump_stats(options.profile_file)
1198 global OptionsParser
1200 global first_command_start
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()))
1211 parts = ["SCons by Steven Knight et al.:\n"]
1213 parts.append(version_string("script", __main__))
1214 except KeyboardInterrupt:
1217 # On Windows there is no scons.py, so there is no
1218 # __main__.__version__, hence there is no script version.
1220 parts.append(version_string("engine", SCons))
1221 parts.append("__COPYRIGHT__")
1222 version = string.join(parts, '')
1225 parser = SConsOptions.Parser(version)
1226 values = SConsOptions.SConsValues(parser.get_default_values())
1228 OptionsParser = parser
1231 _exec_main(parser, values)
1232 except SystemExit, s:
1235 except KeyboardInterrupt:
1236 print "Build interrupted."
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:
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()
1254 memory_stats.print_stats()
1255 count_stats.print_stats()
1258 SCons.Debug.listLoggedInstances('*')
1259 #SCons.Debug.dumpLoggedInstances('*')
1262 SCons.Memoize.Dump("Memoizer (memory cache) hits and misses:")
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()
1272 total_time = time.time() - SCons.Script.start_time
1274 ct = cumulative_command_time
1276 if last_command_end is None or first_command_start is None:
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
1286 sys.exit(exit_status)