ca7f68cfc2910a002c2abe8908d91ad4ef77ddcc
[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 SCons.compat
40
41 import os
42 import os.path
43 import random
44 import string
45 import sys
46 import time
47 import traceback
48
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
53 # modules.
54 #sys.path = [os.path.join(sys.prefix,
55 #                         'lib',
56 #                         'scons-%d' % SCons.__version__)] + sys.path[1:]
57
58 import SCons.Debug
59 import SCons.Defaults
60 import SCons.Environment
61 import SCons.Errors
62 import SCons.Job
63 import SCons.Node
64 import SCons.Node.FS
65 from SCons.Optik import OptionParser, SUPPRESS_HELP, OptionValueError
66 import SCons.SConf
67 import SCons.Script
68 import SCons.Sig
69 import SCons.Taskmaster
70 import SCons.Util
71 import SCons.Warnings
72
73 #
74 display = SCons.Util.display
75 progress_display = SCons.Util.DisplayEngine()
76
77 first_command_start = None
78 last_command_end = None
79
80 # Task control.
81 #
82 class BuildTask(SCons.Taskmaster.Task):
83     """An SCons build task."""
84     def display(self, message):
85         display('scons: ' + message)
86
87     def execute(self):
88         for target in self.targets:
89             if target.get_state() == SCons.Node.up_to_date: 
90                 continue
91             if target.has_builder() and not hasattr(target.builder, 'status'):
92                 if print_time:
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)
98                 if print_time:
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))
105                 break
106         else:
107             if self.top and target.has_builder():
108                 display("scons: `%s' is up to date." % str(self.node))
109
110     def do_failed(self, status=2):
111         global exit_status
112         if ignore_errors:
113             SCons.Taskmaster.Task.executed(self)
114         elif keep_going_on_error:
115             SCons.Taskmaster.Task.fail_continue(self)
116             exit_status = status
117         else:
118             SCons.Taskmaster.Task.fail_stop(self)
119             exit_status = status
120             
121     def executed(self):
122         t = self.targets[0]
123         if self.top and not t.has_builder() and not t.side_effect:
124             if not t.exists():
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")
129                 self.do_failed()
130             else:
131                 print "scons: Nothing to be done for `%s'." % t
132                 SCons.Taskmaster.Task.executed(self)
133         else:
134             SCons.Taskmaster.Task.executed(self)
135
136     def failed(self):
137         # Handle the failure of a build task.  The primary purpose here
138         # is to display the various types of Errors and Exceptions
139         # appropriately.
140         status = 2
141         exc_info = self.exc_info()
142         try:
143             t, e, tb = exc_info
144         except ValueError:
145             t, e = exc_info
146             tb = None
147         if t is None:
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]
151
152         def nodestring(n):
153             if not SCons.Util.is_List(n):
154                 n = [ n ]
155             return string.join(map(str, n), ', ')
156
157         errfmt = "scons: *** [%s] %s\n"
158
159         if t == SCons.Errors.BuildError:
160             tname = nodestring(e.node)
161             errstr = e.errstr
162             if e.filename:
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:
171             status = e.status
172             tname = nodestring(e.node)
173             errstr = 'Explicit exit, status %s' % status
174             sys.stderr.write(errfmt % (tname, errstr))
175         else:
176             if e is None:
177                 e = t
178             s = str(e)
179             if t == SCons.Errors.StopError and not keep_going_on_error:
180                 s = s + '  Stop.'
181             sys.stderr.write("scons: *** %s\n" % s)
182
183             if tb and print_stacktrace:
184                 sys.stderr.write("scons: internal stack trace:\n")
185                 traceback.print_tb(tb, file=sys.stderr)
186
187         self.do_failed(status)
188
189         self.exc_clear()
190
191     def postprocess(self):
192         if self.top:
193             t = self.targets[0]
194             for tp in tree_printers:
195                 tp.display(t)
196             if print_includes:
197                 tree = t.render_include_tree()
198                 if tree:
199                     print
200                     print tree
201         SCons.Taskmaster.Task.postprocess(self)
202
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()
208             if explanation:
209                 sys.stdout.write("scons: " + explanation)
210
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))
216
217         # os.listdir() isn't guaranteed to return files in any specific order,
218         # but some of the test code expects sorted output.
219         files.sort()
220         return files
221
222     def fs_delete(self, path, remove=1):
223         try:
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)
234                         else:
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
241
242     def show(self):
243         target = self.targets[0]
244         if (target.has_builder() or target.side_effect) and not target.noclean:
245             for t in self.targets:
246                 if not t.isdir():
247                     display("Removed " + str(t))
248         if SCons.Environment.CleanTargets.has_key(target):
249             files = SCons.Environment.CleanTargets[target]
250             for f in files:
251                 self.fs_delete(str(f), 0)
252
253     def remove(self):
254         target = self.targets[0]
255         if (target.has_builder() or target.side_effect) and not target.noclean:
256             for t in self.targets:
257                 try:
258                     removed = t.remove()
259                 except OSError, e:
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
266                 else:
267                     if removed:
268                         display("Removed " + str(t))
269         if SCons.Environment.CleanTargets.has_key(target):
270             files = SCons.Environment.CleanTargets[target]
271             for f in files:
272                 self.fs_delete(str(f))
273
274     execute = remove
275
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
280
281     def prepare(self):
282         pass
283
284 class QuestionTask(SCons.Taskmaster.Task):
285     """An SCons task for the -q (question) option."""
286     def prepare(self):
287         pass
288     
289     def execute(self):
290         if self.targets[0].get_state() != SCons.Node.up_to_date:
291             global exit_status
292             exit_status = 1
293             self.tm.stop()
294
295     def executed(self):
296         pass
297
298
299 class TreePrinter:
300     def __init__(self, derived=False, prune=False, status=False):
301         self.derived = derived
302         self.prune = prune
303         self.status = status
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):
310         if self.derived:
311             func = self.get_derived_children
312         else:
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)
316
317
318 # Global variables
319
320 tree_printers = []
321
322 keep_going_on_error = 0
323 print_explanations = 0
324 print_includes = 0
325 print_objects = 0
326 print_memoizer = 0
327 print_stacktrace = 0
328 print_time = 0
329 ignore_errors = 0
330 sconscript_time = 0
331 cumulative_command_time = 0
332 exit_status = 0 # exit status, assume success by default
333 repositories = []
334 num_jobs = None
335 delayed_warnings = []
336
337 diskcheck_all = SCons.Node.FS.diskcheck_types()
338 diskcheck_option_set = None
339
340 def diskcheck_convert(value):
341     if value is None:
342         return []
343     if not SCons.Util.is_List(value):
344         value = string.split(value, ',')
345     result = []
346     for v in map(string.lower, value):
347         if v == 'all':
348             result = diskcheck_all
349         elif v == 'none':
350             result = []
351         elif v in diskcheck_all:
352             result.append(v)
353         else:
354             raise ValueError, v
355     return result
356
357 #
358 class Stats:
359     def __init__(self):
360         self.stats = []
361         self.labels = []
362         self.append = self.do_nothing
363         self.print_stats = self.do_nothing
364     def enable(self, outfp):
365         self.outfp = outfp
366         self.append = self.do_append
367         self.print_stats = self.do_print
368     def do_nothing(self, *args, **kw):
369         pass
370
371 class CountStats(Stats):
372     def do_append(self, label):
373         self.labels.append(label)
374         self.stats.append(SCons.Debug.fetchLoggedInstances())
375     def do_print(self):
376         stats_table = {}
377         for s in self.stats:
378             for n in map(lambda t: t[0], s):
379                 stats_table[n] = [0, 0, 0, 0]
380         i = 0
381         for s in self.stats:
382             for n, c in s:
383                 stats_table[n][i] = c
384             i = i + 1
385         keys = stats_table.keys()
386         keys.sort()
387         self.outfp.write("Object counts:\n")
388         pre = ["   "]
389         post = ["   %s\n"]
390         l = len(self.stats)
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)))
397         for k in keys:
398             r = stats_table[k][:l] + [k]
399             self.outfp.write(fmt2 % tuple(r))
400
401 count_stats = CountStats()
402
403 class MemStats(Stats):
404     def do_append(self, label):
405         self.labels.append(label)
406         self.stats.append(SCons.Debug.memory())
407     def do_print(self):
408         fmt = 'Memory %-32s %12d\n'
409         for label, stats in map(None, self.labels, self.stats):
410             self.outfp.write(fmt % (label, stats))
411
412 memory_stats = MemStats()
413
414 # utility functions
415
416 def _scons_syntax_error(e):
417     """Handle syntax errors. Print out a message and show where the error
418     occurred.
419     """
420     etype, value, tb = sys.exc_info()
421     lines = traceback.format_exception_only(etype, value)
422     for line in lines:
423         sys.stderr.write(line+'\n')
424     sys.exit(2)
425
426 def find_deepest_user_frame(tb):
427     """
428     Find the deepest stack frame that is not part of SCons.
429
430     Input is a "pre-processed" stack trace in the form
431     returned by traceback.extract_tb() or traceback.extract_stack()
432     """
433     
434     tb.reverse()
435
436     # find the deepest traceback frame that is not part
437     # of SCons:
438     for frame in tb:
439         filename = frame[0]
440         if string.find(filename, os.sep+'SCons'+os.sep) == -1:
441             return frame
442     return tb[0]
443
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.
449     """
450     global print_stacktrace
451     etype, value, tb = sys.exc_info()
452     if print_stacktrace:
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))
457     sys.exit(2)
458
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.
464     """
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))
469
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))
477
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.
481     """
482     print 'internal error'
483     traceback.print_exc()
484     sys.exit(2)
485
486 def _varargs(option, parser):
487     value = None
488     if parser.rargs:
489         arg = parser.rargs[0]
490         if arg[0] != "-":
491             value = arg
492             del parser.rargs[0]
493     return value
494
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.
504
505     For example, --warn=deprecated will enable the
506     SCons.Warnings.DeprecatedWarning class.
507
508     --warn=no-dependency will disable the
509     SCons.Warnings.DependencyWarning class.
510
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."""
514
515     elems = string.split(string.lower(arg), '-')
516     enable = 1
517     if elems[0] == 'no':
518         enable = 0
519         del elems[0]
520
521     if len(elems) == 1 and elems[0] == 'all':
522         class_name = "Warning"
523     else:
524         def _capitalize(s):
525             if s[:5] == "scons":
526                 return "SCons" + s[5:]
527             else:
528                 return string.capitalize(s)
529         class_name = string.join(map(_capitalize, elems), '') + "Warning"
530     try:
531         clazz = getattr(SCons.Warnings, class_name)
532     except AttributeError:
533         sys.stderr.write("No warning type: '%s'\n" % arg)
534     else:
535         if enable:
536             SCons.Warnings.enableWarningClass(clazz)
537         else:
538             SCons.Warnings.suppressWarningClass(clazz)
539
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
543     current directory.
544     """
545     global repositories
546     for file in ['SConstruct', 'Sconstruct', 'sconstruct']:
547         sfile = os.path.join(dirname, file)
548         if os.path.isfile(sfile):
549             return sfile
550         if not os.path.isabs(sfile):
551             for rep in repositories:
552                 if os.path.isfile(os.path.join(rep, sfile)):
553                     return sfile
554     return None
555
556 def _set_globals(options):
557     global keep_going_on_error, ignore_errors
558     global count_stats
559     global print_explanations, print_includes, print_memoizer
560     global print_objects, print_stacktrace, print_time
561     global tree_printers
562     global memory_stats
563
564     keep_going_on_error = options.keep_going
565     try:
566         debug_values = options.debug
567         if debug_values is None:
568             debug_values = []
569     except AttributeError:
570         pass
571     else:
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:
581             print_includes = 1
582         if "memoizer" in debug_values:
583             print_memoizer = 1
584         if "memory" in debug_values:
585             memory_stats.enable(sys.stdout)
586         if "objects" in debug_values:
587             print_objects = 1
588         if "presub" in debug_values:
589             SCons.Action.print_actions_presub = 1
590         if "stacktrace" in debug_values:
591             print_stacktrace = 1
592         if "stree" in debug_values:
593             tree_printers.append(TreePrinter(status=True))
594         if "time" in debug_values:
595             print_time = 1
596         if "tree" in debug_values:
597             tree_printers.append(TreePrinter())
598     ignore_errors = options.ignore_errors
599
600 def _create_path(plist):
601     path = '.'
602     for d in plist:
603         if os.path.isabs(d):
604             path = d
605         else:
606             path = path + '/' + d
607     return path
608
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."""
613     if site_dir_name:
614         err_if_not_found = True       # user specified: err if missing
615     else:
616         site_dir_name = "site_scons"
617         err_if_not_found = False
618         
619     site_dir = os.path.join(topdir.path, site_dir_name)
620     if not os.path.exists(site_dir):
621         if err_if_not_found:
622             raise SCons.Errors.UserError, "site dir %s not found."%site_dir
623         return
624
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):
632         import imp
633         try:
634             fp, pathname, description = imp.find_module(site_init_modname,
635                                                         [site_dir])
636             try:
637                 imp.load_module(site_init_modname, fp, pathname, description)
638             finally:
639                 if fp:
640                     fp.close()
641         except ImportError, e:
642             sys.stderr.write("Can't import site init file '%s': %s\n"%(site_init_file, e))
643             raise
644         except Exception, e:
645             sys.stderr.write("Site init file '%s' raised exception: %s\n"%(site_init_file, e))
646             raise
647     if os.path.exists(site_tools_dir):
648         SCons.Tool.DefaultToolpath.append(os.path.abspath(site_tools_dir))
649
650 def version_string(label, module):
651     fmt = "\t%s: v%s.%s, %s, by %s on %s\n"
652     return fmt % (label,
653                   module.__version__,
654                   module.__build__,
655                   module.__date__,
656                   module.__developer__,
657                   module.__buildsys__)
658
659 class OptParser(OptionParser):
660     def __init__(self):
661         import __main__
662
663         parts = ["SCons by Steven Knight et al.:\n"]
664         try:
665             parts.append(version_string("script", __main__))
666         except KeyboardInterrupt:
667             raise
668         except:
669             # On Windows there is no scons.py, so there is no
670             # __main__.__version__, hence there is no script version.
671             pass 
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] ...")
676
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.")
683
684         self.add_option('-c', '--clean', '--remove', action="store_true",
685                         dest="clean",
686                         help="Remove specified targets and dependencies.")
687
688         self.add_option('-C', '--directory', type="string", action = "append",
689                         metavar="DIR",
690                         help="Change to DIR before doing anything.")
691
692         self.add_option('--cache-debug', action="store",
693                         dest="cache_debug", metavar="FILE",
694                         help="Print CacheDir debug info to FILE.")
695
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.")
699
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.")
703
704         self.add_option('--cache-show',
705                         action="store_true", dest='cache_show', default=0,
706                         help="Print build actions for files from CacheDir.")
707
708         config_options = ["auto", "force" ,"cache"]
709
710         def opt_config(option, opt, value, parser, c_options=config_options):
711             if value in c_options:
712                 parser.values.config = value
713             else:
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, ", "))
720
721         def opt_not_yet(option, opt, value, parser):
722             sys.stderr.write("Warning:  the %s option is not yet implemented\n" % opt)
723             sys.exit(0)
724         self.add_option('-d', action="callback",
725                         callback=opt_not_yet,
726                         help = "Print file dependency information.")
727         
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.")
731
732         debug_options = ["count", "dtree", "explain", "findlibs",
733                          "includes", "memoizer", "memory", "objects",
734                          "pdb", "presub", "stacktrace", "stree",
735                          "time", "tree"]
736
737         deprecated_debug_options = {
738             "nomemoizer" : ' and has no effect',
739         }
740
741         def opt_debug(option, opt, value, parser, debug_options=debug_options, deprecated_debug_options=deprecated_debug_options):
742             if value in debug_options:
743                 try:
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))
753             else:
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",
757                         metavar="TYPE",
758                         help="Print various types of debugging information: "
759                              "%s." % string.join(debug_options, ", "))
760
761         def opt_diskcheck(option, opt, value, parser):
762             try:
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)
768
769             
770         self.add_option('--diskcheck', action="callback", type="string",
771                         callback=opt_diskcheck, dest='diskcheck',
772                         metavar="TYPE",
773                         help="Enable specific on-disk checks.")
774
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, ", "))
786
787         self.add_option('-f', '--file', '--makefile', '--sconstruct',
788                         action="append", nargs=1,
789                         help="Read FILE as the top-level SConstruct file.")
790
791         self.add_option('-h', '--help', action="store_true", default=0,
792                         dest="help_msg",
793                         help="Print defined help message, or this one.")
794
795         self.add_option("-H", "--help-options",
796                         action="help",
797                         help="Print this message and exit.")
798
799         self.add_option('-i', '--ignore-errors', action="store_true",
800                         default=0, dest='ignore_errors',
801                         help="Ignore errors from build actions.")
802
803         self.add_option('-I', '--include-dir', action="append",
804                         dest='include_dir', metavar="DIR",
805                         help="Search DIR for imported Python modules.")
806
807         self.add_option('--implicit-cache', action="store_true",
808                         dest='implicit_cache',
809                         help="Cache implicit dependencies")
810
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.")
817
818         def opt_j(option, opt, value, parser):
819             value = int(value)
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.")
824
825         self.add_option('-k', '--keep-going', action="store_true", default=0,
826                         dest='keep_going',
827                         help="Keep going when a target can't be made.")
828
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.")
832
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.")
836
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.")
840
841         self.add_option('--profile', action="store",
842                         dest="profile_file", metavar="FILE",
843                         help="Profile SCons and put results in FILE.")
844
845         self.add_option('-q', '--question', action="store_true", default=0,
846                         help="Don't build; exit status says if up to date.")
847
848         self.add_option('-Q', dest='no_progress', action="store_true",
849                         default=0,
850                         help="Suppress \"Reading/Building\" progress messages.")
851
852         self.add_option('--random', dest="random", action="store_true",
853                         default=0, help="Build dependencies in random order.")
854
855         self.add_option('-s', '--silent', '--quiet', action="store_true",
856                         default=0, help="Don't print commands.")
857
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.")
861
862         self.add_option('--taskmastertrace', action="store",
863                         dest="taskmastertrace_file", metavar="FILE",
864                         help="Trace Node evaluation to FILE.")
865
866         tree_options = ["all", "derived", "prune", "status"]
867
868         def opt_tree(option, opt, value, parser, tree_options=tree_options):
869             tp = TreePrinter()
870             for o in string.split(value, ','):
871                 if o == 'all':
872                     tp.derived = False
873                 elif o == 'derived':
874                     tp.derived = True
875                 elif o == 'prune':
876                     tp.prune = True
877                 elif o == 'status':
878                     tp.status = True
879                 else:
880                     raise OptionValueError("Warning:  %s is not a valid --tree option" % o)
881             tree_printers.append(tp)
882
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, ", "))
887
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",
893                         default=0, const=3,
894                         help="Search up directory tree for SConstruct,       "
895                              "build Default() targets from local SConscript.")
896
897         self.add_option("-v", "--version",
898                         action="version",
899                         help="Print the SCons version number and exit.")
900
901         self.add_option('--warn', '--warning', nargs=1, action="store",
902                         metavar="WARNING-SPEC",
903                         help="Enable or disable warnings.")
904
905         self.add_option('-Y', '--repository', '--srcdir',
906                         nargs=1, action="append",
907                         help="Search REPOSITORY for source and target files.")
908
909         self.add_option('-e', '--environment-overrides', action="callback",
910                         callback=opt_not_yet,
911                         # help="Environment variables override makefiles."
912                         help=SUPPRESS_HELP)
913         self.add_option('-l', '--load-average', '--max-load', action="callback",
914                         callback=opt_not_yet, type="int", dest="load_average",
915                         # action="store",
916                         # help="Don't start multiple jobs unless load is below "
917                         #      "LOAD-AVERAGE."
918                         # type="int",
919                         help=SUPPRESS_HELP)
920         self.add_option('--list-derived', action="callback",
921                         callback=opt_not_yet,
922                         # help="Don't build; list files that would be built."
923                         help=SUPPRESS_HELP)
924         self.add_option('--list-actions', action="callback",
925                         callback=opt_not_yet,
926                         # help="Don't build; list files and build actions."
927                         help=SUPPRESS_HELP)
928         self.add_option('--list-where', action="callback",
929                         callback=opt_not_yet,
930                         # help="Don't build; list files and where defined."
931                         help=SUPPRESS_HELP)
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."
935                         help=SUPPRESS_HELP)
936         self.add_option('--override', action="callback", dest="override",
937                         callback=opt_not_yet, type="string",
938                         # help="Override variables as specified in FILE."
939                         help=SUPPRESS_HELP)
940         self.add_option('-p', action="callback",
941                         callback=opt_not_yet,
942                         # help="Print internal environments/objects."
943                         help=SUPPRESS_HELP)
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."
948                         help=SUPPRESS_HELP)
949         self.add_option('-w', '--print-directory', action="callback",
950                         callback=opt_not_yet,
951                         # help="Print the current directory."
952                         help=SUPPRESS_HELP)
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."
956                         help=SUPPRESS_HELP)
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."
960                         help=SUPPRESS_HELP)
961         self.add_option('-W', '--what-if', '--new-file', '--assume-new',
962                         dest="new_file",
963                         action="callback", callback=opt_not_yet, type="string",
964                         # help="Consider FILE to be changed."
965                         help=SUPPRESS_HELP)
966         self.add_option('--warn-undefined-variables', action="callback",
967                         callback=opt_not_yet,
968                         # help="Warn when an undefined variable is referenced."
969                         help=SUPPRESS_HELP)
970
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
975         return opt, arglist
976
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."""
985     
986     def __init__(self, options):
987         self.options = options
988
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,
994                          'implicit_cache':0,
995                          'clean':0,
996                          'duplicate':'hard-soft-copy',
997                          'diskcheck':diskcheck_all}
998
999     def get(self, name):
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)
1004         else:
1005             return self.settable[name]
1006
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
1010
1011         if name == 'num_jobs':
1012             try:
1013                 value = int(value)
1014                 if value < 1:
1015                     raise ValueError
1016             except ValueError:
1017                 raise SCons.Errors.UserError, "A positive integer is required: %s"%repr(value)
1018         elif name == 'max_drift':
1019             try:
1020                 value = int(value)
1021             except ValueError:
1022                 raise SCons.Errors.UserError, "An integer is required: %s"%repr(value)
1023         elif name == 'duplicate':
1024             try:
1025                 value = str(value)
1026             except ValueError:
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':
1034             try:
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)
1040
1041         self.settable[name] = value
1042     
1043
1044 def _main(args, parser):
1045     global exit_status
1046
1047     # Here's where everything really happens.
1048
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
1063     if options.warn:
1064         _setup_warn(options.warn)
1065
1066     for warning_type, message in delayed_warnings:
1067         SCons.Warnings.warn(warning_type, message)
1068
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)
1076         try:
1077             os.chdir(cdir)
1078         except OSError:
1079             sys.stderr.write("Could not change directory to %s\n" % cdir)
1080
1081     # The SConstruct file may be in a repository, so initialize those
1082     # before we start the search up our path for one.
1083     global repositories
1084     if options.repository:
1085         repositories.extend(options.repository)
1086
1087     target_top = None
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)
1093             if last_part:
1094                 target_top = os.path.join(last_part, target_top)
1095             else:
1096                 script_dir = ''
1097         if script_dir:
1098             display("scons: Entering directory `%s'" % script_dir)
1099             os.chdir(script_dir)
1100
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()
1105
1106     for rep in repositories:
1107         fs.Repository(rep)
1108
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.
1112     scripts = []
1113     if options.file:
1114         scripts.extend(options.file)
1115     if not scripts:
1116         sfile = _SConstruct_exists()
1117         if sfile:
1118             scripts.append(sfile)
1119
1120     if not scripts:
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.
1125             parser.print_help()
1126             exit_status = 0
1127             return
1128         raise SCons.Errors.UserError, "No SConstruct file found."
1129
1130     if scripts[0] == "-":
1131         d = fs.getcwd()
1132     else:
1133         d = fs.File(scripts[0]).dir
1134     fs.set_SConstruct_dir(d)
1135
1136     # Now that we have the FS object and it's intialized, set up (most
1137     # of) the rest of the options.
1138     global ssoptions
1139     ssoptions = SConscriptSettableOptions(options)
1140
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
1145     if options.noexec:
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)
1153
1154     if options.no_progress or options.silent:
1155         progress_display.set_mode(0)
1156     if options.silent:
1157         display.set_mode(0)
1158     if options.silent:
1159         SCons.Action.print_actions = None
1160
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:
1167         fs.cache_force = 1
1168     if options.cache_show:
1169         fs.cache_show = 1
1170
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)
1175         
1176     if options.include_dir:
1177         sys.path = options.include_dir + sys.path
1178
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.
1182     targets = []
1183     xmit_args = []
1184     for a in args:
1185         if '=' in a:
1186             xmit_args.append(a)
1187         else:
1188             targets.append(a)
1189     SCons.Script._Add_Targets(targets)
1190     SCons.Script._Add_Arguments(xmit_args)
1191
1192     sys.stdout = SCons.Util.Unbuffered(sys.stdout)
1193
1194     memory_stats.append('before reading SConscript files:')
1195     count_stats.append(('pre-', 'read'))
1196
1197     progress_display("scons: Reading SConscript files ...")
1198
1199     start_time = time.time()
1200     try:
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
1208         # or anything else.
1209         sys.stderr.write("scons: *** %s  Stop.\n" % e)
1210         exit_status = 2
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.")
1216
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)
1222
1223     memory_stats.append('after reading SConscript files:')
1224     count_stats.append(('post-', 'read'))
1225
1226     fs.chdir(fs.Top)
1227
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)
1234         else:
1235             print help_text
1236             print "Use scons -H for help about command-line options."
1237         exit_status = 0
1238         return
1239
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'))
1245
1246     lookup_top = None
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.
1252         if target_top:
1253             lookup_top = fs.Dir(target_top)
1254             target_top = None
1255
1256         targets = SCons.Script.BUILD_TARGETS
1257     else:
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.
1261         d = None
1262         if target_top:
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
1269                 target_top = None
1270                 lookup_top = None
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
1278                     else:
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:
1282                         return 1                
1283                 d = filter(check_dir, SCons.Script.DEFAULT_TARGETS)
1284                 SCons.Script.DEFAULT_TARGETS[:] = d
1285                 target_top = None
1286                 lookup_top = None
1287
1288         targets = SCons.Script._Get_Default_Targets(d, fs)
1289
1290     if not targets:
1291         sys.stderr.write("scons: *** No targets specified and no Default() targets found.  Stop.\n")
1292         sys.exit(2)
1293
1294     def Entry(x, ltop=lookup_top, ttop=target_top, fs=fs):
1295         if isinstance(x, SCons.Node.Node):
1296             node = x
1297         else:
1298             node = None
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)
1307                 if node != None:
1308                     break
1309             if node is None:
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):
1313                 node = ttop
1314             else:
1315                 node = None
1316         return node
1317
1318     nodes = filter(None, map(Entry, targets))
1319
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)."
1325     else:
1326         failure_message = "building terminated because of errors."
1327     if options.question:
1328         task_class = QuestionTask
1329     try:
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)."
1336             else:
1337                 failure_message = "cleaning terminated because of errors."
1338     except AttributeError:
1339         pass
1340
1341     if options.random:
1342         def order(dependencies):
1343             """Randomize the dependencies."""
1344             # This is cribbed from the implementation of
1345             # random.shuffle() in Python 2.X.
1346             d = dependencies
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]
1350             return d
1351     else:
1352         def order(dependencies):
1353             """Leave the order of dependencies alone."""
1354             return dependencies
1355
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')
1361     else:
1362         tmtrace = None
1363     taskmaster = SCons.Taskmaster.Taskmaster(nodes, task_class, order, tmtrace)
1364
1365     global num_jobs
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)
1372
1373     memory_stats.append('before building targets:')
1374     count_stats.append(('pre-', 'build'))
1375
1376     try:
1377         jobs.run()
1378     finally:
1379         if exit_status:
1380             progress_display("scons: " + failure_message)
1381         else:
1382             progress_display("scons: " + closing_message)
1383         if not options.noexec:
1384             SCons.SConsign.write()
1385
1386     memory_stats.append('after building targets:')
1387     count_stats.append(('post-', 'build'))
1388
1389 def _exec_main():
1390     sconsflags = os.environ.get('SCONSFLAGS', '')
1391     all_args = string.split(sconsflags) + sys.argv[1:]
1392
1393     parser = OptParser()
1394     global options
1395     options, args = parser.parse_args(all_args)
1396     if type(options.debug) == type([]) and "pdb" in options.debug:
1397         import pdb
1398         pdb.Pdb().runcall(_main, args, parser)
1399     elif options.profile_file:
1400         from profile import Profile
1401
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).
1407         try:
1408             dispatch = Profile.dispatch
1409         except AttributeError:
1410             pass
1411         else:
1412             dispatch['c_exception'] = Profile.trace_dispatch_return
1413
1414         prof = Profile()
1415         try:
1416             prof.runcall(_main, args, parser)
1417         except SystemExit:
1418             pass
1419         prof.dump_stats(options.profile_file)
1420     else:
1421         _main(args, parser)
1422
1423 def main():
1424     global exit_status
1425     global first_command_start
1426     
1427     try:
1428         _exec_main()
1429     except SystemExit, s:
1430         if s:
1431             exit_status = s
1432     except KeyboardInterrupt:
1433         print "Build interrupted."
1434         sys.exit(2)
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)
1441     except:
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()
1446         sys.exit(2)
1447
1448     memory_stats.print_stats()
1449     count_stats.print_stats()
1450
1451     if print_objects:
1452         SCons.Debug.listLoggedInstances('*')
1453         #SCons.Debug.dumpLoggedInstances('*')
1454
1455     if print_memoizer:
1456         SCons.Memoize.Dump("Memoizer (memory cache) hits and misses:")
1457
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()
1464
1465     if print_time:
1466         total_time = time.time() - SCons.Script.start_time
1467         if num_jobs == 1:
1468             ct = cumulative_command_time
1469         else:
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
1476
1477     sys.exit(exit_status)