3 This file implements the main() function used by the scons script.
5 Architecturally, this *is* the scons script, and will likely only be
6 called from the external "scons" wrapper. Consequently, anything here
7 should not be, or be considered, part of the build engine. If it's
8 something that we expect other software to want to use, it should go in
9 some other module. If it's specific to the "scons" script invocation,
17 # Permission is hereby granted, free of charge, to any person obtaining
18 # a copy of this software and associated documentation files (the
19 # "Software"), to deal in the Software without restriction, including
20 # without limitation the rights to use, copy, modify, merge, publish,
21 # distribute, sublicense, and/or sell copies of the Software, and to
22 # permit persons to whom the Software is furnished to do so, subject to
23 # the following conditions:
25 # The above copyright notice and this permission notice shall be included
26 # in all copies or substantial portions of the Software.
28 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
29 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
30 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
31 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
32 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
33 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
34 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
37 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
47 # Strip the script directory from sys.path() so on case-insensitive
48 # (WIN32) systems Python doesn't think that the "scons" script is the
49 # "SCons" package. Replace it with our own version directory so, if
50 # if they're there, we pick up the right version of the build engine
52 #sys.path = [os.path.join(sys.prefix,
54 # 'scons-%d' % SCons.__version__)] + sys.path[1:]
58 import SCons.Environment
63 from SCons.Optik import OptionParser, SUPPRESS_HELP, OptionValueError
66 import SCons.Taskmaster
71 display = SCons.Util.display
72 progress_display = SCons.Util.DisplayEngine()
76 class BuildTask(SCons.Taskmaster.Task):
77 """An SCons build task."""
78 def display(self, message):
79 display('scons: ' + message)
82 target = self.targets[0]
83 if target.get_state() == SCons.Node.up_to_date:
84 if self.top and target.has_builder():
85 display("scons: `%s' is up to date." % str(self.node))
86 elif target.has_builder() and not hasattr(target.builder, 'status'):
88 start_time = time.time()
89 SCons.Taskmaster.Task.execute(self)
91 finish_time = time.time()
93 command_time = command_time+finish_time-start_time
94 print "Command execution time: %f seconds"%(finish_time-start_time)
96 def do_failed(self, status=2):
99 SCons.Taskmaster.Task.executed(self)
100 elif keep_going_on_error:
101 SCons.Taskmaster.Task.fail_continue(self)
104 SCons.Taskmaster.Task.fail_stop(self)
109 if self.top and not t.has_builder() and not t.side_effect:
111 sys.stderr.write("scons: *** Do not know how to make target `%s'." % t)
112 if not keep_going_on_error:
113 sys.stderr.write(" Stop.")
114 sys.stderr.write("\n")
117 print "scons: Nothing to be done for `%s'." % t
118 SCons.Taskmaster.Task.executed(self)
120 SCons.Taskmaster.Task.executed(self)
123 # Handle the failure of a build task. The primary purpose here
124 # is to display the various types of Errors and Exceptions
127 exc_info = self.exc_info()
134 # The Taskmaster didn't record an exception for this Task;
135 # see if the sys module has one.
136 t, e = sys.exc_info()[:2]
138 if t == SCons.Errors.BuildError:
140 if SCons.Util.is_List(e.node):
141 fname = string.join(map(str, e.node), ', ')
142 sys.stderr.write("scons: *** [%s] %s\n" % (fname, e.errstr))
143 if e.errstr == 'Exception':
144 traceback.print_exception(e.args[0], e.args[1], e.args[2])
145 elif t == SCons.Errors.ExplicitExit:
147 sys.stderr.write("scons: *** [%s] Explicit exit, status %s\n" % (e.node, e.status))
152 if t == SCons.Errors.StopError and not keep_going_on_error:
154 sys.stderr.write("scons: *** %s\n" % s)
156 if tb and print_stacktrace:
157 sys.stderr.write("scons: internal stack trace:\n")
158 traceback.print_tb(tb, file=sys.stderr)
160 self.do_failed(status)
164 def postprocess(self):
169 SCons.Util.print_tree(t, get_all_children)
172 SCons.Util.print_tree(t, get_all_children, showtags=2)
175 SCons.Util.print_tree(t, get_derived_children)
177 tree = t.render_include_tree()
181 SCons.Taskmaster.Task.postprocess(self)
183 def make_ready(self):
184 """Make a task ready for execution"""
185 SCons.Taskmaster.Task.make_ready(self)
186 if self.out_of_date and print_explanations:
187 explanation = self.out_of_date[0].explain()
189 sys.stdout.write("scons: " + explanation)
191 class CleanTask(SCons.Taskmaster.Task):
192 """An SCons clean task."""
194 target = self.targets[0]
195 if (target.has_builder() or target.side_effect) and not target.isdir():
196 display("Removed " + str(target))
197 if SCons.Environment.CleanTargets.has_key(target):
198 files = SCons.Environment.CleanTargets[target]
200 SCons.Util.fs_delete(str(f), 0)
203 target = self.targets[0]
204 if target.has_builder() or target.side_effect:
205 for t in self.targets:
209 print "scons: Could not remove '%s':" % str(t), e.strerror
212 display("Removed " + str(t))
213 if SCons.Environment.CleanTargets.has_key(target):
214 files = SCons.Environment.CleanTargets[target]
216 SCons.Util.fs_delete(str(f))
220 # Have the taskmaster arrange to "execute" all of the targets, because
221 # we'll figure out ourselves (in remove() or show() above) whether
222 # anything really needs to be done.
223 make_ready = SCons.Taskmaster.Task.make_ready_all
228 class QuestionTask(SCons.Taskmaster.Task):
229 """An SCons task for the -q (question) option."""
234 if self.targets[0].get_state() != SCons.Node.up_to_date:
244 keep_going_on_error = 0
246 print_explanations = 0
257 exit_status = 0 # exit status, assume success by default
259 num_jobs = 1 # this is modifed by SConscript.SetJobs()
261 diskcheck_all = SCons.Node.FS.diskcheck_types()
262 diskcheck_option_set = None
264 def diskcheck_convert(value):
267 if not SCons.Util.is_List(value):
268 value = string.split(value, ',')
270 for v in map(string.lower, value):
272 result = diskcheck_all
275 elif v in diskcheck_all:
286 self.append = self.do_nothing
287 self.print_stats = self.do_nothing
288 def enable(self, outfp):
290 self.append = self.do_append
291 self.print_stats = self.do_print
292 def do_nothing(self, *args, **kw):
295 class CountStats(Stats):
296 def do_append(self, label):
297 self.labels.append(label)
298 self.stats.append(SCons.Debug.fetchLoggedInstances())
302 for n in map(lambda t: t[0], s):
303 stats_table[n] = [0, 0, 0, 0]
307 stats_table[n][i] = c
309 keys = stats_table.keys()
311 self.outfp.write("Object counts:\n")
315 fmt1 = string.join(pre + [' %7s']*l + post, '')
316 fmt2 = string.join(pre + [' %7d']*l + post, '')
317 labels = self.labels[:l]
318 labels.append(("", "Class"))
319 self.outfp.write(fmt1 % tuple(map(lambda x: x[0], labels)))
320 self.outfp.write(fmt1 % tuple(map(lambda x: x[1], labels)))
322 r = stats_table[k][:l] + [k]
323 self.outfp.write(fmt2 % tuple(r))
325 count_stats = CountStats()
327 class MemStats(Stats):
328 def do_append(self, label):
329 self.labels.append(label)
330 self.stats.append(SCons.Debug.memory())
332 fmt = 'Memory %-32s %12d\n'
333 for label, stats in map(None, self.labels, self.stats):
334 self.outfp.write(fmt % (label, stats))
336 memory_stats = MemStats()
340 def get_all_children(node): return node.all_children()
342 def get_derived_children(node):
343 children = node.all_children(None)
344 return filter(lambda x: x.has_builder(), children)
346 def _scons_syntax_error(e):
347 """Handle syntax errors. Print out a message and show where the error
350 etype, value, tb = sys.exc_info()
351 lines = traceback.format_exception_only(etype, value)
353 sys.stderr.write(line+'\n')
356 def find_deepest_user_frame(tb):
358 Find the deepest stack frame that is not part of SCons.
360 Input is a "pre-processed" stack trace in the form
361 returned by traceback.extract_tb() or traceback.extract_stack()
366 # find the deepest traceback frame that is not part
370 if string.find(filename, os.sep+'SCons'+os.sep) == -1:
374 def _scons_user_error(e):
375 """Handle user errors. Print out a message and a description of the
376 error, along with the line number and routine where it occured.
377 The file and line number will be the deepest stack frame that is
378 not part of SCons itself.
380 etype, value, tb = sys.exc_info()
381 filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_tb(tb))
382 sys.stderr.write("\nscons: *** %s\n" % value)
383 sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
386 def _scons_user_warning(e):
387 """Handle user warnings. Print out a message and a description of
388 the warning, along with the line number and routine where it occured.
389 The file and line number will be the deepest stack frame that is
390 not part of SCons itself.
392 etype, value, tb = sys.exc_info()
393 filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_tb(tb))
394 sys.stderr.write("\nscons: warning: %s\n" % e)
395 sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
397 def _scons_internal_warning(e):
398 """Slightly different from _scons_user_warning in that we use the
399 *current call stack* rather than sys.exc_info() to get our stack trace.
400 This is used by the warnings framework to print warnings."""
401 filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_stack())
402 sys.stderr.write("\nscons: warning: %s\n" % e[0])
403 sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
405 def _scons_internal_error():
406 """Handle all errors but user errors. Print out a message telling
407 the user what to do in this case and print a normal trace.
409 print 'internal error'
410 traceback.print_exc()
413 def _varargs(option, parser):
416 arg = parser.rargs[0]
422 def _setup_warn(arg):
423 """The --warn option. An argument to this option
424 should be of the form <warning-class> or no-<warning-class>.
425 The warning class is munged in order to get an actual class
426 name from the SCons.Warnings module to enable or disable.
427 The supplied <warning-class> is split on hyphens, each element
428 is captialized, then smushed back together. Then the string
429 "SCons.Warnings." is added to the front and "Warning" is added
430 to the back to get the fully qualified class name.
432 For example, --warn=deprecated will enable the
433 SCons.Warnings.DeprecatedWarning class.
435 --warn=no-dependency will disable the
436 SCons.Warnings.DependencyWarning class.
438 As a special case, --warn=all and --warn=no-all
439 will enable or disable (respectively) the base
440 class of all warnings, which is SCons.Warning.Warning."""
442 elems = string.split(string.lower(arg), '-')
448 if len(elems) == 1 and elems[0] == 'all':
449 class_name = "Warning"
453 return "SCons" + s[5:]
455 return string.capitalize(s)
456 class_name = string.join(map(_capitalize, elems), '') + "Warning"
458 clazz = getattr(SCons.Warnings, class_name)
459 except AttributeError:
460 sys.stderr.write("No warning type: '%s'\n" % arg)
463 SCons.Warnings.enableWarningClass(clazz)
465 SCons.Warnings.suppressWarningClass(clazz)
467 def _SConstruct_exists(dirname=''):
468 """This function checks that an SConstruct file exists in a directory.
469 If so, it returns the path of the file. By default, it checks the
473 for file in ['SConstruct', 'Sconstruct', 'sconstruct']:
474 sfile = os.path.join(dirname, file)
475 if os.path.isfile(sfile):
477 if not os.path.isabs(sfile):
478 for rep in repositories:
479 if os.path.isfile(os.path.join(rep, sfile)):
483 def _set_globals(options):
484 global keep_going_on_error, ignore_errors
485 global count_stats, print_dtree
486 global print_explanations, print_includes, print_memoizer
487 global print_objects, print_stacktrace, print_stree
488 global print_time, print_tree
491 keep_going_on_error = options.keep_going
493 debug_values = options.debug
494 if debug_values is None:
496 except AttributeError:
499 if "count" in debug_values:
500 count_stats.enable(sys.stdout)
501 if "dtree" in debug_values:
503 if "explain" in debug_values:
504 print_explanations = 1
505 if "findlibs" in debug_values:
506 SCons.Scanner.Prog.print_find_libs = "findlibs"
507 if "includes" in debug_values:
509 if "memoizer" in debug_values:
511 if "memory" in debug_values:
512 memory_stats.enable(sys.stdout)
513 if "objects" in debug_values:
515 if "presub" in debug_values:
516 SCons.Action.print_actions_presub = 1
517 if "stacktrace" in debug_values:
519 if "stree" in debug_values:
521 if "time" in debug_values:
523 if "tree" in debug_values:
525 ignore_errors = options.ignore_errors
527 def _create_path(plist):
533 path = path + '/' + d
537 class OptParser(OptionParser):
541 parts = ["SCons by Steven Knight et al.:\n"]
543 parts.append("\tscript: v%s.%s, %s, by %s on %s\n" % (__main__.__version__,
546 __main__.__developer__,
547 __main__.__buildsys__))
548 except KeyboardInterrupt:
551 # On win32 there is no scons.py, so there is no __main__.__version__,
552 # hence there is no script version.
554 parts.append("\tengine: v%s.%s, %s, by %s on %s\n" % (SCons.__version__,
559 parts.append("__COPYRIGHT__")
560 OptionParser.__init__(self, version=string.join(parts, ''),
561 usage="usage: scons [OPTION] [TARGET] ...")
563 # options ignored for compatibility
564 def opt_ignore(option, opt, value, parser):
565 sys.stderr.write("Warning: ignoring %s option\n" % opt)
566 self.add_option("-b", "-m", "-S", "-t", "--no-keep-going", "--stop",
567 "--touch", action="callback", callback=opt_ignore,
568 help="Ignored for compatibility.")
570 self.add_option('-c', '--clean', '--remove', action="store_true",
572 help="Remove specified targets and dependencies.")
574 self.add_option('-C', '--directory', type="string", action = "append",
576 help="Change to DIR before doing anything.")
578 self.add_option('--cache-disable', '--no-cache',
579 action="store_true", dest='cache_disable', default=0,
580 help="Do not retrieve built targets from CacheDir.")
582 self.add_option('--cache-force', '--cache-populate',
583 action="store_true", dest='cache_force', default=0,
584 help="Copy already-built targets into the CacheDir.")
586 self.add_option('--cache-show',
587 action="store_true", dest='cache_show', default=0,
588 help="Print build actions for files from CacheDir.")
590 config_options = ["auto", "force" ,"cache"]
592 def opt_config(option, opt, value, parser, c_options=config_options):
593 if value in c_options:
594 parser.values.config = value
596 raise OptionValueError("Warning: %s is not a valid config type" % value)
597 self.add_option('--config', action="callback", type="string",
598 callback=opt_config, nargs=1, dest="config",
599 metavar="MODE", default="auto",
600 help="Controls Configure subsystem: "
601 "%s." % string.join(config_options, ", "))
603 def opt_not_yet(option, opt, value, parser):
604 sys.stderr.write("Warning: the %s option is not yet implemented\n" % opt)
606 self.add_option('-d', action="callback",
607 callback=opt_not_yet,
608 help = "Print file dependency information.")
610 self.add_option('-D', action="store_const", const=2, dest="climb_up",
611 help="Search up directory tree for SConstruct, "
612 "build all Default() targets.")
614 debug_options = ["count", "dtree", "explain", "findlibs",
615 "includes", "memoizer", "memory",
616 "nomemoizer", "objects",
617 "pdb", "presub", "stacktrace", "stree",
620 def opt_debug(option, opt, value, parser, debug_options=debug_options):
621 if value in debug_options:
623 if parser.values.debug is None:
624 parser.values.debug = []
625 except AttributeError:
626 parser.values.debug = []
627 parser.values.debug.append(value)
629 raise OptionValueError("Warning: %s is not a valid debug type" % value)
630 self.add_option('--debug', action="callback", type="string",
631 callback=opt_debug, nargs=1, dest="debug",
633 help="Print various types of debugging information: "
634 "%s." % string.join(debug_options, ", "))
636 def opt_diskcheck(option, opt, value, parser):
638 global diskcheck_option_set
639 diskcheck_option_set = diskcheck_convert(value)
640 SCons.Node.FS.set_diskcheck(diskcheck_option_set)
641 except ValueError, e:
642 raise OptionValueError("Warning: `%s' is not a valid diskcheck type" % e)
645 self.add_option('--diskcheck', action="callback", type="string",
646 callback=opt_diskcheck, dest='diskcheck',
648 help="Enable specific on-disk checks.")
650 def opt_duplicate(option, opt, value, parser):
651 if not value in SCons.Node.FS.Valid_Duplicates:
652 raise OptionValueError("`%s' is not a valid duplication style." % value)
653 parser.values.duplicate = value
654 # Set the duplicate style right away so it can affect linking
655 # of SConscript files.
656 SCons.Node.FS.set_duplicate(value)
657 self.add_option('--duplicate', action="callback", type="string",
658 callback=opt_duplicate, nargs=1, dest="duplicate",
659 help="Set the preferred duplication methods. Must be one of "
660 + string.join(SCons.Node.FS.Valid_Duplicates, ", "))
662 self.add_option('-f', '--file', '--makefile', '--sconstruct',
663 action="append", nargs=1,
664 help="Read FILE as the top-level SConstruct file.")
666 self.add_option('-h', '--help', action="store_true", default=0,
668 help="Print defined help message, or this one.")
670 self.add_option("-H", "--help-options",
672 help="Print this message and exit.")
674 self.add_option('-i', '--ignore-errors', action="store_true",
675 default=0, dest='ignore_errors',
676 help="Ignore errors from build actions.")
678 self.add_option('-I', '--include-dir', action="append",
679 dest='include_dir', metavar="DIR",
680 help="Search DIR for imported Python modules.")
682 self.add_option('--implicit-cache', action="store_true",
683 dest='implicit_cache',
684 help="Cache implicit dependencies")
686 self.add_option('--implicit-deps-changed', action="store_true",
687 default=0, dest='implicit_deps_changed',
688 help="Ignore cached implicit dependencies.")
689 self.add_option('--implicit-deps-unchanged', action="store_true",
690 default=0, dest='implicit_deps_unchanged',
691 help="Ignore changes in implicit dependencies.")
693 def opt_j(option, opt, value, parser):
695 parser.values.num_jobs = value
696 self.add_option('-j', '--jobs', action="callback", type="int",
697 callback=opt_j, metavar="N",
698 help="Allow N jobs at once.")
700 self.add_option('-k', '--keep-going', action="store_true", default=0,
702 help="Keep going when a target can't be made.")
704 self.add_option('--max-drift', type="int", action="store",
705 dest='max_drift', metavar="N",
706 help="Set maximum system clock drift to N seconds.")
708 self.add_option('-n', '--no-exec', '--just-print', '--dry-run',
709 '--recon', action="store_true", dest='noexec',
710 default=0, help="Don't build; just print commands.")
712 self.add_option('--profile', action="store",
713 dest="profile_file", metavar="FILE",
714 help="Profile SCons and put results in FILE.")
716 self.add_option('-q', '--question', action="store_true", default=0,
717 help="Don't build; exit status says if up to date.")
719 self.add_option('-Q', dest='no_progress', action="store_true",
721 help="Suppress \"Reading/Building\" progress messages.")
723 self.add_option('--random', dest="random", action="store_true",
724 default=0, help="Build dependencies in random order.")
726 self.add_option('-s', '--silent', '--quiet', action="store_true",
727 default=0, help="Don't print commands.")
729 self.add_option('-u', '--up', '--search-up', action="store_const",
730 dest="climb_up", default=0, const=1,
731 help="Search up directory tree for SConstruct, "
732 "build targets at or below current directory.")
733 self.add_option('-U', action="store_const", dest="climb_up",
735 help="Search up directory tree for SConstruct, "
736 "build Default() targets from local SConscript.")
738 self.add_option("-v", "--version",
740 help="Print the SCons version number and exit.")
742 self.add_option('--warn', '--warning', nargs=1, action="store",
743 metavar="WARNING-SPEC",
744 help="Enable or disable warnings.")
746 self.add_option('-Y', '--repository', nargs=1, action="append",
747 help="Search REPOSITORY for source and target files.")
749 self.add_option('-e', '--environment-overrides', action="callback",
750 callback=opt_not_yet,
751 # help="Environment variables override makefiles."
753 self.add_option('-l', '--load-average', '--max-load', action="callback",
754 callback=opt_not_yet, type="int", dest="load_average",
756 # help="Don't start multiple jobs unless load is below "
760 self.add_option('--list-derived', action="callback",
761 callback=opt_not_yet,
762 # help="Don't build; list files that would be built."
764 self.add_option('--list-actions', action="callback",
765 callback=opt_not_yet,
766 # help="Don't build; list files and build actions."
768 self.add_option('--list-where', action="callback",
769 callback=opt_not_yet,
770 # help="Don't build; list files and where defined."
772 self.add_option('-o', '--old-file', '--assume-old', action="callback",
773 callback=opt_not_yet, type="string", dest="old_file",
774 # help = "Consider FILE to be old; don't rebuild it."
776 self.add_option('--override', action="callback", dest="override",
777 callback=opt_not_yet, type="string",
778 # help="Override variables as specified in FILE."
780 self.add_option('-p', action="callback",
781 callback=opt_not_yet,
782 # help="Print internal environments/objects."
784 self.add_option('-r', '-R', '--no-builtin-rules',
785 '--no-builtin-variables', action="callback",
786 callback=opt_not_yet,
787 # help="Clear default environments and variables."
789 self.add_option('-w', '--print-directory', action="callback",
790 callback=opt_not_yet,
791 # help="Print the current directory."
793 self.add_option('--no-print-directory', action="callback",
794 callback=opt_not_yet,
795 # help="Turn off -w, even if it was turned on implicitly."
797 self.add_option('--write-filenames', action="callback",
798 callback=opt_not_yet, type="string", dest="write_filenames",
799 # help="Write all filenames examined into FILE."
801 self.add_option('-W', '--what-if', '--new-file', '--assume-new',
803 action="callback", callback=opt_not_yet, type="string",
804 # help="Consider FILE to be changed."
806 self.add_option('--warn-undefined-variables', action="callback",
807 callback=opt_not_yet,
808 # help="Warn when an undefined variable is referenced."
811 def parse_args(self, args=None, values=None):
812 opt, arglist = OptionParser.parse_args(self, args, values)
813 if opt.implicit_deps_changed or opt.implicit_deps_unchanged:
814 opt.implicit_cache = 1
817 class SConscriptSettableOptions:
818 """This class wraps an OptParser instance and provides
819 uniform access to options that can be either set on the command
820 line or from a SConscript file. A value specified on the command
821 line always overrides a value set in a SConscript file.
822 Not all command line options are SConscript settable, and the ones
823 that are must be explicitly added to settable dictionary and optionally
824 validated and coerced in the set() method."""
826 def __init__(self, options):
827 self.options = options
829 # This dictionary stores the defaults for all the SConscript
830 # settable options, as well as indicating which options
831 # are SConscript settable.
832 self.settable = {'num_jobs':1,
833 'max_drift':SCons.Sig.default_max_drift,
836 'duplicate':'hard-soft-copy',
837 'diskcheck':diskcheck_all}
840 if not self.settable.has_key(name):
841 raise SCons.Error.UserError, "This option is not settable from a SConscript file: %s"%name
842 if hasattr(self.options, name) and getattr(self.options, name) is not None:
843 return getattr(self.options, name)
845 return self.settable[name]
847 def set(self, name, value):
848 if not self.settable.has_key(name):
849 raise SCons.Error.UserError, "This option is not settable from a SConscript file: %s"%name
851 if name == 'num_jobs':
857 raise SCons.Errors.UserError, "A positive integer is required: %s"%repr(value)
858 elif name == 'max_drift':
862 raise SCons.Errors.UserError, "An integer is required: %s"%repr(value)
863 elif name == 'duplicate':
867 raise SCons.Errors.UserError, "A string is required: %s"%repr(value)
868 if not value in SCons.Node.FS.Valid_Duplicates:
869 raise SCons.Errors.UserError, "Not a valid duplication style: %s" % value
870 # Set the duplicate stye right away so it can affect linking
871 # of SConscript files.
872 SCons.Node.FS.set_duplicate(value)
873 elif name == 'diskcheck':
875 value = diskcheck_convert(value)
876 except ValueError, v:
877 raise SCons.Errors.UserError, "Not a valid diskcheck value: %s"%v
878 if not diskcheck_option_set:
879 SCons.Node.FS.set_diskcheck(value)
881 self.settable[name] = value
884 def _main(args, parser):
885 # Here's where everything really happens.
887 # First order of business: set up default warnings and and then
888 # handle the user's warning options, so we can warn about anything
889 # that happens appropriately.
890 default_warnings = [ SCons.Warnings.CorruptSConsignWarning,
891 SCons.Warnings.DeprecatedWarning,
892 SCons.Warnings.DuplicateEnvironmentWarning,
893 SCons.Warnings.MissingSConscriptWarning,
894 SCons.Warnings.NoParallelSupportWarning,
895 SCons.Warnings.MisleadingKeywordsWarning, ]
896 for warning in default_warnings:
897 SCons.Warnings.enableWarningClass(warning)
898 SCons.Warnings._warningOut = _scons_internal_warning
900 _setup_warn(options.warn)
902 # Next, we want to create the FS object that represents the outside
903 # world's file system, as that's central to a lot of initialization.
904 # To do this, however, we need to be in the directory from which we
905 # want to start everything, which means first handling any relevant
906 # options that might cause us to chdir somewhere (-C, -D, -U, -u).
907 if options.directory:
908 cdir = _create_path(options.directory)
912 sys.stderr.write("Could not change directory to %s\n" % cdir)
914 # The SConstruct file may be in a repository, so initialize those
915 # before we start the search up our path for one.
917 if options.repository:
918 repositories.extend(options.repository)
922 target_top = '.' # directory to prepend to targets
923 script_dir = os.getcwd() # location of script
924 while script_dir and not _SConstruct_exists(script_dir):
925 script_dir, last_part = os.path.split(script_dir)
927 target_top = os.path.join(last_part, target_top)
931 display("scons: Entering directory `%s'" % script_dir)
934 # Now that we're in the top-level SConstruct directory, go ahead
935 # and initialize the FS object that represents the file system,
936 # and make it the build engine default.
937 fs = SCons.Node.FS.default_fs = SCons.Node.FS.FS()
939 for rep in repositories:
942 # Now that we have the FS object, the next order of business is to
943 # check for an SConstruct file (or other specified config file).
944 # If there isn't one, we can bail before doing any more work.
947 scripts.extend(options.file)
949 sfile = _SConstruct_exists()
951 scripts.append(sfile)
955 # There's no SConstruct, but they specified -h.
956 # Give them the options usage now, before we fail
957 # trying to read a non-existent SConstruct file.
960 raise SCons.Errors.UserError, "No SConstruct file found."
962 if scripts[0] == "-":
965 d = fs.File(scripts[0]).dir
966 fs.set_SConstruct_dir(d)
968 # Now that we have the FS object and it's intialized, set up (most
969 # of) the rest of the options.
971 ssoptions = SConscriptSettableOptions(options)
973 _set_globals(options)
974 SCons.Node.implicit_cache = options.implicit_cache
975 SCons.Node.implicit_deps_changed = options.implicit_deps_changed
976 SCons.Node.implicit_deps_unchanged = options.implicit_deps_unchanged
978 SCons.SConf.dryrun = 1
979 SCons.Action.execute_actions = None
980 CleanTask.execute = CleanTask.show
982 SCons.SConf.dryrun = 1
983 SCons.SConf.SetCacheMode(options.config)
984 SCons.SConf.SetProgressDisplay(progress_display)
986 if options.no_progress or options.silent:
987 progress_display.set_mode(0)
991 SCons.Action.print_actions = None
992 if options.cache_disable:
993 def disable(self): pass
994 fs.CacheDir = disable
995 if options.cache_force:
997 if options.cache_show:
1000 if options.include_dir:
1001 sys.path = options.include_dir + sys.path
1003 # That should cover (most of) the options. Next, set up the variables
1004 # that hold command-line arguments, so the SConscript files that we
1005 # read and execute have access to them.
1013 SCons.Script._Add_Targets(targets)
1014 SCons.Script._Add_Arguments(xmit_args)
1017 def __init__(self, file):
1019 def write(self, arg):
1020 self.file.write(arg)
1022 def __getattr__(self, attr):
1023 return getattr(self.file, attr)
1025 sys.stdout = Unbuffered(sys.stdout)
1027 memory_stats.append('before reading SConscript files:')
1028 count_stats.append(('pre-', 'read'))
1030 progress_display("scons: Reading SConscript files ...")
1032 start_time = time.time()
1034 for script in scripts:
1035 SCons.Script._SConscript._SConscript(fs, script)
1036 except SCons.Errors.StopError, e:
1037 # We had problems reading an SConscript file, such as it
1038 # couldn't be copied in to the BuildDir. Since we're just
1039 # reading SConscript files and haven't started building
1040 # things yet, stop regardless of whether they used -i or -k
1043 sys.stderr.write("scons: *** %s Stop.\n" % e)
1045 sys.exit(exit_status)
1046 global sconscript_time
1047 sconscript_time = time.time() - start_time
1048 SCons.SConf.CreateConfigHBuilder(SCons.Defaults.DefaultEnvironment())
1049 progress_display("scons: done reading SConscript files.")
1051 # Tell the Node.FS subsystem that we're all done reading the
1052 # SConscript files and calling Repository() and BuildDir() and the
1053 # like, so it can go ahead and start memoizing the string values of
1054 # file system nodes.
1055 SCons.Node.FS.save_strings(1)
1057 memory_stats.append('after reading SConscript files:')
1058 count_stats.append(('post-', 'read'))
1062 if options.help_msg:
1063 help_text = SCons.Script.help_text
1064 if help_text is None:
1065 # They specified -h, but there was no Help() inside the
1066 # SConscript files. Give them the options usage.
1067 parser.print_help(sys.stdout)
1070 print "Use scons -H for help about command-line options."
1073 # Now that we've read the SConscripts we can set the options
1074 # that are SConscript settable:
1075 SCons.Node.implicit_cache = ssoptions.get('implicit_cache')
1076 SCons.Node.FS.set_duplicate(ssoptions.get('duplicate'))
1080 # They specified targets on the command line, so if they
1081 # used -u, -U or -D, we have to look up targets relative
1082 # to the top, but we build whatever they specified.
1084 lookup_top = fs.Dir(target_top)
1087 # There are no targets specified on the command line,
1088 # so if they used -u, -U or -D, we may have to restrict
1089 # what actually gets built.
1092 if options.climb_up == 1:
1093 # -u, local directory and below
1094 target_top = fs.Dir(target_top)
1095 lookup_top = target_top
1096 elif options.climb_up == 2:
1097 # -D, all Default() targets
1100 elif options.climb_up == 3:
1101 # -U, local SConscript Default() targets
1102 target_top = fs.Dir(target_top)
1103 def check_dir(x, target_top=target_top):
1104 if hasattr(x, 'cwd') and not x.cwd is None:
1105 cwd = x.cwd.srcnode()
1106 return cwd == target_top
1108 # x doesn't have a cwd, so it's either not a target,
1109 # or not a file, so go ahead and keep it as a default
1110 # target and let the engine sort it out:
1112 d = filter(check_dir, SCons.Script.DEFAULT_TARGETS)
1113 SCons.Script.DEFAULT_TARGETS[:] = d
1117 targets = SCons.Script._Get_Default_Targets(d, fs)
1120 sys.stderr.write("scons: *** No targets specified and no Default() targets found. Stop.\n")
1123 def Entry(x, ltop=lookup_top, ttop=target_top, fs=fs):
1124 if isinstance(x, SCons.Node.Node):
1128 # Why would ltop be None? Unfortunately this happens.
1129 if ltop == None: ltop = ''
1130 # Curdir becomes important when SCons is called with -u, -C,
1131 # or similar option that changes directory, and so the paths
1132 # of targets given on the command line need to be adjusted.
1133 curdir = os.path.join(os.getcwd(), str(ltop))
1134 for lookup in SCons.Node.arg2nodes_lookups:
1135 node = lookup(x, curdir=curdir)
1139 node = fs.Entry(x, directory=ltop, create=1)
1140 if ttop and not node.is_under(ttop):
1141 if isinstance(node, SCons.Node.FS.Dir) and ttop.is_under(node):
1147 nodes = filter(None, map(Entry, targets))
1149 task_class = BuildTask # default action is to build targets
1150 opening_message = "Building targets ..."
1151 closing_message = "done building targets."
1152 if keep_going_on_error:
1153 failure_message = "done building targets (errors occurred during build)."
1155 failure_message = "building terminated because of errors."
1156 if options.question:
1157 task_class = QuestionTask
1159 if ssoptions.get('clean'):
1160 task_class = CleanTask
1161 opening_message = "Cleaning targets ..."
1162 closing_message = "done cleaning targets."
1163 if keep_going_on_error:
1164 closing_message = "done cleaning targets (errors occurred during clean)."
1166 failure_message = "cleaning terminated because of errors."
1167 except AttributeError:
1170 SCons.Environment.CalculatorArgs['max_drift'] = ssoptions.get('max_drift')
1173 def order(dependencies):
1174 """Randomize the dependencies."""
1175 # This is cribbed from the implementation of
1176 # random.shuffle() in Python 2.X.
1178 for i in xrange(len(d)-1, 0, -1):
1179 j = int(random.random() * (i+1))
1180 d[i], d[j] = d[j], d[i]
1183 def order(dependencies):
1184 """Leave the order of dependencies alone."""
1187 progress_display("scons: " + opening_message)
1188 taskmaster = SCons.Taskmaster.Taskmaster(nodes, task_class, order)
1190 nj = ssoptions.get('num_jobs')
1191 jobs = SCons.Job.Jobs(nj, taskmaster)
1192 if nj > 1 and jobs.num_jobs == 1:
1193 msg = "parallel builds are unsupported by this version of Python;\n" + \
1194 "\tignoring -j or num_jobs option.\n"
1195 SCons.Warnings.warn(SCons.Warnings.NoParallelSupportWarning, msg)
1197 memory_stats.append('before building targets:')
1198 count_stats.append(('pre-', 'build'))
1204 progress_display("scons: " + failure_message)
1206 progress_display("scons: " + closing_message)
1207 if not options.noexec:
1208 SCons.SConsign.write()
1210 memory_stats.append('after building targets:')
1211 count_stats.append(('post-', 'build'))
1214 all_args = sys.argv[1:]
1216 all_args = string.split(os.environ['SCONSFLAGS']) + all_args
1218 # it's OK if there's no SCONSFLAGS
1220 parser = OptParser()
1222 options, args = parser.parse_args(all_args)
1223 if type(options.debug) == type([]) and "pdb" in options.debug:
1225 pdb.Pdb().runcall(_main, args, parser)
1226 elif options.profile_file:
1228 prof = profile.Profile()
1230 prof.runcall(_main, args, parser)
1233 prof.dump_stats(options.profile_file)
1242 except SystemExit, s:
1245 except KeyboardInterrupt:
1246 print "Build interrupted."
1248 except SyntaxError, e:
1249 _scons_syntax_error(e)
1250 except SCons.Errors.InternalError:
1251 _scons_internal_error()
1252 except SCons.Errors.UserError, e:
1253 _scons_user_error(e)
1255 # An exception here is likely a builtin Python exception Python
1256 # code in an SConscript file. Show them precisely what the
1257 # problem was and where it happened.
1258 SCons.Script._SConscript.SConscript_exception()
1261 memory_stats.print_stats()
1262 count_stats.print_stats()
1265 SCons.Debug.listLoggedInstances('*')
1266 #SCons.Debug.dumpLoggedInstances('*')
1269 print "Memoizer (memory cache) hits and misses:"
1270 SCons.Memoize.Dump()
1272 # Dump any development debug info that may have been enabled.
1273 # These are purely for internal debugging during development, so
1274 # there's no need to control them with --debug= options; they're
1275 # controlled by changing the source code.
1276 SCons.Debug.dump_caller_counts()
1277 SCons.Taskmaster.dump_stats()
1280 total_time = time.time()-SCons.Script.start_time
1281 scons_time = total_time-sconscript_time-command_time
1282 print "Total build time: %f seconds"%total_time
1283 print "Total SConscript file execution time: %f seconds"%sconscript_time
1284 print "Total SCons execution time: %f seconds"%scons_time
1285 print "Total command execution time: %f seconds"%command_time
1287 sys.exit(exit_status)