Don't print duplicate stats when --profile is used.
[scons.git] / src / engine / SCons / Script / Main.py
1 """SCons.Script
2
3 This file implements the main() function used by the scons script.
4
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,
10 it goes here.
11
12 """
13
14 #
15 # __COPYRIGHT__
16 #
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:
24 #
25 # The above copyright notice and this permission notice shall be included
26 # in all copies or substantial portions of the Software.
27 #
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.
35 #
36
37 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
38
39 import os
40 import os.path
41 import random
42 import string
43 import sys
44 import time
45 import traceback
46
47 # Strip the script directory from sys.path() so on case-insensitive
48 # (WIN32) systems Python doesn't think that the "scons" script is the
49 # "SCons" package.  Replace it with our own version directory so, if
50 # if they're there, we pick up the right version of the build engine
51 # modules.
52 #sys.path = [os.path.join(sys.prefix,
53 #                         'lib',
54 #                         'scons-%d' % SCons.__version__)] + sys.path[1:]
55
56 import SCons.Debug
57 import SCons.Defaults
58 import SCons.Environment
59 import SCons.Errors
60 import SCons.Job
61 import SCons.Node
62 import SCons.Node.FS
63 from SCons.Optik import OptionParser, SUPPRESS_HELP, OptionValueError
64 import SCons.SConf
65 import SCons.Sig
66 import SCons.Taskmaster
67 import SCons.Util
68 import SCons.Warnings
69
70 #
71 display = SCons.Util.display
72 progress_display = SCons.Util.DisplayEngine()
73
74 # Task control.
75 #
76 class BuildTask(SCons.Taskmaster.Task):
77     """An SCons build task."""
78     def display(self, message):
79         display('scons: ' + message)
80
81     def execute(self):
82         target = self.targets[0]
83         if target.get_state() == SCons.Node.up_to_date:
84             if self.top and target.has_builder():
85                 display("scons: `%s' is up to date." % str(self.node))
86         elif target.has_builder() and not hasattr(target.builder, 'status'):
87             if print_time:
88                 start_time = time.time()
89             SCons.Taskmaster.Task.execute(self)
90             if print_time:
91                 finish_time = time.time()
92                 global command_time
93                 command_time = command_time+finish_time-start_time
94                 print "Command execution time: %f seconds"%(finish_time-start_time)
95
96     def do_failed(self, status=2):
97         global exit_status
98         if ignore_errors:
99             SCons.Taskmaster.Task.executed(self)
100         elif keep_going_on_error:
101             SCons.Taskmaster.Task.fail_continue(self)
102             exit_status = status
103         else:
104             SCons.Taskmaster.Task.fail_stop(self)
105             exit_status = status
106             
107     def executed(self):
108         t = self.targets[0]
109         if self.top and not t.has_builder() and not t.side_effect:
110             if not t.exists():
111                 sys.stderr.write("scons: *** Do not know how to make target `%s'." % t)
112                 if not keep_going_on_error:
113                     sys.stderr.write("  Stop.")
114                 sys.stderr.write("\n")
115                 self.do_failed()
116             else:
117                 print "scons: Nothing to be done for `%s'." % t
118                 SCons.Taskmaster.Task.executed(self)
119         else:
120             SCons.Taskmaster.Task.executed(self)
121
122     def failed(self):
123         # Handle the failure of a build task.  The primary purpose here
124         # is to display the various types of Errors and Exceptions
125         # appropriately.
126         status = 2
127         exc_info = self.exc_info()
128         try:
129             t, e, tb = exc_info
130         except ValueError:
131             t, e = exc_info
132             tb = None
133         if t is None:
134             # The Taskmaster didn't record an exception for this Task;
135             # see if the sys module has one.
136             t, e = sys.exc_info()[:2]
137
138         if t == SCons.Errors.BuildError:
139             fname = e.node
140             if SCons.Util.is_List(e.node):
141                 fname = string.join(map(str, e.node), ', ')
142             sys.stderr.write("scons: *** [%s] %s\n" % (fname, e.errstr))
143             if e.errstr == 'Exception':
144                 traceback.print_exception(e.args[0], e.args[1], e.args[2])
145         elif t == SCons.Errors.ExplicitExit:
146             status = e.status
147             sys.stderr.write("scons: *** [%s] Explicit exit, status %s\n" % (e.node, e.status))
148         else:
149             if e is None:
150                 e = t
151             s = str(e)
152             if t == SCons.Errors.StopError and not keep_going_on_error:
153                 s = s + '  Stop.'
154             sys.stderr.write("scons: *** %s\n" % s)
155
156             if tb and print_stacktrace:
157                 sys.stderr.write("scons: internal stack trace:\n")
158                 traceback.print_tb(tb, file=sys.stderr)
159
160         self.do_failed(status)
161
162         self.exc_clear()
163
164     def postprocess(self):
165         if self.top:
166             t = self.targets[0]
167             if print_tree:
168                 print
169                 SCons.Util.print_tree(t, get_all_children)
170             if print_stree:
171                 print
172                 SCons.Util.print_tree(t, get_all_children, showtags=2)
173             if print_dtree:
174                 print
175                 SCons.Util.print_tree(t, get_derived_children)
176             if print_includes:
177                 tree = t.render_include_tree()
178                 if tree:
179                     print
180                     print tree
181         SCons.Taskmaster.Task.postprocess(self)
182
183     def make_ready(self):
184         """Make a task ready for execution"""
185         SCons.Taskmaster.Task.make_ready(self)
186         if self.out_of_date and print_explanations:
187             explanation = self.out_of_date[0].explain()
188             if explanation:
189                 sys.stdout.write("scons: " + explanation)
190
191 class CleanTask(SCons.Taskmaster.Task):
192     """An SCons clean task."""
193     def show(self):
194         target = self.targets[0]
195         if (target.has_builder() or target.side_effect) and not target.isdir():
196             display("Removed " + str(target))
197         if SCons.Environment.CleanTargets.has_key(target):
198             files = SCons.Environment.CleanTargets[target]
199             for f in files:
200                 SCons.Util.fs_delete(str(f), 0)
201
202     def remove(self):
203         target = self.targets[0]
204         if target.has_builder() or target.side_effect:
205             for t in self.targets:
206                 try:
207                     removed = t.remove()
208                 except OSError, e:
209                     print "scons: Could not remove '%s':" % str(t), e.strerror
210                 else:
211                     if removed:
212                         display("Removed " + str(t))
213         if SCons.Environment.CleanTargets.has_key(target):
214             files = SCons.Environment.CleanTargets[target]
215             for f in files:
216                 SCons.Util.fs_delete(str(f))
217
218     execute = remove
219
220     # Have the taskmaster arrange to "execute" all of the targets, because
221     # we'll figure out ourselves (in remove() or show() above) whether
222     # anything really needs to be done.
223     make_ready = SCons.Taskmaster.Task.make_ready_all
224
225     def prepare(self):
226         pass
227
228 class QuestionTask(SCons.Taskmaster.Task):
229     """An SCons task for the -q (question) option."""
230     def prepare(self):
231         pass
232     
233     def execute(self):
234         if self.targets[0].get_state() != SCons.Node.up_to_date:
235             global exit_status
236             exit_status = 1
237             self.tm.stop()
238
239     def executed(self):
240         pass
241
242 # Global variables
243
244 keep_going_on_error = 0
245 print_dtree = 0
246 print_explanations = 0
247 print_includes = 0
248 print_objects = 0
249 print_memoizer = 0
250 print_stacktrace = 0
251 print_stree = 0
252 print_time = 0
253 print_tree = 0
254 ignore_errors = 0
255 sconscript_time = 0
256 command_time = 0
257 exit_status = 0 # exit status, assume success by default
258 repositories = []
259 num_jobs = 1 # this is modifed by SConscript.SetJobs()
260
261 #
262 class Stats:
263     def __init__(self):
264         self.stats = []
265         self.labels = []
266         self.append = self.do_nothing
267         self.print_stats = self.do_nothing
268     def enable(self, outfp):
269         self.outfp = outfp
270         self.append = self.do_append
271         self.print_stats = self.do_print
272     def do_nothing(self, *args, **kw):
273         pass
274
275 class CountStats(Stats):
276     def do_append(self, label):
277         self.labels.append(label)
278         self.stats.append(SCons.Debug.fetchLoggedInstances())
279     def do_print(self):
280         stats_table = {}
281         for s in self.stats:
282             for n in map(lambda t: t[0], s):
283                 stats_table[n] = [0, 0, 0, 0]
284         i = 0
285         for s in self.stats:
286             for n, c in s:
287                 stats_table[n][i] = c
288             i = i + 1
289         keys = stats_table.keys()
290         keys.sort()
291         self.outfp.write("Object counts:\n")
292         pre = ["   "]
293         post = ["   %s\n"]
294         l = len(self.stats)
295         fmt1 = string.join(pre + [' %7s']*l + post, '')
296         fmt2 = string.join(pre + [' %7d']*l + post, '')
297         labels = self.labels[:l]
298         labels.append(("", "Class"))
299         self.outfp.write(fmt1 % tuple(map(lambda x: x[0], labels)))
300         self.outfp.write(fmt1 % tuple(map(lambda x: x[1], labels)))
301         for k in keys:
302             r = stats_table[k][:l] + [k]
303             self.outfp.write(fmt2 % tuple(r))
304
305 count_stats = CountStats()
306
307 class MemStats(Stats):
308     def do_append(self, label):
309         self.labels.append(label)
310         self.stats.append(SCons.Debug.memory())
311     def do_print(self):
312         fmt = 'Memory %-32s %12d\n'
313         for label, stats in map(None, self.labels, self.stats):
314             self.outfp.write(fmt % (label, stats))
315
316 memory_stats = MemStats()
317
318 # utility functions
319
320 def get_all_children(node): return node.all_children()
321
322 def get_derived_children(node):
323     children = node.all_children(None)
324     return filter(lambda x: x.has_builder(), children)
325
326 def _scons_syntax_error(e):
327     """Handle syntax errors. Print out a message and show where the error
328     occurred.
329     """
330     etype, value, tb = sys.exc_info()
331     lines = traceback.format_exception_only(etype, value)
332     for line in lines:
333         sys.stderr.write(line+'\n')
334     sys.exit(2)
335
336 def find_deepest_user_frame(tb):
337     """
338     Find the deepest stack frame that is not part of SCons.
339
340     Input is a "pre-processed" stack trace in the form
341     returned by traceback.extract_tb() or traceback.extract_stack()
342     """
343     
344     tb.reverse()
345
346     # find the deepest traceback frame that is not part
347     # of SCons:
348     for frame in tb:
349         filename = frame[0]
350         if string.find(filename, os.sep+'SCons'+os.sep) == -1:
351             return frame
352     return tb[0]
353
354 def _scons_user_error(e):
355     """Handle user errors. Print out a message and a description of the
356     error, along with the line number and routine where it occured. 
357     The file and line number will be the deepest stack frame that is
358     not part of SCons itself.
359     """
360     etype, value, tb = sys.exc_info()
361     filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_tb(tb))
362     sys.stderr.write("\nscons: *** %s\n" % value)
363     sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
364     sys.exit(2)
365
366 def _scons_user_warning(e):
367     """Handle user warnings. Print out a message and a description of
368     the warning, along with the line number and routine where it occured.
369     The file and line number will be the deepest stack frame that is
370     not part of SCons itself.
371     """
372     etype, value, tb = sys.exc_info()
373     filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_tb(tb))
374     sys.stderr.write("\nscons: warning: %s\n" % e)
375     sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
376
377 def _scons_internal_warning(e):
378     """Slightly different from _scons_user_warning in that we use the
379     *current call stack* rather than sys.exc_info() to get our stack trace.
380     This is used by the warnings framework to print warnings."""
381     filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_stack())
382     sys.stderr.write("\nscons: warning: %s\n" % e[0])
383     sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
384
385 def _scons_internal_error():
386     """Handle all errors but user errors. Print out a message telling
387     the user what to do in this case and print a normal trace.
388     """
389     print 'internal error'
390     traceback.print_exc()
391     sys.exit(2)
392
393 def _varargs(option, parser):
394     value = None
395     if parser.rargs:
396         arg = parser.rargs[0]
397         if arg[0] != "-":
398             value = arg
399             del parser.rargs[0]
400     return value
401
402 def _setup_warn(arg):
403     """The --warn option.  An argument to this option
404     should be of the form <warning-class> or no-<warning-class>.
405     The warning class is munged in order to get an actual class
406     name from the SCons.Warnings module to enable or disable.
407     The supplied <warning-class> is split on hyphens, each element
408     is captialized, then smushed back together.  Then the string
409     "SCons.Warnings." is added to the front and "Warning" is added
410     to the back to get the fully qualified class name.
411
412     For example, --warn=deprecated will enable the
413     SCons.Warnings.DeprecatedWarning class.
414
415     --warn=no-dependency will disable the
416     SCons.Warnings.DependencyWarning class.
417
418     As a special case, --warn=all and --warn=no-all
419     will enable or disable (respectively) the base
420     class of all warnings, which is SCons.Warning.Warning."""
421
422     elems = string.split(string.lower(arg), '-')
423     enable = 1
424     if elems[0] == 'no':
425         enable = 0
426         del elems[0]
427
428     if len(elems) == 1 and elems[0] == 'all':
429         class_name = "Warning"
430     else:
431         def _capitalize(s):
432             if s[:5] == "scons":
433                 return "SCons" + s[5:]
434             else:
435                 return string.capitalize(s)
436         class_name = string.join(map(_capitalize, elems), '') + "Warning"
437     try:
438         clazz = getattr(SCons.Warnings, class_name)
439     except AttributeError:
440         sys.stderr.write("No warning type: '%s'\n" % arg)
441     else:
442         if enable:
443             SCons.Warnings.enableWarningClass(clazz)
444         else:
445             SCons.Warnings.suppressWarningClass(clazz)
446
447 def _SConstruct_exists(dirname=''):
448     """This function checks that an SConstruct file exists in a directory.
449     If so, it returns the path of the file. By default, it checks the
450     current directory.
451     """
452     global repositories
453     for file in ['SConstruct', 'Sconstruct', 'sconstruct']:
454         sfile = os.path.join(dirname, file)
455         if os.path.isfile(sfile):
456             return sfile
457         if not os.path.isabs(sfile):
458             for rep in repositories:
459                 if os.path.isfile(os.path.join(rep, sfile)):
460                     return sfile
461     return None
462
463 def _set_globals(options):
464     global keep_going_on_error, ignore_errors
465     global count_stats, print_dtree
466     global print_explanations, print_includes, print_memoizer
467     global print_objects, print_stacktrace, print_stree
468     global print_time, print_tree
469     global memory_stats
470
471     keep_going_on_error = options.keep_going
472     try:
473         debug_values = options.debug
474         if debug_values is None:
475             debug_values = []
476     except AttributeError:
477         pass
478     else:
479         if "count" in debug_values:
480             count_stats.enable(sys.stdout)
481         if "dtree" in debug_values:
482             print_dtree = 1
483         if "explain" in debug_values:
484             print_explanations = 1
485         if "findlibs" in debug_values:
486             SCons.Scanner.Prog.print_find_libs = "findlibs"
487         if "includes" in debug_values:
488             print_includes = 1
489         if "memoizer" in debug_values:
490             print_memoizer = 1
491         if "memory" in debug_values:
492             memory_stats.enable(sys.stdout)
493         if "objects" in debug_values:
494             print_objects = 1
495         if "presub" in debug_values:
496             SCons.Action.print_actions_presub = 1
497         if "stacktrace" in debug_values:
498             print_stacktrace = 1
499         if "stree" in debug_values:
500             print_stree = 1
501         if "time" in debug_values:
502             print_time = 1
503         if "tree" in debug_values:
504             print_tree = 1
505     ignore_errors = options.ignore_errors
506
507 def _create_path(plist):
508     path = '.'
509     for d in plist:
510         if os.path.isabs(d):
511             path = d
512         else:
513             path = path + '/' + d
514     return path
515
516
517 class OptParser(OptionParser):
518     def __init__(self):
519         import __main__
520         import SCons
521         parts = ["SCons by Steven Knight et al.:\n"]
522         try:
523             parts.append("\tscript: v%s.%s, %s, by %s on %s\n" % (__main__.__version__,
524                                                                   __main__.__build__,
525                                                                   __main__.__date__,
526                                                                   __main__.__developer__,
527                                                                   __main__.__buildsys__))
528         except KeyboardInterrupt:
529             raise
530         except:
531             # On win32 there is no scons.py, so there is no __main__.__version__,
532             # hence there is no script version.
533             pass 
534         parts.append("\tengine: v%s.%s, %s, by %s on %s\n" % (SCons.__version__,
535                                                               SCons.__build__,
536                                                               SCons.__date__,
537                                                               SCons.__developer__,
538                                                               SCons.__buildsys__))
539         parts.append("__COPYRIGHT__")
540         OptionParser.__init__(self, version=string.join(parts, ''),
541                               usage="usage: scons [OPTION] [TARGET] ...")
542
543         # options ignored for compatibility
544         def opt_ignore(option, opt, value, parser):
545             sys.stderr.write("Warning:  ignoring %s option\n" % opt)
546         self.add_option("-b", "-m", "-S", "-t", "--no-keep-going", "--stop",
547                         "--touch", action="callback", callback=opt_ignore,
548                         help="Ignored for compatibility.")
549
550         self.add_option('-c', '--clean', '--remove', action="store_true",
551                         dest="clean",
552                         help="Remove specified targets and dependencies.")
553
554         self.add_option('-C', '--directory', type="string", action = "append",
555                         metavar="DIR",
556                         help="Change to DIR before doing anything.")
557
558         self.add_option('--cache-disable', '--no-cache',
559                         action="store_true", dest='cache_disable', default=0,
560                         help="Do not retrieve built targets from CacheDir.")
561
562         self.add_option('--cache-force', '--cache-populate',
563                         action="store_true", dest='cache_force', default=0,
564                         help="Copy already-built targets into the CacheDir.")
565
566         self.add_option('--cache-show',
567                         action="store_true", dest='cache_show', default=0,
568                         help="Print build actions for files from CacheDir.")
569
570         config_options = ["auto", "force" ,"cache"]
571
572         def opt_config(option, opt, value, parser, c_options=config_options):
573             if value in c_options:
574                 parser.values.config = value
575             else:
576                 raise OptionValueError("Warning:  %s is not a valid config type" % value)
577         self.add_option('--config', action="callback", type="string",
578                         callback=opt_config, nargs=1, dest="config",
579                         metavar="MODE", default="auto",
580                         help="Controls Configure subsystem: "
581                              "%s." % string.join(config_options, ", "))
582
583         def opt_not_yet(option, opt, value, parser):
584             sys.stderr.write("Warning:  the %s option is not yet implemented\n" % opt)
585             sys.exit(0)
586         self.add_option('-d', action="callback",
587                         callback=opt_not_yet,
588                         help = "Print file dependency information.")
589         
590         self.add_option('-D', action="store_const", const=2, dest="climb_up",
591                         help="Search up directory tree for SConstruct,       "
592                              "build all Default() targets.")
593
594         debug_options = ["count", "dtree", "explain", "findlibs",
595                          "includes", "memoizer", "memory",
596                          "nomemoizer", "objects",
597                          "pdb", "presub", "stacktrace", "stree",
598                          "time", "tree"]
599
600         def opt_debug(option, opt, value, parser, debug_options=debug_options):
601             if value in debug_options:
602                 try:
603                     if parser.values.debug is None:
604                         parser.values.debug = []
605                 except AttributeError:
606                     parser.values.debug = []
607                 parser.values.debug.append(value)
608             else:
609                 raise OptionValueError("Warning:  %s is not a valid debug type" % value)
610         self.add_option('--debug', action="callback", type="string",
611                         callback=opt_debug, nargs=1, dest="debug",
612                         metavar="TYPE",
613                         help="Print various types of debugging information: "
614                              "%s." % string.join(debug_options, ", "))
615
616         def opt_duplicate(option, opt, value, parser):
617             if not value in SCons.Node.FS.Valid_Duplicates:
618                 raise OptionValueError("`%s' is not a valid duplication style." % value)
619             parser.values.duplicate = value
620             # Set the duplicate style right away so it can affect linking
621             # of SConscript files.
622             SCons.Node.FS.set_duplicate(value)
623         self.add_option('--duplicate', action="callback", type="string",
624                         callback=opt_duplicate, nargs=1, dest="duplicate",
625                         help="Set the preferred duplication methods. Must be one of "
626                         + string.join(SCons.Node.FS.Valid_Duplicates, ", "))
627
628         self.add_option('-f', '--file', '--makefile', '--sconstruct',
629                         action="append", nargs=1,
630                         help="Read FILE as the top-level SConstruct file.")
631
632         self.add_option('-h', '--help', action="store_true", default=0,
633                         dest="help_msg",
634                         help="Print defined help message, or this one.")
635
636         self.add_option("-H", "--help-options",
637                         action="help",
638                         help="Print this message and exit.")
639
640         self.add_option('-i', '--ignore-errors', action="store_true",
641                         default=0, dest='ignore_errors',
642                         help="Ignore errors from build actions.")
643
644         self.add_option('-I', '--include-dir', action="append",
645                         dest='include_dir', metavar="DIR",
646                         help="Search DIR for imported Python modules.")
647
648         self.add_option('--implicit-cache', action="store_true",
649                         dest='implicit_cache',
650                         help="Cache implicit dependencies")
651
652         self.add_option('--implicit-deps-changed', action="store_true",
653                         default=0, dest='implicit_deps_changed',
654                         help="Ignore cached implicit dependencies.")
655         self.add_option('--implicit-deps-unchanged', action="store_true",
656                         default=0, dest='implicit_deps_unchanged',
657                         help="Ignore changes in implicit dependencies.")
658
659         def opt_j(option, opt, value, parser):
660             value = int(value)
661             parser.values.num_jobs = value
662         self.add_option('-j', '--jobs', action="callback", type="int",
663                         callback=opt_j, metavar="N",
664                         help="Allow N jobs at once.")
665
666         self.add_option('-k', '--keep-going', action="store_true", default=0,
667                         dest='keep_going',
668                         help="Keep going when a target can't be made.")
669
670         self.add_option('--max-drift', type="int", action="store",
671                         dest='max_drift', metavar="N",
672                         help="Set maximum system clock drift to N seconds.")
673
674         self.add_option('-n', '--no-exec', '--just-print', '--dry-run',
675                         '--recon', action="store_true", dest='noexec',
676                         default=0, help="Don't build; just print commands.")
677
678         self.add_option('--profile', action="store",
679                         dest="profile_file", metavar="FILE",
680                         help="Profile SCons and put results in FILE.")
681
682         self.add_option('-q', '--question', action="store_true", default=0,
683                         help="Don't build; exit status says if up to date.")
684
685         self.add_option('-Q', dest='no_progress', action="store_true",
686                         default=0,
687                         help="Suppress \"Reading/Building\" progress messages.")
688
689         self.add_option('--random', dest="random", action="store_true",
690                         default=0, help="Build dependencies in random order.")
691
692         self.add_option('-s', '--silent', '--quiet', action="store_true",
693                         default=0, help="Don't print commands.")
694
695         self.add_option('-u', '--up', '--search-up', action="store_const",
696                         dest="climb_up", default=0, const=1,
697                         help="Search up directory tree for SConstruct,       "
698                              "build targets at or below current directory.")
699         self.add_option('-U', action="store_const", dest="climb_up",
700                         default=0, const=3,
701                         help="Search up directory tree for SConstruct,       "
702                              "build Default() targets from local SConscript.")
703
704         self.add_option("-v", "--version",
705                         action="version",
706                         help="Print the SCons version number and exit.")
707
708         self.add_option('--warn', '--warning', nargs=1, action="store",
709                         metavar="WARNING-SPEC",
710                         help="Enable or disable warnings.")
711
712         self.add_option('-Y', '--repository', nargs=1, action="append",
713                         help="Search REPOSITORY for source and target files.")
714
715         self.add_option('-e', '--environment-overrides', action="callback",
716                         callback=opt_not_yet,
717                         # help="Environment variables override makefiles."
718                         help=SUPPRESS_HELP)
719         self.add_option('-l', '--load-average', '--max-load', action="callback",
720                         callback=opt_not_yet, type="int", dest="load_average",
721                         # action="store",
722                         # help="Don't start multiple jobs unless load is below "
723                         #      "LOAD-AVERAGE."
724                         # type="int",
725                         help=SUPPRESS_HELP)
726         self.add_option('--list-derived', action="callback",
727                         callback=opt_not_yet,
728                         # help="Don't build; list files that would be built."
729                         help=SUPPRESS_HELP)
730         self.add_option('--list-actions', action="callback",
731                         callback=opt_not_yet,
732                         # help="Don't build; list files and build actions."
733                         help=SUPPRESS_HELP)
734         self.add_option('--list-where', action="callback",
735                         callback=opt_not_yet,
736                         # help="Don't build; list files and where defined."
737                         help=SUPPRESS_HELP)
738         self.add_option('-o', '--old-file', '--assume-old', action="callback",
739                         callback=opt_not_yet, type="string", dest="old_file",
740                         # help = "Consider FILE to be old; don't rebuild it."
741                         help=SUPPRESS_HELP)
742         self.add_option('--override', action="callback", dest="override",
743                         callback=opt_not_yet, type="string",
744                         # help="Override variables as specified in FILE."
745                         help=SUPPRESS_HELP)
746         self.add_option('-p', action="callback",
747                         callback=opt_not_yet,
748                         # help="Print internal environments/objects."
749                         help=SUPPRESS_HELP)
750         self.add_option('-r', '-R', '--no-builtin-rules',
751                         '--no-builtin-variables', action="callback",
752                         callback=opt_not_yet,
753                         # help="Clear default environments and variables."
754                         help=SUPPRESS_HELP)
755         self.add_option('-w', '--print-directory', action="callback",
756                         callback=opt_not_yet,
757                         # help="Print the current directory."
758                         help=SUPPRESS_HELP)
759         self.add_option('--no-print-directory', action="callback",
760                         callback=opt_not_yet,
761                         # help="Turn off -w, even if it was turned on implicitly."
762                         help=SUPPRESS_HELP)
763         self.add_option('--write-filenames', action="callback",
764                         callback=opt_not_yet, type="string", dest="write_filenames",
765                         # help="Write all filenames examined into FILE."
766                         help=SUPPRESS_HELP)
767         self.add_option('-W', '--what-if', '--new-file', '--assume-new',
768                         dest="new_file",
769                         action="callback", callback=opt_not_yet, type="string",
770                         # help="Consider FILE to be changed."
771                         help=SUPPRESS_HELP)
772         self.add_option('--warn-undefined-variables', action="callback",
773                         callback=opt_not_yet,
774                         # help="Warn when an undefined variable is referenced."
775                         help=SUPPRESS_HELP)
776
777     def parse_args(self, args=None, values=None):
778         opt, arglist = OptionParser.parse_args(self, args, values)
779         if opt.implicit_deps_changed or opt.implicit_deps_unchanged:
780             opt.implicit_cache = 1
781         return opt, arglist
782
783 class SConscriptSettableOptions:
784     """This class wraps an OptParser instance and provides
785     uniform access to options that can be either set on the command
786     line or from a SConscript file. A value specified on the command
787     line always overrides a value set in a SConscript file.
788     Not all command line options are SConscript settable, and the ones
789     that are must be explicitly added to settable dictionary and optionally
790     validated and coerced in the set() method."""
791     
792     def __init__(self, options):
793         self.options = options
794
795         # This dictionary stores the defaults for all the SConscript
796         # settable options, as well as indicating which options
797         # are SConscript settable. 
798         self.settable = {'num_jobs':1,
799                          'max_drift':SCons.Sig.default_max_drift,
800                          'implicit_cache':0,
801                          'clean':0,
802                          'duplicate':'hard-soft-copy'}
803
804     def get(self, name):
805         if not self.settable.has_key(name):
806             raise SCons.Error.UserError, "This option is not settable from a SConscript file: %s"%name
807         if hasattr(self.options, name) and getattr(self.options, name) is not None:
808             return getattr(self.options, name)
809         else:
810             return self.settable[name]
811
812     def set(self, name, value):
813         if not self.settable.has_key(name):
814             raise SCons.Error.UserError, "This option is not settable from a SConscript file: %s"%name
815
816         if name == 'num_jobs':
817             try:
818                 value = int(value)
819                 if value < 1:
820                     raise ValueError
821             except ValueError:
822                 raise SCons.Errors.UserError, "A positive integer is required: %s"%repr(value)
823         elif name == 'max_drift':
824             try:
825                 value = int(value)
826             except ValueError:
827                 raise SCons.Errors.UserError, "An integer is required: %s"%repr(value)
828         elif name == 'duplicate':
829             try:
830                 value = str(value)
831             except ValueError:
832                 raise SCons.Errors.UserError, "A string is required: %s"%repr(value)
833             if not value in SCons.Node.FS.Valid_Duplicates:
834                 raise SCons.Errors.UserError, "Not a valid duplication style: %s" % value
835             # Set the duplicate stye right away so it can affect linking
836             # of SConscript files.
837             SCons.Node.FS.set_duplicate(value)
838
839         self.settable[name] = value
840     
841
842 def _main(args, parser):
843     # Here's where everything really happens.
844
845     # First order of business:  set up default warnings and and then
846     # handle the user's warning options, so we can warn about anything
847     # that happens appropriately.
848     default_warnings = [ SCons.Warnings.CorruptSConsignWarning,
849                          SCons.Warnings.DeprecatedWarning,
850                          SCons.Warnings.DuplicateEnvironmentWarning,
851                          SCons.Warnings.MissingSConscriptWarning,
852                          SCons.Warnings.NoParallelSupportWarning,
853                          SCons.Warnings.MisleadingKeywordsWarning, ]
854     for warning in default_warnings:
855         SCons.Warnings.enableWarningClass(warning)
856     SCons.Warnings._warningOut = _scons_internal_warning
857     if options.warn:
858         _setup_warn(options.warn)
859
860     # Next, we want to create the FS object that represents the outside
861     # world's file system, as that's central to a lot of initialization.
862     # To do this, however, we need to be in the directory from which we
863     # want to start everything, which means first handling any relevant
864     # options that might cause us to chdir somewhere (-C, -D, -U, -u).
865     if options.directory:
866         cdir = _create_path(options.directory)
867         try:
868             os.chdir(cdir)
869         except OSError:
870             sys.stderr.write("Could not change directory to %s\n" % cdir)
871
872     # The SConstruct file may be in a repository, so initialize those
873     # before we start the search up our path for one.
874     global repositories
875     if options.repository:
876         repositories.extend(options.repository)
877
878     target_top = None
879     if options.climb_up:
880         target_top = '.'  # directory to prepend to targets
881         script_dir = os.getcwd()  # location of script
882         while script_dir and not _SConstruct_exists(script_dir):
883             script_dir, last_part = os.path.split(script_dir)
884             if last_part:
885                 target_top = os.path.join(last_part, target_top)
886             else:
887                 script_dir = ''
888         if script_dir:
889             display("scons: Entering directory `%s'" % script_dir)
890             os.chdir(script_dir)
891
892     # Now that we're in the top-level SConstruct directory, go ahead
893     # and initialize the FS object that represents the file system,
894     # and make it the build engine default.
895     fs = SCons.Node.FS.default_fs = SCons.Node.FS.FS()
896
897     for rep in repositories:
898         fs.Repository(rep)
899
900     # Now that we have the FS object, the next order of business is to
901     # check for an SConstruct file (or other specified config file).
902     # If there isn't one, we can bail before doing any more work.
903     scripts = []
904     if options.file:
905         scripts.extend(options.file)
906     if not scripts:
907         sfile = _SConstruct_exists()
908         if sfile:
909             scripts.append(sfile)
910
911     if not scripts:
912         if options.help_msg:
913             # There's no SConstruct, but they specified -h.
914             # Give them the options usage now, before we fail
915             # trying to read a non-existent SConstruct file.
916             parser.print_help()
917             sys.exit(0)
918         raise SCons.Errors.UserError, "No SConstruct file found."
919
920     if scripts[0] == "-":
921         d = fs.getcwd()
922     else:
923         d = fs.File(scripts[0]).dir
924     fs.set_SConstruct_dir(d)
925
926     # Now that we have the FS object and it's intialized, set up (most
927     # of) the rest of the options.
928     global ssoptions
929     ssoptions = SConscriptSettableOptions(options)
930
931     _set_globals(options)
932     SCons.Node.implicit_cache = options.implicit_cache
933     SCons.Node.implicit_deps_changed = options.implicit_deps_changed
934     SCons.Node.implicit_deps_unchanged = options.implicit_deps_unchanged
935     if options.noexec:
936         SCons.SConf.dryrun = 1
937         SCons.Action.execute_actions = None
938         CleanTask.execute = CleanTask.show
939     if options.question:
940         SCons.SConf.dryrun = 1
941     SCons.SConf.SetCacheMode(options.config)
942     SCons.SConf.SetProgressDisplay(progress_display)
943
944     if options.no_progress or options.silent:
945         progress_display.set_mode(0)
946     if options.silent:
947         display.set_mode(0)
948     if options.silent:
949         SCons.Action.print_actions = None
950     if options.cache_disable:
951         def disable(self): pass
952         fs.CacheDir = disable
953     if options.cache_force:
954         fs.cache_force = 1
955     if options.cache_show:
956         fs.cache_show = 1
957
958     if options.include_dir:
959         sys.path = options.include_dir + sys.path
960
961     # That should cover (most of) the options.  Next, set up the variables
962     # that hold command-line arguments, so the SConscript files that we
963     # read and execute have access to them.
964     targets = []
965     xmit_args = []
966     for a in args:
967         if '=' in a:
968             xmit_args.append(a)
969         else:
970             targets.append(a)
971     SCons.Script._Add_Targets(targets)
972     SCons.Script._Add_Arguments(xmit_args)
973
974     class Unbuffered:
975         def __init__(self, file):
976             self.file = file
977         def write(self, arg):
978             self.file.write(arg)
979             self.file.flush()
980         def __getattr__(self, attr):
981             return getattr(self.file, attr)
982
983     sys.stdout = Unbuffered(sys.stdout)
984
985     memory_stats.append('before reading SConscript files:')
986     count_stats.append(('pre-', 'read'))
987
988     progress_display("scons: Reading SConscript files ...")
989
990     start_time = time.time()
991     try:
992         for script in scripts:
993             SCons.Script._SConscript._SConscript(fs, script)
994     except SCons.Errors.StopError, e:
995         # We had problems reading an SConscript file, such as it
996         # couldn't be copied in to the BuildDir.  Since we're just
997         # reading SConscript files and haven't started building
998         # things yet, stop regardless of whether they used -i or -k
999         # or anything else.
1000         global exit_status
1001         sys.stderr.write("scons: *** %s  Stop.\n" % e)
1002         exit_status = 2
1003         sys.exit(exit_status)
1004     global sconscript_time
1005     sconscript_time = time.time() - start_time
1006     SCons.SConf.CreateConfigHBuilder(SCons.Defaults.DefaultEnvironment())
1007     progress_display("scons: done reading SConscript files.")
1008
1009     # Tell the Node.FS subsystem that we're all done reading the
1010     # SConscript files and calling Repository() and BuildDir() and the
1011     # like, so it can go ahead and start memoizing the string values of
1012     # file system nodes.
1013     SCons.Node.FS.save_strings(1)
1014
1015     memory_stats.append('after reading SConscript files:')
1016     count_stats.append(('post-', 'read'))
1017
1018     fs.chdir(fs.Top)
1019
1020     if options.help_msg:
1021         help_text = SCons.Script.help_text
1022         if help_text is None:
1023             # They specified -h, but there was no Help() inside the
1024             # SConscript files.  Give them the options usage.
1025             parser.print_help(sys.stdout)
1026         else:
1027             print help_text
1028             print "Use scons -H for help about command-line options."
1029         sys.exit(0)
1030
1031     # Now that we've read the SConscripts we can set the options
1032     # that are SConscript settable:
1033     SCons.Node.implicit_cache = ssoptions.get('implicit_cache')
1034     SCons.Node.FS.set_duplicate(ssoptions.get('duplicate'))
1035
1036     lookup_top = None
1037     if targets:
1038         # They specified targets on the command line, so if they
1039         # used -u, -U or -D, we have to look up targets relative
1040         # to the top, but we build whatever they specified.
1041         if target_top:
1042             lookup_top = fs.Dir(target_top)
1043             target_top = None
1044     else:
1045         # There are no targets specified on the command line,
1046         # so if they used -u, -U or -D, we may have to restrict
1047         # what actually gets built.
1048         d = None
1049         if target_top:
1050             if options.climb_up == 1:
1051                 # -u, local directory and below
1052                 target_top = fs.Dir(target_top)
1053                 lookup_top = target_top
1054             elif options.climb_up == 2:
1055                 # -D, all Default() targets
1056                 target_top = None
1057                 lookup_top = None
1058             elif options.climb_up == 3:
1059                 # -U, local SConscript Default() targets
1060                 target_top = fs.Dir(target_top)
1061                 def check_dir(x, target_top=target_top):
1062                     if hasattr(x, 'cwd') and not x.cwd is None:
1063                         cwd = x.cwd.srcnode()
1064                         return cwd == target_top
1065                     else:
1066                         # x doesn't have a cwd, so it's either not a target,
1067                         # or not a file, so go ahead and keep it as a default
1068                         # target and let the engine sort it out:
1069                         return 1                
1070                 d = filter(check_dir, SCons.Script.DEFAULT_TARGETS)
1071                 SCons.Script.DEFAULT_TARGETS[:] = d
1072                 target_top = None
1073                 lookup_top = None
1074
1075         targets = SCons.Script._Get_Default_Targets(d, fs)
1076
1077     if not targets:
1078         sys.stderr.write("scons: *** No targets specified and no Default() targets found.  Stop.\n")
1079         sys.exit(2)
1080
1081     def Entry(x, ltop=lookup_top, ttop=target_top, fs=fs):
1082         if isinstance(x, SCons.Node.Node):
1083             node = x
1084         else:
1085             node = None
1086             # Why would ltop be None? Unfortunately this happens.
1087             if ltop == None: ltop = ''
1088             # Curdir becomes important when SCons is called with -u, -C,
1089             # or similar option that changes directory, and so the paths
1090             # of targets given on the command line need to be adjusted.
1091             curdir = os.path.join(os.getcwd(), str(ltop))
1092             for lookup in SCons.Node.arg2nodes_lookups:
1093                 node = lookup(x, curdir=curdir)
1094                 if node != None:
1095                     break
1096             if node is None:
1097                 node = fs.Entry(x, directory=ltop, create=1)
1098         if ttop and not node.is_under(ttop):
1099             if isinstance(node, SCons.Node.FS.Dir) and ttop.is_under(node):
1100                 node = ttop
1101             else:
1102                 node = None
1103         return node
1104
1105     nodes = filter(None, map(Entry, targets))
1106
1107     task_class = BuildTask      # default action is to build targets
1108     opening_message = "Building targets ..."
1109     closing_message = "done building targets."
1110     if keep_going_on_error:
1111         failure_message = "done building targets (errors occurred during build)."
1112     else:
1113         failure_message = "building terminated because of errors."
1114     if options.question:
1115         task_class = QuestionTask
1116     try:
1117         if ssoptions.get('clean'):
1118             task_class = CleanTask
1119             opening_message = "Cleaning targets ..."
1120             closing_message = "done cleaning targets."
1121             if keep_going_on_error:
1122                 closing_message = "done cleaning targets (errors occurred during clean)."
1123             else:
1124                 failure_message = "cleaning terminated because of errors."
1125     except AttributeError:
1126         pass
1127
1128     SCons.Environment.CalculatorArgs['max_drift'] = ssoptions.get('max_drift')
1129
1130     if options.random:
1131         def order(dependencies):
1132             """Randomize the dependencies."""
1133             # This is cribbed from the implementation of
1134             # random.shuffle() in Python 2.X.
1135             d = dependencies
1136             for i in xrange(len(d)-1, 0, -1):
1137                 j = int(random.random() * (i+1))
1138                 d[i], d[j] = d[j], d[i]
1139             return d
1140     else:
1141         def order(dependencies):
1142             """Leave the order of dependencies alone."""
1143             return dependencies
1144
1145     progress_display("scons: " + opening_message)
1146     taskmaster = SCons.Taskmaster.Taskmaster(nodes, task_class, order)
1147
1148     nj = ssoptions.get('num_jobs')
1149     jobs = SCons.Job.Jobs(nj, taskmaster)
1150     if nj > 1 and jobs.num_jobs == 1:
1151         msg = "parallel builds are unsupported by this version of Python;\n" + \
1152               "\tignoring -j or num_jobs option.\n"
1153         SCons.Warnings.warn(SCons.Warnings.NoParallelSupportWarning, msg)
1154
1155     memory_stats.append('before building targets:')
1156     count_stats.append(('pre-', 'build'))
1157
1158     try:
1159         jobs.run()
1160     finally:
1161         if exit_status:
1162             progress_display("scons: " + failure_message)
1163         else:
1164             progress_display("scons: " + closing_message)
1165         if not options.noexec:
1166             SCons.SConsign.write()
1167
1168     memory_stats.append('after building targets:')
1169     count_stats.append(('post-', 'build'))
1170
1171 def _exec_main():
1172     all_args = sys.argv[1:]
1173     try:
1174         all_args = string.split(os.environ['SCONSFLAGS']) + all_args
1175     except KeyError:
1176             # it's OK if there's no SCONSFLAGS
1177             pass
1178     parser = OptParser()
1179     global options
1180     options, args = parser.parse_args(all_args)
1181     if type(options.debug) == type([]) and "pdb" in options.debug:
1182         import pdb
1183         pdb.Pdb().runcall(_main, args, parser)
1184     elif options.profile_file:
1185         import profile
1186         prof = profile.Profile()
1187         try:
1188             prof.runcall(_main, args, parser)
1189         except SystemExit:
1190             pass
1191         prof.dump_stats(options.profile_file)
1192     else:
1193         _main(args, parser)
1194
1195 def main():
1196     global exit_status
1197     
1198     try:
1199         _exec_main()
1200     except SystemExit, s:
1201         if s:
1202             exit_status = s
1203     except KeyboardInterrupt:
1204         print "Build interrupted."
1205         sys.exit(2)
1206     except SyntaxError, e:
1207         _scons_syntax_error(e)
1208     except SCons.Errors.InternalError:
1209         _scons_internal_error()
1210     except SCons.Errors.UserError, e:
1211         _scons_user_error(e)
1212     except:
1213         # An exception here is likely a builtin Python exception Python
1214         # code in an SConscript file.  Show them precisely what the
1215         # problem was and where it happened.
1216         SCons.Script._SConscript.SConscript_exception()
1217         sys.exit(2)
1218
1219     memory_stats.print_stats()
1220     count_stats.print_stats()
1221
1222     if print_objects:
1223         SCons.Debug.listLoggedInstances('*')
1224         #SCons.Debug.dumpLoggedInstances('*')
1225
1226     if print_memoizer:
1227         print "Memoizer (memory cache) hits and misses:"
1228         SCons.Memoize.Dump()
1229
1230     # Dump any development debug info that may have been enabled.
1231     # These are purely for internal debugging during development, so
1232     # there's no need to control them with --debug= options; they're
1233     # controlled by changing the source code.
1234     SCons.Debug.dump_caller_counts()
1235     SCons.Taskmaster.dump_stats()
1236
1237     if print_time:
1238         total_time = time.time()-SCons.Script.start_time
1239         scons_time = total_time-sconscript_time-command_time
1240         print "Total build time: %f seconds"%total_time
1241         print "Total SConscript file execution time: %f seconds"%sconscript_time
1242         print "Total SCons execution time: %f seconds"%scons_time
1243         print "Total command execution time: %f seconds"%command_time
1244
1245     sys.exit(exit_status)