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__"
49 # Strip the script directory from sys.path() so on case-insensitive
50 # (Windows) systems Python doesn't think that the "scons" script is the
51 # "SCons" package. Replace it with our own version directory so, if
52 # if they're there, we pick up the right version of the build engine
54 #sys.path = [os.path.join(sys.prefix,
56 # 'scons-%d' % SCons.__version__)] + sys.path[1:]
60 import SCons.Environment
65 from SCons.Optik import OptionParser, SUPPRESS_HELP, OptionValueError
69 import SCons.Taskmaster
74 display = SCons.Util.display
75 progress_display = SCons.Util.DisplayEngine()
77 first_command_start = None
78 last_command_end = None
82 class BuildTask(SCons.Taskmaster.Task):
83 """An SCons build task."""
84 def display(self, message):
85 display('scons: ' + message)
88 for target in self.targets:
89 if target.get_state() == SCons.Node.up_to_date:
91 if target.has_builder() and not hasattr(target.builder, 'status'):
93 start_time = time.time()
94 global first_command_start
95 if first_command_start is None:
96 first_command_start = start_time
97 SCons.Taskmaster.Task.execute(self)
99 global cumulative_command_time
100 global last_command_end
101 finish_time = time.time()
102 last_command_end = finish_time
103 cumulative_command_time = cumulative_command_time+finish_time-start_time
104 sys.stdout.write("Command execution time: %f seconds\n"%(finish_time-start_time))
107 if self.top and target.has_builder():
108 display("scons: `%s' is up to date." % str(self.node))
110 def do_failed(self, status=2):
113 SCons.Taskmaster.Task.executed(self)
114 elif keep_going_on_error:
115 SCons.Taskmaster.Task.fail_continue(self)
118 SCons.Taskmaster.Task.fail_stop(self)
123 if self.top and not t.has_builder() and not t.side_effect:
125 sys.stderr.write("scons: *** Do not know how to make target `%s'." % t)
126 if not keep_going_on_error:
127 sys.stderr.write(" Stop.")
128 sys.stderr.write("\n")
131 print "scons: Nothing to be done for `%s'." % t
132 SCons.Taskmaster.Task.executed(self)
134 SCons.Taskmaster.Task.executed(self)
137 # Handle the failure of a build task. The primary purpose here
138 # is to display the various types of Errors and Exceptions
141 exc_info = self.exc_info()
148 # The Taskmaster didn't record an exception for this Task;
149 # see if the sys module has one.
150 t, e = sys.exc_info()[:2]
153 if not SCons.Util.is_List(n):
155 return string.join(map(str, n), ', ')
157 errfmt = "scons: *** [%s] %s\n"
159 if t == SCons.Errors.BuildError:
160 tname = nodestring(e.node)
163 errstr = e.filename + ': ' + errstr
164 sys.stderr.write(errfmt % (tname, errstr))
165 elif t == SCons.Errors.TaskmasterException:
166 tname = nodestring(e.node)
167 sys.stderr.write(errfmt % (tname, e.errstr))
168 type, value, trace = e.exc_info
169 traceback.print_exception(type, value, trace)
170 elif t == SCons.Errors.ExplicitExit:
172 tname = nodestring(e.node)
173 errstr = 'Explicit exit, status %s' % status
174 sys.stderr.write(errfmt % (tname, errstr))
179 if t == SCons.Errors.StopError and not keep_going_on_error:
181 sys.stderr.write("scons: *** %s\n" % s)
183 if tb and print_stacktrace:
184 sys.stderr.write("scons: internal stack trace:\n")
185 traceback.print_tb(tb, file=sys.stderr)
187 self.do_failed(status)
191 def postprocess(self):
194 for tp in tree_printers:
197 tree = t.render_include_tree()
201 SCons.Taskmaster.Task.postprocess(self)
203 def make_ready(self):
204 """Make a task ready for execution"""
205 SCons.Taskmaster.Task.make_ready(self)
206 if self.out_of_date and print_explanations:
207 explanation = self.out_of_date[0].explain()
209 sys.stdout.write("scons: " + explanation)
211 class CleanTask(SCons.Taskmaster.Task):
212 """An SCons clean task."""
213 def dir_index(self, directory):
214 dirname = lambda f, d=directory: os.path.join(d, f)
215 files = map(dirname, os.listdir(directory))
217 # os.listdir() isn't guaranteed to return files in any specific order,
218 # but some of the test code expects sorted output.
222 def fs_delete(self, path, remove=1):
224 if os.path.exists(path):
225 if os.path.isfile(path):
226 if remove: os.unlink(path)
227 display("Removed " + path)
228 elif os.path.isdir(path) and not os.path.islink(path):
229 # delete everything in the dir
230 for p in self.dir_index(path):
231 if os.path.isfile(p):
232 if remove: os.unlink(p)
233 display("Removed " + p)
235 self.fs_delete(p, remove)
236 # then delete dir itself
237 if remove: os.rmdir(path)
238 display("Removed directory " + path)
239 except (IOError, OSError), e:
240 print "scons: Could not remove '%s':" % str(path), e.strerror
243 target = self.targets[0]
244 if (target.has_builder() or target.side_effect) and not target.noclean:
245 for t in self.targets:
247 display("Removed " + str(t))
248 if SCons.Environment.CleanTargets.has_key(target):
249 files = SCons.Environment.CleanTargets[target]
251 self.fs_delete(str(f), 0)
254 target = self.targets[0]
255 if (target.has_builder() or target.side_effect) and not target.noclean:
256 for t in self.targets:
260 # An OSError may indicate something like a permissions
261 # issue, an IOError would indicate something like
262 # the file not existing. In either case, print a
263 # message and keep going to try to remove as many
264 # targets aa possible.
265 print "scons: Could not remove '%s':" % str(t), e.strerror
268 display("Removed " + str(t))
269 if SCons.Environment.CleanTargets.has_key(target):
270 files = SCons.Environment.CleanTargets[target]
272 self.fs_delete(str(f))
276 # Have the taskmaster arrange to "execute" all of the targets, because
277 # we'll figure out ourselves (in remove() or show() above) whether
278 # anything really needs to be done.
279 make_ready = SCons.Taskmaster.Task.make_ready_all
284 class QuestionTask(SCons.Taskmaster.Task):
285 """An SCons task for the -q (question) option."""
290 if self.targets[0].get_state() != SCons.Node.up_to_date:
300 def __init__(self, derived=False, prune=False, status=False):
301 self.derived = derived
304 def get_all_children(self, node):
305 return node.all_children()
306 def get_derived_children(self, node):
307 children = node.all_children(None)
308 return filter(lambda x: x.has_builder(), children)
309 def display(self, t):
311 func = self.get_derived_children
313 func = self.get_all_children
314 s = self.status and 2 or 0
315 SCons.Util.print_tree(t, func, prune=self.prune, showtags=s)
322 keep_going_on_error = 0
323 print_explanations = 0
331 cumulative_command_time = 0
332 exit_status = 0 # exit status, assume success by default
335 delayed_warnings = []
337 diskcheck_all = SCons.Node.FS.diskcheck_types()
338 diskcheck_option_set = None
340 def diskcheck_convert(value):
343 if not SCons.Util.is_List(value):
344 value = string.split(value, ',')
346 for v in map(string.lower, value):
348 result = diskcheck_all
351 elif v in diskcheck_all:
362 self.append = self.do_nothing
363 self.print_stats = self.do_nothing
364 def enable(self, outfp):
366 self.append = self.do_append
367 self.print_stats = self.do_print
368 def do_nothing(self, *args, **kw):
371 class CountStats(Stats):
372 def do_append(self, label):
373 self.labels.append(label)
374 self.stats.append(SCons.Debug.fetchLoggedInstances())
378 for n in map(lambda t: t[0], s):
379 stats_table[n] = [0, 0, 0, 0]
383 stats_table[n][i] = c
385 keys = stats_table.keys()
387 self.outfp.write("Object counts:\n")
391 fmt1 = string.join(pre + [' %7s']*l + post, '')
392 fmt2 = string.join(pre + [' %7d']*l + post, '')
393 labels = self.labels[:l]
394 labels.append(("", "Class"))
395 self.outfp.write(fmt1 % tuple(map(lambda x: x[0], labels)))
396 self.outfp.write(fmt1 % tuple(map(lambda x: x[1], labels)))
398 r = stats_table[k][:l] + [k]
399 self.outfp.write(fmt2 % tuple(r))
401 count_stats = CountStats()
403 class MemStats(Stats):
404 def do_append(self, label):
405 self.labels.append(label)
406 self.stats.append(SCons.Debug.memory())
408 fmt = 'Memory %-32s %12d\n'
409 for label, stats in map(None, self.labels, self.stats):
410 self.outfp.write(fmt % (label, stats))
412 memory_stats = MemStats()
416 def _scons_syntax_error(e):
417 """Handle syntax errors. Print out a message and show where the error
420 etype, value, tb = sys.exc_info()
421 lines = traceback.format_exception_only(etype, value)
423 sys.stderr.write(line+'\n')
426 def find_deepest_user_frame(tb):
428 Find the deepest stack frame that is not part of SCons.
430 Input is a "pre-processed" stack trace in the form
431 returned by traceback.extract_tb() or traceback.extract_stack()
436 # find the deepest traceback frame that is not part
440 if string.find(filename, os.sep+'SCons'+os.sep) == -1:
444 def _scons_user_error(e):
445 """Handle user errors. Print out a message and a description of the
446 error, along with the line number and routine where it occured.
447 The file and line number will be the deepest stack frame that is
448 not part of SCons itself.
450 global print_stacktrace
451 etype, value, tb = sys.exc_info()
453 traceback.print_exception(etype, value, tb)
454 filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_tb(tb))
455 sys.stderr.write("\nscons: *** %s\n" % value)
456 sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
459 def _scons_user_warning(e):
460 """Handle user warnings. Print out a message and a description of
461 the warning, along with the line number and routine where it occured.
462 The file and line number will be the deepest stack frame that is
463 not part of SCons itself.
465 etype, value, tb = sys.exc_info()
466 filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_tb(tb))
467 sys.stderr.write("\nscons: warning: %s\n" % e)
468 sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
470 def _scons_internal_warning(e):
471 """Slightly different from _scons_user_warning in that we use the
472 *current call stack* rather than sys.exc_info() to get our stack trace.
473 This is used by the warnings framework to print warnings."""
474 filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_stack())
475 sys.stderr.write("\nscons: warning: %s\n" % e[0])
476 sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
478 def _scons_internal_error():
479 """Handle all errors but user errors. Print out a message telling
480 the user what to do in this case and print a normal trace.
482 print 'internal error'
483 traceback.print_exc()
486 def _varargs(option, parser):
489 arg = parser.rargs[0]
495 def _setup_warn(arg):
496 """The --warn option. An argument to this option
497 should be of the form <warning-class> or no-<warning-class>.
498 The warning class is munged in order to get an actual class
499 name from the SCons.Warnings module to enable or disable.
500 The supplied <warning-class> is split on hyphens, each element
501 is captialized, then smushed back together. Then the string
502 "SCons.Warnings." is added to the front and "Warning" is added
503 to the back to get the fully qualified class name.
505 For example, --warn=deprecated will enable the
506 SCons.Warnings.DeprecatedWarning class.
508 --warn=no-dependency will disable the
509 SCons.Warnings.DependencyWarning class.
511 As a special case, --warn=all and --warn=no-all
512 will enable or disable (respectively) the base
513 class of all warnings, which is SCons.Warning.Warning."""
515 elems = string.split(string.lower(arg), '-')
521 if len(elems) == 1 and elems[0] == 'all':
522 class_name = "Warning"
526 return "SCons" + s[5:]
528 return string.capitalize(s)
529 class_name = string.join(map(_capitalize, elems), '') + "Warning"
531 clazz = getattr(SCons.Warnings, class_name)
532 except AttributeError:
533 sys.stderr.write("No warning type: '%s'\n" % arg)
536 SCons.Warnings.enableWarningClass(clazz)
538 SCons.Warnings.suppressWarningClass(clazz)
540 def _SConstruct_exists(dirname=''):
541 """This function checks that an SConstruct file exists in a directory.
542 If so, it returns the path of the file. By default, it checks the
546 for file in ['SConstruct', 'Sconstruct', 'sconstruct']:
547 sfile = os.path.join(dirname, file)
548 if os.path.isfile(sfile):
550 if not os.path.isabs(sfile):
551 for rep in repositories:
552 if os.path.isfile(os.path.join(rep, sfile)):
556 def _set_globals(options):
557 global keep_going_on_error, ignore_errors
559 global print_explanations, print_includes, print_memoizer
560 global print_objects, print_stacktrace, print_time
564 keep_going_on_error = options.keep_going
566 debug_values = options.debug
567 if debug_values is None:
569 except AttributeError:
572 if "count" in debug_values:
573 count_stats.enable(sys.stdout)
574 if "dtree" in debug_values:
575 tree_printers.append(TreePrinter(derived=True))
576 if "explain" in debug_values:
577 print_explanations = 1
578 if "findlibs" in debug_values:
579 SCons.Scanner.Prog.print_find_libs = "findlibs"
580 if "includes" in debug_values:
582 if "memoizer" in debug_values:
584 if "memory" in debug_values:
585 memory_stats.enable(sys.stdout)
586 if "objects" in debug_values:
588 if "presub" in debug_values:
589 SCons.Action.print_actions_presub = 1
590 if "stacktrace" in debug_values:
592 if "stree" in debug_values:
593 tree_printers.append(TreePrinter(status=True))
594 if "time" in debug_values:
596 if "tree" in debug_values:
597 tree_printers.append(TreePrinter())
598 ignore_errors = options.ignore_errors
600 def _create_path(plist):
606 path = path + '/' + d
609 def _load_site_scons_dir(topdir, site_dir_name=None):
610 """Load the site_scons dir under topdir.
611 Adds site_scons to sys.path, imports site_scons/site_init.py,
612 and adds site_scons/site_tools to default toolpath."""
614 err_if_not_found = True # user specified: err if missing
616 site_dir_name = "site_scons"
617 err_if_not_found = False
619 site_dir = os.path.join(topdir.path, site_dir_name)
620 if not os.path.exists(site_dir):
622 raise SCons.Errors.UserError, "site dir %s not found."%site_dir
625 site_init_filename = "site_init.py"
626 site_init_modname = "site_init"
627 site_tools_dirname = "site_tools"
628 sys.path = [os.path.abspath(site_dir)] + sys.path
629 site_init_file = os.path.join(site_dir, site_init_filename)
630 site_tools_dir = os.path.join(site_dir, site_tools_dirname)
631 if os.path.exists(site_init_file):
634 fp, pathname, description = imp.find_module(site_init_modname,
637 imp.load_module(site_init_modname, fp, pathname, description)
641 except ImportError, e:
642 sys.stderr.write("Can't import site init file '%s': %s\n"%(site_init_file, e))
645 sys.stderr.write("Site init file '%s' raised exception: %s\n"%(site_init_file, e))
647 if os.path.exists(site_tools_dir):
648 SCons.Tool.DefaultToolpath.append(os.path.abspath(site_tools_dir))
650 def version_string(label, module):
651 fmt = "\t%s: v%s.%s, %s, by %s on %s\n"
656 module.__developer__,
659 class OptParser(OptionParser):
663 parts = ["SCons by Steven Knight et al.:\n"]
665 parts.append(version_string("script", __main__))
666 except KeyboardInterrupt:
669 # On Windows there is no scons.py, so there is no
670 # __main__.__version__, hence there is no script version.
672 parts.append(version_string("engine", SCons))
673 parts.append("__COPYRIGHT__")
674 OptionParser.__init__(self, version=string.join(parts, ''),
675 usage="usage: scons [OPTION] [TARGET] ...")
677 # options ignored for compatibility
678 def opt_ignore(option, opt, value, parser):
679 sys.stderr.write("Warning: ignoring %s option\n" % opt)
680 self.add_option("-b", "-m", "-S", "-t", "--no-keep-going", "--stop",
681 "--touch", action="callback", callback=opt_ignore,
682 help="Ignored for compatibility.")
684 self.add_option('-c', '--clean', '--remove', action="store_true",
686 help="Remove specified targets and dependencies.")
688 self.add_option('-C', '--directory', type="string", action = "append",
690 help="Change to DIR before doing anything.")
692 self.add_option('--cache-debug', action="store",
693 dest="cache_debug", metavar="FILE",
694 help="Print CacheDir debug info to FILE.")
696 self.add_option('--cache-disable', '--no-cache',
697 action="store_true", dest='cache_disable', default=0,
698 help="Do not retrieve built targets from CacheDir.")
700 self.add_option('--cache-force', '--cache-populate',
701 action="store_true", dest='cache_force', default=0,
702 help="Copy already-built targets into the CacheDir.")
704 self.add_option('--cache-show',
705 action="store_true", dest='cache_show', default=0,
706 help="Print build actions for files from CacheDir.")
708 config_options = ["auto", "force" ,"cache"]
710 def opt_config(option, opt, value, parser, c_options=config_options):
711 if value in c_options:
712 parser.values.config = value
714 raise OptionValueError("Warning: %s is not a valid config type" % value)
715 self.add_option('--config', action="callback", type="string",
716 callback=opt_config, nargs=1, dest="config",
717 metavar="MODE", default="auto",
718 help="Controls Configure subsystem: "
719 "%s." % string.join(config_options, ", "))
721 def opt_not_yet(option, opt, value, parser):
722 sys.stderr.write("Warning: the %s option is not yet implemented\n" % opt)
724 self.add_option('-d', action="callback",
725 callback=opt_not_yet,
726 help = "Print file dependency information.")
728 self.add_option('-D', action="store_const", const=2, dest="climb_up",
729 help="Search up directory tree for SConstruct, "
730 "build all Default() targets.")
732 debug_options = ["count", "dtree", "explain", "findlibs",
733 "includes", "memoizer", "memory", "objects",
734 "pdb", "presub", "stacktrace", "stree",
737 deprecated_debug_options = {
738 "nomemoizer" : ' and has no effect',
741 def opt_debug(option, opt, value, parser, debug_options=debug_options, deprecated_debug_options=deprecated_debug_options):
742 if value in debug_options:
744 if parser.values.debug is None:
745 parser.values.debug = []
746 except AttributeError:
747 parser.values.debug = []
748 parser.values.debug.append(value)
749 elif value in deprecated_debug_options.keys():
750 msg = deprecated_debug_options[value]
751 w = "The --debug=%s option is deprecated%s." % (value, msg)
752 delayed_warnings.append((SCons.Warnings.DeprecatedWarning, w))
754 raise OptionValueError("Warning: %s is not a valid debug type" % value)
755 self.add_option('--debug', action="callback", type="string",
756 callback=opt_debug, nargs=1, dest="debug",
758 help="Print various types of debugging information: "
759 "%s." % string.join(debug_options, ", "))
761 def opt_diskcheck(option, opt, value, parser):
763 global diskcheck_option_set
764 diskcheck_option_set = diskcheck_convert(value)
765 SCons.Node.FS.set_diskcheck(diskcheck_option_set)
766 except ValueError, e:
767 raise OptionValueError("Warning: `%s' is not a valid diskcheck type" % e)
770 self.add_option('--diskcheck', action="callback", type="string",
771 callback=opt_diskcheck, dest='diskcheck',
773 help="Enable specific on-disk checks.")
775 def opt_duplicate(option, opt, value, parser):
776 if not value in SCons.Node.FS.Valid_Duplicates:
777 raise OptionValueError("`%s' is not a valid duplication style." % value)
778 parser.values.duplicate = value
779 # Set the duplicate style right away so it can affect linking
780 # of SConscript files.
781 SCons.Node.FS.set_duplicate(value)
782 self.add_option('--duplicate', action="callback", type="string",
783 callback=opt_duplicate, nargs=1, dest="duplicate",
784 help="Set the preferred duplication methods. Must be one of "
785 + string.join(SCons.Node.FS.Valid_Duplicates, ", "))
787 self.add_option('-f', '--file', '--makefile', '--sconstruct',
788 action="append", nargs=1,
789 help="Read FILE as the top-level SConstruct file.")
791 self.add_option('-h', '--help', action="store_true", default=0,
793 help="Print defined help message, or this one.")
795 self.add_option("-H", "--help-options",
797 help="Print this message and exit.")
799 self.add_option('-i', '--ignore-errors', action="store_true",
800 default=0, dest='ignore_errors',
801 help="Ignore errors from build actions.")
803 self.add_option('-I', '--include-dir', action="append",
804 dest='include_dir', metavar="DIR",
805 help="Search DIR for imported Python modules.")
807 self.add_option('--implicit-cache', action="store_true",
808 dest='implicit_cache',
809 help="Cache implicit dependencies")
811 self.add_option('--implicit-deps-changed', action="store_true",
812 default=0, dest='implicit_deps_changed',
813 help="Ignore cached implicit dependencies.")
814 self.add_option('--implicit-deps-unchanged', action="store_true",
815 default=0, dest='implicit_deps_unchanged',
816 help="Ignore changes in implicit dependencies.")
818 def opt_j(option, opt, value, parser):
820 parser.values.num_jobs = value
821 self.add_option('-j', '--jobs', action="callback", type="int",
822 callback=opt_j, metavar="N",
823 help="Allow N jobs at once.")
825 self.add_option('-k', '--keep-going', action="store_true", default=0,
827 help="Keep going when a target can't be made.")
829 self.add_option('--max-drift', type="int", action="store",
830 dest='max_drift', metavar="N",
831 help="Set maximum system clock drift to N seconds.")
833 self.add_option('-n', '--no-exec', '--just-print', '--dry-run',
834 '--recon', action="store_true", dest='noexec',
835 default=0, help="Don't build; just print commands.")
837 self.add_option('--no-site-dir', action="store_true",
838 dest='no_site_dir', default=0,
839 help="Don't search or use the usual site_scons dir.")
841 self.add_option('--profile', action="store",
842 dest="profile_file", metavar="FILE",
843 help="Profile SCons and put results in FILE.")
845 self.add_option('-q', '--question', action="store_true", default=0,
846 help="Don't build; exit status says if up to date.")
848 self.add_option('-Q', dest='no_progress', action="store_true",
850 help="Suppress \"Reading/Building\" progress messages.")
852 self.add_option('--random', dest="random", action="store_true",
853 default=0, help="Build dependencies in random order.")
855 self.add_option('-s', '--silent', '--quiet', action="store_true",
856 default=0, help="Don't print commands.")
858 self.add_option('--site-dir', action="store",
859 dest='site_dir', metavar="DIR",
860 help="Use DIR instead of the usual site_scons dir.")
862 self.add_option('--taskmastertrace', action="store",
863 dest="taskmastertrace_file", metavar="FILE",
864 help="Trace Node evaluation to FILE.")
866 tree_options = ["all", "derived", "prune", "status"]
868 def opt_tree(option, opt, value, parser, tree_options=tree_options):
870 for o in string.split(value, ','):
880 raise OptionValueError("Warning: %s is not a valid --tree option" % o)
881 tree_printers.append(tp)
883 self.add_option('--tree', action="callback", type="string",
884 callback=opt_tree, nargs=1, metavar="OPTIONS",
885 help="Print a dependency tree in various formats: "
886 "%s." % string.join(tree_options, ", "))
888 self.add_option('-u', '--up', '--search-up', action="store_const",
889 dest="climb_up", default=0, const=1,
890 help="Search up directory tree for SConstruct, "
891 "build targets at or below current directory.")
892 self.add_option('-U', action="store_const", dest="climb_up",
894 help="Search up directory tree for SConstruct, "
895 "build Default() targets from local SConscript.")
897 self.add_option("-v", "--version",
899 help="Print the SCons version number and exit.")
901 self.add_option('--warn', '--warning', nargs=1, action="store",
902 metavar="WARNING-SPEC",
903 help="Enable or disable warnings.")
905 self.add_option('-Y', '--repository', '--srcdir',
906 nargs=1, action="append",
907 help="Search REPOSITORY for source and target files.")
909 self.add_option('-e', '--environment-overrides', action="callback",
910 callback=opt_not_yet,
911 # help="Environment variables override makefiles."
913 self.add_option('-l', '--load-average', '--max-load', action="callback",
914 callback=opt_not_yet, type="int", dest="load_average",
916 # help="Don't start multiple jobs unless load is below "
920 self.add_option('--list-derived', action="callback",
921 callback=opt_not_yet,
922 # help="Don't build; list files that would be built."
924 self.add_option('--list-actions', action="callback",
925 callback=opt_not_yet,
926 # help="Don't build; list files and build actions."
928 self.add_option('--list-where', action="callback",
929 callback=opt_not_yet,
930 # help="Don't build; list files and where defined."
932 self.add_option('-o', '--old-file', '--assume-old', action="callback",
933 callback=opt_not_yet, type="string", dest="old_file",
934 # help = "Consider FILE to be old; don't rebuild it."
936 self.add_option('--override', action="callback", dest="override",
937 callback=opt_not_yet, type="string",
938 # help="Override variables as specified in FILE."
940 self.add_option('-p', action="callback",
941 callback=opt_not_yet,
942 # help="Print internal environments/objects."
944 self.add_option('-r', '-R', '--no-builtin-rules',
945 '--no-builtin-variables', action="callback",
946 callback=opt_not_yet,
947 # help="Clear default environments and variables."
949 self.add_option('-w', '--print-directory', action="callback",
950 callback=opt_not_yet,
951 # help="Print the current directory."
953 self.add_option('--no-print-directory', action="callback",
954 callback=opt_not_yet,
955 # help="Turn off -w, even if it was turned on implicitly."
957 self.add_option('--write-filenames', action="callback",
958 callback=opt_not_yet, type="string", dest="write_filenames",
959 # help="Write all filenames examined into FILE."
961 self.add_option('-W', '--what-if', '--new-file', '--assume-new',
963 action="callback", callback=opt_not_yet, type="string",
964 # help="Consider FILE to be changed."
966 self.add_option('--warn-undefined-variables', action="callback",
967 callback=opt_not_yet,
968 # help="Warn when an undefined variable is referenced."
971 def parse_args(self, args=None, values=None):
972 opt, arglist = OptionParser.parse_args(self, args, values)
973 if opt.implicit_deps_changed or opt.implicit_deps_unchanged:
974 opt.implicit_cache = 1
977 class SConscriptSettableOptions:
978 """This class wraps an OptParser instance and provides
979 uniform access to options that can be either set on the command
980 line or from a SConscript file. A value specified on the command
981 line always overrides a value set in a SConscript file.
982 Not all command line options are SConscript settable, and the ones
983 that are must be explicitly added to settable dictionary and optionally
984 validated and coerced in the set() method."""
986 def __init__(self, options):
987 self.options = options
989 # This dictionary stores the defaults for all the SConscript
990 # settable options, as well as indicating which options
991 # are SConscript settable.
992 self.settable = {'num_jobs':1,
993 'max_drift':SCons.Node.FS.default_max_drift,
996 'duplicate':'hard-soft-copy',
997 'diskcheck':diskcheck_all}
1000 if not self.settable.has_key(name):
1001 raise SCons.Errors.UserError, "This option is not settable from a SConscript file: %s"%name
1002 if hasattr(self.options, name) and getattr(self.options, name) is not None:
1003 return getattr(self.options, name)
1005 return self.settable[name]
1007 def set(self, name, value):
1008 if not self.settable.has_key(name):
1009 raise SCons.Errors.UserError, "This option is not settable from a SConscript file: %s"%name
1011 if name == 'num_jobs':
1017 raise SCons.Errors.UserError, "A positive integer is required: %s"%repr(value)
1018 elif name == 'max_drift':
1022 raise SCons.Errors.UserError, "An integer is required: %s"%repr(value)
1023 elif name == 'duplicate':
1027 raise SCons.Errors.UserError, "A string is required: %s"%repr(value)
1028 if not value in SCons.Node.FS.Valid_Duplicates:
1029 raise SCons.Errors.UserError, "Not a valid duplication style: %s" % value
1030 # Set the duplicate stye right away so it can affect linking
1031 # of SConscript files.
1032 SCons.Node.FS.set_duplicate(value)
1033 elif name == 'diskcheck':
1035 value = diskcheck_convert(value)
1036 except ValueError, v:
1037 raise SCons.Errors.UserError, "Not a valid diskcheck value: %s"%v
1038 if not diskcheck_option_set:
1039 SCons.Node.FS.set_diskcheck(value)
1041 self.settable[name] = value
1044 def _main(args, parser):
1047 # Here's where everything really happens.
1049 # First order of business: set up default warnings and and then
1050 # handle the user's warning options, so we can warn about anything
1051 # that happens appropriately.
1052 default_warnings = [ SCons.Warnings.CorruptSConsignWarning,
1053 SCons.Warnings.DeprecatedWarning,
1054 SCons.Warnings.DuplicateEnvironmentWarning,
1055 SCons.Warnings.MissingSConscriptWarning,
1056 SCons.Warnings.NoMD5ModuleWarning,
1057 SCons.Warnings.NoMetaclassSupportWarning,
1058 SCons.Warnings.NoParallelSupportWarning,
1059 SCons.Warnings.MisleadingKeywordsWarning, ]
1060 for warning in default_warnings:
1061 SCons.Warnings.enableWarningClass(warning)
1062 SCons.Warnings._warningOut = _scons_internal_warning
1064 _setup_warn(options.warn)
1066 for warning_type, message in delayed_warnings:
1067 SCons.Warnings.warn(warning_type, message)
1069 # Next, we want to create the FS object that represents the outside
1070 # world's file system, as that's central to a lot of initialization.
1071 # To do this, however, we need to be in the directory from which we
1072 # want to start everything, which means first handling any relevant
1073 # options that might cause us to chdir somewhere (-C, -D, -U, -u).
1074 if options.directory:
1075 cdir = _create_path(options.directory)
1079 sys.stderr.write("Could not change directory to %s\n" % cdir)
1081 # The SConstruct file may be in a repository, so initialize those
1082 # before we start the search up our path for one.
1084 if options.repository:
1085 repositories.extend(options.repository)
1088 if options.climb_up:
1089 target_top = '.' # directory to prepend to targets
1090 script_dir = os.getcwd() # location of script
1091 while script_dir and not _SConstruct_exists(script_dir):
1092 script_dir, last_part = os.path.split(script_dir)
1094 target_top = os.path.join(last_part, target_top)
1098 display("scons: Entering directory `%s'" % script_dir)
1099 os.chdir(script_dir)
1101 # Now that we're in the top-level SConstruct directory, go ahead
1102 # and initialize the FS object that represents the file system,
1103 # and make it the build engine default.
1104 fs = SCons.Node.FS.default_fs = SCons.Node.FS.FS()
1106 for rep in repositories:
1109 # Now that we have the FS object, the next order of business is to
1110 # check for an SConstruct file (or other specified config file).
1111 # If there isn't one, we can bail before doing any more work.
1114 scripts.extend(options.file)
1116 sfile = _SConstruct_exists()
1118 scripts.append(sfile)
1121 if options.help_msg:
1122 # There's no SConstruct, but they specified -h.
1123 # Give them the options usage now, before we fail
1124 # trying to read a non-existent SConstruct file.
1128 raise SCons.Errors.UserError, "No SConstruct file found."
1130 if scripts[0] == "-":
1133 d = fs.File(scripts[0]).dir
1134 fs.set_SConstruct_dir(d)
1136 # Now that we have the FS object and it's intialized, set up (most
1137 # of) the rest of the options.
1139 ssoptions = SConscriptSettableOptions(options)
1141 _set_globals(options)
1142 SCons.Node.implicit_cache = options.implicit_cache
1143 SCons.Node.implicit_deps_changed = options.implicit_deps_changed
1144 SCons.Node.implicit_deps_unchanged = options.implicit_deps_unchanged
1146 SCons.SConf.dryrun = 1
1147 SCons.Action.execute_actions = None
1148 CleanTask.execute = CleanTask.show
1149 if options.question:
1150 SCons.SConf.dryrun = 1
1151 SCons.SConf.SetCacheMode(options.config)
1152 SCons.SConf.SetProgressDisplay(progress_display)
1154 if options.no_progress or options.silent:
1155 progress_display.set_mode(0)
1159 SCons.Action.print_actions = None
1161 if options.cache_debug:
1162 fs.CacheDebugEnable(options.cache_debug)
1163 if options.cache_disable:
1164 def disable(self): pass
1165 fs.CacheDir = disable
1166 if options.cache_force:
1168 if options.cache_show:
1171 if options.site_dir:
1172 _load_site_scons_dir(d, options.site_dir)
1173 elif not options.no_site_dir:
1174 _load_site_scons_dir(d)
1176 if options.include_dir:
1177 sys.path = options.include_dir + sys.path
1179 # That should cover (most of) the options. Next, set up the variables
1180 # that hold command-line arguments, so the SConscript files that we
1181 # read and execute have access to them.
1189 SCons.Script._Add_Targets(targets)
1190 SCons.Script._Add_Arguments(xmit_args)
1192 sys.stdout = SCons.Util.Unbuffered(sys.stdout)
1194 memory_stats.append('before reading SConscript files:')
1195 count_stats.append(('pre-', 'read'))
1197 progress_display("scons: Reading SConscript files ...")
1199 start_time = time.time()
1201 for script in scripts:
1202 SCons.Script._SConscript._SConscript(fs, script)
1203 except SCons.Errors.StopError, e:
1204 # We had problems reading an SConscript file, such as it
1205 # couldn't be copied in to the BuildDir. Since we're just
1206 # reading SConscript files and haven't started building
1207 # things yet, stop regardless of whether they used -i or -k
1209 sys.stderr.write("scons: *** %s Stop.\n" % e)
1211 sys.exit(exit_status)
1212 global sconscript_time
1213 sconscript_time = time.time() - start_time
1214 SCons.SConf.CreateConfigHBuilder(SCons.Defaults.DefaultEnvironment())
1215 progress_display("scons: done reading SConscript files.")
1217 # Tell the Node.FS subsystem that we're all done reading the
1218 # SConscript files and calling Repository() and BuildDir() and the
1219 # like, so it can go ahead and start memoizing the string values of
1220 # file system nodes.
1221 SCons.Node.FS.save_strings(1)
1223 memory_stats.append('after reading SConscript files:')
1224 count_stats.append(('post-', 'read'))
1228 if options.help_msg:
1229 help_text = SCons.Script.help_text
1230 if help_text is None:
1231 # They specified -h, but there was no Help() inside the
1232 # SConscript files. Give them the options usage.
1233 parser.print_help(sys.stdout)
1236 print "Use scons -H for help about command-line options."
1240 # Now that we've read the SConscripts we can set the options
1241 # that are SConscript settable:
1242 SCons.Node.implicit_cache = ssoptions.get('implicit_cache')
1243 SCons.Node.FS.set_duplicate(ssoptions.get('duplicate'))
1244 fs.set_max_drift(ssoptions.get('max_drift'))
1247 if targets or SCons.Script.BUILD_TARGETS != SCons.Script._build_plus_default:
1248 # They specified targets on the command line or modified
1249 # BUILD_TARGETS in the SConscript file(s), so if they used -u,
1250 # -U or -D, we have to look up targets relative to the top,
1251 # but we build whatever they specified.
1253 lookup_top = fs.Dir(target_top)
1256 targets = SCons.Script.BUILD_TARGETS
1258 # There are no targets specified on the command line,
1259 # so if they used -u, -U or -D, we may have to restrict
1260 # what actually gets built.
1263 if options.climb_up == 1:
1264 # -u, local directory and below
1265 target_top = fs.Dir(target_top)
1266 lookup_top = target_top
1267 elif options.climb_up == 2:
1268 # -D, all Default() targets
1271 elif options.climb_up == 3:
1272 # -U, local SConscript Default() targets
1273 target_top = fs.Dir(target_top)
1274 def check_dir(x, target_top=target_top):
1275 if hasattr(x, 'cwd') and not x.cwd is None:
1276 cwd = x.cwd.srcnode()
1277 return cwd == target_top
1279 # x doesn't have a cwd, so it's either not a target,
1280 # or not a file, so go ahead and keep it as a default
1281 # target and let the engine sort it out:
1283 d = filter(check_dir, SCons.Script.DEFAULT_TARGETS)
1284 SCons.Script.DEFAULT_TARGETS[:] = d
1288 targets = SCons.Script._Get_Default_Targets(d, fs)
1291 sys.stderr.write("scons: *** No targets specified and no Default() targets found. Stop.\n")
1294 def Entry(x, ltop=lookup_top, ttop=target_top, fs=fs):
1295 if isinstance(x, SCons.Node.Node):
1299 # Why would ltop be None? Unfortunately this happens.
1300 if ltop == None: ltop = ''
1301 # Curdir becomes important when SCons is called with -u, -C,
1302 # or similar option that changes directory, and so the paths
1303 # of targets given on the command line need to be adjusted.
1304 curdir = os.path.join(os.getcwd(), str(ltop))
1305 for lookup in SCons.Node.arg2nodes_lookups:
1306 node = lookup(x, curdir=curdir)
1310 node = fs.Entry(x, directory=ltop, create=1)
1311 if ttop and not node.is_under(ttop):
1312 if isinstance(node, SCons.Node.FS.Dir) and ttop.is_under(node):
1318 nodes = filter(None, map(Entry, targets))
1320 task_class = BuildTask # default action is to build targets
1321 opening_message = "Building targets ..."
1322 closing_message = "done building targets."
1323 if keep_going_on_error:
1324 failure_message = "done building targets (errors occurred during build)."
1326 failure_message = "building terminated because of errors."
1327 if options.question:
1328 task_class = QuestionTask
1330 if ssoptions.get('clean'):
1331 task_class = CleanTask
1332 opening_message = "Cleaning targets ..."
1333 closing_message = "done cleaning targets."
1334 if keep_going_on_error:
1335 closing_message = "done cleaning targets (errors occurred during clean)."
1337 failure_message = "cleaning terminated because of errors."
1338 except AttributeError:
1342 def order(dependencies):
1343 """Randomize the dependencies."""
1344 # This is cribbed from the implementation of
1345 # random.shuffle() in Python 2.X.
1347 for i in xrange(len(d)-1, 0, -1):
1348 j = int(random.random() * (i+1))
1349 d[i], d[j] = d[j], d[i]
1352 def order(dependencies):
1353 """Leave the order of dependencies alone."""
1356 progress_display("scons: " + opening_message)
1357 if options.taskmastertrace_file == '-':
1358 tmtrace = sys.stdout
1359 elif options.taskmastertrace_file:
1360 tmtrace = open(options.taskmastertrace_file, 'wb')
1363 taskmaster = SCons.Taskmaster.Taskmaster(nodes, task_class, order, tmtrace)
1366 num_jobs = ssoptions.get('num_jobs')
1367 jobs = SCons.Job.Jobs(num_jobs, taskmaster)
1368 if num_jobs > 1 and jobs.num_jobs == 1:
1369 msg = "parallel builds are unsupported by this version of Python;\n" + \
1370 "\tignoring -j or num_jobs option.\n"
1371 SCons.Warnings.warn(SCons.Warnings.NoParallelSupportWarning, msg)
1373 memory_stats.append('before building targets:')
1374 count_stats.append(('pre-', 'build'))
1380 progress_display("scons: " + failure_message)
1382 progress_display("scons: " + closing_message)
1383 if not options.noexec:
1384 SCons.SConsign.write()
1386 memory_stats.append('after building targets:')
1387 count_stats.append(('post-', 'build'))
1390 sconsflags = os.environ.get('SCONSFLAGS', '')
1391 all_args = string.split(sconsflags) + sys.argv[1:]
1393 parser = OptParser()
1395 options, args = parser.parse_args(all_args)
1396 if type(options.debug) == type([]) and "pdb" in options.debug:
1398 pdb.Pdb().runcall(_main, args, parser)
1399 elif options.profile_file:
1400 from profile import Profile
1402 # Some versions of Python 2.4 shipped a profiler that had the
1403 # wrong 'c_exception' entry in its dispatch table. Make sure
1404 # we have the right one. (This may put an unnecessary entry
1405 # in the table in earlier versions of Python, but its presence
1406 # shouldn't hurt anything).
1408 dispatch = Profile.dispatch
1409 except AttributeError:
1412 dispatch['c_exception'] = Profile.trace_dispatch_return
1416 prof.runcall(_main, args, parser)
1419 prof.dump_stats(options.profile_file)
1425 global first_command_start
1429 except SystemExit, s:
1432 except KeyboardInterrupt:
1433 print "Build interrupted."
1435 except SyntaxError, e:
1436 _scons_syntax_error(e)
1437 except SCons.Errors.InternalError:
1438 _scons_internal_error()
1439 except SCons.Errors.UserError, e:
1440 _scons_user_error(e)
1442 # An exception here is likely a builtin Python exception Python
1443 # code in an SConscript file. Show them precisely what the
1444 # problem was and where it happened.
1445 SCons.Script._SConscript.SConscript_exception()
1448 memory_stats.print_stats()
1449 count_stats.print_stats()
1452 SCons.Debug.listLoggedInstances('*')
1453 #SCons.Debug.dumpLoggedInstances('*')
1456 SCons.Memoize.Dump("Memoizer (memory cache) hits and misses:")
1458 # Dump any development debug info that may have been enabled.
1459 # These are purely for internal debugging during development, so
1460 # there's no need to control them with --debug= options; they're
1461 # controlled by changing the source code.
1462 SCons.Debug.dump_caller_counts()
1463 SCons.Taskmaster.dump_stats()
1466 total_time = time.time() - SCons.Script.start_time
1468 ct = cumulative_command_time
1470 ct = last_command_end - first_command_start
1471 scons_time = total_time - sconscript_time - ct
1472 print "Total build time: %f seconds"%total_time
1473 print "Total SConscript file execution time: %f seconds"%sconscript_time
1474 print "Total SCons execution time: %f seconds"%scons_time
1475 print "Total command execution time: %f seconds"%ct
1477 sys.exit(exit_status)