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__"
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 obj.find(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(self.obj.replace(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 = 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:
211 return str(obj.__class__).split('.')[-1]
212 if classname(t) in ('File', 'Dir', 'Entry'):
213 errstr="Do not know how to make %s target `%s' (%s)." % (classname(t), t, t.abspath)
214 else: # Alias or Python or ...
215 errstr="Do not know how to make %s target `%s'." % (classname(t), t)
216 sys.stderr.write("scons: *** " + errstr)
217 if not self.options.keep_going:
218 sys.stderr.write(" Stop.")
219 sys.stderr.write("\n")
221 raise SCons.Errors.BuildError(t, errstr)
222 except KeyboardInterrupt:
228 print "scons: Nothing to be done for `%s'." % t
229 SCons.Taskmaster.OutOfDateTask.executed(self)
231 SCons.Taskmaster.OutOfDateTask.executed(self)
234 # Handle the failure of a build task. The primary purpose here
235 # is to display the various types of Errors and Exceptions
237 exc_info = self.exc_info()
245 # The Taskmaster didn't record an exception for this Task;
246 # see if the sys module has one.
248 t, e, tb = sys.exc_info()[:]
253 # Deprecated string exceptions will have their string stored
254 # in the first entry of the tuple.
258 buildError = SCons.Errors.convert_to_BuildError(e)
259 if not buildError.node:
260 buildError.node = self.node
262 node = buildError.node
263 if not SCons.Util.is_List(node):
265 nodename = ', '.join(map(str, node))
267 errfmt = "scons: *** [%s] %s\n"
268 sys.stderr.write(errfmt % (nodename, buildError))
270 if (buildError.exc_info[2] and buildError.exc_info[1] and
273 # buildError.exc_info[1],
274 # (EnvironmentError, SCons.Errors.StopError, SCons.Errors.UserError))):
275 not isinstance(buildError.exc_info[1], EnvironmentError) and
276 not isinstance(buildError.exc_info[1], SCons.Errors.StopError) and
277 not isinstance(buildError.exc_info[1], SCons.Errors.UserError)):
278 type, value, trace = buildError.exc_info
279 traceback.print_exception(type, value, trace)
280 elif tb and print_stacktrace:
281 sys.stderr.write("scons: internal stack trace:\n")
282 traceback.print_tb(tb, file=sys.stderr)
284 self.exception = (e, buildError, tb) # type, value, traceback
285 self.do_failed(buildError.exitstatus)
289 def postprocess(self):
292 for tp in self.options.tree_printers:
294 if self.options.debug_includes:
295 tree = t.render_include_tree()
299 SCons.Taskmaster.OutOfDateTask.postprocess(self)
301 def make_ready(self):
302 """Make a task ready for execution"""
303 SCons.Taskmaster.OutOfDateTask.make_ready(self)
304 if self.out_of_date and self.options.debug_explain:
305 explanation = self.out_of_date[0].explain()
307 sys.stdout.write("scons: " + explanation)
309 class CleanTask(SCons.Taskmaster.AlwaysTask):
310 """An SCons clean task."""
311 def fs_delete(self, path, pathstr, remove=1):
313 if os.path.lexists(path):
314 if os.path.isfile(path) or os.path.islink(path):
315 if remove: os.unlink(path)
316 display("Removed " + pathstr)
317 elif os.path.isdir(path) and not os.path.islink(path):
318 # delete everything in the dir
319 entries = os.listdir(path)
320 # Sort for deterministic output (os.listdir() Can
321 # return entries in a random order).
324 p = os.path.join(path, e)
325 s = os.path.join(pathstr, e)
326 if os.path.isfile(p):
327 if remove: os.unlink(p)
328 display("Removed " + s)
330 self.fs_delete(p, s, remove)
331 # then delete dir itself
332 if remove: os.rmdir(path)
333 display("Removed directory " + pathstr)
335 errstr = "Path '%s' exists but isn't a file or directory."
336 raise SCons.Errors.UserError(errstr % (pathstr))
337 except SCons.Errors.UserError, e:
339 except (IOError, OSError), e:
340 print "scons: Could not remove '%s':" % pathstr, e.strerror
343 target = self.targets[0]
344 if (target.has_builder() or target.side_effect) and not target.noclean:
345 for t in self.targets:
347 display("Removed " + str(t))
348 if target in SCons.Environment.CleanTargets:
349 files = SCons.Environment.CleanTargets[target]
351 self.fs_delete(f.abspath, str(f), 0)
354 target = self.targets[0]
355 if (target.has_builder() or target.side_effect) and not target.noclean:
356 for t in self.targets:
360 # An OSError may indicate something like a permissions
361 # issue, an IOError would indicate something like
362 # the file not existing. In either case, print a
363 # message and keep going to try to remove as many
364 # targets aa possible.
365 print "scons: Could not remove '%s':" % str(t), e.strerror
368 display("Removed " + str(t))
369 if target in SCons.Environment.CleanTargets:
370 files = SCons.Environment.CleanTargets[target]
372 self.fs_delete(f.abspath, str(f))
376 # We want the Taskmaster to update the Node states (and therefore
377 # handle reference counts, etc.), but we don't want to call
378 # back to the Node's post-build methods, which would do things
379 # we don't want, like store .sconsign information.
380 executed = SCons.Taskmaster.Task.executed_without_callbacks
382 # Have the taskmaster arrange to "execute" all of the targets, because
383 # we'll figure out ourselves (in remove() or show() above) whether
384 # anything really needs to be done.
385 make_ready = SCons.Taskmaster.Task.make_ready_all
390 class QuestionTask(SCons.Taskmaster.AlwaysTask):
391 """An SCons task for the -q (question) option."""
396 if self.targets[0].get_state() != SCons.Node.up_to_date or \
397 (self.top and not self.targets[0].exists()):
399 global this_build_status
401 this_build_status = 1
409 def __init__(self, derived=False, prune=False, status=False):
410 self.derived = derived
413 def get_all_children(self, node):
414 return node.all_children()
415 def get_derived_children(self, node):
416 children = node.all_children(None)
417 return [x for x in children if x.has_builder()]
418 def display(self, t):
420 func = self.get_derived_children
422 func = self.get_all_children
423 s = self.status and 2 or 0
424 SCons.Util.print_tree(t, func, prune=self.prune, showtags=s)
427 def python_version_string():
428 return sys.version.split()[0]
430 def python_version_unsupported(version=sys.version_info):
431 return version < (1, 5, 2)
433 def python_version_deprecated(version=sys.version_info):
434 return version < (2, 4, 0)
444 cumulative_command_time = 0
445 exit_status = 0 # final exit status, assume success by default
446 this_build_status = 0 # "exit status" of an individual build
448 delayed_warnings = []
450 class FakeOptionParser:
452 A do-nothing option parser, used for the initial OptionsParser variable.
454 During normal SCons operation, the OptionsParser is created right
455 away by the main() function. Certain tests scripts however, can
456 introspect on different Tool modules, the initialization of which
457 can try to add a new, local option to an otherwise uninitialized
458 OptionsParser object. This allows that introspection to happen
462 class FakeOptionValues:
463 def __getattr__(self, attr):
465 values = FakeOptionValues()
466 def add_local_option(self, *args, **kw):
469 OptionsParser = FakeOptionParser()
471 def AddOption(*args, **kw):
472 if 'default' not in kw:
474 result = OptionsParser.add_local_option(*args, **kw)
478 return getattr(OptionsParser.values, name)
480 def SetOption(name, value):
481 return OptionsParser.values.set_option(name, value)
488 self.append = self.do_nothing
489 self.print_stats = self.do_nothing
490 def enable(self, outfp):
492 self.append = self.do_append
493 self.print_stats = self.do_print
494 def do_nothing(self, *args, **kw):
497 class CountStats(Stats):
498 def do_append(self, label):
499 self.labels.append(label)
500 self.stats.append(SCons.Debug.fetchLoggedInstances())
504 for n in [t[0] for t in s]:
505 stats_table[n] = [0, 0, 0, 0]
509 stats_table[n][i] = c
511 keys = stats_table.keys()
513 self.outfp.write("Object counts:\n")
517 fmt1 = ''.join(pre + [' %7s']*l + post)
518 fmt2 = ''.join(pre + [' %7d']*l + post)
519 labels = self.labels[:l]
520 labels.append(("", "Class"))
521 self.outfp.write(fmt1 % tuple([x[0] for x in labels]))
522 self.outfp.write(fmt1 % tuple([x[1] for x in labels]))
524 r = stats_table[k][:l] + [k]
525 self.outfp.write(fmt2 % tuple(r))
527 count_stats = CountStats()
529 class MemStats(Stats):
530 def do_append(self, label):
531 self.labels.append(label)
532 self.stats.append(SCons.Debug.memory())
534 fmt = 'Memory %-32s %12d\n'
535 for label, stats in map(None, self.labels, self.stats):
536 self.outfp.write(fmt % (label, stats))
538 memory_stats = MemStats()
542 def _scons_syntax_error(e):
543 """Handle syntax errors. Print out a message and show where the error
546 etype, value, tb = sys.exc_info()
547 lines = traceback.format_exception_only(etype, value)
549 sys.stderr.write(line+'\n')
552 def find_deepest_user_frame(tb):
554 Find the deepest stack frame that is not part of SCons.
556 Input is a "pre-processed" stack trace in the form
557 returned by traceback.extract_tb() or traceback.extract_stack()
562 # find the deepest traceback frame that is not part
566 if filename.find(os.sep+'SCons'+os.sep) == -1:
570 def _scons_user_error(e):
571 """Handle user errors. Print out a message and a description of the
572 error, along with the line number and routine where it occured.
573 The file and line number will be the deepest stack frame that is
574 not part of SCons itself.
576 global print_stacktrace
577 etype, value, tb = sys.exc_info()
579 traceback.print_exception(etype, value, tb)
580 filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_tb(tb))
581 sys.stderr.write("\nscons: *** %s\n" % value)
582 sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
585 def _scons_user_warning(e):
586 """Handle user warnings. Print out a message and a description of
587 the warning, along with the line number and routine where it occured.
588 The file and line number will be the deepest stack frame that is
589 not part of SCons itself.
591 etype, value, tb = sys.exc_info()
592 filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_tb(tb))
593 sys.stderr.write("\nscons: warning: %s\n" % e)
594 sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
596 def _scons_internal_warning(e):
597 """Slightly different from _scons_user_warning in that we use the
598 *current call stack* rather than sys.exc_info() to get our stack trace.
599 This is used by the warnings framework to print warnings."""
600 filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_stack())
601 sys.stderr.write("\nscons: warning: %s\n" % e[0])
602 sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
604 def _scons_internal_error():
605 """Handle all errors but user errors. Print out a message telling
606 the user what to do in this case and print a normal trace.
608 print 'internal error'
609 traceback.print_exc()
612 def _SConstruct_exists(dirname='', repositories=[], filelist=None):
613 """This function checks that an SConstruct file exists in a directory.
614 If so, it returns the path of the file. By default, it checks the
618 filelist = ['SConstruct', 'Sconstruct', 'sconstruct']
619 for file in filelist:
620 sfile = os.path.join(dirname, file)
621 if os.path.isfile(sfile):
623 if not os.path.isabs(sfile):
624 for rep in repositories:
625 if os.path.isfile(os.path.join(rep, sfile)):
629 def _set_debug_values(options):
630 global print_memoizer, print_objects, print_stacktrace, print_time
632 debug_values = options.debug
634 if "count" in debug_values:
635 # All of the object counts are within "if __debug__:" blocks,
636 # which get stripped when running optimized (with python -O or
637 # from compiled *.pyo files). Provide a warning if __debug__ is
638 # stripped, so it doesn't just look like --debug=count is broken.
640 if __debug__: enable_count = True
642 count_stats.enable(sys.stdout)
644 msg = "--debug=count is not supported when running SCons\n" + \
645 "\twith the python -O option or optimized (.pyo) modules."
646 SCons.Warnings.warn(SCons.Warnings.NoObjectCountWarning, msg)
647 if "dtree" in debug_values:
648 options.tree_printers.append(TreePrinter(derived=True))
649 options.debug_explain = ("explain" in debug_values)
650 if "findlibs" in debug_values:
651 SCons.Scanner.Prog.print_find_libs = "findlibs"
652 options.debug_includes = ("includes" in debug_values)
653 print_memoizer = ("memoizer" in debug_values)
654 if "memory" in debug_values:
655 memory_stats.enable(sys.stdout)
656 print_objects = ("objects" in debug_values)
657 if "presub" in debug_values:
658 SCons.Action.print_actions_presub = 1
659 if "stacktrace" in debug_values:
661 if "stree" in debug_values:
662 options.tree_printers.append(TreePrinter(status=True))
663 if "time" in debug_values:
665 if "tree" in debug_values:
666 options.tree_printers.append(TreePrinter())
668 def _create_path(plist):
674 path = path + '/' + d
677 def _load_site_scons_dir(topdir, site_dir_name=None):
678 """Load the site_scons dir under topdir.
679 Adds site_scons to sys.path, imports site_scons/site_init.py,
680 and adds site_scons/site_tools to default toolpath."""
682 err_if_not_found = True # user specified: err if missing
684 site_dir_name = "site_scons"
685 err_if_not_found = False
687 site_dir = os.path.join(topdir.path, site_dir_name)
688 if not os.path.exists(site_dir):
690 raise SCons.Errors.UserError, "site dir %s not found."%site_dir
693 site_init_filename = "site_init.py"
694 site_init_modname = "site_init"
695 site_tools_dirname = "site_tools"
696 sys.path = [os.path.abspath(site_dir)] + sys.path
697 site_init_file = os.path.join(site_dir, site_init_filename)
698 site_tools_dir = os.path.join(site_dir, site_tools_dirname)
699 if os.path.exists(site_init_file):
701 # TODO(2.4): turn this into try:-except:-finally:
704 fp, pathname, description = imp.find_module(site_init_modname,
706 # Load the file into SCons.Script namespace. This is
707 # opaque and clever; m is the module object for the
708 # SCons.Script module, and the exec ... in call executes a
709 # file (or string containing code) in the context of the
710 # module's dictionary, so anything that code defines ends
711 # up adding to that module. This is really short, but all
712 # the error checking makes it longer.
714 m = sys.modules['SCons.Script']
716 fmt = 'cannot import site_init.py: missing SCons.Script module %s'
717 raise SCons.Errors.InternalError, fmt % repr(e)
720 exec fp in m.__dict__
721 except KeyboardInterrupt:
724 fmt = '*** Error loading site_init file %s:\n'
725 sys.stderr.write(fmt % repr(site_init_file))
727 except KeyboardInterrupt:
729 except ImportError, e:
730 fmt = '*** cannot import site init file %s:\n'
731 sys.stderr.write(fmt % repr(site_init_file))
736 if os.path.exists(site_tools_dir):
737 SCons.Tool.DefaultToolpath.append(os.path.abspath(site_tools_dir))
739 def version_string(label, module):
740 version = module.__version__
741 build = module.__build__
745 version = version + build
746 fmt = "\t%s: v%s, %s, by %s on %s\n"
750 module.__developer__,
755 global this_build_status
757 options = parser.values
759 # Here's where everything really happens.
761 # First order of business: set up default warnings and then
762 # handle the user's warning options, so that we can issue (or
763 # suppress) appropriate warnings about anything that might happen,
764 # as configured by the user.
766 default_warnings = [ SCons.Warnings.CorruptSConsignWarning,
767 SCons.Warnings.DeprecatedWarning,
768 SCons.Warnings.DuplicateEnvironmentWarning,
769 SCons.Warnings.FutureReservedVariableWarning,
770 SCons.Warnings.LinkWarning,
771 SCons.Warnings.MissingSConscriptWarning,
772 SCons.Warnings.NoMD5ModuleWarning,
773 SCons.Warnings.NoMetaclassSupportWarning,
774 SCons.Warnings.NoObjectCountWarning,
775 SCons.Warnings.NoParallelSupportWarning,
776 SCons.Warnings.MisleadingKeywordsWarning,
777 SCons.Warnings.ReservedVariableWarning,
778 SCons.Warnings.StackSizeWarning,
779 SCons.Warnings.VisualVersionMismatch,
780 SCons.Warnings.VisualCMissingWarning,
783 for warning in default_warnings:
784 SCons.Warnings.enableWarningClass(warning)
785 SCons.Warnings._warningOut = _scons_internal_warning
786 SCons.Warnings.process_warn_strings(options.warn)
788 # Now that we have the warnings configuration set up, we can actually
789 # issue (or suppress) any warnings about warning-worthy things that
790 # occurred while the command-line options were getting parsed.
792 dw = options.delayed_warnings
793 except AttributeError:
796 delayed_warnings.extend(dw)
797 for warning_type, message in delayed_warnings:
798 SCons.Warnings.warn(warning_type, message)
800 if options.diskcheck:
801 SCons.Node.FS.set_diskcheck(options.diskcheck)
803 # Next, we want to create the FS object that represents the outside
804 # world's file system, as that's central to a lot of initialization.
805 # To do this, however, we need to be in the directory from which we
806 # want to start everything, which means first handling any relevant
807 # options that might cause us to chdir somewhere (-C, -D, -U, -u).
808 if options.directory:
809 script_dir = os.path.abspath(_create_path(options.directory))
811 script_dir = os.getcwd()
815 target_top = '.' # directory to prepend to targets
816 while script_dir and not _SConstruct_exists(script_dir,
819 script_dir, last_part = os.path.split(script_dir)
821 target_top = os.path.join(last_part, target_top)
825 if script_dir and script_dir != os.getcwd():
826 display("scons: Entering directory `%s'" % script_dir)
830 sys.stderr.write("Could not change directory to %s\n" % script_dir)
832 # Now that we're in the top-level SConstruct directory, go ahead
833 # and initialize the FS object that represents the file system,
834 # and make it the build engine default.
835 fs = SCons.Node.FS.get_default_fs()
837 for rep in options.repository:
840 # Now that we have the FS object, the next order of business is to
841 # check for an SConstruct file (or other specified config file).
842 # If there isn't one, we can bail before doing any more work.
845 scripts.extend(options.file)
847 sfile = _SConstruct_exists(repositories=options.repository,
848 filelist=options.file)
850 scripts.append(sfile)
854 # There's no SConstruct, but they specified -h.
855 # Give them the options usage now, before we fail
856 # trying to read a non-existent SConstruct file.
857 raise SConsPrintHelpException
858 raise SCons.Errors.UserError, "No SConstruct file found."
860 if scripts[0] == "-":
863 d = fs.File(scripts[0]).dir
864 fs.set_SConstruct_dir(d)
866 _set_debug_values(options)
867 SCons.Node.implicit_cache = options.implicit_cache
868 SCons.Node.implicit_deps_changed = options.implicit_deps_changed
869 SCons.Node.implicit_deps_unchanged = options.implicit_deps_unchanged
872 SCons.SConf.dryrun = 1
873 SCons.Action.execute_actions = None
875 SCons.SConf.dryrun = 1
877 SCons.SConf.SetBuildType('clean')
879 SCons.SConf.SetBuildType('help')
880 SCons.SConf.SetCacheMode(options.config)
881 SCons.SConf.SetProgressDisplay(progress_display)
883 if options.no_progress or options.silent:
884 progress_display.set_mode(0)
887 _load_site_scons_dir(d, options.site_dir)
888 elif not options.no_site_dir:
889 _load_site_scons_dir(d)
891 if options.include_dir:
892 sys.path = options.include_dir + sys.path
894 # That should cover (most of) the options. Next, set up the variables
895 # that hold command-line arguments, so the SConscript files that we
896 # read and execute have access to them.
899 for a in parser.largs:
906 SCons.Script._Add_Targets(targets + parser.rargs)
907 SCons.Script._Add_Arguments(xmit_args)
909 # If stdout is not a tty, replace it with a wrapper object to call flush
912 # Tty devices automatically flush after every newline, so the replacement
913 # isn't necessary. Furthermore, if we replace sys.stdout, the readline
914 # module will no longer work. This affects the behavior during
915 # --interactive mode. --interactive should only be used when stdin and
916 # stdout refer to a tty.
917 if not hasattr(sys.stdout, 'isatty') or not sys.stdout.isatty():
918 sys.stdout = SCons.Util.Unbuffered(sys.stdout)
919 if not hasattr(sys.stderr, 'isatty') or not sys.stderr.isatty():
920 sys.stderr = SCons.Util.Unbuffered(sys.stderr)
922 memory_stats.append('before reading SConscript files:')
923 count_stats.append(('pre-', 'read'))
925 # And here's where we (finally) read the SConscript files.
927 progress_display("scons: Reading SConscript files ...")
929 start_time = time.time()
931 for script in scripts:
932 SCons.Script._SConscript._SConscript(fs, script)
933 except SCons.Errors.StopError, e:
934 # We had problems reading an SConscript file, such as it
935 # couldn't be copied in to the VariantDir. Since we're just
936 # reading SConscript files and haven't started building
937 # things yet, stop regardless of whether they used -i or -k
939 sys.stderr.write("scons: *** %s Stop.\n" % e)
941 sys.exit(exit_status)
942 global sconscript_time
943 sconscript_time = time.time() - start_time
945 progress_display("scons: done reading SConscript files.")
947 memory_stats.append('after reading SConscript files:')
948 count_stats.append(('post-', 'read'))
950 # Re-{enable,disable} warnings in case they disabled some in
951 # the SConscript file.
953 # We delay enabling the PythonVersionWarning class until here so that,
954 # if they explicity disabled it in either in the command line or in
955 # $SCONSFLAGS, or in the SConscript file, then the search through
956 # the list of deprecated warning classes will find that disabling
957 # first and not issue the warning.
958 SCons.Warnings.enableWarningClass(SCons.Warnings.PythonVersionWarning)
959 SCons.Warnings.process_warn_strings(options.warn)
961 # Now that we've read the SConscript files, we can check for the
962 # warning about deprecated Python versions--delayed until here
963 # in case they disabled the warning in the SConscript files.
964 if python_version_deprecated():
965 msg = "Support for pre-2.4 Python (%s) is deprecated.\n" + \
966 " If this will cause hardship, contact dev@scons.tigris.org."
967 SCons.Warnings.warn(SCons.Warnings.PythonVersionWarning,
968 msg % python_version_string())
971 SCons.SConf.CreateConfigHBuilder(SCons.Defaults.DefaultEnvironment())
973 # Now re-parse the command-line options (any to the left of a '--'
974 # argument, that is) with any user-defined command-line options that
975 # the SConscript files may have added to the parser object. This will
976 # emit the appropriate error message and exit if any unknown option
977 # was specified on the command line.
979 parser.preserve_unknown_options = False
980 parser.parse_args(parser.largs, options)
983 help_text = SCons.Script.help_text
984 if help_text is None:
985 # They specified -h, but there was no Help() inside the
986 # SConscript files. Give them the options usage.
987 raise SConsPrintHelpException
990 print "Use scons -H for help about command-line options."
994 # Change directory to the top-level SConstruct directory, then tell
995 # the Node.FS subsystem that we're all done reading the SConscript
996 # files and calling Repository() and VariantDir() and changing
997 # directories and the like, so it can go ahead and start memoizing
998 # the string values of file system nodes.
1002 SCons.Node.FS.save_strings(1)
1004 # Now that we've read the SConscripts we can set the options
1005 # that are SConscript settable:
1006 SCons.Node.implicit_cache = options.implicit_cache
1007 SCons.Node.FS.set_duplicate(options.duplicate)
1008 fs.set_max_drift(options.max_drift)
1010 SCons.Job.explicit_stack_size = options.stack_size
1012 if options.md5_chunksize:
1013 SCons.Node.FS.File.md5_chunksize = options.md5_chunksize
1015 platform = SCons.Platform.platform_module()
1017 if options.interactive:
1018 SCons.Script.Interactive.interact(fs, OptionsParser, options,
1019 targets, target_top)
1024 nodes = _build_targets(fs, options, targets, target_top)
1028 def _build_targets(fs, options, targets, target_top):
1030 global this_build_status
1031 this_build_status = 0
1033 progress_display.set_mode(not (options.no_progress or options.silent))
1034 display.set_mode(not options.silent)
1035 SCons.Action.print_actions = not options.silent
1036 SCons.Action.execute_actions = not options.no_exec
1037 SCons.Node.FS.do_store_info = not options.no_exec
1038 SCons.SConf.dryrun = options.no_exec
1040 if options.diskcheck:
1041 SCons.Node.FS.set_diskcheck(options.diskcheck)
1043 SCons.CacheDir.cache_enabled = not options.cache_disable
1044 SCons.CacheDir.cache_debug = options.cache_debug
1045 SCons.CacheDir.cache_force = options.cache_force
1046 SCons.CacheDir.cache_show = options.cache_show
1049 CleanTask.execute = CleanTask.show
1051 CleanTask.execute = CleanTask.remove
1054 if targets or SCons.Script.BUILD_TARGETS != SCons.Script._build_plus_default:
1055 # They specified targets on the command line or modified
1056 # BUILD_TARGETS in the SConscript file(s), so if they used -u,
1057 # -U or -D, we have to look up targets relative to the top,
1058 # but we build whatever they specified.
1060 lookup_top = fs.Dir(target_top)
1063 targets = SCons.Script.BUILD_TARGETS
1065 # There are no targets specified on the command line,
1066 # so if they used -u, -U or -D, we may have to restrict
1067 # what actually gets built.
1070 if options.climb_up == 1:
1071 # -u, local directory and below
1072 target_top = fs.Dir(target_top)
1073 lookup_top = target_top
1074 elif options.climb_up == 2:
1075 # -D, all Default() targets
1078 elif options.climb_up == 3:
1079 # -U, local SConscript Default() targets
1080 target_top = fs.Dir(target_top)
1081 def check_dir(x, target_top=target_top):
1082 if hasattr(x, 'cwd') and not x.cwd is None:
1083 cwd = x.cwd.srcnode()
1084 return cwd == target_top
1086 # x doesn't have a cwd, so it's either not a target,
1087 # or not a file, so go ahead and keep it as a default
1088 # target and let the engine sort it out:
1090 d = list(filter(check_dir, SCons.Script.DEFAULT_TARGETS))
1091 SCons.Script.DEFAULT_TARGETS[:] = d
1095 targets = SCons.Script._Get_Default_Targets(d, fs)
1098 sys.stderr.write("scons: *** No targets specified and no Default() targets found. Stop.\n")
1101 def Entry(x, ltop=lookup_top, ttop=target_top, fs=fs):
1102 if isinstance(x, SCons.Node.Node):
1106 # Why would ltop be None? Unfortunately this happens.
1107 if ltop is None: ltop = ''
1108 # Curdir becomes important when SCons is called with -u, -C,
1109 # or similar option that changes directory, and so the paths
1110 # of targets given on the command line need to be adjusted.
1111 curdir = os.path.join(os.getcwd(), str(ltop))
1112 for lookup in SCons.Node.arg2nodes_lookups:
1113 node = lookup(x, curdir=curdir)
1114 if node is not None:
1117 node = fs.Entry(x, directory=ltop, create=1)
1118 if ttop and not node.is_under(ttop):
1119 if isinstance(node, SCons.Node.FS.Dir) and ttop.is_under(node):
1125 nodes = [_f for _f in map(Entry, targets) if _f]
1127 task_class = BuildTask # default action is to build targets
1128 opening_message = "Building targets ..."
1129 closing_message = "done building targets."
1130 if options.keep_going:
1131 failure_message = "done building targets (errors occurred during build)."
1133 failure_message = "building terminated because of errors."
1134 if options.question:
1135 task_class = QuestionTask
1138 task_class = CleanTask
1139 opening_message = "Cleaning targets ..."
1140 closing_message = "done cleaning targets."
1141 if options.keep_going:
1142 failure_message = "done cleaning targets (errors occurred during clean)."
1144 failure_message = "cleaning terminated because of errors."
1145 except AttributeError:
1148 task_class.progress = ProgressObject
1151 def order(dependencies):
1152 """Randomize the dependencies."""
1154 # This is cribbed from the implementation of
1155 # random.shuffle() in Python 2.X.
1157 for i in xrange(len(d)-1, 0, -1):
1158 j = int(random.random() * (i+1))
1159 d[i], d[j] = d[j], d[i]
1162 def order(dependencies):
1163 """Leave the order of dependencies alone."""
1166 if options.taskmastertrace_file == '-':
1167 tmtrace = sys.stdout
1168 elif options.taskmastertrace_file:
1169 tmtrace = open(options.taskmastertrace_file, 'wb')
1172 taskmaster = SCons.Taskmaster.Taskmaster(nodes, task_class, order, tmtrace)
1174 # Let the BuildTask objects get at the options to respond to the
1175 # various print_* settings, tree_printer list, etc.
1176 BuildTask.options = options
1179 num_jobs = options.num_jobs
1180 jobs = SCons.Job.Jobs(num_jobs, taskmaster)
1183 if jobs.num_jobs == 1:
1184 msg = "parallel builds are unsupported by this version of Python;\n" + \
1185 "\tignoring -j or num_jobs option.\n"
1186 elif sys.platform == 'win32':
1187 msg = fetch_win32_parallel_msg()
1189 SCons.Warnings.warn(SCons.Warnings.NoParallelSupportWarning, msg)
1191 memory_stats.append('before building targets:')
1192 count_stats.append(('pre-', 'build'))
1197 closing_message=closing_message,
1198 failure_message=failure_message
1200 if jobs.were_interrupted():
1201 if not options.no_progress and not options.silent:
1202 sys.stderr.write("scons: Build interrupted.\n")
1204 global this_build_status
1206 this_build_status = 2
1208 if this_build_status:
1209 progress_display("scons: " + failure_message)
1211 progress_display("scons: " + closing_message)
1212 if not options.no_exec:
1213 if jobs.were_interrupted():
1214 progress_display("scons: writing .sconsign file.")
1215 SCons.SConsign.write()
1217 progress_display("scons: " + opening_message)
1218 jobs.run(postfunc = jobs_postfunc)
1220 memory_stats.append('after building targets:')
1221 count_stats.append(('post-', 'build'))
1225 def _exec_main(parser, values):
1226 sconsflags = os.environ.get('SCONSFLAGS', '')
1227 all_args = sconsflags.split() + sys.argv[1:]
1229 options, args = parser.parse_args(all_args, values)
1231 if type(options.debug) == type([]) and "pdb" in options.debug:
1233 pdb.Pdb().runcall(_main, parser)
1234 elif options.profile_file:
1236 from cProfile import Profile
1237 except ImportError, e:
1238 from profile import Profile
1240 # Some versions of Python 2.4 shipped a profiler that had the
1241 # wrong 'c_exception' entry in its dispatch table. Make sure
1242 # we have the right one. (This may put an unnecessary entry
1243 # in the table in earlier versions of Python, but its presence
1244 # shouldn't hurt anything).
1246 dispatch = Profile.dispatch
1247 except AttributeError:
1250 dispatch['c_exception'] = Profile.trace_dispatch_return
1254 prof.runcall(_main, parser)
1255 except SConsPrintHelpException, e:
1256 prof.dump_stats(options.profile_file)
1260 prof.dump_stats(options.profile_file)
1265 global OptionsParser
1267 global first_command_start
1269 # Check up front for a Python version we do not support. We
1270 # delay the check for deprecated Python versions until later,
1271 # after the SConscript files have been read, in case they
1272 # disable that warning.
1273 if python_version_unsupported():
1274 msg = "scons: *** SCons version %s does not run under Python version %s.\n"
1275 sys.stderr.write(msg % (SCons.__version__, python_version_string()))
1278 parts = ["SCons by Steven Knight et al.:\n"]
1281 parts.append(version_string("script", __main__))
1282 except (ImportError, AttributeError):
1283 # On Windows there is no scons.py, so there is no
1284 # __main__.__version__, hence there is no script version.
1286 parts.append(version_string("engine", SCons))
1287 parts.append("__COPYRIGHT__")
1288 version = ''.join(parts)
1291 parser = SConsOptions.Parser(version)
1292 values = SConsOptions.SConsValues(parser.get_default_values())
1294 OptionsParser = parser
1297 _exec_main(parser, values)
1298 except SystemExit, s:
1301 except KeyboardInterrupt:
1302 print("scons: Build interrupted.")
1304 except SyntaxError, e:
1305 _scons_syntax_error(e)
1306 except SCons.Errors.InternalError:
1307 _scons_internal_error()
1308 except SCons.Errors.UserError, e:
1309 _scons_user_error(e)
1310 except SConsPrintHelpException:
1313 except SCons.Errors.BuildError, e:
1314 exit_status = e.exitstatus
1316 # An exception here is likely a builtin Python exception Python
1317 # code in an SConscript file. Show them precisely what the
1318 # problem was and where it happened.
1319 SCons.Script._SConscript.SConscript_exception()
1322 memory_stats.print_stats()
1323 count_stats.print_stats()
1326 SCons.Debug.listLoggedInstances('*')
1327 #SCons.Debug.dumpLoggedInstances('*')
1330 SCons.Memoize.Dump("Memoizer (memory cache) hits and misses:")
1332 # Dump any development debug info that may have been enabled.
1333 # These are purely for internal debugging during development, so
1334 # there's no need to control them with --debug= options; they're
1335 # controlled by changing the source code.
1336 SCons.Debug.dump_caller_counts()
1337 SCons.Taskmaster.dump_stats()
1340 total_time = time.time() - SCons.Script.start_time
1342 ct = cumulative_command_time
1344 if last_command_end is None or first_command_start is None:
1347 ct = last_command_end - first_command_start
1348 scons_time = total_time - sconscript_time - ct
1349 print "Total build time: %f seconds"%total_time
1350 print "Total SConscript file execution time: %f seconds"%sconscript_time
1351 print "Total SCons execution time: %f seconds"%scons_time
1352 print "Total command execution time: %f seconds"%ct
1354 sys.exit(exit_status)
1358 # indent-tabs-mode:nil
1360 # vim: set expandtab tabstop=4 shiftwidth=4: