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__"
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
51 #sys.path = [os.path.join(sys.prefix,
53 # 'scons-%d' % SCons.__version__)] + sys.path[1:]
58 import SCons.Environment
65 import SCons.Taskmaster
69 import SCons.Script.Interactive
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
83 class SConsPrintHelpException(Exception):
86 display = SCons.Util.display
87 progress_display = SCons.Util.DisplayEngine()
89 first_command_start = None
90 last_command_end = None
95 target_string = '$TARGET'
97 def __init__(self, obj, interval=1, file=None, overwrite=False):
103 self.interval = interval
104 self.overwrite = overwrite
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
113 self.func = self.string
120 def erase_previous(self):
122 length = len(self.prev)
123 if self.prev[-1] in ('\n', '\r'):
125 self.write(' ' * length + '\r')
128 def spinner(self, node):
129 self.write(self.obj[self.count % len(self.obj)])
131 def string(self, node):
134 def replace_string(self, node):
135 self.write(string.replace(self.obj, self.target_string, str(node)))
137 def __call__(self, node):
138 self.count = self.count + 1
139 if (self.count % self.interval) == 0:
141 self.erase_previous()
144 ProgressObject = SCons.Util.Null()
146 def Progress(*args, **kw):
147 global ProgressObject
148 ProgressObject = apply(Progressor, args, kw)
155 def GetBuildFailures():
156 return _BuildFailures
158 class BuildTask(SCons.Taskmaster.OutOfDateTask):
159 """An SCons build task."""
160 progress = ProgressObject
162 def display(self, message):
163 display('scons: ' + message)
166 self.progress(self.targets[0])
167 return SCons.Taskmaster.OutOfDateTask.prepare(self)
169 def needs_execute(self):
170 if SCons.Taskmaster.OutOfDateTask.needs_execute(self):
172 if self.top and self.targets[0].has_builder():
173 display("scons: `%s' is up to date." % str(self.node))
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)
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))
191 def do_failed(self, status=2):
192 _BuildFailures.append(self.exception[1])
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)
200 this_build_status = status
202 SCons.Taskmaster.OutOfDateTask.fail_stop(self)
204 this_build_status = status
208 if self.top and not t.has_builder() and not t.side_effect:
210 errstr="Do not know how to make target `%s'." % t
211 sys.stderr.write("scons: *** " + errstr)
212 if not self.options.keep_going:
213 sys.stderr.write(" Stop.")
214 sys.stderr.write("\n")
216 raise SCons.Errors.BuildError(t, errstr)
217 except KeyboardInterrupt:
223 print "scons: Nothing to be done for `%s'." % t
224 SCons.Taskmaster.OutOfDateTask.executed(self)
226 SCons.Taskmaster.OutOfDateTask.executed(self)
229 # Handle the failure of a build task. The primary purpose here
230 # is to display the various types of Errors and Exceptions
232 exc_info = self.exc_info()
240 # The Taskmaster didn't record an exception for this Task;
241 # see if the sys module has one.
243 t, e, tb = sys.exc_info()[:]
248 # Deprecated string exceptions will have their string stored
249 # in the first entry of the tuple.
253 buildError = SCons.Errors.convert_to_BuildError(e)
254 if not buildError.node:
255 buildError.node = self.node
257 node = buildError.node
258 if not SCons.Util.is_List(node):
260 nodename = string.join(map(str, node), ', ')
262 errfmt = "scons: *** [%s] %s\n"
263 sys.stderr.write(errfmt % (nodename, buildError))
265 if (buildError.exc_info[2] and buildError.exc_info[1] and
268 # buildError.exc_info[1],
269 # (EnvironmentError, SCons.Errors.StopError, SCons.Errors.UserError))):
270 not isinstance(buildError.exc_info[1], EnvironmentError) and
271 not isinstance(buildError.exc_info[1], SCons.Errors.StopError) and
272 not isinstance(buildError.exc_info[1], SCons.Errors.UserError)):
273 type, value, trace = buildError.exc_info
274 traceback.print_exception(type, value, trace)
275 elif tb and print_stacktrace:
276 sys.stderr.write("scons: internal stack trace:\n")
277 traceback.print_tb(tb, file=sys.stderr)
279 self.exception = (e, buildError, tb) # type, value, traceback
280 self.do_failed(buildError.exitstatus)
284 def postprocess(self):
287 for tp in self.options.tree_printers:
289 if self.options.debug_includes:
290 tree = t.render_include_tree()
294 SCons.Taskmaster.OutOfDateTask.postprocess(self)
296 def make_ready(self):
297 """Make a task ready for execution"""
298 SCons.Taskmaster.OutOfDateTask.make_ready(self)
299 if self.out_of_date and self.options.debug_explain:
300 explanation = self.out_of_date[0].explain()
302 sys.stdout.write("scons: " + explanation)
304 class CleanTask(SCons.Taskmaster.AlwaysTask):
305 """An SCons clean task."""
306 def fs_delete(self, path, pathstr, remove=1):
308 if os.path.exists(path):
309 if os.path.isfile(path):
310 if remove: os.unlink(path)
311 display("Removed " + pathstr)
312 elif os.path.isdir(path) and not os.path.islink(path):
313 # delete everything in the dir
314 entries = os.listdir(path)
315 # Sort for deterministic output (os.listdir() Can
316 # return entries in a random order).
319 p = os.path.join(path, e)
320 s = os.path.join(pathstr, e)
321 if os.path.isfile(p):
322 if remove: os.unlink(p)
323 display("Removed " + s)
325 self.fs_delete(p, s, remove)
326 # then delete dir itself
327 if remove: os.rmdir(path)
328 display("Removed directory " + pathstr)
329 except (IOError, OSError), e:
330 print "scons: Could not remove '%s':" % pathstr, e.strerror
333 target = self.targets[0]
334 if (target.has_builder() or target.side_effect) and not target.noclean:
335 for t in self.targets:
337 display("Removed " + str(t))
338 if SCons.Environment.CleanTargets.has_key(target):
339 files = SCons.Environment.CleanTargets[target]
341 self.fs_delete(f.abspath, str(f), 0)
344 target = self.targets[0]
345 if (target.has_builder() or target.side_effect) and not target.noclean:
346 for t in self.targets:
350 # An OSError may indicate something like a permissions
351 # issue, an IOError would indicate something like
352 # the file not existing. In either case, print a
353 # message and keep going to try to remove as many
354 # targets aa possible.
355 print "scons: Could not remove '%s':" % str(t), e.strerror
358 display("Removed " + str(t))
359 if SCons.Environment.CleanTargets.has_key(target):
360 files = SCons.Environment.CleanTargets[target]
362 self.fs_delete(f.abspath, str(f))
366 # We want the Taskmaster to update the Node states (and therefore
367 # handle reference counts, etc.), but we don't want to call
368 # back to the Node's post-build methods, which would do things
369 # we don't want, like store .sconsign information.
370 executed = SCons.Taskmaster.Task.executed_without_callbacks
372 # Have the taskmaster arrange to "execute" all of the targets, because
373 # we'll figure out ourselves (in remove() or show() above) whether
374 # anything really needs to be done.
375 make_ready = SCons.Taskmaster.Task.make_ready_all
380 class QuestionTask(SCons.Taskmaster.AlwaysTask):
381 """An SCons task for the -q (question) option."""
386 if self.targets[0].get_state() != SCons.Node.up_to_date or \
387 (self.top and not self.targets[0].exists()):
389 global this_build_status
391 this_build_status = 1
399 def __init__(self, derived=False, prune=False, status=False):
400 self.derived = derived
403 def get_all_children(self, node):
404 return node.all_children()
405 def get_derived_children(self, node):
406 children = node.all_children(None)
407 return filter(lambda x: x.has_builder(), children)
408 def display(self, t):
410 func = self.get_derived_children
412 func = self.get_all_children
413 s = self.status and 2 or 0
414 SCons.Util.print_tree(t, func, prune=self.prune, showtags=s)
417 def python_version_string():
418 return string.split(sys.version)[0]
420 def python_version_unsupported(version=sys.version_info):
421 return version < (1, 5, 2)
423 def python_version_deprecated(version=sys.version_info):
424 return version < (2, 2, 0)
434 cumulative_command_time = 0
435 exit_status = 0 # final exit status, assume success by default
436 this_build_status = 0 # "exit status" of an individual build
438 delayed_warnings = []
440 class FakeOptionParser:
442 A do-nothing option parser, used for the initial OptionsParser variable.
444 During normal SCons operation, the OptionsParser is created right
445 away by the main() function. Certain tests scripts however, can
446 introspect on different Tool modules, the initialization of which
447 can try to add a new, local option to an otherwise uninitialized
448 OptionsParser object. This allows that introspection to happen
452 class FakeOptionValues:
453 def __getattr__(self, attr):
455 values = FakeOptionValues()
456 def add_local_option(self, *args, **kw):
459 OptionsParser = FakeOptionParser()
461 def AddOption(*args, **kw):
462 if not kw.has_key('default'):
464 result = apply(OptionsParser.add_local_option, args, kw)
468 return getattr(OptionsParser.values, name)
470 def SetOption(name, value):
471 return OptionsParser.values.set_option(name, value)
478 self.append = self.do_nothing
479 self.print_stats = self.do_nothing
480 def enable(self, outfp):
482 self.append = self.do_append
483 self.print_stats = self.do_print
484 def do_nothing(self, *args, **kw):
487 class CountStats(Stats):
488 def do_append(self, label):
489 self.labels.append(label)
490 self.stats.append(SCons.Debug.fetchLoggedInstances())
494 for n in map(lambda t: t[0], s):
495 stats_table[n] = [0, 0, 0, 0]
499 stats_table[n][i] = c
501 keys = stats_table.keys()
503 self.outfp.write("Object counts:\n")
507 fmt1 = string.join(pre + [' %7s']*l + post, '')
508 fmt2 = string.join(pre + [' %7d']*l + post, '')
509 labels = self.labels[:l]
510 labels.append(("", "Class"))
511 self.outfp.write(fmt1 % tuple(map(lambda x: x[0], labels)))
512 self.outfp.write(fmt1 % tuple(map(lambda x: x[1], labels)))
514 r = stats_table[k][:l] + [k]
515 self.outfp.write(fmt2 % tuple(r))
517 count_stats = CountStats()
519 class MemStats(Stats):
520 def do_append(self, label):
521 self.labels.append(label)
522 self.stats.append(SCons.Debug.memory())
524 fmt = 'Memory %-32s %12d\n'
525 for label, stats in map(None, self.labels, self.stats):
526 self.outfp.write(fmt % (label, stats))
528 memory_stats = MemStats()
532 def _scons_syntax_error(e):
533 """Handle syntax errors. Print out a message and show where the error
536 etype, value, tb = sys.exc_info()
537 lines = traceback.format_exception_only(etype, value)
539 sys.stderr.write(line+'\n')
542 def find_deepest_user_frame(tb):
544 Find the deepest stack frame that is not part of SCons.
546 Input is a "pre-processed" stack trace in the form
547 returned by traceback.extract_tb() or traceback.extract_stack()
552 # find the deepest traceback frame that is not part
556 if string.find(filename, os.sep+'SCons'+os.sep) == -1:
560 def _scons_user_error(e):
561 """Handle user errors. Print out a message and a description of the
562 error, along with the line number and routine where it occured.
563 The file and line number will be the deepest stack frame that is
564 not part of SCons itself.
566 global print_stacktrace
567 etype, value, tb = sys.exc_info()
569 traceback.print_exception(etype, value, tb)
570 filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_tb(tb))
571 sys.stderr.write("\nscons: *** %s\n" % value)
572 sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
575 def _scons_user_warning(e):
576 """Handle user warnings. Print out a message and a description of
577 the warning, along with the line number and routine where it occured.
578 The file and line number will be the deepest stack frame that is
579 not part of SCons itself.
581 etype, value, tb = sys.exc_info()
582 filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_tb(tb))
583 sys.stderr.write("\nscons: warning: %s\n" % e)
584 sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
586 def _scons_internal_warning(e):
587 """Slightly different from _scons_user_warning in that we use the
588 *current call stack* rather than sys.exc_info() to get our stack trace.
589 This is used by the warnings framework to print warnings."""
590 filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_stack())
591 sys.stderr.write("\nscons: warning: %s\n" % e[0])
592 sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
594 def _scons_internal_error():
595 """Handle all errors but user errors. Print out a message telling
596 the user what to do in this case and print a normal trace.
598 print 'internal error'
599 traceback.print_exc()
602 def _SConstruct_exists(dirname='', repositories=[], filelist=None):
603 """This function checks that an SConstruct file exists in a directory.
604 If so, it returns the path of the file. By default, it checks the
608 filelist = ['SConstruct', 'Sconstruct', 'sconstruct']
609 for file in filelist:
610 sfile = os.path.join(dirname, file)
611 if os.path.isfile(sfile):
613 if not os.path.isabs(sfile):
614 for rep in repositories:
615 if os.path.isfile(os.path.join(rep, sfile)):
619 def _set_debug_values(options):
620 global print_memoizer, print_objects, print_stacktrace, print_time
622 debug_values = options.debug
624 if "count" in debug_values:
625 # All of the object counts are within "if __debug__:" blocks,
626 # which get stripped when running optimized (with python -O or
627 # from compiled *.pyo files). Provide a warning if __debug__ is
628 # stripped, so it doesn't just look like --debug=count is broken.
630 if __debug__: enable_count = True
632 count_stats.enable(sys.stdout)
634 msg = "--debug=count is not supported when running SCons\n" + \
635 "\twith the python -O option or optimized (.pyo) modules."
636 SCons.Warnings.warn(SCons.Warnings.NoObjectCountWarning, msg)
637 if "dtree" in debug_values:
638 options.tree_printers.append(TreePrinter(derived=True))
639 options.debug_explain = ("explain" in debug_values)
640 if "findlibs" in debug_values:
641 SCons.Scanner.Prog.print_find_libs = "findlibs"
642 options.debug_includes = ("includes" in debug_values)
643 print_memoizer = ("memoizer" in debug_values)
644 if "memory" in debug_values:
645 memory_stats.enable(sys.stdout)
646 print_objects = ("objects" in debug_values)
647 if "presub" in debug_values:
648 SCons.Action.print_actions_presub = 1
649 if "stacktrace" in debug_values:
651 if "stree" in debug_values:
652 options.tree_printers.append(TreePrinter(status=True))
653 if "time" in debug_values:
655 if "tree" in debug_values:
656 options.tree_printers.append(TreePrinter())
658 def _create_path(plist):
664 path = path + '/' + d
667 def _load_site_scons_dir(topdir, site_dir_name=None):
668 """Load the site_scons dir under topdir.
669 Adds site_scons to sys.path, imports site_scons/site_init.py,
670 and adds site_scons/site_tools to default toolpath."""
672 err_if_not_found = True # user specified: err if missing
674 site_dir_name = "site_scons"
675 err_if_not_found = False
677 site_dir = os.path.join(topdir.path, site_dir_name)
678 if not os.path.exists(site_dir):
680 raise SCons.Errors.UserError, "site dir %s not found."%site_dir
683 site_init_filename = "site_init.py"
684 site_init_modname = "site_init"
685 site_tools_dirname = "site_tools"
686 sys.path = [os.path.abspath(site_dir)] + sys.path
687 site_init_file = os.path.join(site_dir, site_init_filename)
688 site_tools_dir = os.path.join(site_dir, site_tools_dirname)
689 if os.path.exists(site_init_file):
692 fp, pathname, description = imp.find_module(site_init_modname,
695 imp.load_module(site_init_modname, fp, pathname, description)
699 except ImportError, e:
700 sys.stderr.write("Can't import site init file '%s': %s\n"%(site_init_file, e))
703 sys.stderr.write("Site init file '%s' raised exception: %s\n"%(site_init_file, e))
705 if os.path.exists(site_tools_dir):
706 SCons.Tool.DefaultToolpath.append(os.path.abspath(site_tools_dir))
708 def version_string(label, module):
709 version = module.__version__
710 build = module.__build__
714 version = version + build
715 fmt = "\t%s: v%s, %s, by %s on %s\n"
719 module.__developer__,
724 global this_build_status
726 options = parser.values
728 # Here's where everything really happens.
730 # First order of business: set up default warnings and then
731 # handle the user's warning options, so that we can issue (or
732 # suppress) appropriate warnings about anything that might happen,
733 # as configured by the user.
735 default_warnings = [ SCons.Warnings.CorruptSConsignWarning,
736 SCons.Warnings.DeprecatedWarning,
737 SCons.Warnings.DuplicateEnvironmentWarning,
738 SCons.Warnings.FutureReservedVariableWarning,
739 SCons.Warnings.LinkWarning,
740 SCons.Warnings.MissingSConscriptWarning,
741 SCons.Warnings.NoMD5ModuleWarning,
742 SCons.Warnings.NoMetaclassSupportWarning,
743 SCons.Warnings.NoObjectCountWarning,
744 SCons.Warnings.NoParallelSupportWarning,
745 SCons.Warnings.MisleadingKeywordsWarning,
746 SCons.Warnings.ReservedVariableWarning,
747 SCons.Warnings.StackSizeWarning,
750 for warning in default_warnings:
751 SCons.Warnings.enableWarningClass(warning)
752 SCons.Warnings._warningOut = _scons_internal_warning
753 SCons.Warnings.process_warn_strings(options.warn)
755 # Now that we have the warnings configuration set up, we can actually
756 # issue (or suppress) any warnings about warning-worthy things that
757 # occurred while the command-line options were getting parsed.
759 dw = options.delayed_warnings
760 except AttributeError:
763 delayed_warnings.extend(dw)
764 for warning_type, message in delayed_warnings:
765 SCons.Warnings.warn(warning_type, message)
767 if options.diskcheck:
768 SCons.Node.FS.set_diskcheck(options.diskcheck)
770 # Next, we want to create the FS object that represents the outside
771 # world's file system, as that's central to a lot of initialization.
772 # To do this, however, we need to be in the directory from which we
773 # want to start everything, which means first handling any relevant
774 # options that might cause us to chdir somewhere (-C, -D, -U, -u).
775 if options.directory:
776 cdir = _create_path(options.directory)
780 sys.stderr.write("Could not change directory to %s\n" % cdir)
784 target_top = '.' # directory to prepend to targets
785 script_dir = os.getcwd() # location of script
786 while script_dir and not _SConstruct_exists(script_dir,
789 script_dir, last_part = os.path.split(script_dir)
791 target_top = os.path.join(last_part, target_top)
794 if script_dir and script_dir != os.getcwd():
795 display("scons: Entering directory `%s'" % script_dir)
798 # Now that we're in the top-level SConstruct directory, go ahead
799 # and initialize the FS object that represents the file system,
800 # and make it the build engine default.
801 fs = SCons.Node.FS.get_default_fs()
803 for rep in options.repository:
806 # Now that we have the FS object, the next order of business is to
807 # check for an SConstruct file (or other specified config file).
808 # If there isn't one, we can bail before doing any more work.
811 scripts.extend(options.file)
813 sfile = _SConstruct_exists(repositories=options.repository,
814 filelist=options.file)
816 scripts.append(sfile)
820 # There's no SConstruct, but they specified -h.
821 # Give them the options usage now, before we fail
822 # trying to read a non-existent SConstruct file.
823 raise SConsPrintHelpException
824 raise SCons.Errors.UserError, "No SConstruct file found."
826 if scripts[0] == "-":
829 d = fs.File(scripts[0]).dir
830 fs.set_SConstruct_dir(d)
832 _set_debug_values(options)
833 SCons.Node.implicit_cache = options.implicit_cache
834 SCons.Node.implicit_deps_changed = options.implicit_deps_changed
835 SCons.Node.implicit_deps_unchanged = options.implicit_deps_unchanged
838 SCons.SConf.dryrun = 1
839 SCons.Action.execute_actions = None
841 SCons.SConf.dryrun = 1
843 SCons.SConf.SetBuildType('clean')
845 SCons.SConf.SetBuildType('help')
846 SCons.SConf.SetCacheMode(options.config)
847 SCons.SConf.SetProgressDisplay(progress_display)
849 if options.no_progress or options.silent:
850 progress_display.set_mode(0)
853 _load_site_scons_dir(d, options.site_dir)
854 elif not options.no_site_dir:
855 _load_site_scons_dir(d)
857 if options.include_dir:
858 sys.path = options.include_dir + sys.path
860 # That should cover (most of) the options. Next, set up the variables
861 # that hold command-line arguments, so the SConscript files that we
862 # read and execute have access to them.
865 for a in parser.largs:
872 SCons.Script._Add_Targets(targets + parser.rargs)
873 SCons.Script._Add_Arguments(xmit_args)
875 # If stdout is not a tty, replace it with a wrapper object to call flush
878 # Tty devices automatically flush after every newline, so the replacement
879 # isn't necessary. Furthermore, if we replace sys.stdout, the readline
880 # module will no longer work. This affects the behavior during
881 # --interactive mode. --interactive should only be used when stdin and
882 # stdout refer to a tty.
883 if not sys.stdout.isatty():
884 sys.stdout = SCons.Util.Unbuffered(sys.stdout)
885 if not sys.stderr.isatty():
886 sys.stderr = SCons.Util.Unbuffered(sys.stderr)
888 memory_stats.append('before reading SConscript files:')
889 count_stats.append(('pre-', 'read'))
891 # And here's where we (finally) read the SConscript files.
893 progress_display("scons: Reading SConscript files ...")
895 start_time = time.time()
897 for script in scripts:
898 SCons.Script._SConscript._SConscript(fs, script)
899 except SCons.Errors.StopError, e:
900 # We had problems reading an SConscript file, such as it
901 # couldn't be copied in to the VariantDir. Since we're just
902 # reading SConscript files and haven't started building
903 # things yet, stop regardless of whether they used -i or -k
905 sys.stderr.write("scons: *** %s Stop.\n" % e)
907 sys.exit(exit_status)
908 global sconscript_time
909 sconscript_time = time.time() - start_time
911 progress_display("scons: done reading SConscript files.")
913 memory_stats.append('after reading SConscript files:')
914 count_stats.append(('post-', 'read'))
916 # Re-{enable,disable} warnings in case they disabled some in
917 # the SConscript file.
919 # We delay enabling the PythonVersionWarning class until here so that,
920 # if they explicity disabled it in either in the command line or in
921 # $SCONSFLAGS, or in the SConscript file, then the search through
922 # the list of deprecated warning classes will find that disabling
923 # first and not issue the warning.
924 SCons.Warnings.enableWarningClass(SCons.Warnings.PythonVersionWarning)
925 SCons.Warnings.process_warn_strings(options.warn)
927 # Now that we've read the SConscript files, we can check for the
928 # warning about deprecated Python versions--delayed until here
929 # in case they disabled the warning in the SConscript files.
930 if python_version_deprecated():
931 msg = "Support for pre-2.2 Python (%s) is deprecated.\n" + \
932 " If this will cause hardship, contact dev@scons.tigris.org."
933 SCons.Warnings.warn(SCons.Warnings.PythonVersionWarning,
934 msg % python_version_string())
937 SCons.SConf.CreateConfigHBuilder(SCons.Defaults.DefaultEnvironment())
939 # Now re-parse the command-line options (any to the left of a '--'
940 # argument, that is) with any user-defined command-line options that
941 # the SConscript files may have added to the parser object. This will
942 # emit the appropriate error message and exit if any unknown option
943 # was specified on the command line.
945 parser.preserve_unknown_options = False
946 parser.parse_args(parser.largs, options)
949 help_text = SCons.Script.help_text
950 if help_text is None:
951 # They specified -h, but there was no Help() inside the
952 # SConscript files. Give them the options usage.
953 raise SConsPrintHelpException
956 print "Use scons -H for help about command-line options."
960 # Change directory to the top-level SConstruct directory, then tell
961 # the Node.FS subsystem that we're all done reading the SConscript
962 # files and calling Repository() and VariantDir() and changing
963 # directories and the like, so it can go ahead and start memoizing
964 # the string values of file system nodes.
968 SCons.Node.FS.save_strings(1)
970 # Now that we've read the SConscripts we can set the options
971 # that are SConscript settable:
972 SCons.Node.implicit_cache = options.implicit_cache
973 SCons.Node.FS.set_duplicate(options.duplicate)
974 fs.set_max_drift(options.max_drift)
976 SCons.Job.explicit_stack_size = options.stack_size
978 if options.md5_chunksize:
979 SCons.Node.FS.File.md5_chunksize = options.md5_chunksize
981 platform = SCons.Platform.platform_module()
983 if options.interactive:
984 SCons.Script.Interactive.interact(fs, OptionsParser, options,
990 nodes = _build_targets(fs, options, targets, target_top)
994 def _build_targets(fs, options, targets, target_top):
996 global this_build_status
997 this_build_status = 0
999 progress_display.set_mode(not (options.no_progress or options.silent))
1000 display.set_mode(not options.silent)
1001 SCons.Action.print_actions = not options.silent
1002 SCons.Action.execute_actions = not options.no_exec
1003 SCons.Node.FS.do_store_info = not options.no_exec
1004 SCons.SConf.dryrun = options.no_exec
1006 if options.diskcheck:
1007 SCons.Node.FS.set_diskcheck(options.diskcheck)
1009 SCons.CacheDir.cache_enabled = not options.cache_disable
1010 SCons.CacheDir.cache_debug = options.cache_debug
1011 SCons.CacheDir.cache_force = options.cache_force
1012 SCons.CacheDir.cache_show = options.cache_show
1015 CleanTask.execute = CleanTask.show
1017 CleanTask.execute = CleanTask.remove
1020 if targets or SCons.Script.BUILD_TARGETS != SCons.Script._build_plus_default:
1021 # They specified targets on the command line or modified
1022 # BUILD_TARGETS in the SConscript file(s), so if they used -u,
1023 # -U or -D, we have to look up targets relative to the top,
1024 # but we build whatever they specified.
1026 lookup_top = fs.Dir(target_top)
1029 targets = SCons.Script.BUILD_TARGETS
1031 # There are no targets specified on the command line,
1032 # so if they used -u, -U or -D, we may have to restrict
1033 # what actually gets built.
1036 if options.climb_up == 1:
1037 # -u, local directory and below
1038 target_top = fs.Dir(target_top)
1039 lookup_top = target_top
1040 elif options.climb_up == 2:
1041 # -D, all Default() targets
1044 elif options.climb_up == 3:
1045 # -U, local SConscript Default() targets
1046 target_top = fs.Dir(target_top)
1047 def check_dir(x, target_top=target_top):
1048 if hasattr(x, 'cwd') and not x.cwd is None:
1049 cwd = x.cwd.srcnode()
1050 return cwd == target_top
1052 # x doesn't have a cwd, so it's either not a target,
1053 # or not a file, so go ahead and keep it as a default
1054 # target and let the engine sort it out:
1056 d = filter(check_dir, SCons.Script.DEFAULT_TARGETS)
1057 SCons.Script.DEFAULT_TARGETS[:] = d
1061 targets = SCons.Script._Get_Default_Targets(d, fs)
1064 sys.stderr.write("scons: *** No targets specified and no Default() targets found. Stop.\n")
1067 def Entry(x, ltop=lookup_top, ttop=target_top, fs=fs):
1068 if isinstance(x, SCons.Node.Node):
1072 # Why would ltop be None? Unfortunately this happens.
1073 if ltop == None: ltop = ''
1074 # Curdir becomes important when SCons is called with -u, -C,
1075 # or similar option that changes directory, and so the paths
1076 # of targets given on the command line need to be adjusted.
1077 curdir = os.path.join(os.getcwd(), str(ltop))
1078 for lookup in SCons.Node.arg2nodes_lookups:
1079 node = lookup(x, curdir=curdir)
1083 node = fs.Entry(x, directory=ltop, create=1)
1084 if ttop and not node.is_under(ttop):
1085 if isinstance(node, SCons.Node.FS.Dir) and ttop.is_under(node):
1091 nodes = filter(None, map(Entry, targets))
1093 task_class = BuildTask # default action is to build targets
1094 opening_message = "Building targets ..."
1095 closing_message = "done building targets."
1096 if options.keep_going:
1097 failure_message = "done building targets (errors occurred during build)."
1099 failure_message = "building terminated because of errors."
1100 if options.question:
1101 task_class = QuestionTask
1104 task_class = CleanTask
1105 opening_message = "Cleaning targets ..."
1106 closing_message = "done cleaning targets."
1107 if options.keep_going:
1108 failure_message = "done cleaning targets (errors occurred during clean)."
1110 failure_message = "cleaning terminated because of errors."
1111 except AttributeError:
1114 task_class.progress = ProgressObject
1117 def order(dependencies):
1118 """Randomize the dependencies."""
1120 # This is cribbed from the implementation of
1121 # random.shuffle() in Python 2.X.
1123 for i in xrange(len(d)-1, 0, -1):
1124 j = int(random.random() * (i+1))
1125 d[i], d[j] = d[j], d[i]
1128 def order(dependencies):
1129 """Leave the order of dependencies alone."""
1132 if options.taskmastertrace_file == '-':
1133 tmtrace = sys.stdout
1134 elif options.taskmastertrace_file:
1135 tmtrace = open(options.taskmastertrace_file, 'wb')
1138 taskmaster = SCons.Taskmaster.Taskmaster(nodes, task_class, order, tmtrace)
1140 # Let the BuildTask objects get at the options to respond to the
1141 # various print_* settings, tree_printer list, etc.
1142 BuildTask.options = options
1145 num_jobs = options.num_jobs
1146 jobs = SCons.Job.Jobs(num_jobs, taskmaster)
1149 if jobs.num_jobs == 1:
1150 msg = "parallel builds are unsupported by this version of Python;\n" + \
1151 "\tignoring -j or num_jobs option.\n"
1152 elif sys.platform == 'win32':
1153 msg = fetch_win32_parallel_msg()
1155 SCons.Warnings.warn(SCons.Warnings.NoParallelSupportWarning, msg)
1157 memory_stats.append('before building targets:')
1158 count_stats.append(('pre-', 'build'))
1163 closing_message=closing_message,
1164 failure_message=failure_message
1166 if jobs.were_interrupted():
1167 if not options.no_progress and not options.silent:
1168 sys.stderr.write("scons: Build interrupted.\n")
1170 global this_build_status
1172 this_build_status = 2
1174 if this_build_status:
1175 progress_display("scons: " + failure_message)
1177 progress_display("scons: " + closing_message)
1178 if not options.no_exec:
1179 if jobs.were_interrupted():
1180 progress_display("scons: writing .sconsign file.")
1181 SCons.SConsign.write()
1183 progress_display("scons: " + opening_message)
1184 jobs.run(postfunc = jobs_postfunc)
1186 memory_stats.append('after building targets:')
1187 count_stats.append(('post-', 'build'))
1191 def _exec_main(parser, values):
1192 sconsflags = os.environ.get('SCONSFLAGS', '')
1193 all_args = string.split(sconsflags) + sys.argv[1:]
1195 options, args = parser.parse_args(all_args, values)
1197 if type(options.debug) == type([]) and "pdb" in options.debug:
1199 pdb.Pdb().runcall(_main, parser)
1200 elif options.profile_file:
1202 from cProfile import Profile
1203 except ImportError, e:
1204 from profile import Profile
1206 # Some versions of Python 2.4 shipped a profiler that had the
1207 # wrong 'c_exception' entry in its dispatch table. Make sure
1208 # we have the right one. (This may put an unnecessary entry
1209 # in the table in earlier versions of Python, but its presence
1210 # shouldn't hurt anything).
1212 dispatch = Profile.dispatch
1213 except AttributeError:
1216 dispatch['c_exception'] = Profile.trace_dispatch_return
1220 prof.runcall(_main, parser)
1221 except SConsPrintHelpException, e:
1222 prof.dump_stats(options.profile_file)
1226 prof.dump_stats(options.profile_file)
1231 global OptionsParser
1233 global first_command_start
1235 # Check up front for a Python version we do not support. We
1236 # delay the check for deprecated Python versions until later,
1237 # after the SConscript files have been read, in case they
1238 # disable that warning.
1239 if python_version_unsupported():
1240 msg = "scons: *** SCons version %s does not run under Python version %s.\n"
1241 sys.stderr.write(msg % (SCons.__version__, python_version_string()))
1244 parts = ["SCons by Steven Knight et al.:\n"]
1247 parts.append(version_string("script", __main__))
1248 except (ImportError, AttributeError):
1249 # On Windows there is no scons.py, so there is no
1250 # __main__.__version__, hence there is no script version.
1252 parts.append(version_string("engine", SCons))
1253 parts.append("__COPYRIGHT__")
1254 version = string.join(parts, '')
1257 parser = SConsOptions.Parser(version)
1258 values = SConsOptions.SConsValues(parser.get_default_values())
1260 OptionsParser = parser
1263 _exec_main(parser, values)
1264 except SystemExit, s:
1267 except KeyboardInterrupt:
1268 print("scons: Build interrupted.")
1270 except SyntaxError, e:
1271 _scons_syntax_error(e)
1272 except SCons.Errors.InternalError:
1273 _scons_internal_error()
1274 except SCons.Errors.UserError, e:
1275 _scons_user_error(e)
1276 except SConsPrintHelpException:
1279 except SCons.Errors.BuildError, e:
1280 exit_status = e.exitstatus
1282 # An exception here is likely a builtin Python exception Python
1283 # code in an SConscript file. Show them precisely what the
1284 # problem was and where it happened.
1285 SCons.Script._SConscript.SConscript_exception()
1288 memory_stats.print_stats()
1289 count_stats.print_stats()
1292 SCons.Debug.listLoggedInstances('*')
1293 #SCons.Debug.dumpLoggedInstances('*')
1296 SCons.Memoize.Dump("Memoizer (memory cache) hits and misses:")
1298 # Dump any development debug info that may have been enabled.
1299 # These are purely for internal debugging during development, so
1300 # there's no need to control them with --debug= options; they're
1301 # controlled by changing the source code.
1302 SCons.Debug.dump_caller_counts()
1303 SCons.Taskmaster.dump_stats()
1306 total_time = time.time() - SCons.Script.start_time
1308 ct = cumulative_command_time
1310 if last_command_end is None or first_command_start is None:
1313 ct = last_command_end - first_command_start
1314 scons_time = total_time - sconscript_time - ct
1315 print "Total build time: %f seconds"%total_time
1316 print "Total SConscript file execution time: %f seconds"%sconscript_time
1317 print "Total SCons execution time: %f seconds"%scons_time
1318 print "Total command execution time: %f seconds"%ct
1320 sys.exit(exit_status)
1324 # indent-tabs-mode:nil
1326 # vim: set expandtab tabstop=4 shiftwidth=4: