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,
15 # Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008 The SCons Foundation
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__ = "src/engine/SCons/Script/Main.py 3842 2008/12/20 22:59:52 scons"
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.Task):
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.Task.prepare(self)
169 def needs_execute(self):
170 target = self.targets[0]
171 if target.get_state() == SCons.Node.executing:
174 if self.top and target.has_builder():
175 display("scons: `%s' is up to date." % str(self.node))
180 start_time = time.time()
181 global first_command_start
182 if first_command_start is None:
183 first_command_start = start_time
184 SCons.Taskmaster.Task.execute(self)
186 global cumulative_command_time
187 global last_command_end
188 finish_time = time.time()
189 last_command_end = finish_time
190 cumulative_command_time = cumulative_command_time+finish_time-start_time
191 sys.stdout.write("Command execution time: %f seconds\n"%(finish_time-start_time))
193 def do_failed(self, status=2):
194 _BuildFailures.append(self.exception[1])
196 global this_build_status
197 if self.options.ignore_errors:
198 SCons.Taskmaster.Task.executed(self)
199 elif self.options.keep_going:
200 SCons.Taskmaster.Task.fail_continue(self)
202 this_build_status = status
204 SCons.Taskmaster.Task.fail_stop(self)
206 this_build_status = status
210 if self.top and not t.has_builder() and not t.side_effect:
212 errstr="Do not know how to make target `%s'." % t
213 sys.stderr.write("scons: *** " + errstr)
214 if not self.options.keep_going:
215 sys.stderr.write(" Stop.")
216 sys.stderr.write("\n")
218 raise SCons.Errors.BuildError(t, errstr)
219 except KeyboardInterrupt:
225 print "scons: Nothing to be done for `%s'." % t
226 SCons.Taskmaster.Task.executed(self)
228 SCons.Taskmaster.Task.executed(self)
231 # Handle the failure of a build task. The primary purpose here
232 # is to display the various types of Errors and Exceptions
234 exc_info = self.exc_info()
242 # The Taskmaster didn't record an exception for this Task;
243 # see if the sys module has one.
245 t, e, tb = sys.exc_info()[:]
250 # Deprecated string exceptions will have their string stored
251 # in the first entry of the tuple.
255 buildError = SCons.Errors.convert_to_BuildError(e)
256 if not buildError.node:
257 buildError.node = self.node
259 node = buildError.node
260 if not SCons.Util.is_List(node):
262 nodename = string.join(map(str, node), ', ')
264 errfmt = "scons: *** [%s] %s\n"
265 sys.stderr.write(errfmt % (nodename, buildError))
267 if (buildError.exc_info[2] and buildError.exc_info[1] and
270 # buildError.exc_info[1],
271 # (EnvironmentError, SCons.Errors.StopError, SCons.Errors.UserError))):
272 not isinstance(buildError.exc_info[1], EnvironmentError) and
273 not isinstance(buildError.exc_info[1], SCons.Errors.StopError) and
274 not isinstance(buildError.exc_info[1], SCons.Errors.UserError)):
275 type, value, trace = buildError.exc_info
276 traceback.print_exception(type, value, trace)
277 elif tb and print_stacktrace:
278 sys.stderr.write("scons: internal stack trace:\n")
279 traceback.print_tb(tb, file=sys.stderr)
281 self.exception = (e, buildError, tb) # type, value, traceback
282 self.do_failed(buildError.exitstatus)
286 def postprocess(self):
289 for tp in self.options.tree_printers:
291 if self.options.debug_includes:
292 tree = t.render_include_tree()
296 SCons.Taskmaster.Task.postprocess(self)
298 def make_ready(self):
299 """Make a task ready for execution"""
300 SCons.Taskmaster.Task.make_ready(self)
301 if self.out_of_date and self.options.debug_explain:
302 explanation = self.out_of_date[0].explain()
304 sys.stdout.write("scons: " + explanation)
306 class CleanTask(SCons.Taskmaster.Task):
307 """An SCons clean task."""
308 def fs_delete(self, path, pathstr, remove=1):
310 if os.path.exists(path):
311 if os.path.isfile(path):
312 if remove: os.unlink(path)
313 display("Removed " + pathstr)
314 elif os.path.isdir(path) and not os.path.islink(path):
315 # delete everything in the dir
316 entries = os.listdir(path)
317 # Sort for deterministic output (os.listdir() Can
318 # return entries in a random order).
321 p = os.path.join(path, e)
322 s = os.path.join(pathstr, e)
323 if os.path.isfile(p):
324 if remove: os.unlink(p)
325 display("Removed " + s)
327 self.fs_delete(p, s, remove)
328 # then delete dir itself
329 if remove: os.rmdir(path)
330 display("Removed directory " + pathstr)
331 except (IOError, OSError), e:
332 print "scons: Could not remove '%s':" % pathstr, e.strerror
335 target = self.targets[0]
336 if (target.has_builder() or target.side_effect) and not target.noclean:
337 for t in self.targets:
339 display("Removed " + str(t))
340 if SCons.Environment.CleanTargets.has_key(target):
341 files = SCons.Environment.CleanTargets[target]
343 self.fs_delete(f.abspath, str(f), 0)
346 target = self.targets[0]
347 if (target.has_builder() or target.side_effect) and not target.noclean:
348 for t in self.targets:
352 # An OSError may indicate something like a permissions
353 # issue, an IOError would indicate something like
354 # the file not existing. In either case, print a
355 # message and keep going to try to remove as many
356 # targets aa possible.
357 print "scons: Could not remove '%s':" % str(t), e.strerror
360 display("Removed " + str(t))
361 if SCons.Environment.CleanTargets.has_key(target):
362 files = SCons.Environment.CleanTargets[target]
364 self.fs_delete(f.abspath, str(f))
368 # We want the Taskmaster to update the Node states (and therefore
369 # handle reference counts, etc.), but we don't want to call
370 # back to the Node's post-build methods, which would do things
371 # we don't want, like store .sconsign information.
372 executed = SCons.Taskmaster.Task.executed_without_callbacks
374 # Have the taskmaster arrange to "execute" all of the targets, because
375 # we'll figure out ourselves (in remove() or show() above) whether
376 # anything really needs to be done.
377 make_ready = SCons.Taskmaster.Task.make_ready_all
382 class QuestionTask(SCons.Taskmaster.Task):
383 """An SCons task for the -q (question) option."""
388 if self.targets[0].get_state() != SCons.Node.up_to_date or \
389 (self.top and not self.targets[0].exists()):
391 global this_build_status
393 this_build_status = 1
401 def __init__(self, derived=False, prune=False, status=False):
402 self.derived = derived
405 def get_all_children(self, node):
406 return node.all_children()
407 def get_derived_children(self, node):
408 children = node.all_children(None)
409 return filter(lambda x: x.has_builder(), children)
410 def display(self, t):
412 func = self.get_derived_children
414 func = self.get_all_children
415 s = self.status and 2 or 0
416 SCons.Util.print_tree(t, func, prune=self.prune, showtags=s)
419 def python_version_string():
420 return string.split(sys.version)[0]
422 def python_version_unsupported(version=sys.version_info):
423 return version < (1, 5, 2)
425 def python_version_deprecated(version=sys.version_info):
426 return version < (2, 2, 0)
436 cumulative_command_time = 0
437 exit_status = 0 # final exit status, assume success by default
438 this_build_status = 0 # "exit status" of an individual build
440 delayed_warnings = []
442 class FakeOptionParser:
444 A do-nothing option parser, used for the initial OptionsParser variable.
446 During normal SCons operation, the OptionsParser is created right
447 away by the main() function. Certain tests scripts however, can
448 introspect on different Tool modules, the initialization of which
449 can try to add a new, local option to an otherwise uninitialized
450 OptionsParser object. This allows that introspection to happen
454 class FakeOptionValues:
455 def __getattr__(self, attr):
457 values = FakeOptionValues()
458 def add_local_option(self, *args, **kw):
461 OptionsParser = FakeOptionParser()
463 def AddOption(*args, **kw):
464 if not kw.has_key('default'):
466 result = apply(OptionsParser.add_local_option, args, kw)
470 return getattr(OptionsParser.values, name)
472 def SetOption(name, value):
473 return OptionsParser.values.set_option(name, value)
480 self.append = self.do_nothing
481 self.print_stats = self.do_nothing
482 def enable(self, outfp):
484 self.append = self.do_append
485 self.print_stats = self.do_print
486 def do_nothing(self, *args, **kw):
489 class CountStats(Stats):
490 def do_append(self, label):
491 self.labels.append(label)
492 self.stats.append(SCons.Debug.fetchLoggedInstances())
496 for n in map(lambda t: t[0], s):
497 stats_table[n] = [0, 0, 0, 0]
501 stats_table[n][i] = c
503 keys = stats_table.keys()
505 self.outfp.write("Object counts:\n")
509 fmt1 = string.join(pre + [' %7s']*l + post, '')
510 fmt2 = string.join(pre + [' %7d']*l + post, '')
511 labels = self.labels[:l]
512 labels.append(("", "Class"))
513 self.outfp.write(fmt1 % tuple(map(lambda x: x[0], labels)))
514 self.outfp.write(fmt1 % tuple(map(lambda x: x[1], labels)))
516 r = stats_table[k][:l] + [k]
517 self.outfp.write(fmt2 % tuple(r))
519 count_stats = CountStats()
521 class MemStats(Stats):
522 def do_append(self, label):
523 self.labels.append(label)
524 self.stats.append(SCons.Debug.memory())
526 fmt = 'Memory %-32s %12d\n'
527 for label, stats in map(None, self.labels, self.stats):
528 self.outfp.write(fmt % (label, stats))
530 memory_stats = MemStats()
534 def _scons_syntax_error(e):
535 """Handle syntax errors. Print out a message and show where the error
538 etype, value, tb = sys.exc_info()
539 lines = traceback.format_exception_only(etype, value)
541 sys.stderr.write(line+'\n')
544 def find_deepest_user_frame(tb):
546 Find the deepest stack frame that is not part of SCons.
548 Input is a "pre-processed" stack trace in the form
549 returned by traceback.extract_tb() or traceback.extract_stack()
554 # find the deepest traceback frame that is not part
558 if string.find(filename, os.sep+'SCons'+os.sep) == -1:
562 def _scons_user_error(e):
563 """Handle user errors. Print out a message and a description of the
564 error, along with the line number and routine where it occured.
565 The file and line number will be the deepest stack frame that is
566 not part of SCons itself.
568 global print_stacktrace
569 etype, value, tb = sys.exc_info()
571 traceback.print_exception(etype, value, tb)
572 filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_tb(tb))
573 sys.stderr.write("\nscons: *** %s\n" % value)
574 sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
577 def _scons_user_warning(e):
578 """Handle user warnings. Print out a message and a description of
579 the warning, along with the line number and routine where it occured.
580 The file and line number will be the deepest stack frame that is
581 not part of SCons itself.
583 etype, value, tb = sys.exc_info()
584 filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_tb(tb))
585 sys.stderr.write("\nscons: warning: %s\n" % e)
586 sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
588 def _scons_internal_warning(e):
589 """Slightly different from _scons_user_warning in that we use the
590 *current call stack* rather than sys.exc_info() to get our stack trace.
591 This is used by the warnings framework to print warnings."""
592 filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_stack())
593 sys.stderr.write("\nscons: warning: %s\n" % e[0])
594 sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
596 def _scons_internal_error():
597 """Handle all errors but user errors. Print out a message telling
598 the user what to do in this case and print a normal trace.
600 print 'internal error'
601 traceback.print_exc()
604 def _SConstruct_exists(dirname='', repositories=[], filelist=None):
605 """This function checks that an SConstruct file exists in a directory.
606 If so, it returns the path of the file. By default, it checks the
610 filelist = ['SConstruct', 'Sconstruct', 'sconstruct']
611 for file in filelist:
612 sfile = os.path.join(dirname, file)
613 if os.path.isfile(sfile):
615 if not os.path.isabs(sfile):
616 for rep in repositories:
617 if os.path.isfile(os.path.join(rep, sfile)):
621 def _set_debug_values(options):
622 global print_memoizer, print_objects, print_stacktrace, print_time
624 debug_values = options.debug
626 if "count" in debug_values:
627 # All of the object counts are within "if __debug__:" blocks,
628 # which get stripped when running optimized (with python -O or
629 # from compiled *.pyo files). Provide a warning if __debug__ is
630 # stripped, so it doesn't just look like --debug=count is broken.
632 if __debug__: enable_count = True
634 count_stats.enable(sys.stdout)
636 msg = "--debug=count is not supported when running SCons\n" + \
637 "\twith the python -O option or optimized (.pyo) modules."
638 SCons.Warnings.warn(SCons.Warnings.NoObjectCountWarning, msg)
639 if "dtree" in debug_values:
640 options.tree_printers.append(TreePrinter(derived=True))
641 options.debug_explain = ("explain" in debug_values)
642 if "findlibs" in debug_values:
643 SCons.Scanner.Prog.print_find_libs = "findlibs"
644 options.debug_includes = ("includes" in debug_values)
645 print_memoizer = ("memoizer" in debug_values)
646 if "memory" in debug_values:
647 memory_stats.enable(sys.stdout)
648 print_objects = ("objects" in debug_values)
649 if "presub" in debug_values:
650 SCons.Action.print_actions_presub = 1
651 if "stacktrace" in debug_values:
653 if "stree" in debug_values:
654 options.tree_printers.append(TreePrinter(status=True))
655 if "time" in debug_values:
657 if "tree" in debug_values:
658 options.tree_printers.append(TreePrinter())
660 def _create_path(plist):
666 path = path + '/' + d
669 def _load_site_scons_dir(topdir, site_dir_name=None):
670 """Load the site_scons dir under topdir.
671 Adds site_scons to sys.path, imports site_scons/site_init.py,
672 and adds site_scons/site_tools to default toolpath."""
674 err_if_not_found = True # user specified: err if missing
676 site_dir_name = "site_scons"
677 err_if_not_found = False
679 site_dir = os.path.join(topdir.path, site_dir_name)
680 if not os.path.exists(site_dir):
682 raise SCons.Errors.UserError, "site dir %s not found."%site_dir
685 site_init_filename = "site_init.py"
686 site_init_modname = "site_init"
687 site_tools_dirname = "site_tools"
688 sys.path = [os.path.abspath(site_dir)] + sys.path
689 site_init_file = os.path.join(site_dir, site_init_filename)
690 site_tools_dir = os.path.join(site_dir, site_tools_dirname)
691 if os.path.exists(site_init_file):
694 fp, pathname, description = imp.find_module(site_init_modname,
697 imp.load_module(site_init_modname, fp, pathname, description)
701 except ImportError, e:
702 sys.stderr.write("Can't import site init file '%s': %s\n"%(site_init_file, e))
705 sys.stderr.write("Site init file '%s' raised exception: %s\n"%(site_init_file, e))
707 if os.path.exists(site_tools_dir):
708 SCons.Tool.DefaultToolpath.append(os.path.abspath(site_tools_dir))
710 def version_string(label, module):
711 version = module.__version__
712 build = module.__build__
716 version = version + build
717 fmt = "\t%s: v%s, %s, by %s on %s\n"
721 module.__developer__,
726 global this_build_status
728 options = parser.values
730 # Here's where everything really happens.
732 # First order of business: set up default warnings and then
733 # handle the user's warning options, so that we can issue (or
734 # suppress) appropriate warnings about anything that might happen,
735 # as configured by the user.
737 default_warnings = [ SCons.Warnings.CorruptSConsignWarning,
738 SCons.Warnings.DeprecatedWarning,
739 SCons.Warnings.DuplicateEnvironmentWarning,
740 SCons.Warnings.FutureReservedVariableWarning,
741 SCons.Warnings.LinkWarning,
742 SCons.Warnings.MissingSConscriptWarning,
743 SCons.Warnings.NoMD5ModuleWarning,
744 SCons.Warnings.NoMetaclassSupportWarning,
745 SCons.Warnings.NoObjectCountWarning,
746 SCons.Warnings.NoParallelSupportWarning,
747 SCons.Warnings.MisleadingKeywordsWarning,
748 SCons.Warnings.ReservedVariableWarning,
749 SCons.Warnings.StackSizeWarning,
752 for warning in default_warnings:
753 SCons.Warnings.enableWarningClass(warning)
754 SCons.Warnings._warningOut = _scons_internal_warning
755 SCons.Warnings.process_warn_strings(options.warn)
757 # Now that we have the warnings configuration set up, we can actually
758 # issue (or suppress) any warnings about warning-worthy things that
759 # occurred while the command-line options were getting parsed.
761 dw = options.delayed_warnings
762 except AttributeError:
765 delayed_warnings.extend(dw)
766 for warning_type, message in delayed_warnings:
767 SCons.Warnings.warn(warning_type, message)
769 if options.diskcheck:
770 SCons.Node.FS.set_diskcheck(options.diskcheck)
772 # Next, we want to create the FS object that represents the outside
773 # world's file system, as that's central to a lot of initialization.
774 # To do this, however, we need to be in the directory from which we
775 # want to start everything, which means first handling any relevant
776 # options that might cause us to chdir somewhere (-C, -D, -U, -u).
777 if options.directory:
778 cdir = _create_path(options.directory)
782 sys.stderr.write("Could not change directory to %s\n" % cdir)
786 target_top = '.' # directory to prepend to targets
787 script_dir = os.getcwd() # location of script
788 while script_dir and not _SConstruct_exists(script_dir,
791 script_dir, last_part = os.path.split(script_dir)
793 target_top = os.path.join(last_part, target_top)
796 if script_dir and script_dir != os.getcwd():
797 display("scons: Entering directory `%s'" % script_dir)
800 # Now that we're in the top-level SConstruct directory, go ahead
801 # and initialize the FS object that represents the file system,
802 # and make it the build engine default.
803 fs = SCons.Node.FS.get_default_fs()
805 for rep in options.repository:
808 # Now that we have the FS object, the next order of business is to
809 # check for an SConstruct file (or other specified config file).
810 # If there isn't one, we can bail before doing any more work.
813 scripts.extend(options.file)
815 sfile = _SConstruct_exists(repositories=options.repository,
816 filelist=options.file)
818 scripts.append(sfile)
822 # There's no SConstruct, but they specified -h.
823 # Give them the options usage now, before we fail
824 # trying to read a non-existent SConstruct file.
825 raise SConsPrintHelpException
826 raise SCons.Errors.UserError, "No SConstruct file found."
828 if scripts[0] == "-":
831 d = fs.File(scripts[0]).dir
832 fs.set_SConstruct_dir(d)
834 _set_debug_values(options)
835 SCons.Node.implicit_cache = options.implicit_cache
836 SCons.Node.implicit_deps_changed = options.implicit_deps_changed
837 SCons.Node.implicit_deps_unchanged = options.implicit_deps_unchanged
840 SCons.SConf.dryrun = 1
841 SCons.Action.execute_actions = None
843 SCons.SConf.dryrun = 1
845 SCons.SConf.SetBuildType('clean')
847 SCons.SConf.SetBuildType('help')
848 SCons.SConf.SetCacheMode(options.config)
849 SCons.SConf.SetProgressDisplay(progress_display)
851 if options.no_progress or options.silent:
852 progress_display.set_mode(0)
855 _load_site_scons_dir(d, options.site_dir)
856 elif not options.no_site_dir:
857 _load_site_scons_dir(d)
859 if options.include_dir:
860 sys.path = options.include_dir + sys.path
862 # That should cover (most of) the options. Next, set up the variables
863 # that hold command-line arguments, so the SConscript files that we
864 # read and execute have access to them.
867 for a in parser.largs:
874 SCons.Script._Add_Targets(targets + parser.rargs)
875 SCons.Script._Add_Arguments(xmit_args)
877 # If stdout is not a tty, replace it with a wrapper object to call flush
880 # Tty devices automatically flush after every newline, so the replacement
881 # isn't necessary. Furthermore, if we replace sys.stdout, the readline
882 # module will no longer work. This affects the behavior during
883 # --interactive mode. --interactive should only be used when stdin and
884 # stdout refer to a tty.
885 if not sys.stdout.isatty():
886 sys.stdout = SCons.Util.Unbuffered(sys.stdout)
887 if not sys.stderr.isatty():
888 sys.stderr = SCons.Util.Unbuffered(sys.stderr)
890 memory_stats.append('before reading SConscript files:')
891 count_stats.append(('pre-', 'read'))
893 # And here's where we (finally) read the SConscript files.
895 progress_display("scons: Reading SConscript files ...")
897 start_time = time.time()
899 for script in scripts:
900 SCons.Script._SConscript._SConscript(fs, script)
901 except SCons.Errors.StopError, e:
902 # We had problems reading an SConscript file, such as it
903 # couldn't be copied in to the VariantDir. Since we're just
904 # reading SConscript files and haven't started building
905 # things yet, stop regardless of whether they used -i or -k
907 sys.stderr.write("scons: *** %s Stop.\n" % e)
909 sys.exit(exit_status)
910 global sconscript_time
911 sconscript_time = time.time() - start_time
913 progress_display("scons: done reading SConscript files.")
915 memory_stats.append('after reading SConscript files:')
916 count_stats.append(('post-', 'read'))
918 # Re-{enable,disable} warnings in case they disabled some in
919 # the SConscript file.
921 # We delay enabling the PythonVersionWarning class until here so that,
922 # if they explicity disabled it in either in the command line or in
923 # $SCONSFLAGS, or in the SConscript file, then the search through
924 # the list of deprecated warning classes will find that disabling
925 # first and not issue the warning.
926 SCons.Warnings.enableWarningClass(SCons.Warnings.PythonVersionWarning)
927 SCons.Warnings.process_warn_strings(options.warn)
929 # Now that we've read the SConscript files, we can check for the
930 # warning about deprecated Python versions--delayed until here
931 # in case they disabled the warning in the SConscript files.
932 if python_version_deprecated():
933 msg = "Support for pre-2.2 Python (%s) is deprecated.\n" + \
934 " If this will cause hardship, contact dev@scons.tigris.org."
935 SCons.Warnings.warn(SCons.Warnings.PythonVersionWarning,
936 msg % python_version_string())
939 SCons.SConf.CreateConfigHBuilder(SCons.Defaults.DefaultEnvironment())
941 # Now re-parse the command-line options (any to the left of a '--'
942 # argument, that is) with any user-defined command-line options that
943 # the SConscript files may have added to the parser object. This will
944 # emit the appropriate error message and exit if any unknown option
945 # was specified on the command line.
947 parser.preserve_unknown_options = False
948 parser.parse_args(parser.largs, options)
951 help_text = SCons.Script.help_text
952 if help_text is None:
953 # They specified -h, but there was no Help() inside the
954 # SConscript files. Give them the options usage.
955 raise SConsPrintHelpException
958 print "Use scons -H for help about command-line options."
962 # Change directory to the top-level SConstruct directory, then tell
963 # the Node.FS subsystem that we're all done reading the SConscript
964 # files and calling Repository() and VariantDir() and changing
965 # directories and the like, so it can go ahead and start memoizing
966 # the string values of file system nodes.
970 SCons.Node.FS.save_strings(1)
972 # Now that we've read the SConscripts we can set the options
973 # that are SConscript settable:
974 SCons.Node.implicit_cache = options.implicit_cache
975 SCons.Node.FS.set_duplicate(options.duplicate)
976 fs.set_max_drift(options.max_drift)
978 SCons.Job.explicit_stack_size = options.stack_size
980 if options.md5_chunksize:
981 SCons.Node.FS.File.md5_chunksize = options.md5_chunksize
983 platform = SCons.Platform.platform_module()
985 if options.interactive:
986 SCons.Script.Interactive.interact(fs, OptionsParser, options,
992 nodes = _build_targets(fs, options, targets, target_top)
996 def _build_targets(fs, options, targets, target_top):
998 global this_build_status
999 this_build_status = 0
1001 progress_display.set_mode(not (options.no_progress or options.silent))
1002 display.set_mode(not options.silent)
1003 SCons.Action.print_actions = not options.silent
1004 SCons.Action.execute_actions = not options.no_exec
1005 SCons.Node.FS.do_store_info = not options.no_exec
1006 SCons.SConf.dryrun = options.no_exec
1008 if options.diskcheck:
1009 SCons.Node.FS.set_diskcheck(options.diskcheck)
1011 SCons.CacheDir.cache_enabled = not options.cache_disable
1012 SCons.CacheDir.cache_debug = options.cache_debug
1013 SCons.CacheDir.cache_force = options.cache_force
1014 SCons.CacheDir.cache_show = options.cache_show
1017 CleanTask.execute = CleanTask.show
1019 CleanTask.execute = CleanTask.remove
1022 if targets or SCons.Script.BUILD_TARGETS != SCons.Script._build_plus_default:
1023 # They specified targets on the command line or modified
1024 # BUILD_TARGETS in the SConscript file(s), so if they used -u,
1025 # -U or -D, we have to look up targets relative to the top,
1026 # but we build whatever they specified.
1028 lookup_top = fs.Dir(target_top)
1031 targets = SCons.Script.BUILD_TARGETS
1033 # There are no targets specified on the command line,
1034 # so if they used -u, -U or -D, we may have to restrict
1035 # what actually gets built.
1038 if options.climb_up == 1:
1039 # -u, local directory and below
1040 target_top = fs.Dir(target_top)
1041 lookup_top = target_top
1042 elif options.climb_up == 2:
1043 # -D, all Default() targets
1046 elif options.climb_up == 3:
1047 # -U, local SConscript Default() targets
1048 target_top = fs.Dir(target_top)
1049 def check_dir(x, target_top=target_top):
1050 if hasattr(x, 'cwd') and not x.cwd is None:
1051 cwd = x.cwd.srcnode()
1052 return cwd == target_top
1054 # x doesn't have a cwd, so it's either not a target,
1055 # or not a file, so go ahead and keep it as a default
1056 # target and let the engine sort it out:
1058 d = filter(check_dir, SCons.Script.DEFAULT_TARGETS)
1059 SCons.Script.DEFAULT_TARGETS[:] = d
1063 targets = SCons.Script._Get_Default_Targets(d, fs)
1066 sys.stderr.write("scons: *** No targets specified and no Default() targets found. Stop.\n")
1069 def Entry(x, ltop=lookup_top, ttop=target_top, fs=fs):
1070 if isinstance(x, SCons.Node.Node):
1074 # Why would ltop be None? Unfortunately this happens.
1075 if ltop == None: ltop = ''
1076 # Curdir becomes important when SCons is called with -u, -C,
1077 # or similar option that changes directory, and so the paths
1078 # of targets given on the command line need to be adjusted.
1079 curdir = os.path.join(os.getcwd(), str(ltop))
1080 for lookup in SCons.Node.arg2nodes_lookups:
1081 node = lookup(x, curdir=curdir)
1085 node = fs.Entry(x, directory=ltop, create=1)
1086 if ttop and not node.is_under(ttop):
1087 if isinstance(node, SCons.Node.FS.Dir) and ttop.is_under(node):
1093 nodes = filter(None, map(Entry, targets))
1095 task_class = BuildTask # default action is to build targets
1096 opening_message = "Building targets ..."
1097 closing_message = "done building targets."
1098 if options.keep_going:
1099 failure_message = "done building targets (errors occurred during build)."
1101 failure_message = "building terminated because of errors."
1102 if options.question:
1103 task_class = QuestionTask
1106 task_class = CleanTask
1107 opening_message = "Cleaning targets ..."
1108 closing_message = "done cleaning targets."
1109 if options.keep_going:
1110 failure_message = "done cleaning targets (errors occurred during clean)."
1112 failure_message = "cleaning terminated because of errors."
1113 except AttributeError:
1116 task_class.progress = ProgressObject
1119 def order(dependencies):
1120 """Randomize the dependencies."""
1122 # This is cribbed from the implementation of
1123 # random.shuffle() in Python 2.X.
1125 for i in xrange(len(d)-1, 0, -1):
1126 j = int(random.random() * (i+1))
1127 d[i], d[j] = d[j], d[i]
1130 def order(dependencies):
1131 """Leave the order of dependencies alone."""
1134 if options.taskmastertrace_file == '-':
1135 tmtrace = sys.stdout
1136 elif options.taskmastertrace_file:
1137 tmtrace = open(options.taskmastertrace_file, 'wb')
1140 taskmaster = SCons.Taskmaster.Taskmaster(nodes, task_class, order, tmtrace)
1142 # Let the BuildTask objects get at the options to respond to the
1143 # various print_* settings, tree_printer list, etc.
1144 BuildTask.options = options
1147 num_jobs = options.num_jobs
1148 jobs = SCons.Job.Jobs(num_jobs, taskmaster)
1151 if jobs.num_jobs == 1:
1152 msg = "parallel builds are unsupported by this version of Python;\n" + \
1153 "\tignoring -j or num_jobs option.\n"
1154 elif sys.platform == 'win32':
1155 msg = fetch_win32_parallel_msg()
1157 SCons.Warnings.warn(SCons.Warnings.NoParallelSupportWarning, msg)
1159 memory_stats.append('before building targets:')
1160 count_stats.append(('pre-', 'build'))
1165 closing_message=closing_message,
1166 failure_message=failure_message
1168 if jobs.were_interrupted():
1169 progress_display("scons: Build interrupted.")
1171 global this_build_status
1173 this_build_status = 2
1175 if this_build_status:
1176 progress_display("scons: " + failure_message)
1178 progress_display("scons: " + closing_message)
1179 if not options.no_exec:
1180 if jobs.were_interrupted():
1181 progress_display("scons: writing .sconsign file.")
1182 SCons.SConsign.write()
1184 progress_display("scons: " + opening_message)
1185 jobs.run(postfunc = jobs_postfunc)
1187 memory_stats.append('after building targets:')
1188 count_stats.append(('post-', 'build'))
1192 def _exec_main(parser, values):
1193 sconsflags = os.environ.get('SCONSFLAGS', '')
1194 all_args = string.split(sconsflags) + sys.argv[1:]
1196 options, args = parser.parse_args(all_args, values)
1198 if type(options.debug) == type([]) and "pdb" in options.debug:
1200 pdb.Pdb().runcall(_main, parser)
1201 elif options.profile_file:
1203 from cProfile import Profile
1204 except ImportError, e:
1205 from profile import Profile
1207 # Some versions of Python 2.4 shipped a profiler that had the
1208 # wrong 'c_exception' entry in its dispatch table. Make sure
1209 # we have the right one. (This may put an unnecessary entry
1210 # in the table in earlier versions of Python, but its presence
1211 # shouldn't hurt anything).
1213 dispatch = Profile.dispatch
1214 except AttributeError:
1217 dispatch['c_exception'] = Profile.trace_dispatch_return
1221 prof.runcall(_main, parser)
1222 except SConsPrintHelpException, e:
1223 prof.dump_stats(options.profile_file)
1227 prof.dump_stats(options.profile_file)
1232 global OptionsParser
1234 global first_command_start
1236 # Check up front for a Python version we do not support. We
1237 # delay the check for deprecated Python versions until later,
1238 # after the SConscript files have been read, in case they
1239 # disable that warning.
1240 if python_version_unsupported():
1241 msg = "scons: *** SCons version %s does not run under Python version %s.\n"
1242 sys.stderr.write(msg % (SCons.__version__, python_version_string()))
1245 parts = ["SCons by Steven Knight et al.:\n"]
1248 parts.append(version_string("script", __main__))
1249 except (ImportError, AttributeError):
1250 # On Windows there is no scons.py, so there is no
1251 # __main__.__version__, hence there is no script version.
1253 parts.append(version_string("engine", SCons))
1254 parts.append("Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008 The SCons Foundation")
1255 version = string.join(parts, '')
1258 parser = SConsOptions.Parser(version)
1259 values = SConsOptions.SConsValues(parser.get_default_values())
1261 OptionsParser = parser
1264 _exec_main(parser, values)
1265 except SystemExit, s:
1268 except KeyboardInterrupt:
1269 print("scons: Build interrupted.")
1271 except SyntaxError, e:
1272 _scons_syntax_error(e)
1273 except SCons.Errors.InternalError:
1274 _scons_internal_error()
1275 except SCons.Errors.UserError, e:
1276 _scons_user_error(e)
1277 except SConsPrintHelpException:
1280 except SCons.Errors.BuildError, e:
1281 exit_status = e.exitstatus
1283 # An exception here is likely a builtin Python exception Python
1284 # code in an SConscript file. Show them precisely what the
1285 # problem was and where it happened.
1286 SCons.Script._SConscript.SConscript_exception()
1289 memory_stats.print_stats()
1290 count_stats.print_stats()
1293 SCons.Debug.listLoggedInstances('*')
1294 #SCons.Debug.dumpLoggedInstances('*')
1297 SCons.Memoize.Dump("Memoizer (memory cache) hits and misses:")
1299 # Dump any development debug info that may have been enabled.
1300 # These are purely for internal debugging during development, so
1301 # there's no need to control them with --debug= options; they're
1302 # controlled by changing the source code.
1303 SCons.Debug.dump_caller_counts()
1304 SCons.Taskmaster.dump_stats()
1307 total_time = time.time() - SCons.Script.start_time
1309 ct = cumulative_command_time
1311 if last_command_end is None or first_command_start is None:
1314 ct = last_command_end - first_command_start
1315 scons_time = total_time - sconscript_time - ct
1316 print "Total build time: %f seconds"%total_time
1317 print "Total SConscript file execution time: %f seconds"%sconscript_time
1318 print "Total SCons execution time: %f seconds"%scons_time
1319 print "Total command execution time: %f seconds"%ct
1321 sys.exit(exit_status)