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