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__"
48 # Strip the script directory from sys.path() so on case-insensitive
49 # (Windows) systems Python doesn't think that the "scons" script is the
50 # "SCons" package. Replace it with our own version directory so, if
51 # if they're there, we pick up the right version of the build engine
53 #sys.path = [os.path.join(sys.prefix,
55 # 'scons-%d' % SCons.__version__)] + sys.path[1:]
59 import SCons.Environment
64 from SCons.Optik import OptionParser, SUPPRESS_HELP, OptionValueError
68 import SCons.Taskmaster
73 display = SCons.Util.display
74 progress_display = SCons.Util.DisplayEngine()
76 first_command_start = None
77 last_command_end = None
81 class BuildTask(SCons.Taskmaster.Task):
82 """An SCons build task."""
83 def display(self, message):
84 display('scons: ' + message)
87 for target in self.targets:
88 if target.get_state() == SCons.Node.up_to_date:
90 if target.has_builder() and not hasattr(target.builder, 'status'):
92 start_time = time.time()
93 global first_command_start
94 if first_command_start is None:
95 first_command_start = start_time
96 SCons.Taskmaster.Task.execute(self)
98 global cumulative_command_time
99 global last_command_end
100 finish_time = time.time()
101 last_command_end = finish_time
102 cumulative_command_time = cumulative_command_time+finish_time-start_time
103 sys.stdout.write("Command execution time: %f seconds\n"%(finish_time-start_time))
106 if self.top and target.has_builder():
107 display("scons: `%s' is up to date." % str(self.node))
109 def do_failed(self, status=2):
112 SCons.Taskmaster.Task.executed(self)
113 elif keep_going_on_error:
114 SCons.Taskmaster.Task.fail_continue(self)
117 SCons.Taskmaster.Task.fail_stop(self)
122 if self.top and not t.has_builder() and not t.side_effect:
124 sys.stderr.write("scons: *** Do not know how to make target `%s'." % t)
125 if not keep_going_on_error:
126 sys.stderr.write(" Stop.")
127 sys.stderr.write("\n")
130 print "scons: Nothing to be done for `%s'." % t
131 SCons.Taskmaster.Task.executed(self)
133 SCons.Taskmaster.Task.executed(self)
136 # Handle the failure of a build task. The primary purpose here
137 # is to display the various types of Errors and Exceptions
140 exc_info = self.exc_info()
147 # The Taskmaster didn't record an exception for this Task;
148 # see if the sys module has one.
149 t, e = sys.exc_info()[:2]
152 if not SCons.Util.is_List(n):
154 return string.join(map(str, n), ', ')
156 errfmt = "scons: *** [%s] %s\n"
158 if t == SCons.Errors.BuildError:
159 tname = nodestring(e.node)
162 errstr = e.filename + ': ' + errstr
163 sys.stderr.write(errfmt % (tname, errstr))
164 elif t == SCons.Errors.TaskmasterException:
165 tname = nodestring(e.node)
166 sys.stderr.write(errfmt % (tname, e.errstr))
167 type, value, trace = e.exc_info
168 traceback.print_exception(type, value, trace)
169 elif t == SCons.Errors.ExplicitExit:
171 tname = nodestring(e.node)
172 errstr = 'Explicit exit, status %s' % status
173 sys.stderr.write(errfmt % (tname, errstr))
178 if t == SCons.Errors.StopError and not keep_going_on_error:
180 sys.stderr.write("scons: *** %s\n" % s)
182 if tb and print_stacktrace:
183 sys.stderr.write("scons: internal stack trace:\n")
184 traceback.print_tb(tb, file=sys.stderr)
186 self.do_failed(status)
190 def postprocess(self):
193 for tp in tree_printers:
196 tree = t.render_include_tree()
200 SCons.Taskmaster.Task.postprocess(self)
202 def make_ready(self):
203 """Make a task ready for execution"""
204 SCons.Taskmaster.Task.make_ready(self)
205 if self.out_of_date and print_explanations:
206 explanation = self.out_of_date[0].explain()
208 sys.stdout.write("scons: " + explanation)
210 class CleanTask(SCons.Taskmaster.Task):
211 """An SCons clean task."""
212 def dir_index(self, directory):
213 dirname = lambda f, d=directory: os.path.join(d, f)
214 files = map(dirname, os.listdir(directory))
216 # os.listdir() isn't guaranteed to return files in any specific order,
217 # but some of the test code expects sorted output.
221 def fs_delete(self, path, remove=1):
223 if os.path.exists(path):
224 if os.path.isfile(path):
225 if remove: os.unlink(path)
226 display("Removed " + path)
227 elif os.path.isdir(path) and not os.path.islink(path):
228 # delete everything in the dir
229 for p in self.dir_index(path):
230 if os.path.isfile(p):
231 if remove: os.unlink(p)
232 display("Removed " + p)
234 self.fs_delete(p, remove)
235 # then delete dir itself
236 if remove: os.rmdir(path)
237 display("Removed directory " + path)
238 except (IOError, OSError), e:
239 print "scons: Could not remove '%s':" % str(path), e.strerror
242 target = self.targets[0]
243 if (target.has_builder() or target.side_effect) and not target.noclean:
244 for t in self.targets:
246 display("Removed " + str(t))
247 if SCons.Environment.CleanTargets.has_key(target):
248 files = SCons.Environment.CleanTargets[target]
250 self.fs_delete(str(f), 0)
253 target = self.targets[0]
254 if (target.has_builder() or target.side_effect) and not target.noclean:
255 for t in self.targets:
259 # An OSError may indicate something like a permissions
260 # issue, an IOError would indicate something like
261 # the file not existing. In either case, print a
262 # message and keep going to try to remove as many
263 # targets aa possible.
264 print "scons: Could not remove '%s':" % str(t), e.strerror
267 display("Removed " + str(t))
268 if SCons.Environment.CleanTargets.has_key(target):
269 files = SCons.Environment.CleanTargets[target]
271 self.fs_delete(str(f))
275 # Have the taskmaster arrange to "execute" all of the targets, because
276 # we'll figure out ourselves (in remove() or show() above) whether
277 # anything really needs to be done.
278 make_ready = SCons.Taskmaster.Task.make_ready_all
283 class QuestionTask(SCons.Taskmaster.Task):
284 """An SCons task for the -q (question) option."""
289 if self.targets[0].get_state() != SCons.Node.up_to_date:
299 def __init__(self, derived=False, prune=False, status=False):
300 self.derived = derived
303 def get_all_children(self, node):
304 return node.all_children()
305 def get_derived_children(self, node):
306 children = node.all_children(None)
307 return filter(lambda x: x.has_builder(), children)
308 def display(self, t):
310 func = self.get_derived_children
312 func = self.get_all_children
313 s = self.status and 2 or 0
314 SCons.Util.print_tree(t, func, prune=self.prune, showtags=s)
321 keep_going_on_error = 0
322 print_explanations = 0
330 cumulative_command_time = 0
331 exit_status = 0 # exit status, assume success by default
334 delayed_warnings = []
336 diskcheck_all = SCons.Node.FS.diskcheck_types()
337 diskcheck_option_set = None
339 def diskcheck_convert(value):
342 if not SCons.Util.is_List(value):
343 value = string.split(value, ',')
345 for v in map(string.lower, value):
347 result = diskcheck_all
350 elif v in diskcheck_all:
361 self.append = self.do_nothing
362 self.print_stats = self.do_nothing
363 def enable(self, outfp):
365 self.append = self.do_append
366 self.print_stats = self.do_print
367 def do_nothing(self, *args, **kw):
370 class CountStats(Stats):
371 def do_append(self, label):
372 self.labels.append(label)
373 self.stats.append(SCons.Debug.fetchLoggedInstances())
377 for n in map(lambda t: t[0], s):
378 stats_table[n] = [0, 0, 0, 0]
382 stats_table[n][i] = c
384 keys = stats_table.keys()
386 self.outfp.write("Object counts:\n")
390 fmt1 = string.join(pre + [' %7s']*l + post, '')
391 fmt2 = string.join(pre + [' %7d']*l + post, '')
392 labels = self.labels[:l]
393 labels.append(("", "Class"))
394 self.outfp.write(fmt1 % tuple(map(lambda x: x[0], labels)))
395 self.outfp.write(fmt1 % tuple(map(lambda x: x[1], labels)))
397 r = stats_table[k][:l] + [k]
398 self.outfp.write(fmt2 % tuple(r))
400 count_stats = CountStats()
402 class MemStats(Stats):
403 def do_append(self, label):
404 self.labels.append(label)
405 self.stats.append(SCons.Debug.memory())
407 fmt = 'Memory %-32s %12d\n'
408 for label, stats in map(None, self.labels, self.stats):
409 self.outfp.write(fmt % (label, stats))
411 memory_stats = MemStats()
415 def _scons_syntax_error(e):
416 """Handle syntax errors. Print out a message and show where the error
419 etype, value, tb = sys.exc_info()
420 lines = traceback.format_exception_only(etype, value)
422 sys.stderr.write(line+'\n')
425 def find_deepest_user_frame(tb):
427 Find the deepest stack frame that is not part of SCons.
429 Input is a "pre-processed" stack trace in the form
430 returned by traceback.extract_tb() or traceback.extract_stack()
435 # find the deepest traceback frame that is not part
439 if string.find(filename, os.sep+'SCons'+os.sep) == -1:
443 def _scons_user_error(e):
444 """Handle user errors. Print out a message and a description of the
445 error, along with the line number and routine where it occured.
446 The file and line number will be the deepest stack frame that is
447 not part of SCons itself.
449 global print_stacktrace
450 etype, value, tb = sys.exc_info()
452 traceback.print_exception(etype, value, tb)
453 filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_tb(tb))
454 sys.stderr.write("\nscons: *** %s\n" % value)
455 sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
458 def _scons_user_warning(e):
459 """Handle user warnings. Print out a message and a description of
460 the warning, along with the line number and routine where it occured.
461 The file and line number will be the deepest stack frame that is
462 not part of SCons itself.
464 etype, value, tb = sys.exc_info()
465 filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_tb(tb))
466 sys.stderr.write("\nscons: warning: %s\n" % e)
467 sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
469 def _scons_internal_warning(e):
470 """Slightly different from _scons_user_warning in that we use the
471 *current call stack* rather than sys.exc_info() to get our stack trace.
472 This is used by the warnings framework to print warnings."""
473 filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_stack())
474 sys.stderr.write("\nscons: warning: %s\n" % e[0])
475 sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
477 def _scons_internal_error():
478 """Handle all errors but user errors. Print out a message telling
479 the user what to do in this case and print a normal trace.
481 print 'internal error'
482 traceback.print_exc()
485 def _varargs(option, parser):
488 arg = parser.rargs[0]
494 def _setup_warn(arg):
495 """The --warn option. An argument to this option
496 should be of the form <warning-class> or no-<warning-class>.
497 The warning class is munged in order to get an actual class
498 name from the SCons.Warnings module to enable or disable.
499 The supplied <warning-class> is split on hyphens, each element
500 is captialized, then smushed back together. Then the string
501 "SCons.Warnings." is added to the front and "Warning" is added
502 to the back to get the fully qualified class name.
504 For example, --warn=deprecated will enable the
505 SCons.Warnings.DeprecatedWarning class.
507 --warn=no-dependency will disable the
508 SCons.Warnings.DependencyWarning class.
510 As a special case, --warn=all and --warn=no-all
511 will enable or disable (respectively) the base
512 class of all warnings, which is SCons.Warning.Warning."""
514 elems = string.split(string.lower(arg), '-')
520 if len(elems) == 1 and elems[0] == 'all':
521 class_name = "Warning"
525 return "SCons" + s[5:]
527 return string.capitalize(s)
528 class_name = string.join(map(_capitalize, elems), '') + "Warning"
530 clazz = getattr(SCons.Warnings, class_name)
531 except AttributeError:
532 sys.stderr.write("No warning type: '%s'\n" % arg)
535 SCons.Warnings.enableWarningClass(clazz)
537 SCons.Warnings.suppressWarningClass(clazz)
539 def _SConstruct_exists(dirname=''):
540 """This function checks that an SConstruct file exists in a directory.
541 If so, it returns the path of the file. By default, it checks the
545 for file in ['SConstruct', 'Sconstruct', 'sconstruct']:
546 sfile = os.path.join(dirname, file)
547 if os.path.isfile(sfile):
549 if not os.path.isabs(sfile):
550 for rep in repositories:
551 if os.path.isfile(os.path.join(rep, sfile)):
555 def _set_globals(options):
556 global keep_going_on_error, ignore_errors
558 global print_explanations, print_includes, print_memoizer
559 global print_objects, print_stacktrace, print_time
563 keep_going_on_error = options.keep_going
565 debug_values = options.debug
566 if debug_values is None:
568 except AttributeError:
571 if "count" in debug_values:
572 count_stats.enable(sys.stdout)
573 if "dtree" in debug_values:
574 tree_printers.append(TreePrinter(derived=True))
575 if "explain" in debug_values:
576 print_explanations = 1
577 if "findlibs" in debug_values:
578 SCons.Scanner.Prog.print_find_libs = "findlibs"
579 if "includes" in debug_values:
581 if "memoizer" in debug_values:
583 if "memory" in debug_values:
584 memory_stats.enable(sys.stdout)
585 if "objects" in debug_values:
587 if "presub" in debug_values:
588 SCons.Action.print_actions_presub = 1
589 if "stacktrace" in debug_values:
591 if "stree" in debug_values:
592 tree_printers.append(TreePrinter(status=True))
593 if "time" in debug_values:
595 if "tree" in debug_values:
596 tree_printers.append(TreePrinter())
597 ignore_errors = options.ignore_errors
599 def _create_path(plist):
605 path = path + '/' + d
608 def _load_site_scons_dir(topdir, site_dir_name=None):
609 """Load the site_scons dir under topdir.
610 Adds site_scons to sys.path, imports site_scons/site_init.py,
611 and adds site_scons/site_tools to default toolpath."""
613 err_if_not_found = True # user specified: err if missing
615 site_dir_name = "site_scons"
616 err_if_not_found = False
618 site_dir = os.path.join(topdir.path, site_dir_name)
619 if not os.path.exists(site_dir):
621 raise SCons.Errors.UserError, "site dir %s not found."%site_dir
624 site_init_filename = "site_init.py"
625 site_init_modname = "site_init"
626 site_tools_dirname = "site_tools"
627 sys.path = [os.path.abspath(site_dir)] + sys.path
628 site_init_file = os.path.join(site_dir, site_init_filename)
629 site_tools_dir = os.path.join(site_dir, site_tools_dirname)
630 if os.path.exists(site_init_file):
633 fp, pathname, description = imp.find_module(site_init_modname,
636 imp.load_module(site_init_modname, fp, pathname, description)
640 except ImportError, e:
641 sys.stderr.write("Can't import site init file '%s': %s\n"%(site_init_file, e))
644 sys.stderr.write("Site init file '%s' raised exception: %s\n"%(site_init_file, e))
646 if os.path.exists(site_tools_dir):
647 SCons.Tool.DefaultToolpath.append(os.path.abspath(site_tools_dir))
649 def version_string(label, module):
650 fmt = "\t%s: v%s.%s, %s, by %s on %s\n"
655 module.__developer__,
658 class OptParser(OptionParser):
662 parts = ["SCons by Steven Knight et al.:\n"]
664 parts.append(version_string("script", __main__))
665 except KeyboardInterrupt:
668 # On Windows there is no scons.py, so there is no
669 # __main__.__version__, hence there is no script version.
671 parts.append(version_string("engine", SCons))
672 parts.append("__COPYRIGHT__")
673 OptionParser.__init__(self, version=string.join(parts, ''),
674 usage="usage: scons [OPTION] [TARGET] ...")
676 # options ignored for compatibility
677 def opt_ignore(option, opt, value, parser):
678 sys.stderr.write("Warning: ignoring %s option\n" % opt)
679 self.add_option("-b", "-m", "-S", "-t", "--no-keep-going", "--stop",
680 "--touch", action="callback", callback=opt_ignore,
681 help="Ignored for compatibility.")
683 self.add_option('-c', '--clean', '--remove', action="store_true",
685 help="Remove specified targets and dependencies.")
687 self.add_option('-C', '--directory', type="string", action = "append",
689 help="Change to DIR before doing anything.")
691 self.add_option('--cache-debug', action="store",
692 dest="cache_debug", metavar="FILE",
693 help="Print CacheDir debug info to FILE.")
695 self.add_option('--cache-disable', '--no-cache',
696 action="store_true", dest='cache_disable', default=0,
697 help="Do not retrieve built targets from CacheDir.")
699 self.add_option('--cache-force', '--cache-populate',
700 action="store_true", dest='cache_force', default=0,
701 help="Copy already-built targets into the CacheDir.")
703 self.add_option('--cache-show',
704 action="store_true", dest='cache_show', default=0,
705 help="Print build actions for files from CacheDir.")
707 config_options = ["auto", "force" ,"cache"]
709 def opt_config(option, opt, value, parser, c_options=config_options):
710 if value in c_options:
711 parser.values.config = value
713 raise OptionValueError("Warning: %s is not a valid config type" % value)
714 self.add_option('--config', action="callback", type="string",
715 callback=opt_config, nargs=1, dest="config",
716 metavar="MODE", default="auto",
717 help="Controls Configure subsystem: "
718 "%s." % string.join(config_options, ", "))
720 def opt_not_yet(option, opt, value, parser):
721 sys.stderr.write("Warning: the %s option is not yet implemented\n" % opt)
723 self.add_option('-d', action="callback",
724 callback=opt_not_yet,
725 help = "Print file dependency information.")
727 self.add_option('-D', action="store_const", const=2, dest="climb_up",
728 help="Search up directory tree for SConstruct, "
729 "build all Default() targets.")
731 debug_options = ["count", "dtree", "explain", "findlibs",
732 "includes", "memoizer", "memory", "objects",
733 "pdb", "presub", "stacktrace", "stree",
736 deprecated_debug_options = {
737 "nomemoizer" : ' and has no effect',
740 def opt_debug(option, opt, value, parser, debug_options=debug_options, deprecated_debug_options=deprecated_debug_options):
741 if value in debug_options:
743 if parser.values.debug is None:
744 parser.values.debug = []
745 except AttributeError:
746 parser.values.debug = []
747 parser.values.debug.append(value)
748 elif value in deprecated_debug_options.keys():
749 msg = deprecated_debug_options[value]
750 w = "The --debug=%s option is deprecated%s." % (value, msg)
751 delayed_warnings.append((SCons.Warnings.DeprecatedWarning, w))
753 raise OptionValueError("Warning: %s is not a valid debug type" % value)
754 self.add_option('--debug', action="callback", type="string",
755 callback=opt_debug, nargs=1, dest="debug",
757 help="Print various types of debugging information: "
758 "%s." % string.join(debug_options, ", "))
760 def opt_diskcheck(option, opt, value, parser):
762 global diskcheck_option_set
763 diskcheck_option_set = diskcheck_convert(value)
764 SCons.Node.FS.set_diskcheck(diskcheck_option_set)
765 except ValueError, e:
766 raise OptionValueError("Warning: `%s' is not a valid diskcheck type" % e)
769 self.add_option('--diskcheck', action="callback", type="string",
770 callback=opt_diskcheck, dest='diskcheck',
772 help="Enable specific on-disk checks.")
774 def opt_duplicate(option, opt, value, parser):
775 if not value in SCons.Node.FS.Valid_Duplicates:
776 raise OptionValueError("`%s' is not a valid duplication style." % value)
777 parser.values.duplicate = value
778 # Set the duplicate style right away so it can affect linking
779 # of SConscript files.
780 SCons.Node.FS.set_duplicate(value)
781 self.add_option('--duplicate', action="callback", type="string",
782 callback=opt_duplicate, nargs=1, dest="duplicate",
783 help="Set the preferred duplication methods. Must be one of "
784 + string.join(SCons.Node.FS.Valid_Duplicates, ", "))
786 self.add_option('-f', '--file', '--makefile', '--sconstruct',
787 action="append", nargs=1,
788 help="Read FILE as the top-level SConstruct file.")
790 self.add_option('-h', '--help', action="store_true", default=0,
792 help="Print defined help message, or this one.")
794 self.add_option("-H", "--help-options",
796 help="Print this message and exit.")
798 self.add_option('-i', '--ignore-errors', action="store_true",
799 default=0, dest='ignore_errors',
800 help="Ignore errors from build actions.")
802 self.add_option('-I', '--include-dir', action="append",
803 dest='include_dir', metavar="DIR",
804 help="Search DIR for imported Python modules.")
806 self.add_option('--implicit-cache', action="store_true",
807 dest='implicit_cache',
808 help="Cache implicit dependencies")
810 self.add_option('--implicit-deps-changed', action="store_true",
811 default=0, dest='implicit_deps_changed',
812 help="Ignore cached implicit dependencies.")
813 self.add_option('--implicit-deps-unchanged', action="store_true",
814 default=0, dest='implicit_deps_unchanged',
815 help="Ignore changes in implicit dependencies.")
817 def opt_j(option, opt, value, parser):
819 parser.values.num_jobs = value
820 self.add_option('-j', '--jobs', action="callback", type="int",
821 callback=opt_j, metavar="N",
822 help="Allow N jobs at once.")
824 self.add_option('-k', '--keep-going', action="store_true", default=0,
826 help="Keep going when a target can't be made.")
828 self.add_option('--max-drift', type="int", action="store",
829 dest='max_drift', metavar="N",
830 help="Set maximum system clock drift to N seconds.")
832 self.add_option('-n', '--no-exec', '--just-print', '--dry-run',
833 '--recon', action="store_true", dest='noexec',
834 default=0, help="Don't build; just print commands.")
836 self.add_option('--no-site-dir', action="store_true",
837 dest='no_site_dir', default=0,
838 help="Don't search or use the usual site_scons dir.")
840 self.add_option('--profile', action="store",
841 dest="profile_file", metavar="FILE",
842 help="Profile SCons and put results in FILE.")
844 self.add_option('-q', '--question', action="store_true", default=0,
845 help="Don't build; exit status says if up to date.")
847 self.add_option('-Q', dest='no_progress', action="store_true",
849 help="Suppress \"Reading/Building\" progress messages.")
851 self.add_option('--random', dest="random", action="store_true",
852 default=0, help="Build dependencies in random order.")
854 self.add_option('-s', '--silent', '--quiet', action="store_true",
855 default=0, help="Don't print commands.")
857 self.add_option('--site-dir', action="store",
858 dest='site_dir', metavar="DIR",
859 help="Use DIR instead of the usual site_scons dir.")
861 self.add_option('--taskmastertrace', action="store",
862 dest="taskmastertrace_file", metavar="FILE",
863 help="Trace Node evaluation to FILE.")
865 tree_options = ["all", "derived", "prune", "status"]
867 def opt_tree(option, opt, value, parser, tree_options=tree_options):
869 for o in string.split(value, ','):
879 raise OptionValueError("Warning: %s is not a valid --tree option" % o)
880 tree_printers.append(tp)
882 self.add_option('--tree', action="callback", type="string",
883 callback=opt_tree, nargs=1, metavar="OPTIONS",
884 help="Print a dependency tree in various formats: "
885 "%s." % string.join(tree_options, ", "))
887 self.add_option('-u', '--up', '--search-up', action="store_const",
888 dest="climb_up", default=0, const=1,
889 help="Search up directory tree for SConstruct, "
890 "build targets at or below current directory.")
891 self.add_option('-U', action="store_const", dest="climb_up",
893 help="Search up directory tree for SConstruct, "
894 "build Default() targets from local SConscript.")
896 self.add_option("-v", "--version",
898 help="Print the SCons version number and exit.")
900 self.add_option('--warn', '--warning', nargs=1, action="store",
901 metavar="WARNING-SPEC",
902 help="Enable or disable warnings.")
904 self.add_option('-Y', '--repository', '--srcdir',
905 nargs=1, action="append",
906 help="Search REPOSITORY for source and target files.")
908 self.add_option('-e', '--environment-overrides', action="callback",
909 callback=opt_not_yet,
910 # help="Environment variables override makefiles."
912 self.add_option('-l', '--load-average', '--max-load', action="callback",
913 callback=opt_not_yet, type="int", dest="load_average",
915 # help="Don't start multiple jobs unless load is below "
919 self.add_option('--list-derived', action="callback",
920 callback=opt_not_yet,
921 # help="Don't build; list files that would be built."
923 self.add_option('--list-actions', action="callback",
924 callback=opt_not_yet,
925 # help="Don't build; list files and build actions."
927 self.add_option('--list-where', action="callback",
928 callback=opt_not_yet,
929 # help="Don't build; list files and where defined."
931 self.add_option('-o', '--old-file', '--assume-old', action="callback",
932 callback=opt_not_yet, type="string", dest="old_file",
933 # help = "Consider FILE to be old; don't rebuild it."
935 self.add_option('--override', action="callback", dest="override",
936 callback=opt_not_yet, type="string",
937 # help="Override variables as specified in FILE."
939 self.add_option('-p', action="callback",
940 callback=opt_not_yet,
941 # help="Print internal environments/objects."
943 self.add_option('-r', '-R', '--no-builtin-rules',
944 '--no-builtin-variables', action="callback",
945 callback=opt_not_yet,
946 # help="Clear default environments and variables."
948 self.add_option('-w', '--print-directory', action="callback",
949 callback=opt_not_yet,
950 # help="Print the current directory."
952 self.add_option('--no-print-directory', action="callback",
953 callback=opt_not_yet,
954 # help="Turn off -w, even if it was turned on implicitly."
956 self.add_option('--write-filenames', action="callback",
957 callback=opt_not_yet, type="string", dest="write_filenames",
958 # help="Write all filenames examined into FILE."
960 self.add_option('-W', '--what-if', '--new-file', '--assume-new',
962 action="callback", callback=opt_not_yet, type="string",
963 # help="Consider FILE to be changed."
965 self.add_option('--warn-undefined-variables', action="callback",
966 callback=opt_not_yet,
967 # help="Warn when an undefined variable is referenced."
970 def parse_args(self, args=None, values=None):
971 opt, arglist = OptionParser.parse_args(self, args, values)
972 if opt.implicit_deps_changed or opt.implicit_deps_unchanged:
973 opt.implicit_cache = 1
976 class SConscriptSettableOptions:
977 """This class wraps an OptParser instance and provides
978 uniform access to options that can be either set on the command
979 line or from a SConscript file. A value specified on the command
980 line always overrides a value set in a SConscript file.
981 Not all command line options are SConscript settable, and the ones
982 that are must be explicitly added to settable dictionary and optionally
983 validated and coerced in the set() method."""
985 def __init__(self, options):
986 self.options = options
988 # This dictionary stores the defaults for all the SConscript
989 # settable options, as well as indicating which options
990 # are SConscript settable (and gettable, which for options
991 # like 'help' is far more important than being settable).
994 'diskcheck' : diskcheck_all,
995 'duplicate' : 'hard-soft-copy',
997 'implicit_cache' : 0,
998 'max_drift' : SCons.Node.FS.default_max_drift,
1003 def get(self, name):
1004 if not self.settable.has_key(name):
1005 raise SCons.Errors.UserError, "This option is not settable from a SConscript file: %s"%name
1006 if hasattr(self.options, name) and getattr(self.options, name) is not None:
1007 return getattr(self.options, name)
1009 return self.settable[name]
1011 def set(self, name, value):
1012 if not self.settable.has_key(name):
1013 raise SCons.Errors.UserError, "This option is not settable from a SConscript file: %s"%name
1015 if name == 'num_jobs':
1021 raise SCons.Errors.UserError, "A positive integer is required: %s"%repr(value)
1022 elif name == 'max_drift':
1026 raise SCons.Errors.UserError, "An integer is required: %s"%repr(value)
1027 elif name == 'duplicate':
1031 raise SCons.Errors.UserError, "A string is required: %s"%repr(value)
1032 if not value in SCons.Node.FS.Valid_Duplicates:
1033 raise SCons.Errors.UserError, "Not a valid duplication style: %s" % value
1034 # Set the duplicate stye right away so it can affect linking
1035 # of SConscript files.
1036 SCons.Node.FS.set_duplicate(value)
1037 elif name == 'diskcheck':
1039 value = diskcheck_convert(value)
1040 except ValueError, v:
1041 raise SCons.Errors.UserError, "Not a valid diskcheck value: %s"%v
1042 if not diskcheck_option_set:
1043 SCons.Node.FS.set_diskcheck(value)
1045 self.settable[name] = value
1048 def _main(args, parser):
1051 # Here's where everything really happens.
1053 # First order of business: set up default warnings and and then
1054 # handle the user's warning options, so we can warn about anything
1055 # that happens appropriately.
1056 default_warnings = [ SCons.Warnings.CorruptSConsignWarning,
1057 SCons.Warnings.DeprecatedWarning,
1058 SCons.Warnings.DuplicateEnvironmentWarning,
1059 SCons.Warnings.MissingSConscriptWarning,
1060 SCons.Warnings.NoMD5ModuleWarning,
1061 SCons.Warnings.NoMetaclassSupportWarning,
1062 SCons.Warnings.NoParallelSupportWarning,
1063 SCons.Warnings.MisleadingKeywordsWarning, ]
1064 for warning in default_warnings:
1065 SCons.Warnings.enableWarningClass(warning)
1066 SCons.Warnings._warningOut = _scons_internal_warning
1068 _setup_warn(options.warn)
1070 for warning_type, message in delayed_warnings:
1071 SCons.Warnings.warn(warning_type, message)
1073 # Next, we want to create the FS object that represents the outside
1074 # world's file system, as that's central to a lot of initialization.
1075 # To do this, however, we need to be in the directory from which we
1076 # want to start everything, which means first handling any relevant
1077 # options that might cause us to chdir somewhere (-C, -D, -U, -u).
1078 if options.directory:
1079 cdir = _create_path(options.directory)
1083 sys.stderr.write("Could not change directory to %s\n" % cdir)
1085 # The SConstruct file may be in a repository, so initialize those
1086 # before we start the search up our path for one.
1088 if options.repository:
1089 repositories.extend(options.repository)
1092 if options.climb_up:
1093 target_top = '.' # directory to prepend to targets
1094 script_dir = os.getcwd() # location of script
1095 while script_dir and not _SConstruct_exists(script_dir):
1096 script_dir, last_part = os.path.split(script_dir)
1098 target_top = os.path.join(last_part, target_top)
1102 display("scons: Entering directory `%s'" % script_dir)
1103 os.chdir(script_dir)
1105 # Now that we're in the top-level SConstruct directory, go ahead
1106 # and initialize the FS object that represents the file system,
1107 # and make it the build engine default.
1108 fs = SCons.Node.FS.default_fs = SCons.Node.FS.FS()
1110 for rep in repositories:
1113 # Now that we have the FS object, the next order of business is to
1114 # check for an SConstruct file (or other specified config file).
1115 # If there isn't one, we can bail before doing any more work.
1118 scripts.extend(options.file)
1120 sfile = _SConstruct_exists()
1122 scripts.append(sfile)
1126 # There's no SConstruct, but they specified -h.
1127 # Give them the options usage now, before we fail
1128 # trying to read a non-existent SConstruct file.
1132 raise SCons.Errors.UserError, "No SConstruct file found."
1134 if scripts[0] == "-":
1137 d = fs.File(scripts[0]).dir
1138 fs.set_SConstruct_dir(d)
1140 # Now that we have the FS object and it's intialized, set up (most
1141 # of) the rest of the options.
1143 ssoptions = SConscriptSettableOptions(options)
1145 _set_globals(options)
1146 SCons.Node.implicit_cache = options.implicit_cache
1147 SCons.Node.implicit_deps_changed = options.implicit_deps_changed
1148 SCons.Node.implicit_deps_unchanged = options.implicit_deps_unchanged
1150 SCons.SConf.dryrun = 1
1151 SCons.Action.execute_actions = None
1152 CleanTask.execute = CleanTask.show
1153 if options.question:
1154 SCons.SConf.dryrun = 1
1155 SCons.SConf.SetCacheMode(options.config)
1156 SCons.SConf.SetProgressDisplay(progress_display)
1158 if options.no_progress or options.silent:
1159 progress_display.set_mode(0)
1163 SCons.Action.print_actions = None
1165 if options.cache_debug:
1166 fs.CacheDebugEnable(options.cache_debug)
1167 if options.cache_disable:
1168 def disable(self): pass
1169 fs.CacheDir = disable
1170 if options.cache_force:
1172 if options.cache_show:
1175 if options.site_dir:
1176 _load_site_scons_dir(d, options.site_dir)
1177 elif not options.no_site_dir:
1178 _load_site_scons_dir(d)
1180 if options.include_dir:
1181 sys.path = options.include_dir + sys.path
1183 # That should cover (most of) the options. Next, set up the variables
1184 # that hold command-line arguments, so the SConscript files that we
1185 # read and execute have access to them.
1193 SCons.Script._Add_Targets(targets)
1194 SCons.Script._Add_Arguments(xmit_args)
1196 sys.stdout = SCons.Util.Unbuffered(sys.stdout)
1198 memory_stats.append('before reading SConscript files:')
1199 count_stats.append(('pre-', 'read'))
1201 progress_display("scons: Reading SConscript files ...")
1203 start_time = time.time()
1205 for script in scripts:
1206 SCons.Script._SConscript._SConscript(fs, script)
1207 except SCons.Errors.StopError, e:
1208 # We had problems reading an SConscript file, such as it
1209 # couldn't be copied in to the BuildDir. Since we're just
1210 # reading SConscript files and haven't started building
1211 # things yet, stop regardless of whether they used -i or -k
1213 sys.stderr.write("scons: *** %s Stop.\n" % e)
1215 sys.exit(exit_status)
1216 global sconscript_time
1217 sconscript_time = time.time() - start_time
1218 SCons.SConf.CreateConfigHBuilder(SCons.Defaults.DefaultEnvironment())
1219 progress_display("scons: done reading SConscript files.")
1221 # Tell the Node.FS subsystem that we're all done reading the
1222 # SConscript files and calling Repository() and BuildDir() and the
1223 # like, so it can go ahead and start memoizing the string values of
1224 # file system nodes.
1225 SCons.Node.FS.save_strings(1)
1227 memory_stats.append('after reading SConscript files:')
1228 count_stats.append(('post-', 'read'))
1232 if ssoptions.get('help'):
1233 help_text = SCons.Script.help_text
1234 if help_text is None:
1235 # They specified -h, but there was no Help() inside the
1236 # SConscript files. Give them the options usage.
1237 parser.print_help(sys.stdout)
1240 print "Use scons -H for help about command-line options."
1244 # Now that we've read the SConscripts we can set the options
1245 # that are SConscript settable:
1246 SCons.Node.implicit_cache = ssoptions.get('implicit_cache')
1247 SCons.Node.FS.set_duplicate(ssoptions.get('duplicate'))
1248 fs.set_max_drift(ssoptions.get('max_drift'))
1251 if targets or SCons.Script.BUILD_TARGETS != SCons.Script._build_plus_default:
1252 # They specified targets on the command line or modified
1253 # BUILD_TARGETS in the SConscript file(s), so if they used -u,
1254 # -U or -D, we have to look up targets relative to the top,
1255 # but we build whatever they specified.
1257 lookup_top = fs.Dir(target_top)
1260 targets = SCons.Script.BUILD_TARGETS
1262 # There are no targets specified on the command line,
1263 # so if they used -u, -U or -D, we may have to restrict
1264 # what actually gets built.
1267 if options.climb_up == 1:
1268 # -u, local directory and below
1269 target_top = fs.Dir(target_top)
1270 lookup_top = target_top
1271 elif options.climb_up == 2:
1272 # -D, all Default() targets
1275 elif options.climb_up == 3:
1276 # -U, local SConscript Default() targets
1277 target_top = fs.Dir(target_top)
1278 def check_dir(x, target_top=target_top):
1279 if hasattr(x, 'cwd') and not x.cwd is None:
1280 cwd = x.cwd.srcnode()
1281 return cwd == target_top
1283 # x doesn't have a cwd, so it's either not a target,
1284 # or not a file, so go ahead and keep it as a default
1285 # target and let the engine sort it out:
1287 d = filter(check_dir, SCons.Script.DEFAULT_TARGETS)
1288 SCons.Script.DEFAULT_TARGETS[:] = d
1292 targets = SCons.Script._Get_Default_Targets(d, fs)
1295 sys.stderr.write("scons: *** No targets specified and no Default() targets found. Stop.\n")
1298 def Entry(x, ltop=lookup_top, ttop=target_top, fs=fs):
1299 if isinstance(x, SCons.Node.Node):
1303 # Why would ltop be None? Unfortunately this happens.
1304 if ltop == None: ltop = ''
1305 # Curdir becomes important when SCons is called with -u, -C,
1306 # or similar option that changes directory, and so the paths
1307 # of targets given on the command line need to be adjusted.
1308 curdir = os.path.join(os.getcwd(), str(ltop))
1309 for lookup in SCons.Node.arg2nodes_lookups:
1310 node = lookup(x, curdir=curdir)
1314 node = fs.Entry(x, directory=ltop, create=1)
1315 if ttop and not node.is_under(ttop):
1316 if isinstance(node, SCons.Node.FS.Dir) and ttop.is_under(node):
1322 nodes = filter(None, map(Entry, targets))
1324 task_class = BuildTask # default action is to build targets
1325 opening_message = "Building targets ..."
1326 closing_message = "done building targets."
1327 if keep_going_on_error:
1328 failure_message = "done building targets (errors occurred during build)."
1330 failure_message = "building terminated because of errors."
1331 if options.question:
1332 task_class = QuestionTask
1334 if ssoptions.get('clean'):
1335 task_class = CleanTask
1336 opening_message = "Cleaning targets ..."
1337 closing_message = "done cleaning targets."
1338 if keep_going_on_error:
1339 closing_message = "done cleaning targets (errors occurred during clean)."
1341 failure_message = "cleaning terminated because of errors."
1342 except AttributeError:
1346 def order(dependencies):
1347 """Randomize the dependencies."""
1349 # This is cribbed from the implementation of
1350 # random.shuffle() in Python 2.X.
1352 for i in xrange(len(d)-1, 0, -1):
1353 j = int(random.random() * (i+1))
1354 d[i], d[j] = d[j], d[i]
1357 def order(dependencies):
1358 """Leave the order of dependencies alone."""
1361 progress_display("scons: " + opening_message)
1362 if options.taskmastertrace_file == '-':
1363 tmtrace = sys.stdout
1364 elif options.taskmastertrace_file:
1365 tmtrace = open(options.taskmastertrace_file, 'wb')
1368 taskmaster = SCons.Taskmaster.Taskmaster(nodes, task_class, order, tmtrace)
1371 num_jobs = ssoptions.get('num_jobs')
1372 jobs = SCons.Job.Jobs(num_jobs, taskmaster)
1373 if num_jobs > 1 and jobs.num_jobs == 1:
1374 msg = "parallel builds are unsupported by this version of Python;\n" + \
1375 "\tignoring -j or num_jobs option.\n"
1376 SCons.Warnings.warn(SCons.Warnings.NoParallelSupportWarning, msg)
1378 memory_stats.append('before building targets:')
1379 count_stats.append(('pre-', 'build'))
1385 progress_display("scons: " + failure_message)
1387 progress_display("scons: " + closing_message)
1388 if not options.noexec:
1389 SCons.SConsign.write()
1391 memory_stats.append('after building targets:')
1392 count_stats.append(('post-', 'build'))
1395 sconsflags = os.environ.get('SCONSFLAGS', '')
1396 all_args = string.split(sconsflags) + sys.argv[1:]
1398 parser = OptParser()
1400 options, args = parser.parse_args(all_args)
1401 if type(options.debug) == type([]) and "pdb" in options.debug:
1403 pdb.Pdb().runcall(_main, args, parser)
1404 elif options.profile_file:
1405 from profile import Profile
1407 # Some versions of Python 2.4 shipped a profiler that had the
1408 # wrong 'c_exception' entry in its dispatch table. Make sure
1409 # we have the right one. (This may put an unnecessary entry
1410 # in the table in earlier versions of Python, but its presence
1411 # shouldn't hurt anything).
1413 dispatch = Profile.dispatch
1414 except AttributeError:
1417 dispatch['c_exception'] = Profile.trace_dispatch_return
1421 prof.runcall(_main, args, parser)
1424 prof.dump_stats(options.profile_file)
1430 global first_command_start
1434 except SystemExit, s:
1437 except KeyboardInterrupt:
1438 print "Build interrupted."
1440 except SyntaxError, e:
1441 _scons_syntax_error(e)
1442 except SCons.Errors.InternalError:
1443 _scons_internal_error()
1444 except SCons.Errors.UserError, e:
1445 _scons_user_error(e)
1447 # An exception here is likely a builtin Python exception Python
1448 # code in an SConscript file. Show them precisely what the
1449 # problem was and where it happened.
1450 SCons.Script._SConscript.SConscript_exception()
1453 memory_stats.print_stats()
1454 count_stats.print_stats()
1457 SCons.Debug.listLoggedInstances('*')
1458 #SCons.Debug.dumpLoggedInstances('*')
1461 SCons.Memoize.Dump("Memoizer (memory cache) hits and misses:")
1463 # Dump any development debug info that may have been enabled.
1464 # These are purely for internal debugging during development, so
1465 # there's no need to control them with --debug= options; they're
1466 # controlled by changing the source code.
1467 SCons.Debug.dump_caller_counts()
1468 SCons.Taskmaster.dump_stats()
1471 total_time = time.time() - SCons.Script.start_time
1473 ct = cumulative_command_time
1475 ct = last_command_end - first_command_start
1476 scons_time = total_time - sconscript_time - ct
1477 print "Total build time: %f seconds"%total_time
1478 print "Total SConscript file execution time: %f seconds"%sconscript_time
1479 print "Total SCons execution time: %f seconds"%scons_time
1480 print "Total command execution time: %f seconds"%ct
1482 sys.exit(exit_status)