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
67 import SCons.Taskmaster
73 class SConsPrintHelpException(Exception):
76 display = SCons.Util.display
77 progress_display = SCons.Util.DisplayEngine()
79 first_command_start = None
80 last_command_end = None
84 class BuildTask(SCons.Taskmaster.Task):
85 """An SCons build task."""
86 def display(self, message):
87 display('scons: ' + message)
90 for target in self.targets:
91 if target.get_state() == SCons.Node.up_to_date:
93 if target.has_builder() and not hasattr(target.builder, 'status'):
95 start_time = time.time()
96 global first_command_start
97 if first_command_start is None:
98 first_command_start = start_time
99 SCons.Taskmaster.Task.execute(self)
101 global cumulative_command_time
102 global last_command_end
103 finish_time = time.time()
104 last_command_end = finish_time
105 cumulative_command_time = cumulative_command_time+finish_time-start_time
106 sys.stdout.write("Command execution time: %f seconds\n"%(finish_time-start_time))
109 if self.top and target.has_builder():
110 display("scons: `%s' is up to date." % str(self.node))
112 def do_failed(self, status=2):
114 if self.options.ignore_errors:
115 SCons.Taskmaster.Task.executed(self)
116 elif self.options.keep_going:
117 SCons.Taskmaster.Task.fail_continue(self)
120 SCons.Taskmaster.Task.fail_stop(self)
125 if self.top and not t.has_builder() and not t.side_effect:
127 sys.stderr.write("scons: *** Do not know how to make target `%s'." % t)
128 if not self.options.keep_going:
129 sys.stderr.write(" Stop.")
130 sys.stderr.write("\n")
133 print "scons: Nothing to be done for `%s'." % t
134 SCons.Taskmaster.Task.executed(self)
136 SCons.Taskmaster.Task.executed(self)
139 # Handle the failure of a build task. The primary purpose here
140 # is to display the various types of Errors and Exceptions
143 exc_info = self.exc_info()
150 # The Taskmaster didn't record an exception for this Task;
151 # see if the sys module has one.
152 t, e = sys.exc_info()[:2]
155 if not SCons.Util.is_List(n):
157 return string.join(map(str, n), ', ')
159 errfmt = "scons: *** [%s] %s\n"
161 if t == SCons.Errors.BuildError:
162 tname = nodestring(e.node)
165 errstr = e.filename + ': ' + errstr
166 sys.stderr.write(errfmt % (tname, errstr))
167 elif t == SCons.Errors.TaskmasterException:
168 tname = nodestring(e.node)
169 sys.stderr.write(errfmt % (tname, e.errstr))
170 type, value, trace = e.exc_info
171 traceback.print_exception(type, value, trace)
172 elif t == SCons.Errors.ExplicitExit:
174 tname = nodestring(e.node)
175 errstr = 'Explicit exit, status %s' % status
176 sys.stderr.write(errfmt % (tname, errstr))
181 if t == SCons.Errors.StopError and not self.options.keep_going:
183 sys.stderr.write("scons: *** %s\n" % s)
185 if tb and print_stacktrace:
186 sys.stderr.write("scons: internal stack trace:\n")
187 traceback.print_tb(tb, file=sys.stderr)
189 self.do_failed(status)
193 def postprocess(self):
196 for tp in self.options.tree_printers:
198 if self.options.debug_includes:
199 tree = t.render_include_tree()
203 SCons.Taskmaster.Task.postprocess(self)
205 def make_ready(self):
206 """Make a task ready for execution"""
207 SCons.Taskmaster.Task.make_ready(self)
208 if self.out_of_date and self.options.debug_explain:
209 explanation = self.out_of_date[0].explain()
211 sys.stdout.write("scons: " + explanation)
213 class CleanTask(SCons.Taskmaster.Task):
214 """An SCons clean task."""
215 def dir_index(self, directory):
216 dirname = lambda f, d=directory: os.path.join(d, f)
217 files = map(dirname, os.listdir(directory))
219 # os.listdir() isn't guaranteed to return files in any specific order,
220 # but some of the test code expects sorted output.
224 def fs_delete(self, path, remove=1):
226 if os.path.exists(path):
227 if os.path.isfile(path):
228 if remove: os.unlink(path)
229 display("Removed " + path)
230 elif os.path.isdir(path) and not os.path.islink(path):
231 # delete everything in the dir
232 for p in self.dir_index(path):
233 if os.path.isfile(p):
234 if remove: os.unlink(p)
235 display("Removed " + p)
237 self.fs_delete(p, remove)
238 # then delete dir itself
239 if remove: os.rmdir(path)
240 display("Removed directory " + path)
241 except (IOError, OSError), e:
242 print "scons: Could not remove '%s':" % str(path), e.strerror
245 target = self.targets[0]
246 if (target.has_builder() or target.side_effect) and not target.noclean:
247 for t in self.targets:
249 display("Removed " + str(t))
250 if SCons.Environment.CleanTargets.has_key(target):
251 files = SCons.Environment.CleanTargets[target]
253 self.fs_delete(str(f), 0)
256 target = self.targets[0]
257 if (target.has_builder() or target.side_effect) and not target.noclean:
258 for t in self.targets:
262 # An OSError may indicate something like a permissions
263 # issue, an IOError would indicate something like
264 # the file not existing. In either case, print a
265 # message and keep going to try to remove as many
266 # targets aa possible.
267 print "scons: Could not remove '%s':" % str(t), e.strerror
270 display("Removed " + str(t))
271 if SCons.Environment.CleanTargets.has_key(target):
272 files = SCons.Environment.CleanTargets[target]
274 self.fs_delete(str(f))
278 # Have the taskmaster arrange to "execute" all of the targets, because
279 # we'll figure out ourselves (in remove() or show() above) whether
280 # anything really needs to be done.
281 make_ready = SCons.Taskmaster.Task.make_ready_all
286 class QuestionTask(SCons.Taskmaster.Task):
287 """An SCons task for the -q (question) option."""
292 if self.targets[0].get_state() != SCons.Node.up_to_date:
302 def __init__(self, derived=False, prune=False, status=False):
303 self.derived = derived
306 def get_all_children(self, node):
307 return node.all_children()
308 def get_derived_children(self, node):
309 children = node.all_children(None)
310 return filter(lambda x: x.has_builder(), children)
311 def display(self, t):
313 func = self.get_derived_children
315 func = self.get_all_children
316 s = self.status and 2 or 0
317 SCons.Util.print_tree(t, func, prune=self.prune, showtags=s)
327 cumulative_command_time = 0
328 exit_status = 0 # exit status, assume success by default
330 delayed_warnings = []
334 def AddOption(*args, **kw):
335 if not kw.has_key('default'):
337 result = apply(OptionsParser.add_local_option, args, kw)
344 self.append = self.do_nothing
345 self.print_stats = self.do_nothing
346 def enable(self, outfp):
348 self.append = self.do_append
349 self.print_stats = self.do_print
350 def do_nothing(self, *args, **kw):
353 class CountStats(Stats):
354 def do_append(self, label):
355 self.labels.append(label)
356 self.stats.append(SCons.Debug.fetchLoggedInstances())
360 for n in map(lambda t: t[0], s):
361 stats_table[n] = [0, 0, 0, 0]
365 stats_table[n][i] = c
367 keys = stats_table.keys()
369 self.outfp.write("Object counts:\n")
373 fmt1 = string.join(pre + [' %7s']*l + post, '')
374 fmt2 = string.join(pre + [' %7d']*l + post, '')
375 labels = self.labels[:l]
376 labels.append(("", "Class"))
377 self.outfp.write(fmt1 % tuple(map(lambda x: x[0], labels)))
378 self.outfp.write(fmt1 % tuple(map(lambda x: x[1], labels)))
380 r = stats_table[k][:l] + [k]
381 self.outfp.write(fmt2 % tuple(r))
383 count_stats = CountStats()
385 class MemStats(Stats):
386 def do_append(self, label):
387 self.labels.append(label)
388 self.stats.append(SCons.Debug.memory())
390 fmt = 'Memory %-32s %12d\n'
391 for label, stats in map(None, self.labels, self.stats):
392 self.outfp.write(fmt % (label, stats))
394 memory_stats = MemStats()
398 def _scons_syntax_error(e):
399 """Handle syntax errors. Print out a message and show where the error
402 etype, value, tb = sys.exc_info()
403 lines = traceback.format_exception_only(etype, value)
405 sys.stderr.write(line+'\n')
408 def find_deepest_user_frame(tb):
410 Find the deepest stack frame that is not part of SCons.
412 Input is a "pre-processed" stack trace in the form
413 returned by traceback.extract_tb() or traceback.extract_stack()
418 # find the deepest traceback frame that is not part
422 if string.find(filename, os.sep+'SCons'+os.sep) == -1:
426 def _scons_user_error(e):
427 """Handle user errors. Print out a message and a description of the
428 error, along with the line number and routine where it occured.
429 The file and line number will be the deepest stack frame that is
430 not part of SCons itself.
432 global print_stacktrace
433 etype, value, tb = sys.exc_info()
435 traceback.print_exception(etype, value, tb)
436 filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_tb(tb))
437 sys.stderr.write("\nscons: *** %s\n" % value)
438 sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
441 def _scons_user_warning(e):
442 """Handle user warnings. Print out a message and a description of
443 the warning, along with the line number and routine where it occured.
444 The file and line number will be the deepest stack frame that is
445 not part of SCons itself.
447 etype, value, tb = sys.exc_info()
448 filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_tb(tb))
449 sys.stderr.write("\nscons: warning: %s\n" % e)
450 sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
452 def _scons_internal_warning(e):
453 """Slightly different from _scons_user_warning in that we use the
454 *current call stack* rather than sys.exc_info() to get our stack trace.
455 This is used by the warnings framework to print warnings."""
456 filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_stack())
457 sys.stderr.write("\nscons: warning: %s\n" % e[0])
458 sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
460 def _scons_internal_error():
461 """Handle all errors but user errors. Print out a message telling
462 the user what to do in this case and print a normal trace.
464 print 'internal error'
465 traceback.print_exc()
468 def _setup_warn(arg):
469 """The --warn option. An argument to this option
470 should be of the form <warning-class> or no-<warning-class>.
471 The warning class is munged in order to get an actual class
472 name from the SCons.Warnings module to enable or disable.
473 The supplied <warning-class> is split on hyphens, each element
474 is captialized, then smushed back together. Then the string
475 "SCons.Warnings." is added to the front and "Warning" is added
476 to the back to get the fully qualified class name.
478 For example, --warn=deprecated will enable the
479 SCons.Warnings.DeprecatedWarning class.
481 --warn=no-dependency will disable the
482 SCons.Warnings.DependencyWarning class.
484 As a special case, --warn=all and --warn=no-all
485 will enable or disable (respectively) the base
486 class of all warnings, which is SCons.Warning.Warning."""
488 elems = string.split(string.lower(arg), '-')
494 if len(elems) == 1 and elems[0] == 'all':
495 class_name = "Warning"
499 return "SCons" + s[5:]
501 return string.capitalize(s)
502 class_name = string.join(map(_capitalize, elems), '') + "Warning"
504 clazz = getattr(SCons.Warnings, class_name)
505 except AttributeError:
506 sys.stderr.write("No warning type: '%s'\n" % arg)
509 SCons.Warnings.enableWarningClass(clazz)
511 SCons.Warnings.suppressWarningClass(clazz)
513 def _SConstruct_exists(dirname='', repositories=[]):
514 """This function checks that an SConstruct file exists in a directory.
515 If so, it returns the path of the file. By default, it checks the
518 for file in ['SConstruct', 'Sconstruct', 'sconstruct']:
519 sfile = os.path.join(dirname, file)
520 if os.path.isfile(sfile):
522 if not os.path.isabs(sfile):
523 for rep in repositories:
524 if os.path.isfile(os.path.join(rep, sfile)):
528 def _set_debug_values(options):
529 global print_memoizer, print_objects, print_stacktrace, print_time
531 debug_values = options.debug
533 if "count" in debug_values:
534 # All of the object counts are within "if __debug__:" blocks,
535 # which get stripped when running optimized (with python -O or
536 # from compiled *.pyo files). Provide a warning if __debug__ is
537 # stripped, so it doesn't just look like --debug=count is broken.
539 if __debug__: enable_count = True
541 count_stats.enable(sys.stdout)
543 msg = "--debug=count is not supported when running SCons\n" + \
544 "\twith the python -O option or optimized (.pyo) modules."
545 SCons.Warnings.warn(SCons.Warnings.NoObjectCountWarning, msg)
546 if "dtree" in debug_values:
547 options.tree_printers.append(TreePrinter(derived=True))
548 options.debug_explain = ("explain" in debug_values)
549 if "findlibs" in debug_values:
550 SCons.Scanner.Prog.print_find_libs = "findlibs"
551 options.debug_includes = ("includes" in debug_values)
552 print_memoizer = ("memoizer" in debug_values)
553 if "memory" in debug_values:
554 memory_stats.enable(sys.stdout)
555 print_objects = ("objects" in debug_values)
556 if "presub" in debug_values:
557 SCons.Action.print_actions_presub = 1
558 if "stacktrace" in debug_values:
560 if "stree" in debug_values:
561 options.tree_printers.append(TreePrinter(status=True))
562 if "time" in debug_values:
564 if "tree" in debug_values:
565 options.tree_printers.append(TreePrinter())
567 def _create_path(plist):
573 path = path + '/' + d
576 def _load_site_scons_dir(topdir, site_dir_name=None):
577 """Load the site_scons dir under topdir.
578 Adds site_scons to sys.path, imports site_scons/site_init.py,
579 and adds site_scons/site_tools to default toolpath."""
581 err_if_not_found = True # user specified: err if missing
583 site_dir_name = "site_scons"
584 err_if_not_found = False
586 site_dir = os.path.join(topdir.path, site_dir_name)
587 if not os.path.exists(site_dir):
589 raise SCons.Errors.UserError, "site dir %s not found."%site_dir
592 site_init_filename = "site_init.py"
593 site_init_modname = "site_init"
594 site_tools_dirname = "site_tools"
595 sys.path = [os.path.abspath(site_dir)] + sys.path
596 site_init_file = os.path.join(site_dir, site_init_filename)
597 site_tools_dir = os.path.join(site_dir, site_tools_dirname)
598 if os.path.exists(site_init_file):
601 fp, pathname, description = imp.find_module(site_init_modname,
604 imp.load_module(site_init_modname, fp, pathname, description)
608 except ImportError, e:
609 sys.stderr.write("Can't import site init file '%s': %s\n"%(site_init_file, e))
612 sys.stderr.write("Site init file '%s' raised exception: %s\n"%(site_init_file, e))
614 if os.path.exists(site_tools_dir):
615 SCons.Tool.DefaultToolpath.append(os.path.abspath(site_tools_dir))
617 def version_string(label, module):
618 fmt = "\t%s: v%s.%s, %s, by %s on %s\n"
623 module.__developer__,
629 options = parser.values
631 # Here's where everything really happens.
633 # First order of business: set up default warnings and then
634 # handle the user's warning options, so that we can issue (or
635 # suppress) appropriate warnings about anything that might happen,
636 # as configured by the user.
638 default_warnings = [ SCons.Warnings.CorruptSConsignWarning,
639 SCons.Warnings.DeprecatedWarning,
640 SCons.Warnings.DuplicateEnvironmentWarning,
641 SCons.Warnings.MissingSConscriptWarning,
642 SCons.Warnings.NoMD5ModuleWarning,
643 SCons.Warnings.NoMetaclassSupportWarning,
644 SCons.Warnings.NoObjectCountWarning,
645 SCons.Warnings.NoParallelSupportWarning,
646 SCons.Warnings.MisleadingKeywordsWarning, ]
647 for warning in default_warnings:
648 SCons.Warnings.enableWarningClass(warning)
649 SCons.Warnings._warningOut = _scons_internal_warning
651 _setup_warn(options.warn)
653 # Now that we have the warnings configuration set up, we can actually
654 # issue (or suppress) any warnings about warning-worthy things that
655 # occurred while the command-line options were getting parsed.
657 dw = options.delayed_warnings
658 except AttributeError:
661 delayed_warnings.extend(dw)
662 for warning_type, message in delayed_warnings:
663 SCons.Warnings.warn(warning_type, message)
665 if options.diskcheck:
666 SCons.Node.FS.set_diskcheck(options.diskcheck)
668 # Next, we want to create the FS object that represents the outside
669 # world's file system, as that's central to a lot of initialization.
670 # To do this, however, we need to be in the directory from which we
671 # want to start everything, which means first handling any relevant
672 # options that might cause us to chdir somewhere (-C, -D, -U, -u).
673 if options.directory:
674 cdir = _create_path(options.directory)
678 sys.stderr.write("Could not change directory to %s\n" % cdir)
682 target_top = '.' # directory to prepend to targets
683 script_dir = os.getcwd() # location of script
684 while script_dir and not _SConstruct_exists(script_dir, options.repository):
685 script_dir, last_part = os.path.split(script_dir)
687 target_top = os.path.join(last_part, target_top)
691 display("scons: Entering directory `%s'" % script_dir)
694 # Now that we're in the top-level SConstruct directory, go ahead
695 # and initialize the FS object that represents the file system,
696 # and make it the build engine default.
697 fs = SCons.Node.FS.default_fs = SCons.Node.FS.FS()
699 for rep in options.repository:
702 # Now that we have the FS object, the next order of business is to
703 # check for an SConstruct file (or other specified config file).
704 # If there isn't one, we can bail before doing any more work.
707 scripts.extend(options.file)
709 sfile = _SConstruct_exists(repositories=options.repository)
711 scripts.append(sfile)
715 # There's no SConstruct, but they specified -h.
716 # Give them the options usage now, before we fail
717 # trying to read a non-existent SConstruct file.
718 raise SConsPrintHelpException
719 raise SCons.Errors.UserError, "No SConstruct file found."
721 if scripts[0] == "-":
724 d = fs.File(scripts[0]).dir
725 fs.set_SConstruct_dir(d)
727 _set_debug_values(options)
728 SCons.Node.implicit_cache = options.implicit_cache
729 SCons.Node.implicit_deps_changed = options.implicit_deps_changed
730 SCons.Node.implicit_deps_unchanged = options.implicit_deps_unchanged
732 SCons.SConf.dryrun = 1
733 SCons.Action.execute_actions = None
734 CleanTask.execute = CleanTask.show
736 SCons.SConf.dryrun = 1
737 SCons.SConf.SetCacheMode(options.config)
738 SCons.SConf.SetProgressDisplay(progress_display)
740 if options.no_progress or options.silent:
741 progress_display.set_mode(0)
745 SCons.Action.print_actions = None
747 if options.cache_debug:
748 fs.CacheDebugEnable(options.cache_debug)
749 if options.cache_disable:
750 def disable(self): pass
751 fs.CacheDir = disable
752 if options.cache_force:
754 if options.cache_show:
758 _load_site_scons_dir(d, options.site_dir)
759 elif not options.no_site_dir:
760 _load_site_scons_dir(d)
762 if options.include_dir:
763 sys.path = options.include_dir + sys.path
765 # That should cover (most of) the options. Next, set up the variables
766 # that hold command-line arguments, so the SConscript files that we
767 # read and execute have access to them.
770 for a in parser.largs:
777 SCons.Script._Add_Targets(targets + parser.rargs)
778 SCons.Script._Add_Arguments(xmit_args)
780 sys.stdout = SCons.Util.Unbuffered(sys.stdout)
782 memory_stats.append('before reading SConscript files:')
783 count_stats.append(('pre-', 'read'))
785 # And here's where we (finally) read the SConscript files.
787 progress_display("scons: Reading SConscript files ...")
789 start_time = time.time()
791 for script in scripts:
792 SCons.Script._SConscript._SConscript(fs, script)
793 except SCons.Errors.StopError, e:
794 # We had problems reading an SConscript file, such as it
795 # couldn't be copied in to the BuildDir. Since we're just
796 # reading SConscript files and haven't started building
797 # things yet, stop regardless of whether they used -i or -k
799 sys.stderr.write("scons: *** %s Stop.\n" % e)
801 sys.exit(exit_status)
802 global sconscript_time
803 sconscript_time = time.time() - start_time
805 progress_display("scons: done reading SConscript files.")
807 memory_stats.append('after reading SConscript files:')
808 count_stats.append(('post-', 'read'))
810 SCons.SConf.CreateConfigHBuilder(SCons.Defaults.DefaultEnvironment())
812 # Now re-parse the command-line options (any to the left of a '--'
813 # argument, that is) with any user-defined command-line options that
814 # the SConscript files may have added to the parser object. This will
815 # emit the appropriate error message and exit if any unknown option
816 # was specified on the command line.
818 parser.preserve_unknown_options = False
819 parser.parse_args(parser.largs, options)
822 help_text = SCons.Script.help_text
823 if help_text is None:
824 # They specified -h, but there was no Help() inside the
825 # SConscript files. Give them the options usage.
826 raise SConsPrintHelpException
829 print "Use scons -H for help about command-line options."
833 # Change directory to the top-level SConstruct directory, then tell
834 # the Node.FS subsystem that we're all done reading the SConscript
835 # files and calling Repository() and BuildDir() and changing
836 # directories and the like, so it can go ahead and start memoizing
837 # the string values of file system nodes.
841 SCons.Node.FS.save_strings(1)
843 # Now that we've read the SConscripts we can set the options
844 # that are SConscript settable:
845 SCons.Node.implicit_cache = options.implicit_cache
846 SCons.Node.FS.set_duplicate(options.duplicate)
847 fs.set_max_drift(options.max_drift)
850 if targets or SCons.Script.BUILD_TARGETS != SCons.Script._build_plus_default:
851 # They specified targets on the command line or modified
852 # BUILD_TARGETS in the SConscript file(s), so if they used -u,
853 # -U or -D, we have to look up targets relative to the top,
854 # but we build whatever they specified.
856 lookup_top = fs.Dir(target_top)
859 targets = SCons.Script.BUILD_TARGETS
861 # There are no targets specified on the command line,
862 # so if they used -u, -U or -D, we may have to restrict
863 # what actually gets built.
866 if options.climb_up == 1:
867 # -u, local directory and below
868 target_top = fs.Dir(target_top)
869 lookup_top = target_top
870 elif options.climb_up == 2:
871 # -D, all Default() targets
874 elif options.climb_up == 3:
875 # -U, local SConscript Default() targets
876 target_top = fs.Dir(target_top)
877 def check_dir(x, target_top=target_top):
878 if hasattr(x, 'cwd') and not x.cwd is None:
879 cwd = x.cwd.srcnode()
880 return cwd == target_top
882 # x doesn't have a cwd, so it's either not a target,
883 # or not a file, so go ahead and keep it as a default
884 # target and let the engine sort it out:
886 d = filter(check_dir, SCons.Script.DEFAULT_TARGETS)
887 SCons.Script.DEFAULT_TARGETS[:] = d
891 targets = SCons.Script._Get_Default_Targets(d, fs)
894 sys.stderr.write("scons: *** No targets specified and no Default() targets found. Stop.\n")
897 def Entry(x, ltop=lookup_top, ttop=target_top, fs=fs):
898 if isinstance(x, SCons.Node.Node):
902 # Why would ltop be None? Unfortunately this happens.
903 if ltop == None: ltop = ''
904 # Curdir becomes important when SCons is called with -u, -C,
905 # or similar option that changes directory, and so the paths
906 # of targets given on the command line need to be adjusted.
907 curdir = os.path.join(os.getcwd(), str(ltop))
908 for lookup in SCons.Node.arg2nodes_lookups:
909 node = lookup(x, curdir=curdir)
913 node = fs.Entry(x, directory=ltop, create=1)
914 if ttop and not node.is_under(ttop):
915 if isinstance(node, SCons.Node.FS.Dir) and ttop.is_under(node):
921 nodes = filter(None, map(Entry, targets))
923 task_class = BuildTask # default action is to build targets
924 opening_message = "Building targets ..."
925 closing_message = "done building targets."
926 if options.keep_going:
927 failure_message = "done building targets (errors occurred during build)."
929 failure_message = "building terminated because of errors."
931 task_class = QuestionTask
934 task_class = CleanTask
935 opening_message = "Cleaning targets ..."
936 closing_message = "done cleaning targets."
937 if options.keep_going:
938 closing_message = "done cleaning targets (errors occurred during clean)."
940 failure_message = "cleaning terminated because of errors."
941 except AttributeError:
945 def order(dependencies):
946 """Randomize the dependencies."""
948 # This is cribbed from the implementation of
949 # random.shuffle() in Python 2.X.
951 for i in xrange(len(d)-1, 0, -1):
952 j = int(random.random() * (i+1))
953 d[i], d[j] = d[j], d[i]
956 def order(dependencies):
957 """Leave the order of dependencies alone."""
960 progress_display("scons: " + opening_message)
961 if options.taskmastertrace_file == '-':
963 elif options.taskmastertrace_file:
964 tmtrace = open(options.taskmastertrace_file, 'wb')
967 taskmaster = SCons.Taskmaster.Taskmaster(nodes, task_class, order, tmtrace)
969 # Let the BuildTask objects get at the options to respond to the
970 # various print_* settings, tree_printer list, etc.
971 BuildTask.options = options
974 num_jobs = options.num_jobs
975 jobs = SCons.Job.Jobs(num_jobs, taskmaster)
976 if num_jobs > 1 and jobs.num_jobs == 1:
977 msg = "parallel builds are unsupported by this version of Python;\n" + \
978 "\tignoring -j or num_jobs option.\n"
979 SCons.Warnings.warn(SCons.Warnings.NoParallelSupportWarning, msg)
981 memory_stats.append('before building targets:')
982 count_stats.append(('pre-', 'build'))
989 progress_display("scons: " + failure_message)
991 progress_display("scons: " + closing_message)
992 if not options.no_exec:
993 SCons.SConsign.write()
995 memory_stats.append('after building targets:')
996 count_stats.append(('post-', 'build'))
998 def _exec_main(parser, values):
999 sconsflags = os.environ.get('SCONSFLAGS', '')
1000 all_args = string.split(sconsflags) + sys.argv[1:]
1002 options, args = parser.parse_args(all_args, values)
1004 if type(options.debug) == type([]) and "pdb" in options.debug:
1006 pdb.Pdb().runcall(_main, parser)
1007 elif options.profile_file:
1008 from profile import Profile
1010 # Some versions of Python 2.4 shipped a profiler that had the
1011 # wrong 'c_exception' entry in its dispatch table. Make sure
1012 # we have the right one. (This may put an unnecessary entry
1013 # in the table in earlier versions of Python, but its presence
1014 # shouldn't hurt anything).
1016 dispatch = Profile.dispatch
1017 except AttributeError:
1020 dispatch['c_exception'] = Profile.trace_dispatch_return
1024 prof.runcall(_main, parser)
1025 except SConsPrintHelpException, e:
1026 prof.dump_stats(options.profile_file)
1030 prof.dump_stats(options.profile_file)
1035 global OptionsParser
1037 global first_command_start
1039 parts = ["SCons by Steven Knight et al.:\n"]
1041 parts.append(version_string("script", __main__))
1042 except KeyboardInterrupt:
1045 # On Windows there is no scons.py, so there is no
1046 # __main__.__version__, hence there is no script version.
1048 parts.append(version_string("engine", SCons))
1049 parts.append("__COPYRIGHT__")
1050 version = string.join(parts, '')
1053 parser = SConsOptions.Parser(version)
1054 values = SConsOptions.SConsValues(parser.get_default_values())
1056 OptionsParser = parser
1059 _exec_main(parser, values)
1060 except SystemExit, s:
1063 except KeyboardInterrupt:
1064 print "Build interrupted."
1066 except SyntaxError, e:
1067 _scons_syntax_error(e)
1068 except SCons.Errors.InternalError:
1069 _scons_internal_error()
1070 except SCons.Errors.UserError, e:
1071 _scons_user_error(e)
1072 except SConsPrintHelpException:
1076 # An exception here is likely a builtin Python exception Python
1077 # code in an SConscript file. Show them precisely what the
1078 # problem was and where it happened.
1079 SCons.Script._SConscript.SConscript_exception()
1082 memory_stats.print_stats()
1083 count_stats.print_stats()
1086 SCons.Debug.listLoggedInstances('*')
1087 #SCons.Debug.dumpLoggedInstances('*')
1090 SCons.Memoize.Dump("Memoizer (memory cache) hits and misses:")
1092 # Dump any development debug info that may have been enabled.
1093 # These are purely for internal debugging during development, so
1094 # there's no need to control them with --debug= options; they're
1095 # controlled by changing the source code.
1096 SCons.Debug.dump_caller_counts()
1097 SCons.Taskmaster.dump_stats()
1100 total_time = time.time() - SCons.Script.start_time
1102 ct = cumulative_command_time
1104 if last_command_end is None or first_command_start is None:
1107 ct = last_command_end - first_command_start
1108 scons_time = total_time - sconscript_time - ct
1109 print "Total build time: %f seconds"%total_time
1110 print "Total SConscript file execution time: %f seconds"%sconscript_time
1111 print "Total SCons execution time: %f seconds"%scons_time
1112 print "Total command execution time: %f seconds"%ct
1114 sys.exit(exit_status)