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.
36 from __future__ import generators ### KEEP FOR COMPATIBILITY FIXERS
38 __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 return 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 obj.find(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(self.obj.replace(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 = Progressor(*args, **kw)
157 def GetBuildFailures():
158 return _BuildFailures
160 class BuildTask(SCons.Taskmaster.OutOfDateTask):
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.OutOfDateTask.prepare(self)
171 def needs_execute(self):
172 if SCons.Taskmaster.OutOfDateTask.needs_execute(self):
174 if self.top and self.targets[0].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.OutOfDateTask.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.OutOfDateTask.executed(self)
199 elif self.options.keep_going:
200 SCons.Taskmaster.OutOfDateTask.fail_continue(self)
202 this_build_status = status
204 SCons.Taskmaster.OutOfDateTask.fail_stop(self)
206 this_build_status = status
210 if self.top and not t.has_builder() and not t.side_effect:
213 return str(obj.__class__).split('.')[-1]
214 if classname(t) in ('File', 'Dir', 'Entry'):
215 errstr="Do not know how to make %s target `%s' (%s)." % (classname(t), t, t.abspath)
216 else: # Alias or Python or ...
217 errstr="Do not know how to make %s target `%s'." % (classname(t), t)
218 sys.stderr.write("scons: *** " + errstr)
219 if not self.options.keep_going:
220 sys.stderr.write(" Stop.")
221 sys.stderr.write("\n")
223 raise SCons.Errors.BuildError(t, errstr)
224 except KeyboardInterrupt:
230 print "scons: Nothing to be done for `%s'." % t
231 SCons.Taskmaster.OutOfDateTask.executed(self)
233 SCons.Taskmaster.OutOfDateTask.executed(self)
236 # Handle the failure of a build task. The primary purpose here
237 # is to display the various types of Errors and Exceptions
239 exc_info = self.exc_info()
247 # The Taskmaster didn't record an exception for this Task;
248 # see if the sys module has one.
250 t, e, tb = sys.exc_info()[:]
255 # Deprecated string exceptions will have their string stored
256 # in the first entry of the tuple.
260 buildError = SCons.Errors.convert_to_BuildError(e)
261 if not buildError.node:
262 buildError.node = self.node
264 node = buildError.node
265 if not SCons.Util.is_List(node):
267 nodename = ', '.join(map(str, node))
269 errfmt = "scons: *** [%s] %s\n"
270 sys.stderr.write(errfmt % (nodename, buildError))
272 if (buildError.exc_info[2] and buildError.exc_info[1] and
275 # buildError.exc_info[1],
276 # (EnvironmentError, SCons.Errors.StopError, SCons.Errors.UserError))):
277 not isinstance(buildError.exc_info[1], EnvironmentError) and
278 not isinstance(buildError.exc_info[1], SCons.Errors.StopError) and
279 not isinstance(buildError.exc_info[1], SCons.Errors.UserError)):
280 type, value, trace = buildError.exc_info
281 traceback.print_exception(type, value, trace)
282 elif tb and print_stacktrace:
283 sys.stderr.write("scons: internal stack trace:\n")
284 traceback.print_tb(tb, file=sys.stderr)
286 self.exception = (e, buildError, tb) # type, value, traceback
287 self.do_failed(buildError.exitstatus)
291 def postprocess(self):
294 for tp in self.options.tree_printers:
296 if self.options.debug_includes:
297 tree = t.render_include_tree()
301 SCons.Taskmaster.OutOfDateTask.postprocess(self)
303 def make_ready(self):
304 """Make a task ready for execution"""
305 SCons.Taskmaster.OutOfDateTask.make_ready(self)
306 if self.out_of_date and self.options.debug_explain:
307 explanation = self.out_of_date[0].explain()
309 sys.stdout.write("scons: " + explanation)
311 class CleanTask(SCons.Taskmaster.AlwaysTask):
312 """An SCons clean task."""
313 def fs_delete(self, path, pathstr, remove=1):
315 if os.path.lexists(path):
316 if os.path.isfile(path) or os.path.islink(path):
317 if remove: os.unlink(path)
318 display("Removed " + pathstr)
319 elif os.path.isdir(path) and not os.path.islink(path):
320 # delete everything in the dir
321 for e in sorted(os.listdir(path)):
322 p = os.path.join(path, e)
323 s = os.path.join(pathstr, e)
324 if os.path.isfile(p):
325 if remove: os.unlink(p)
326 display("Removed " + s)
328 self.fs_delete(p, s, remove)
329 # then delete dir itself
330 if remove: os.rmdir(path)
331 display("Removed directory " + pathstr)
333 errstr = "Path '%s' exists but isn't a file or directory."
334 raise SCons.Errors.UserError(errstr % (pathstr))
335 except SCons.Errors.UserError, e:
337 except (IOError, OSError), e:
338 print "scons: Could not remove '%s':" % pathstr, e.strerror
341 target = self.targets[0]
342 if (target.has_builder() or target.side_effect) and not target.noclean:
343 for t in self.targets:
345 display("Removed " + str(t))
346 if target in SCons.Environment.CleanTargets:
347 files = SCons.Environment.CleanTargets[target]
349 self.fs_delete(f.abspath, str(f), 0)
352 target = self.targets[0]
353 if (target.has_builder() or target.side_effect) and not target.noclean:
354 for t in self.targets:
358 # An OSError may indicate something like a permissions
359 # issue, an IOError would indicate something like
360 # the file not existing. In either case, print a
361 # message and keep going to try to remove as many
362 # targets aa possible.
363 print "scons: Could not remove '%s':" % str(t), e.strerror
366 display("Removed " + str(t))
367 if target in SCons.Environment.CleanTargets:
368 files = SCons.Environment.CleanTargets[target]
370 self.fs_delete(f.abspath, str(f))
374 # We want the Taskmaster to update the Node states (and therefore
375 # handle reference counts, etc.), but we don't want to call
376 # back to the Node's post-build methods, which would do things
377 # we don't want, like store .sconsign information.
378 executed = SCons.Taskmaster.Task.executed_without_callbacks
380 # Have the taskmaster arrange to "execute" all of the targets, because
381 # we'll figure out ourselves (in remove() or show() above) whether
382 # anything really needs to be done.
383 make_ready = SCons.Taskmaster.Task.make_ready_all
388 class QuestionTask(SCons.Taskmaster.AlwaysTask):
389 """An SCons task for the -q (question) option."""
394 if self.targets[0].get_state() != SCons.Node.up_to_date or \
395 (self.top and not self.targets[0].exists()):
397 global this_build_status
399 this_build_status = 1
407 def __init__(self, derived=False, prune=False, status=False):
408 self.derived = derived
411 def get_all_children(self, node):
412 return node.all_children()
413 def get_derived_children(self, node):
414 children = node.all_children(None)
415 return [x for x in children if x.has_builder()]
416 def display(self, t):
418 func = self.get_derived_children
420 func = self.get_all_children
421 s = self.status and 2 or 0
422 SCons.Util.print_tree(t, func, prune=self.prune, showtags=s)
425 def python_version_string():
426 return sys.version.split()[0]
428 def python_version_unsupported(version=sys.version_info):
429 return version < (1, 5, 2)
431 def python_version_deprecated(version=sys.version_info):
432 return version < (2, 4, 0)
442 cumulative_command_time = 0
443 exit_status = 0 # final exit status, assume success by default
444 this_build_status = 0 # "exit status" of an individual build
446 delayed_warnings = []
448 class FakeOptionParser:
450 A do-nothing option parser, used for the initial OptionsParser variable.
452 During normal SCons operation, the OptionsParser is created right
453 away by the main() function. Certain tests scripts however, can
454 introspect on different Tool modules, the initialization of which
455 can try to add a new, local option to an otherwise uninitialized
456 OptionsParser object. This allows that introspection to happen
460 class FakeOptionValues:
461 def __getattr__(self, attr):
463 values = FakeOptionValues()
464 def add_local_option(self, *args, **kw):
467 OptionsParser = FakeOptionParser()
469 def AddOption(*args, **kw):
470 if 'default' not in kw:
472 result = OptionsParser.add_local_option(*args, **kw)
476 return getattr(OptionsParser.values, name)
478 def SetOption(name, value):
479 return OptionsParser.values.set_option(name, value)
486 self.append = self.do_nothing
487 self.print_stats = self.do_nothing
488 def enable(self, outfp):
490 self.append = self.do_append
491 self.print_stats = self.do_print
492 def do_nothing(self, *args, **kw):
495 class CountStats(Stats):
496 def do_append(self, label):
497 self.labels.append(label)
498 self.stats.append(SCons.Debug.fetchLoggedInstances())
502 for n in [t[0] for t in s]:
503 stats_table[n] = [0, 0, 0, 0]
507 stats_table[n][i] = c
509 self.outfp.write("Object counts:\n")
513 fmt1 = ''.join(pre + [' %7s']*l + post)
514 fmt2 = ''.join(pre + [' %7d']*l + post)
515 labels = self.labels[:l]
516 labels.append(("", "Class"))
517 self.outfp.write(fmt1 % tuple([x[0] for x in labels]))
518 self.outfp.write(fmt1 % tuple([x[1] for x in labels]))
519 for k in sorted(stats_table.keys()):
520 r = stats_table[k][:l] + [k]
521 self.outfp.write(fmt2 % tuple(r))
523 count_stats = CountStats()
525 class MemStats(Stats):
526 def do_append(self, label):
527 self.labels.append(label)
528 self.stats.append(SCons.Debug.memory())
530 fmt = 'Memory %-32s %12d\n'
531 for label, stats in map(None, self.labels, self.stats):
532 self.outfp.write(fmt % (label, stats))
534 memory_stats = MemStats()
538 def _scons_syntax_error(e):
539 """Handle syntax errors. Print out a message and show where the error
542 etype, value, tb = sys.exc_info()
543 lines = traceback.format_exception_only(etype, value)
545 sys.stderr.write(line+'\n')
548 def find_deepest_user_frame(tb):
550 Find the deepest stack frame that is not part of SCons.
552 Input is a "pre-processed" stack trace in the form
553 returned by traceback.extract_tb() or traceback.extract_stack()
558 # find the deepest traceback frame that is not part
562 if filename.find(os.sep+'SCons'+os.sep) == -1:
566 def _scons_user_error(e):
567 """Handle user errors. Print out a message and a description of the
568 error, along with the line number and routine where it occured.
569 The file and line number will be the deepest stack frame that is
570 not part of SCons itself.
572 global print_stacktrace
573 etype, value, tb = sys.exc_info()
575 traceback.print_exception(etype, value, tb)
576 filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_tb(tb))
577 sys.stderr.write("\nscons: *** %s\n" % value)
578 sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
581 def _scons_user_warning(e):
582 """Handle user warnings. Print out a message and a description of
583 the warning, along with the line number and routine where it occured.
584 The file and line number will be the deepest stack frame that is
585 not part of SCons itself.
587 etype, value, tb = sys.exc_info()
588 filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_tb(tb))
589 sys.stderr.write("\nscons: warning: %s\n" % e)
590 sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
592 def _scons_internal_warning(e):
593 """Slightly different from _scons_user_warning in that we use the
594 *current call stack* rather than sys.exc_info() to get our stack trace.
595 This is used by the warnings framework to print warnings."""
596 filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_stack())
597 sys.stderr.write("\nscons: warning: %s\n" % e[0])
598 sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
600 def _scons_internal_error():
601 """Handle all errors but user errors. Print out a message telling
602 the user what to do in this case and print a normal trace.
604 print 'internal error'
605 traceback.print_exc()
608 def _SConstruct_exists(dirname='', repositories=[], filelist=None):
609 """This function checks that an SConstruct file exists in a directory.
610 If so, it returns the path of the file. By default, it checks the
614 filelist = ['SConstruct', 'Sconstruct', 'sconstruct']
615 for file in filelist:
616 sfile = os.path.join(dirname, file)
617 if os.path.isfile(sfile):
619 if not os.path.isabs(sfile):
620 for rep in repositories:
621 if os.path.isfile(os.path.join(rep, sfile)):
625 def _set_debug_values(options):
626 global print_memoizer, print_objects, print_stacktrace, print_time
628 debug_values = options.debug
630 if "count" in debug_values:
631 # All of the object counts are within "if __debug__:" blocks,
632 # which get stripped when running optimized (with python -O or
633 # from compiled *.pyo files). Provide a warning if __debug__ is
634 # stripped, so it doesn't just look like --debug=count is broken.
636 if __debug__: enable_count = True
638 count_stats.enable(sys.stdout)
640 msg = "--debug=count is not supported when running SCons\n" + \
641 "\twith the python -O option or optimized (.pyo) modules."
642 SCons.Warnings.warn(SCons.Warnings.NoObjectCountWarning, msg)
643 if "dtree" in debug_values:
644 options.tree_printers.append(TreePrinter(derived=True))
645 options.debug_explain = ("explain" in debug_values)
646 if "findlibs" in debug_values:
647 SCons.Scanner.Prog.print_find_libs = "findlibs"
648 options.debug_includes = ("includes" in debug_values)
649 print_memoizer = ("memoizer" in debug_values)
650 if "memory" in debug_values:
651 memory_stats.enable(sys.stdout)
652 print_objects = ("objects" in debug_values)
653 if "presub" in debug_values:
654 SCons.Action.print_actions_presub = 1
655 if "stacktrace" in debug_values:
657 if "stree" in debug_values:
658 options.tree_printers.append(TreePrinter(status=True))
659 if "time" in debug_values:
661 if "tree" in debug_values:
662 options.tree_printers.append(TreePrinter())
664 def _create_path(plist):
670 path = path + '/' + d
673 def _load_site_scons_dir(topdir, site_dir_name=None):
674 """Load the site_scons dir under topdir.
675 Adds site_scons to sys.path, imports site_scons/site_init.py,
676 and adds site_scons/site_tools to default toolpath."""
678 err_if_not_found = True # user specified: err if missing
680 site_dir_name = "site_scons"
681 err_if_not_found = False
683 site_dir = os.path.join(topdir.path, site_dir_name)
684 if not os.path.exists(site_dir):
686 raise SCons.Errors.UserError("site dir %s not found."%site_dir)
689 site_init_filename = "site_init.py"
690 site_init_modname = "site_init"
691 site_tools_dirname = "site_tools"
692 sys.path = [os.path.abspath(site_dir)] + sys.path
693 site_init_file = os.path.join(site_dir, site_init_filename)
694 site_tools_dir = os.path.join(site_dir, site_tools_dirname)
695 if os.path.exists(site_init_file):
697 # TODO(2.4): turn this into try:-except:-finally:
700 fp, pathname, description = imp.find_module(site_init_modname,
702 # Load the file into SCons.Script namespace. This is
703 # opaque and clever; m is the module object for the
704 # SCons.Script module, and the exec ... in call executes a
705 # file (or string containing code) in the context of the
706 # module's dictionary, so anything that code defines ends
707 # up adding to that module. This is really short, but all
708 # the error checking makes it longer.
710 m = sys.modules['SCons.Script']
712 fmt = 'cannot import site_init.py: missing SCons.Script module %s'
713 raise SCons.Errors.InternalError(fmt % repr(e))
716 exec fp in m.__dict__
717 except KeyboardInterrupt:
720 fmt = '*** Error loading site_init file %s:\n'
721 sys.stderr.write(fmt % repr(site_init_file))
723 except KeyboardInterrupt:
725 except ImportError, e:
726 fmt = '*** cannot import site init file %s:\n'
727 sys.stderr.write(fmt % repr(site_init_file))
732 if os.path.exists(site_tools_dir):
733 SCons.Tool.DefaultToolpath.append(os.path.abspath(site_tools_dir))
735 def version_string(label, module):
736 version = module.__version__
737 build = module.__build__
741 version = version + build
742 fmt = "\t%s: v%s, %s, by %s on %s\n"
746 module.__developer__,
751 global this_build_status
753 options = parser.values
755 # Here's where everything really happens.
757 # First order of business: set up default warnings and then
758 # handle the user's warning options, so that we can issue (or
759 # suppress) appropriate warnings about anything that might happen,
760 # as configured by the user.
762 default_warnings = [ SCons.Warnings.CorruptSConsignWarning,
763 SCons.Warnings.DeprecatedWarning,
764 SCons.Warnings.DuplicateEnvironmentWarning,
765 SCons.Warnings.FutureReservedVariableWarning,
766 SCons.Warnings.LinkWarning,
767 SCons.Warnings.MissingSConscriptWarning,
768 SCons.Warnings.NoMD5ModuleWarning,
769 SCons.Warnings.NoMetaclassSupportWarning,
770 SCons.Warnings.NoObjectCountWarning,
771 SCons.Warnings.NoParallelSupportWarning,
772 SCons.Warnings.MisleadingKeywordsWarning,
773 SCons.Warnings.ReservedVariableWarning,
774 SCons.Warnings.StackSizeWarning,
775 SCons.Warnings.VisualVersionMismatch,
776 SCons.Warnings.VisualCMissingWarning,
779 for warning in default_warnings:
780 SCons.Warnings.enableWarningClass(warning)
781 SCons.Warnings._warningOut = _scons_internal_warning
782 SCons.Warnings.process_warn_strings(options.warn)
784 # Now that we have the warnings configuration set up, we can actually
785 # issue (or suppress) any warnings about warning-worthy things that
786 # occurred while the command-line options were getting parsed.
788 dw = options.delayed_warnings
789 except AttributeError:
792 delayed_warnings.extend(dw)
793 for warning_type, message in delayed_warnings:
794 SCons.Warnings.warn(warning_type, message)
796 if options.diskcheck:
797 SCons.Node.FS.set_diskcheck(options.diskcheck)
799 # Next, we want to create the FS object that represents the outside
800 # world's file system, as that's central to a lot of initialization.
801 # To do this, however, we need to be in the directory from which we
802 # want to start everything, which means first handling any relevant
803 # options that might cause us to chdir somewhere (-C, -D, -U, -u).
804 if options.directory:
805 script_dir = os.path.abspath(_create_path(options.directory))
807 script_dir = os.getcwd()
811 target_top = '.' # directory to prepend to targets
812 while script_dir and not _SConstruct_exists(script_dir,
815 script_dir, last_part = os.path.split(script_dir)
817 target_top = os.path.join(last_part, target_top)
821 if script_dir and script_dir != os.getcwd():
822 display("scons: Entering directory `%s'" % script_dir)
826 sys.stderr.write("Could not change directory to %s\n" % script_dir)
828 # Now that we're in the top-level SConstruct directory, go ahead
829 # and initialize the FS object that represents the file system,
830 # and make it the build engine default.
831 fs = SCons.Node.FS.get_default_fs()
833 for rep in options.repository:
836 # Now that we have the FS object, the next order of business is to
837 # check for an SConstruct file (or other specified config file).
838 # If there isn't one, we can bail before doing any more work.
841 scripts.extend(options.file)
843 sfile = _SConstruct_exists(repositories=options.repository,
844 filelist=options.file)
846 scripts.append(sfile)
850 # There's no SConstruct, but they specified -h.
851 # Give them the options usage now, before we fail
852 # trying to read a non-existent SConstruct file.
853 raise SConsPrintHelpException
854 raise SCons.Errors.UserError("No SConstruct file found.")
856 if scripts[0] == "-":
859 d = fs.File(scripts[0]).dir
860 fs.set_SConstruct_dir(d)
862 _set_debug_values(options)
863 SCons.Node.implicit_cache = options.implicit_cache
864 SCons.Node.implicit_deps_changed = options.implicit_deps_changed
865 SCons.Node.implicit_deps_unchanged = options.implicit_deps_unchanged
868 SCons.SConf.dryrun = 1
869 SCons.Action.execute_actions = None
871 SCons.SConf.dryrun = 1
873 SCons.SConf.SetBuildType('clean')
875 SCons.SConf.SetBuildType('help')
876 SCons.SConf.SetCacheMode(options.config)
877 SCons.SConf.SetProgressDisplay(progress_display)
879 if options.no_progress or options.silent:
880 progress_display.set_mode(0)
883 _load_site_scons_dir(d, options.site_dir)
884 elif not options.no_site_dir:
885 _load_site_scons_dir(d)
887 if options.include_dir:
888 sys.path = options.include_dir + sys.path
890 # That should cover (most of) the options. Next, set up the variables
891 # that hold command-line arguments, so the SConscript files that we
892 # read and execute have access to them.
895 for a in parser.largs:
902 SCons.Script._Add_Targets(targets + parser.rargs)
903 SCons.Script._Add_Arguments(xmit_args)
905 # If stdout is not a tty, replace it with a wrapper object to call flush
908 # Tty devices automatically flush after every newline, so the replacement
909 # isn't necessary. Furthermore, if we replace sys.stdout, the readline
910 # module will no longer work. This affects the behavior during
911 # --interactive mode. --interactive should only be used when stdin and
912 # stdout refer to a tty.
913 if not hasattr(sys.stdout, 'isatty') or not sys.stdout.isatty():
914 sys.stdout = SCons.Util.Unbuffered(sys.stdout)
915 if not hasattr(sys.stderr, 'isatty') or not sys.stderr.isatty():
916 sys.stderr = SCons.Util.Unbuffered(sys.stderr)
918 memory_stats.append('before reading SConscript files:')
919 count_stats.append(('pre-', 'read'))
921 # And here's where we (finally) read the SConscript files.
923 progress_display("scons: Reading SConscript files ...")
925 start_time = time.time()
927 for script in scripts:
928 SCons.Script._SConscript._SConscript(fs, script)
929 except SCons.Errors.StopError, e:
930 # We had problems reading an SConscript file, such as it
931 # couldn't be copied in to the VariantDir. Since we're just
932 # reading SConscript files and haven't started building
933 # things yet, stop regardless of whether they used -i or -k
935 sys.stderr.write("scons: *** %s Stop.\n" % e)
937 sys.exit(exit_status)
938 global sconscript_time
939 sconscript_time = time.time() - start_time
941 progress_display("scons: done reading SConscript files.")
943 memory_stats.append('after reading SConscript files:')
944 count_stats.append(('post-', 'read'))
946 # Re-{enable,disable} warnings in case they disabled some in
947 # the SConscript file.
949 # We delay enabling the PythonVersionWarning class until here so that,
950 # if they explicity disabled it in either in the command line or in
951 # $SCONSFLAGS, or in the SConscript file, then the search through
952 # the list of deprecated warning classes will find that disabling
953 # first and not issue the warning.
954 SCons.Warnings.enableWarningClass(SCons.Warnings.PythonVersionWarning)
955 SCons.Warnings.process_warn_strings(options.warn)
957 # Now that we've read the SConscript files, we can check for the
958 # warning about deprecated Python versions--delayed until here
959 # in case they disabled the warning in the SConscript files.
960 if python_version_deprecated():
961 msg = "Support for pre-2.4 Python (%s) is deprecated.\n" + \
962 " If this will cause hardship, contact dev@scons.tigris.org."
963 SCons.Warnings.warn(SCons.Warnings.PythonVersionWarning,
964 msg % python_version_string())
967 SCons.SConf.CreateConfigHBuilder(SCons.Defaults.DefaultEnvironment())
969 # Now re-parse the command-line options (any to the left of a '--'
970 # argument, that is) with any user-defined command-line options that
971 # the SConscript files may have added to the parser object. This will
972 # emit the appropriate error message and exit if any unknown option
973 # was specified on the command line.
975 parser.preserve_unknown_options = False
976 parser.parse_args(parser.largs, options)
979 help_text = SCons.Script.help_text
980 if help_text is None:
981 # They specified -h, but there was no Help() inside the
982 # SConscript files. Give them the options usage.
983 raise SConsPrintHelpException
986 print "Use scons -H for help about command-line options."
990 # Change directory to the top-level SConstruct directory, then tell
991 # the Node.FS subsystem that we're all done reading the SConscript
992 # files and calling Repository() and VariantDir() and changing
993 # directories and the like, so it can go ahead and start memoizing
994 # the string values of file system nodes.
998 SCons.Node.FS.save_strings(1)
1000 # Now that we've read the SConscripts we can set the options
1001 # that are SConscript settable:
1002 SCons.Node.implicit_cache = options.implicit_cache
1003 SCons.Node.FS.set_duplicate(options.duplicate)
1004 fs.set_max_drift(options.max_drift)
1006 SCons.Job.explicit_stack_size = options.stack_size
1008 if options.md5_chunksize:
1009 SCons.Node.FS.File.md5_chunksize = options.md5_chunksize
1011 platform = SCons.Platform.platform_module()
1013 if options.interactive:
1014 SCons.Script.Interactive.interact(fs, OptionsParser, options,
1015 targets, target_top)
1020 nodes = _build_targets(fs, options, targets, target_top)
1024 def _build_targets(fs, options, targets, target_top):
1026 global this_build_status
1027 this_build_status = 0
1029 progress_display.set_mode(not (options.no_progress or options.silent))
1030 display.set_mode(not options.silent)
1031 SCons.Action.print_actions = not options.silent
1032 SCons.Action.execute_actions = not options.no_exec
1033 SCons.Node.FS.do_store_info = not options.no_exec
1034 SCons.SConf.dryrun = options.no_exec
1036 if options.diskcheck:
1037 SCons.Node.FS.set_diskcheck(options.diskcheck)
1039 SCons.CacheDir.cache_enabled = not options.cache_disable
1040 SCons.CacheDir.cache_debug = options.cache_debug
1041 SCons.CacheDir.cache_force = options.cache_force
1042 SCons.CacheDir.cache_show = options.cache_show
1045 CleanTask.execute = CleanTask.show
1047 CleanTask.execute = CleanTask.remove
1050 if targets or SCons.Script.BUILD_TARGETS != SCons.Script._build_plus_default:
1051 # They specified targets on the command line or modified
1052 # BUILD_TARGETS in the SConscript file(s), so if they used -u,
1053 # -U or -D, we have to look up targets relative to the top,
1054 # but we build whatever they specified.
1056 lookup_top = fs.Dir(target_top)
1059 targets = SCons.Script.BUILD_TARGETS
1061 # There are no targets specified on the command line,
1062 # so if they used -u, -U or -D, we may have to restrict
1063 # what actually gets built.
1066 if options.climb_up == 1:
1067 # -u, local directory and below
1068 target_top = fs.Dir(target_top)
1069 lookup_top = target_top
1070 elif options.climb_up == 2:
1071 # -D, all Default() targets
1074 elif options.climb_up == 3:
1075 # -U, local SConscript Default() targets
1076 target_top = fs.Dir(target_top)
1077 def check_dir(x, target_top=target_top):
1078 if hasattr(x, 'cwd') and not x.cwd is None:
1079 cwd = x.cwd.srcnode()
1080 return cwd == target_top
1082 # x doesn't have a cwd, so it's either not a target,
1083 # or not a file, so go ahead and keep it as a default
1084 # target and let the engine sort it out:
1086 d = list(filter(check_dir, SCons.Script.DEFAULT_TARGETS))
1087 SCons.Script.DEFAULT_TARGETS[:] = d
1091 targets = SCons.Script._Get_Default_Targets(d, fs)
1094 sys.stderr.write("scons: *** No targets specified and no Default() targets found. Stop.\n")
1097 def Entry(x, ltop=lookup_top, ttop=target_top, fs=fs):
1098 if isinstance(x, SCons.Node.Node):
1102 # Why would ltop be None? Unfortunately this happens.
1103 if ltop is None: ltop = ''
1104 # Curdir becomes important when SCons is called with -u, -C,
1105 # or similar option that changes directory, and so the paths
1106 # of targets given on the command line need to be adjusted.
1107 curdir = os.path.join(os.getcwd(), str(ltop))
1108 for lookup in SCons.Node.arg2nodes_lookups:
1109 node = lookup(x, curdir=curdir)
1110 if node is not None:
1113 node = fs.Entry(x, directory=ltop, create=1)
1114 if ttop and not node.is_under(ttop):
1115 if isinstance(node, SCons.Node.FS.Dir) and ttop.is_under(node):
1121 nodes = [_f for _f in map(Entry, targets) if _f]
1123 task_class = BuildTask # default action is to build targets
1124 opening_message = "Building targets ..."
1125 closing_message = "done building targets."
1126 if options.keep_going:
1127 failure_message = "done building targets (errors occurred during build)."
1129 failure_message = "building terminated because of errors."
1130 if options.question:
1131 task_class = QuestionTask
1134 task_class = CleanTask
1135 opening_message = "Cleaning targets ..."
1136 closing_message = "done cleaning targets."
1137 if options.keep_going:
1138 failure_message = "done cleaning targets (errors occurred during clean)."
1140 failure_message = "cleaning terminated because of errors."
1141 except AttributeError:
1144 task_class.progress = ProgressObject
1147 def order(dependencies):
1148 """Randomize the dependencies."""
1150 # This is cribbed from the implementation of
1151 # random.shuffle() in Python 2.X.
1153 for i in range(len(d)-1, 0, -1):
1154 j = int(random.random() * (i+1))
1155 d[i], d[j] = d[j], d[i]
1158 def order(dependencies):
1159 """Leave the order of dependencies alone."""
1162 if options.taskmastertrace_file == '-':
1163 tmtrace = sys.stdout
1164 elif options.taskmastertrace_file:
1165 tmtrace = open(options.taskmastertrace_file, 'wb')
1168 taskmaster = SCons.Taskmaster.Taskmaster(nodes, task_class, order, tmtrace)
1170 # Let the BuildTask objects get at the options to respond to the
1171 # various print_* settings, tree_printer list, etc.
1172 BuildTask.options = options
1175 num_jobs = options.num_jobs
1176 jobs = SCons.Job.Jobs(num_jobs, taskmaster)
1179 if jobs.num_jobs == 1:
1180 msg = "parallel builds are unsupported by this version of Python;\n" + \
1181 "\tignoring -j or num_jobs option.\n"
1182 elif sys.platform == 'win32':
1183 msg = fetch_win32_parallel_msg()
1185 SCons.Warnings.warn(SCons.Warnings.NoParallelSupportWarning, msg)
1187 memory_stats.append('before building targets:')
1188 count_stats.append(('pre-', 'build'))
1193 closing_message=closing_message,
1194 failure_message=failure_message
1196 if jobs.were_interrupted():
1197 if not options.no_progress and not options.silent:
1198 sys.stderr.write("scons: Build interrupted.\n")
1200 global this_build_status
1202 this_build_status = 2
1204 if this_build_status:
1205 progress_display("scons: " + failure_message)
1207 progress_display("scons: " + closing_message)
1208 if not options.no_exec:
1209 if jobs.were_interrupted():
1210 progress_display("scons: writing .sconsign file.")
1211 SCons.SConsign.write()
1213 progress_display("scons: " + opening_message)
1214 jobs.run(postfunc = jobs_postfunc)
1216 memory_stats.append('after building targets:')
1217 count_stats.append(('post-', 'build'))
1221 def _exec_main(parser, values):
1222 sconsflags = os.environ.get('SCONSFLAGS', '')
1223 all_args = sconsflags.split() + sys.argv[1:]
1225 options, args = parser.parse_args(all_args, values)
1227 if isinstance(options.debug, list) and "pdb" in options.debug:
1229 pdb.Pdb().runcall(_main, parser)
1230 elif options.profile_file:
1231 # compat layer imports "cProfile" for us if it's available.
1232 from profile import Profile
1234 # Some versions of Python 2.4 shipped a profiler that had the
1235 # wrong 'c_exception' entry in its dispatch table. Make sure
1236 # we have the right one. (This may put an unnecessary entry
1237 # in the table in earlier versions of Python, but its presence
1238 # shouldn't hurt anything).
1240 dispatch = Profile.dispatch
1241 except AttributeError:
1244 dispatch['c_exception'] = Profile.trace_dispatch_return
1248 prof.runcall(_main, parser)
1249 except SConsPrintHelpException, e:
1250 prof.dump_stats(options.profile_file)
1254 prof.dump_stats(options.profile_file)
1259 global OptionsParser
1261 global first_command_start
1263 # Check up front for a Python version we do not support. We
1264 # delay the check for deprecated Python versions until later,
1265 # after the SConscript files have been read, in case they
1266 # disable that warning.
1267 if python_version_unsupported():
1268 msg = "scons: *** SCons version %s does not run under Python version %s.\n"
1269 sys.stderr.write(msg % (SCons.__version__, python_version_string()))
1272 parts = ["SCons by Steven Knight et al.:\n"]
1275 parts.append(version_string("script", __main__))
1276 except (ImportError, AttributeError):
1277 # On Windows there is no scons.py, so there is no
1278 # __main__.__version__, hence there is no script version.
1280 parts.append(version_string("engine", SCons))
1281 parts.append("__COPYRIGHT__")
1282 version = ''.join(parts)
1285 parser = SConsOptions.Parser(version)
1286 values = SConsOptions.SConsValues(parser.get_default_values())
1288 OptionsParser = parser
1291 _exec_main(parser, values)
1292 except SystemExit, s:
1295 except KeyboardInterrupt:
1296 print("scons: Build interrupted.")
1298 except SyntaxError, e:
1299 _scons_syntax_error(e)
1300 except SCons.Errors.InternalError:
1301 _scons_internal_error()
1302 except SCons.Errors.UserError, e:
1303 _scons_user_error(e)
1304 except SConsPrintHelpException:
1307 except SCons.Errors.BuildError, e:
1308 exit_status = e.exitstatus
1310 # An exception here is likely a builtin Python exception Python
1311 # code in an SConscript file. Show them precisely what the
1312 # problem was and where it happened.
1313 SCons.Script._SConscript.SConscript_exception()
1316 memory_stats.print_stats()
1317 count_stats.print_stats()
1320 SCons.Debug.listLoggedInstances('*')
1321 #SCons.Debug.dumpLoggedInstances('*')
1324 SCons.Memoize.Dump("Memoizer (memory cache) hits and misses:")
1326 # Dump any development debug info that may have been enabled.
1327 # These are purely for internal debugging during development, so
1328 # there's no need to control them with --debug= options; they're
1329 # controlled by changing the source code.
1330 SCons.Debug.dump_caller_counts()
1331 SCons.Taskmaster.dump_stats()
1334 total_time = time.time() - SCons.Script.start_time
1336 ct = cumulative_command_time
1338 if last_command_end is None or first_command_start is None:
1341 ct = last_command_end - first_command_start
1342 scons_time = total_time - sconscript_time - ct
1343 print "Total build time: %f seconds"%total_time
1344 print "Total SConscript file execution time: %f seconds"%sconscript_time
1345 print "Total SCons execution time: %f seconds"%scons_time
1346 print "Total command execution time: %f seconds"%ct
1348 sys.exit(exit_status)
1352 # indent-tabs-mode:nil
1354 # vim: set expandtab tabstop=4 shiftwidth=4: