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 for e in sorted(os.listdir(path)):
320 p = os.path.join(path, e)
321 s = os.path.join(pathstr, e)
322 if os.path.isfile(p):
323 if remove: os.unlink(p)
324 display("Removed " + s)
326 self.fs_delete(p, s, remove)
327 # then delete dir itself
328 if remove: os.rmdir(path)
329 display("Removed directory " + pathstr)
331 errstr = "Path '%s' exists but isn't a file or directory."
332 raise SCons.Errors.UserError(errstr % (pathstr))
333 except SCons.Errors.UserError, e:
335 except (IOError, OSError), e:
336 print "scons: Could not remove '%s':" % pathstr, e.strerror
339 target = self.targets[0]
340 if (target.has_builder() or target.side_effect) and not target.noclean:
341 for t in self.targets:
343 display("Removed " + str(t))
344 if target in SCons.Environment.CleanTargets:
345 files = SCons.Environment.CleanTargets[target]
347 self.fs_delete(f.abspath, str(f), 0)
350 target = self.targets[0]
351 if (target.has_builder() or target.side_effect) and not target.noclean:
352 for t in self.targets:
356 # An OSError may indicate something like a permissions
357 # issue, an IOError would indicate something like
358 # the file not existing. In either case, print a
359 # message and keep going to try to remove as many
360 # targets aa possible.
361 print "scons: Could not remove '%s':" % str(t), e.strerror
364 display("Removed " + str(t))
365 if target in SCons.Environment.CleanTargets:
366 files = SCons.Environment.CleanTargets[target]
368 self.fs_delete(f.abspath, str(f))
372 # We want the Taskmaster to update the Node states (and therefore
373 # handle reference counts, etc.), but we don't want to call
374 # back to the Node's post-build methods, which would do things
375 # we don't want, like store .sconsign information.
376 executed = SCons.Taskmaster.Task.executed_without_callbacks
378 # Have the taskmaster arrange to "execute" all of the targets, because
379 # we'll figure out ourselves (in remove() or show() above) whether
380 # anything really needs to be done.
381 make_ready = SCons.Taskmaster.Task.make_ready_all
386 class QuestionTask(SCons.Taskmaster.AlwaysTask):
387 """An SCons task for the -q (question) option."""
392 if self.targets[0].get_state() != SCons.Node.up_to_date or \
393 (self.top and not self.targets[0].exists()):
395 global this_build_status
397 this_build_status = 1
405 def __init__(self, derived=False, prune=False, status=False):
406 self.derived = derived
409 def get_all_children(self, node):
410 return node.all_children()
411 def get_derived_children(self, node):
412 children = node.all_children(None)
413 return [x for x in children if x.has_builder()]
414 def display(self, t):
416 func = self.get_derived_children
418 func = self.get_all_children
419 s = self.status and 2 or 0
420 SCons.Util.print_tree(t, func, prune=self.prune, showtags=s)
423 def python_version_string():
424 return sys.version.split()[0]
426 def python_version_unsupported(version=sys.version_info):
427 return version < (1, 5, 2)
429 def python_version_deprecated(version=sys.version_info):
430 return version < (2, 4, 0)
440 cumulative_command_time = 0
441 exit_status = 0 # final exit status, assume success by default
442 this_build_status = 0 # "exit status" of an individual build
444 delayed_warnings = []
446 class FakeOptionParser:
448 A do-nothing option parser, used for the initial OptionsParser variable.
450 During normal SCons operation, the OptionsParser is created right
451 away by the main() function. Certain tests scripts however, can
452 introspect on different Tool modules, the initialization of which
453 can try to add a new, local option to an otherwise uninitialized
454 OptionsParser object. This allows that introspection to happen
458 class FakeOptionValues:
459 def __getattr__(self, attr):
461 values = FakeOptionValues()
462 def add_local_option(self, *args, **kw):
465 OptionsParser = FakeOptionParser()
467 def AddOption(*args, **kw):
468 if 'default' not in kw:
470 result = OptionsParser.add_local_option(*args, **kw)
474 return getattr(OptionsParser.values, name)
476 def SetOption(name, value):
477 return OptionsParser.values.set_option(name, value)
484 self.append = self.do_nothing
485 self.print_stats = self.do_nothing
486 def enable(self, outfp):
488 self.append = self.do_append
489 self.print_stats = self.do_print
490 def do_nothing(self, *args, **kw):
493 class CountStats(Stats):
494 def do_append(self, label):
495 self.labels.append(label)
496 self.stats.append(SCons.Debug.fetchLoggedInstances())
500 for n in [t[0] for t in s]:
501 stats_table[n] = [0, 0, 0, 0]
505 stats_table[n][i] = c
507 self.outfp.write("Object counts:\n")
511 fmt1 = ''.join(pre + [' %7s']*l + post)
512 fmt2 = ''.join(pre + [' %7d']*l + post)
513 labels = self.labels[:l]
514 labels.append(("", "Class"))
515 self.outfp.write(fmt1 % tuple([x[0] for x in labels]))
516 self.outfp.write(fmt1 % tuple([x[1] for x in labels]))
517 for k in sorted(stats_table.keys()):
518 r = stats_table[k][:l] + [k]
519 self.outfp.write(fmt2 % tuple(r))
521 count_stats = CountStats()
523 class MemStats(Stats):
524 def do_append(self, label):
525 self.labels.append(label)
526 self.stats.append(SCons.Debug.memory())
528 fmt = 'Memory %-32s %12d\n'
529 for label, stats in map(None, self.labels, self.stats):
530 self.outfp.write(fmt % (label, stats))
532 memory_stats = MemStats()
536 def _scons_syntax_error(e):
537 """Handle syntax errors. Print out a message and show where the error
540 etype, value, tb = sys.exc_info()
541 lines = traceback.format_exception_only(etype, value)
543 sys.stderr.write(line+'\n')
546 def find_deepest_user_frame(tb):
548 Find the deepest stack frame that is not part of SCons.
550 Input is a "pre-processed" stack trace in the form
551 returned by traceback.extract_tb() or traceback.extract_stack()
556 # find the deepest traceback frame that is not part
560 if filename.find(os.sep+'SCons'+os.sep) == -1:
564 def _scons_user_error(e):
565 """Handle user errors. Print out a message and a description of the
566 error, along with the line number and routine where it occured.
567 The file and line number will be the deepest stack frame that is
568 not part of SCons itself.
570 global print_stacktrace
571 etype, value, tb = sys.exc_info()
573 traceback.print_exception(etype, value, tb)
574 filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_tb(tb))
575 sys.stderr.write("\nscons: *** %s\n" % value)
576 sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
579 def _scons_user_warning(e):
580 """Handle user warnings. Print out a message and a description of
581 the warning, along with the line number and routine where it occured.
582 The file and line number will be the deepest stack frame that is
583 not part of SCons itself.
585 etype, value, tb = sys.exc_info()
586 filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_tb(tb))
587 sys.stderr.write("\nscons: warning: %s\n" % e)
588 sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
590 def _scons_internal_warning(e):
591 """Slightly different from _scons_user_warning in that we use the
592 *current call stack* rather than sys.exc_info() to get our stack trace.
593 This is used by the warnings framework to print warnings."""
594 filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_stack())
595 sys.stderr.write("\nscons: warning: %s\n" % e[0])
596 sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
598 def _scons_internal_error():
599 """Handle all errors but user errors. Print out a message telling
600 the user what to do in this case and print a normal trace.
602 print 'internal error'
603 traceback.print_exc()
606 def _SConstruct_exists(dirname='', repositories=[], filelist=None):
607 """This function checks that an SConstruct file exists in a directory.
608 If so, it returns the path of the file. By default, it checks the
612 filelist = ['SConstruct', 'Sconstruct', 'sconstruct']
613 for file in filelist:
614 sfile = os.path.join(dirname, file)
615 if os.path.isfile(sfile):
617 if not os.path.isabs(sfile):
618 for rep in repositories:
619 if os.path.isfile(os.path.join(rep, sfile)):
623 def _set_debug_values(options):
624 global print_memoizer, print_objects, print_stacktrace, print_time
626 debug_values = options.debug
628 if "count" in debug_values:
629 # All of the object counts are within "if __debug__:" blocks,
630 # which get stripped when running optimized (with python -O or
631 # from compiled *.pyo files). Provide a warning if __debug__ is
632 # stripped, so it doesn't just look like --debug=count is broken.
634 if __debug__: enable_count = True
636 count_stats.enable(sys.stdout)
638 msg = "--debug=count is not supported when running SCons\n" + \
639 "\twith the python -O option or optimized (.pyo) modules."
640 SCons.Warnings.warn(SCons.Warnings.NoObjectCountWarning, msg)
641 if "dtree" in debug_values:
642 options.tree_printers.append(TreePrinter(derived=True))
643 options.debug_explain = ("explain" in debug_values)
644 if "findlibs" in debug_values:
645 SCons.Scanner.Prog.print_find_libs = "findlibs"
646 options.debug_includes = ("includes" in debug_values)
647 print_memoizer = ("memoizer" in debug_values)
648 if "memory" in debug_values:
649 memory_stats.enable(sys.stdout)
650 print_objects = ("objects" in debug_values)
651 if "presub" in debug_values:
652 SCons.Action.print_actions_presub = 1
653 if "stacktrace" in debug_values:
655 if "stree" in debug_values:
656 options.tree_printers.append(TreePrinter(status=True))
657 if "time" in debug_values:
659 if "tree" in debug_values:
660 options.tree_printers.append(TreePrinter())
662 def _create_path(plist):
668 path = path + '/' + d
671 def _load_site_scons_dir(topdir, site_dir_name=None):
672 """Load the site_scons dir under topdir.
673 Adds site_scons to sys.path, imports site_scons/site_init.py,
674 and adds site_scons/site_tools to default toolpath."""
676 err_if_not_found = True # user specified: err if missing
678 site_dir_name = "site_scons"
679 err_if_not_found = False
681 site_dir = os.path.join(topdir.path, site_dir_name)
682 if not os.path.exists(site_dir):
684 raise SCons.Errors.UserError, "site dir %s not found."%site_dir
687 site_init_filename = "site_init.py"
688 site_init_modname = "site_init"
689 site_tools_dirname = "site_tools"
690 sys.path = [os.path.abspath(site_dir)] + sys.path
691 site_init_file = os.path.join(site_dir, site_init_filename)
692 site_tools_dir = os.path.join(site_dir, site_tools_dirname)
693 if os.path.exists(site_init_file):
695 # TODO(2.4): turn this into try:-except:-finally:
698 fp, pathname, description = imp.find_module(site_init_modname,
700 # Load the file into SCons.Script namespace. This is
701 # opaque and clever; m is the module object for the
702 # SCons.Script module, and the exec ... in call executes a
703 # file (or string containing code) in the context of the
704 # module's dictionary, so anything that code defines ends
705 # up adding to that module. This is really short, but all
706 # the error checking makes it longer.
708 m = sys.modules['SCons.Script']
710 fmt = 'cannot import site_init.py: missing SCons.Script module %s'
711 raise SCons.Errors.InternalError, fmt % repr(e)
714 exec fp in m.__dict__
715 except KeyboardInterrupt:
718 fmt = '*** Error loading site_init file %s:\n'
719 sys.stderr.write(fmt % repr(site_init_file))
721 except KeyboardInterrupt:
723 except ImportError, e:
724 fmt = '*** cannot import site init file %s:\n'
725 sys.stderr.write(fmt % repr(site_init_file))
730 if os.path.exists(site_tools_dir):
731 SCons.Tool.DefaultToolpath.append(os.path.abspath(site_tools_dir))
733 def version_string(label, module):
734 version = module.__version__
735 build = module.__build__
739 version = version + build
740 fmt = "\t%s: v%s, %s, by %s on %s\n"
744 module.__developer__,
749 global this_build_status
751 options = parser.values
753 # Here's where everything really happens.
755 # First order of business: set up default warnings and then
756 # handle the user's warning options, so that we can issue (or
757 # suppress) appropriate warnings about anything that might happen,
758 # as configured by the user.
760 default_warnings = [ SCons.Warnings.CorruptSConsignWarning,
761 SCons.Warnings.DeprecatedWarning,
762 SCons.Warnings.DuplicateEnvironmentWarning,
763 SCons.Warnings.FutureReservedVariableWarning,
764 SCons.Warnings.LinkWarning,
765 SCons.Warnings.MissingSConscriptWarning,
766 SCons.Warnings.NoMD5ModuleWarning,
767 SCons.Warnings.NoMetaclassSupportWarning,
768 SCons.Warnings.NoObjectCountWarning,
769 SCons.Warnings.NoParallelSupportWarning,
770 SCons.Warnings.MisleadingKeywordsWarning,
771 SCons.Warnings.ReservedVariableWarning,
772 SCons.Warnings.StackSizeWarning,
773 SCons.Warnings.VisualVersionMismatch,
774 SCons.Warnings.VisualCMissingWarning,
777 for warning in default_warnings:
778 SCons.Warnings.enableWarningClass(warning)
779 SCons.Warnings._warningOut = _scons_internal_warning
780 SCons.Warnings.process_warn_strings(options.warn)
782 # Now that we have the warnings configuration set up, we can actually
783 # issue (or suppress) any warnings about warning-worthy things that
784 # occurred while the command-line options were getting parsed.
786 dw = options.delayed_warnings
787 except AttributeError:
790 delayed_warnings.extend(dw)
791 for warning_type, message in delayed_warnings:
792 SCons.Warnings.warn(warning_type, message)
794 if options.diskcheck:
795 SCons.Node.FS.set_diskcheck(options.diskcheck)
797 # Next, we want to create the FS object that represents the outside
798 # world's file system, as that's central to a lot of initialization.
799 # To do this, however, we need to be in the directory from which we
800 # want to start everything, which means first handling any relevant
801 # options that might cause us to chdir somewhere (-C, -D, -U, -u).
802 if options.directory:
803 script_dir = os.path.abspath(_create_path(options.directory))
805 script_dir = os.getcwd()
809 target_top = '.' # directory to prepend to targets
810 while script_dir and not _SConstruct_exists(script_dir,
813 script_dir, last_part = os.path.split(script_dir)
815 target_top = os.path.join(last_part, target_top)
819 if script_dir and script_dir != os.getcwd():
820 display("scons: Entering directory `%s'" % script_dir)
824 sys.stderr.write("Could not change directory to %s\n" % script_dir)
826 # Now that we're in the top-level SConstruct directory, go ahead
827 # and initialize the FS object that represents the file system,
828 # and make it the build engine default.
829 fs = SCons.Node.FS.get_default_fs()
831 for rep in options.repository:
834 # Now that we have the FS object, the next order of business is to
835 # check for an SConstruct file (or other specified config file).
836 # If there isn't one, we can bail before doing any more work.
839 scripts.extend(options.file)
841 sfile = _SConstruct_exists(repositories=options.repository,
842 filelist=options.file)
844 scripts.append(sfile)
848 # There's no SConstruct, but they specified -h.
849 # Give them the options usage now, before we fail
850 # trying to read a non-existent SConstruct file.
851 raise SConsPrintHelpException
852 raise SCons.Errors.UserError, "No SConstruct file found."
854 if scripts[0] == "-":
857 d = fs.File(scripts[0]).dir
858 fs.set_SConstruct_dir(d)
860 _set_debug_values(options)
861 SCons.Node.implicit_cache = options.implicit_cache
862 SCons.Node.implicit_deps_changed = options.implicit_deps_changed
863 SCons.Node.implicit_deps_unchanged = options.implicit_deps_unchanged
866 SCons.SConf.dryrun = 1
867 SCons.Action.execute_actions = None
869 SCons.SConf.dryrun = 1
871 SCons.SConf.SetBuildType('clean')
873 SCons.SConf.SetBuildType('help')
874 SCons.SConf.SetCacheMode(options.config)
875 SCons.SConf.SetProgressDisplay(progress_display)
877 if options.no_progress or options.silent:
878 progress_display.set_mode(0)
881 _load_site_scons_dir(d, options.site_dir)
882 elif not options.no_site_dir:
883 _load_site_scons_dir(d)
885 if options.include_dir:
886 sys.path = options.include_dir + sys.path
888 # That should cover (most of) the options. Next, set up the variables
889 # that hold command-line arguments, so the SConscript files that we
890 # read and execute have access to them.
893 for a in parser.largs:
900 SCons.Script._Add_Targets(targets + parser.rargs)
901 SCons.Script._Add_Arguments(xmit_args)
903 # If stdout is not a tty, replace it with a wrapper object to call flush
906 # Tty devices automatically flush after every newline, so the replacement
907 # isn't necessary. Furthermore, if we replace sys.stdout, the readline
908 # module will no longer work. This affects the behavior during
909 # --interactive mode. --interactive should only be used when stdin and
910 # stdout refer to a tty.
911 if not hasattr(sys.stdout, 'isatty') or not sys.stdout.isatty():
912 sys.stdout = SCons.Util.Unbuffered(sys.stdout)
913 if not hasattr(sys.stderr, 'isatty') or not sys.stderr.isatty():
914 sys.stderr = SCons.Util.Unbuffered(sys.stderr)
916 memory_stats.append('before reading SConscript files:')
917 count_stats.append(('pre-', 'read'))
919 # And here's where we (finally) read the SConscript files.
921 progress_display("scons: Reading SConscript files ...")
923 start_time = time.time()
925 for script in scripts:
926 SCons.Script._SConscript._SConscript(fs, script)
927 except SCons.Errors.StopError, e:
928 # We had problems reading an SConscript file, such as it
929 # couldn't be copied in to the VariantDir. Since we're just
930 # reading SConscript files and haven't started building
931 # things yet, stop regardless of whether they used -i or -k
933 sys.stderr.write("scons: *** %s Stop.\n" % e)
935 sys.exit(exit_status)
936 global sconscript_time
937 sconscript_time = time.time() - start_time
939 progress_display("scons: done reading SConscript files.")
941 memory_stats.append('after reading SConscript files:')
942 count_stats.append(('post-', 'read'))
944 # Re-{enable,disable} warnings in case they disabled some in
945 # the SConscript file.
947 # We delay enabling the PythonVersionWarning class until here so that,
948 # if they explicity disabled it in either in the command line or in
949 # $SCONSFLAGS, or in the SConscript file, then the search through
950 # the list of deprecated warning classes will find that disabling
951 # first and not issue the warning.
952 SCons.Warnings.enableWarningClass(SCons.Warnings.PythonVersionWarning)
953 SCons.Warnings.process_warn_strings(options.warn)
955 # Now that we've read the SConscript files, we can check for the
956 # warning about deprecated Python versions--delayed until here
957 # in case they disabled the warning in the SConscript files.
958 if python_version_deprecated():
959 msg = "Support for pre-2.4 Python (%s) is deprecated.\n" + \
960 " If this will cause hardship, contact dev@scons.tigris.org."
961 SCons.Warnings.warn(SCons.Warnings.PythonVersionWarning,
962 msg % python_version_string())
965 SCons.SConf.CreateConfigHBuilder(SCons.Defaults.DefaultEnvironment())
967 # Now re-parse the command-line options (any to the left of a '--'
968 # argument, that is) with any user-defined command-line options that
969 # the SConscript files may have added to the parser object. This will
970 # emit the appropriate error message and exit if any unknown option
971 # was specified on the command line.
973 parser.preserve_unknown_options = False
974 parser.parse_args(parser.largs, options)
977 help_text = SCons.Script.help_text
978 if help_text is None:
979 # They specified -h, but there was no Help() inside the
980 # SConscript files. Give them the options usage.
981 raise SConsPrintHelpException
984 print "Use scons -H for help about command-line options."
988 # Change directory to the top-level SConstruct directory, then tell
989 # the Node.FS subsystem that we're all done reading the SConscript
990 # files and calling Repository() and VariantDir() and changing
991 # directories and the like, so it can go ahead and start memoizing
992 # the string values of file system nodes.
996 SCons.Node.FS.save_strings(1)
998 # Now that we've read the SConscripts we can set the options
999 # that are SConscript settable:
1000 SCons.Node.implicit_cache = options.implicit_cache
1001 SCons.Node.FS.set_duplicate(options.duplicate)
1002 fs.set_max_drift(options.max_drift)
1004 SCons.Job.explicit_stack_size = options.stack_size
1006 if options.md5_chunksize:
1007 SCons.Node.FS.File.md5_chunksize = options.md5_chunksize
1009 platform = SCons.Platform.platform_module()
1011 if options.interactive:
1012 SCons.Script.Interactive.interact(fs, OptionsParser, options,
1013 targets, target_top)
1018 nodes = _build_targets(fs, options, targets, target_top)
1022 def _build_targets(fs, options, targets, target_top):
1024 global this_build_status
1025 this_build_status = 0
1027 progress_display.set_mode(not (options.no_progress or options.silent))
1028 display.set_mode(not options.silent)
1029 SCons.Action.print_actions = not options.silent
1030 SCons.Action.execute_actions = not options.no_exec
1031 SCons.Node.FS.do_store_info = not options.no_exec
1032 SCons.SConf.dryrun = options.no_exec
1034 if options.diskcheck:
1035 SCons.Node.FS.set_diskcheck(options.diskcheck)
1037 SCons.CacheDir.cache_enabled = not options.cache_disable
1038 SCons.CacheDir.cache_debug = options.cache_debug
1039 SCons.CacheDir.cache_force = options.cache_force
1040 SCons.CacheDir.cache_show = options.cache_show
1043 CleanTask.execute = CleanTask.show
1045 CleanTask.execute = CleanTask.remove
1048 if targets or SCons.Script.BUILD_TARGETS != SCons.Script._build_plus_default:
1049 # They specified targets on the command line or modified
1050 # BUILD_TARGETS in the SConscript file(s), so if they used -u,
1051 # -U or -D, we have to look up targets relative to the top,
1052 # but we build whatever they specified.
1054 lookup_top = fs.Dir(target_top)
1057 targets = SCons.Script.BUILD_TARGETS
1059 # There are no targets specified on the command line,
1060 # so if they used -u, -U or -D, we may have to restrict
1061 # what actually gets built.
1064 if options.climb_up == 1:
1065 # -u, local directory and below
1066 target_top = fs.Dir(target_top)
1067 lookup_top = target_top
1068 elif options.climb_up == 2:
1069 # -D, all Default() targets
1072 elif options.climb_up == 3:
1073 # -U, local SConscript Default() targets
1074 target_top = fs.Dir(target_top)
1075 def check_dir(x, target_top=target_top):
1076 if hasattr(x, 'cwd') and not x.cwd is None:
1077 cwd = x.cwd.srcnode()
1078 return cwd == target_top
1080 # x doesn't have a cwd, so it's either not a target,
1081 # or not a file, so go ahead and keep it as a default
1082 # target and let the engine sort it out:
1084 d = list(filter(check_dir, SCons.Script.DEFAULT_TARGETS))
1085 SCons.Script.DEFAULT_TARGETS[:] = d
1089 targets = SCons.Script._Get_Default_Targets(d, fs)
1092 sys.stderr.write("scons: *** No targets specified and no Default() targets found. Stop.\n")
1095 def Entry(x, ltop=lookup_top, ttop=target_top, fs=fs):
1096 if isinstance(x, SCons.Node.Node):
1100 # Why would ltop be None? Unfortunately this happens.
1101 if ltop is None: ltop = ''
1102 # Curdir becomes important when SCons is called with -u, -C,
1103 # or similar option that changes directory, and so the paths
1104 # of targets given on the command line need to be adjusted.
1105 curdir = os.path.join(os.getcwd(), str(ltop))
1106 for lookup in SCons.Node.arg2nodes_lookups:
1107 node = lookup(x, curdir=curdir)
1108 if node is not None:
1111 node = fs.Entry(x, directory=ltop, create=1)
1112 if ttop and not node.is_under(ttop):
1113 if isinstance(node, SCons.Node.FS.Dir) and ttop.is_under(node):
1119 nodes = [_f for _f in map(Entry, targets) if _f]
1121 task_class = BuildTask # default action is to build targets
1122 opening_message = "Building targets ..."
1123 closing_message = "done building targets."
1124 if options.keep_going:
1125 failure_message = "done building targets (errors occurred during build)."
1127 failure_message = "building terminated because of errors."
1128 if options.question:
1129 task_class = QuestionTask
1132 task_class = CleanTask
1133 opening_message = "Cleaning targets ..."
1134 closing_message = "done cleaning targets."
1135 if options.keep_going:
1136 failure_message = "done cleaning targets (errors occurred during clean)."
1138 failure_message = "cleaning terminated because of errors."
1139 except AttributeError:
1142 task_class.progress = ProgressObject
1145 def order(dependencies):
1146 """Randomize the dependencies."""
1148 # This is cribbed from the implementation of
1149 # random.shuffle() in Python 2.X.
1151 for i in xrange(len(d)-1, 0, -1):
1152 j = int(random.random() * (i+1))
1153 d[i], d[j] = d[j], d[i]
1156 def order(dependencies):
1157 """Leave the order of dependencies alone."""
1160 if options.taskmastertrace_file == '-':
1161 tmtrace = sys.stdout
1162 elif options.taskmastertrace_file:
1163 tmtrace = open(options.taskmastertrace_file, 'wb')
1166 taskmaster = SCons.Taskmaster.Taskmaster(nodes, task_class, order, tmtrace)
1168 # Let the BuildTask objects get at the options to respond to the
1169 # various print_* settings, tree_printer list, etc.
1170 BuildTask.options = options
1173 num_jobs = options.num_jobs
1174 jobs = SCons.Job.Jobs(num_jobs, taskmaster)
1177 if jobs.num_jobs == 1:
1178 msg = "parallel builds are unsupported by this version of Python;\n" + \
1179 "\tignoring -j or num_jobs option.\n"
1180 elif sys.platform == 'win32':
1181 msg = fetch_win32_parallel_msg()
1183 SCons.Warnings.warn(SCons.Warnings.NoParallelSupportWarning, msg)
1185 memory_stats.append('before building targets:')
1186 count_stats.append(('pre-', 'build'))
1191 closing_message=closing_message,
1192 failure_message=failure_message
1194 if jobs.were_interrupted():
1195 if not options.no_progress and not options.silent:
1196 sys.stderr.write("scons: Build interrupted.\n")
1198 global this_build_status
1200 this_build_status = 2
1202 if this_build_status:
1203 progress_display("scons: " + failure_message)
1205 progress_display("scons: " + closing_message)
1206 if not options.no_exec:
1207 if jobs.were_interrupted():
1208 progress_display("scons: writing .sconsign file.")
1209 SCons.SConsign.write()
1211 progress_display("scons: " + opening_message)
1212 jobs.run(postfunc = jobs_postfunc)
1214 memory_stats.append('after building targets:')
1215 count_stats.append(('post-', 'build'))
1219 def _exec_main(parser, values):
1220 sconsflags = os.environ.get('SCONSFLAGS', '')
1221 all_args = sconsflags.split() + sys.argv[1:]
1223 options, args = parser.parse_args(all_args, values)
1225 if isinstance(options.debug, list) and "pdb" in options.debug:
1227 pdb.Pdb().runcall(_main, parser)
1228 elif options.profile_file:
1230 from cProfile import Profile
1231 except ImportError, e:
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: