Merged revisions 2647-2719 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.CacheDir
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 import SCons.SConf
66 import SCons.Script
67 import SCons.Taskmaster
68 import SCons.Util
69 import SCons.Warnings
70
71 import SCons.Script.Interactive
72
73 def fetch_win32_parallel_msg():
74     # A subsidiary function that exists solely to isolate this import
75     # so we don't have to pull it in on all platforms, and so that an
76     # in-line "import" statement in the _main() function below doesn't
77     # cause warnings about local names shadowing use of the 'SCons'
78     # globl in nest scopes and UnboundLocalErrors and the like in some
79     # versions (2.1) of Python.
80     import SCons.Platform.win32
81     SCons.Platform.win32.parallel_msg
82
83 #
84
85 class SConsPrintHelpException(Exception):
86     pass
87
88 display = SCons.Util.display
89 progress_display = SCons.Util.DisplayEngine()
90
91 first_command_start = None
92 last_command_end = None
93
94 class Progressor:
95     prev = ''
96     count = 0
97     target_string = '$TARGET'
98
99     def __init__(self, obj, interval=1, file=None, overwrite=False):
100         if file is None:
101             file = sys.stdout
102
103         self.obj = obj
104         self.file = file
105         self.interval = interval
106         self.overwrite = overwrite
107
108         if callable(obj):
109             self.func = obj
110         elif SCons.Util.is_List(obj):
111             self.func = self.spinner
112         elif string.find(obj, self.target_string) != -1:
113             self.func = self.replace_string
114         else:
115             self.func = self.string
116
117     def write(self, s):
118         self.file.write(s)
119         self.file.flush()
120         self.prev = s
121
122     def erase_previous(self):
123         if self.prev:
124             length = len(self.prev)
125             if self.prev[-1] in ('\n', '\r'):
126                 length = length - 1
127             self.write(' ' * length + '\r')
128             self.prev = ''
129
130     def spinner(self, node):
131         self.write(self.obj[self.count % len(self.obj)])
132
133     def string(self, node):
134         self.write(self.obj)
135
136     def replace_string(self, node):
137         self.write(string.replace(self.obj, self.target_string, str(node)))
138
139     def __call__(self, node):
140         self.count = self.count + 1
141         if (self.count % self.interval) == 0:
142             if self.overwrite:
143                 self.erase_previous()
144             self.func(node)
145
146 ProgressObject = SCons.Util.Null()
147
148 def Progress(*args, **kw):
149     global ProgressObject
150     ProgressObject = apply(Progressor, args, kw)
151
152 # Task control.
153 #
154
155 _BuildFailures = []
156
157 def GetBuildFailures():
158     return _BuildFailures
159
160 class BuildTask(SCons.Taskmaster.Task):
161     """An SCons build task."""
162     progress = ProgressObject
163
164     def display(self, message):
165         display('scons: ' + message)
166
167     def prepare(self):
168         self.progress(self.targets[0])
169         return SCons.Taskmaster.Task.prepare(self)
170
171     def execute(self):
172         for target in self.targets:
173             if target.get_state() == SCons.Node.up_to_date: 
174                 continue
175             if target.has_builder() and not hasattr(target.builder, 'status'):
176                 if print_time:
177                     start_time = time.time()
178                     global first_command_start
179                     if first_command_start is None:
180                         first_command_start = start_time
181                 SCons.Taskmaster.Task.execute(self)
182                 if print_time:
183                     global cumulative_command_time
184                     global last_command_end
185                     finish_time = time.time()
186                     last_command_end = finish_time
187                     cumulative_command_time = cumulative_command_time+finish_time-start_time
188                     sys.stdout.write("Command execution time: %f seconds\n"%(finish_time-start_time))
189                 break
190         else:
191             if self.top and target.has_builder():
192                 display("scons: `%s' is up to date." % str(self.node))
193
194     def do_failed(self, status=2):
195         _BuildFailures.append(self.exception[1])
196         global exit_status
197         if self.options.ignore_errors:
198             SCons.Taskmaster.Task.executed(self)
199         elif self.options.keep_going:
200             SCons.Taskmaster.Task.fail_continue(self)
201             exit_status = status
202         else:
203             SCons.Taskmaster.Task.fail_stop(self)
204             exit_status = status
205             
206     def executed(self):
207         t = self.targets[0]
208         if self.top and not t.has_builder() and not t.side_effect:
209             if not t.exists():
210                 sys.stderr.write("scons: *** Do not know how to make target `%s'." % t)
211                 if not self.options.keep_going:
212                     sys.stderr.write("  Stop.")
213                 sys.stderr.write("\n")
214                 self.do_failed()
215             else:
216                 print "scons: Nothing to be done for `%s'." % t
217                 SCons.Taskmaster.Task.executed(self)
218         else:
219             SCons.Taskmaster.Task.executed(self)
220
221     def failed(self):
222         # Handle the failure of a build task.  The primary purpose here
223         # is to display the various types of Errors and Exceptions
224         # appropriately.
225         status = 2
226         exc_info = self.exc_info()
227         try:
228             t, e, tb = exc_info
229         except ValueError:
230             t, e = exc_info
231             tb = None
232         if t is None:
233             # The Taskmaster didn't record an exception for this Task;
234             # see if the sys module has one.
235             t, e = sys.exc_info()[:2]
236
237         def nodestring(n):
238             if not SCons.Util.is_List(n):
239                 n = [ n ]
240             return string.join(map(str, n), ', ')
241
242         errfmt = "scons: *** [%s] %s\n"
243
244         if t == SCons.Errors.BuildError:
245             tname = nodestring(e.node)
246             errstr = e.errstr
247             if e.filename:
248                 errstr = e.filename + ': ' + errstr
249             sys.stderr.write(errfmt % (tname, errstr))
250         elif t == SCons.Errors.TaskmasterException:
251             tname = nodestring(e.node)
252             sys.stderr.write(errfmt % (tname, e.errstr))
253             type, value, trace = e.exc_info
254             traceback.print_exception(type, value, trace)
255         elif t == SCons.Errors.ExplicitExit:
256             status = e.status
257             tname = nodestring(e.node)
258             errstr = 'Explicit exit, status %s' % status
259             sys.stderr.write(errfmt % (tname, errstr))
260         else:
261             if e is None:
262                 e = t
263             s = str(e)
264             if t == SCons.Errors.StopError and not self.options.keep_going:
265                 s = s + '  Stop.'
266             sys.stderr.write("scons: *** %s\n" % s)
267
268             if tb and print_stacktrace:
269                 sys.stderr.write("scons: internal stack trace:\n")
270                 traceback.print_tb(tb, file=sys.stderr)
271
272         self.do_failed(status)
273
274         self.exc_clear()
275
276     def postprocess(self):
277         if self.top:
278             t = self.targets[0]
279             for tp in self.options.tree_printers:
280                 tp.display(t)
281             if self.options.debug_includes:
282                 tree = t.render_include_tree()
283                 if tree:
284                     print
285                     print tree
286         SCons.Taskmaster.Task.postprocess(self)
287
288     def make_ready(self):
289         """Make a task ready for execution"""
290         SCons.Taskmaster.Task.make_ready(self)
291         if self.out_of_date and self.options.debug_explain:
292             explanation = self.out_of_date[0].explain()
293             if explanation:
294                 sys.stdout.write("scons: " + explanation)
295
296 class CleanTask(SCons.Taskmaster.Task):
297     """An SCons clean task."""
298     def fs_delete(self, path, pathstr, remove=1):
299         try:
300             if os.path.exists(path):
301                 if os.path.isfile(path):
302                     if remove: os.unlink(path)
303                     display("Removed " + pathstr)
304                 elif os.path.isdir(path) and not os.path.islink(path):
305                     # delete everything in the dir
306                     entries = os.listdir(path)
307                     # Sort for deterministic output (os.listdir() Can
308                     # return entries in a random order).
309                     entries.sort()
310                     for e in entries:
311                         p = os.path.join(path, e)
312                         s = os.path.join(pathstr, e)
313                         if os.path.isfile(p):
314                             if remove: os.unlink(p)
315                             display("Removed " + s)
316                         else:
317                             self.fs_delete(p, s, remove)
318                     # then delete dir itself
319                     if remove: os.rmdir(path)
320                     display("Removed directory " + pathstr)
321         except (IOError, OSError), e:
322             print "scons: Could not remove '%s':" % pathstr, e.strerror
323
324     def show(self):
325         target = self.targets[0]
326         if (target.has_builder() or target.side_effect) and not target.noclean:
327             for t in self.targets:
328                 if not t.isdir():
329                     display("Removed " + str(t))
330         if SCons.Environment.CleanTargets.has_key(target):
331             files = SCons.Environment.CleanTargets[target]
332             for f in files:
333                 self.fs_delete(f.abspath, str(f), 0)
334
335     def remove(self):
336         target = self.targets[0]
337         if (target.has_builder() or target.side_effect) and not target.noclean:
338             for t in self.targets:
339                 try:
340                     removed = t.remove()
341                 except OSError, e:
342                     # An OSError may indicate something like a permissions
343                     # issue, an IOError would indicate something like
344                     # the file not existing.  In either case, print a
345                     # message and keep going to try to remove as many
346                     # targets aa possible.
347                     print "scons: Could not remove '%s':" % str(t), e.strerror
348                 else:
349                     if removed:
350                         display("Removed " + str(t))
351         if SCons.Environment.CleanTargets.has_key(target):
352             files = SCons.Environment.CleanTargets[target]
353             for f in files:
354                 self.fs_delete(f.abspath, str(f))
355
356     execute = remove
357
358     # We want the Taskmaster to update the Node states (and therefore
359     # handle reference counts, etc.), but we don't want to call
360     # back to the Node's post-build methods, which would do things
361     # we don't want, like store .sconsign information.
362     executed = SCons.Taskmaster.Task.executed_without_callbacks
363
364     # Have the taskmaster arrange to "execute" all of the targets, because
365     # we'll figure out ourselves (in remove() or show() above) whether
366     # anything really needs to be done.
367     make_ready = SCons.Taskmaster.Task.make_ready_all
368
369     def prepare(self):
370         pass
371
372 class QuestionTask(SCons.Taskmaster.Task):
373     """An SCons task for the -q (question) option."""
374     def prepare(self):
375         pass
376     
377     def execute(self):
378         if self.targets[0].get_state() != SCons.Node.up_to_date or \
379            (self.top and not self.targets[0].exists()):
380             global exit_status
381             exit_status = 1
382             self.tm.stop()
383
384     def executed(self):
385         pass
386
387
388 class TreePrinter:
389     def __init__(self, derived=False, prune=False, status=False):
390         self.derived = derived
391         self.prune = prune
392         self.status = status
393     def get_all_children(self, node):
394         return node.all_children()
395     def get_derived_children(self, node):
396         children = node.all_children(None)
397         return filter(lambda x: x.has_builder(), children)
398     def display(self, t):
399         if self.derived:
400             func = self.get_derived_children
401         else:
402             func = self.get_all_children
403         s = self.status and 2 or 0
404         SCons.Util.print_tree(t, func, prune=self.prune, showtags=s)
405
406
407 def python_version_string():
408     return string.split(sys.version)[0]
409
410 def python_version_unsupported(version=sys.version_info):
411     return version < (1, 5, 2)
412
413 def python_version_deprecated(version=sys.version_info):
414     return version < (2, 2, 0)
415
416
417 # Global variables
418
419 print_objects = 0
420 print_memoizer = 0
421 print_stacktrace = 0
422 print_time = 0
423 sconscript_time = 0
424 cumulative_command_time = 0
425 exit_status = 0 # exit status, assume success by default
426 num_jobs = None
427 delayed_warnings = []
428
429 class FakeOptionParser:
430     """
431     A do-nothing option parser, used for the initial OptionsParser variable.
432
433     During normal SCons operation, the OptionsParser is created right
434     away by the main() function.  Certain tests scripts however, can
435     introspect on different Tool modules, the initialization of which
436     can try to add a new, local option to an otherwise uninitialized
437     OptionsParser object.  This allows that introspection to happen
438     without blowing up.
439
440     """
441     class FakeOptionValues:
442         def __getattr__(self, attr):
443             return None
444     values = FakeOptionValues()
445     def add_local_option(self, *args, **kw):
446         pass
447
448 OptionsParser = FakeOptionParser()
449
450 def AddOption(*args, **kw):
451     if not kw.has_key('default'):
452         kw['default'] = None
453     result = apply(OptionsParser.add_local_option, args, kw)
454     return result
455
456 def GetOption(name):
457     return getattr(OptionsParser.values, name)
458
459 def SetOption(name, value):
460     return OptionsParser.values.set_option(name, value)
461
462 #
463 class Stats:
464     def __init__(self):
465         self.stats = []
466         self.labels = []
467         self.append = self.do_nothing
468         self.print_stats = self.do_nothing
469     def enable(self, outfp):
470         self.outfp = outfp
471         self.append = self.do_append
472         self.print_stats = self.do_print
473     def do_nothing(self, *args, **kw):
474         pass
475
476 class CountStats(Stats):
477     def do_append(self, label):
478         self.labels.append(label)
479         self.stats.append(SCons.Debug.fetchLoggedInstances())
480     def do_print(self):
481         stats_table = {}
482         for s in self.stats:
483             for n in map(lambda t: t[0], s):
484                 stats_table[n] = [0, 0, 0, 0]
485         i = 0
486         for s in self.stats:
487             for n, c in s:
488                 stats_table[n][i] = c
489             i = i + 1
490         keys = stats_table.keys()
491         keys.sort()
492         self.outfp.write("Object counts:\n")
493         pre = ["   "]
494         post = ["   %s\n"]
495         l = len(self.stats)
496         fmt1 = string.join(pre + [' %7s']*l + post, '')
497         fmt2 = string.join(pre + [' %7d']*l + post, '')
498         labels = self.labels[:l]
499         labels.append(("", "Class"))
500         self.outfp.write(fmt1 % tuple(map(lambda x: x[0], labels)))
501         self.outfp.write(fmt1 % tuple(map(lambda x: x[1], labels)))
502         for k in keys:
503             r = stats_table[k][:l] + [k]
504             self.outfp.write(fmt2 % tuple(r))
505
506 count_stats = CountStats()
507
508 class MemStats(Stats):
509     def do_append(self, label):
510         self.labels.append(label)
511         self.stats.append(SCons.Debug.memory())
512     def do_print(self):
513         fmt = 'Memory %-32s %12d\n'
514         for label, stats in map(None, self.labels, self.stats):
515             self.outfp.write(fmt % (label, stats))
516
517 memory_stats = MemStats()
518
519 # utility functions
520
521 def _scons_syntax_error(e):
522     """Handle syntax errors. Print out a message and show where the error
523     occurred.
524     """
525     etype, value, tb = sys.exc_info()
526     lines = traceback.format_exception_only(etype, value)
527     for line in lines:
528         sys.stderr.write(line+'\n')
529     sys.exit(2)
530
531 def find_deepest_user_frame(tb):
532     """
533     Find the deepest stack frame that is not part of SCons.
534
535     Input is a "pre-processed" stack trace in the form
536     returned by traceback.extract_tb() or traceback.extract_stack()
537     """
538     
539     tb.reverse()
540
541     # find the deepest traceback frame that is not part
542     # of SCons:
543     for frame in tb:
544         filename = frame[0]
545         if string.find(filename, os.sep+'SCons'+os.sep) == -1:
546             return frame
547     return tb[0]
548
549 def _scons_user_error(e):
550     """Handle user errors. Print out a message and a description of the
551     error, along with the line number and routine where it occured. 
552     The file and line number will be the deepest stack frame that is
553     not part of SCons itself.
554     """
555     global print_stacktrace
556     etype, value, tb = sys.exc_info()
557     if print_stacktrace:
558         traceback.print_exception(etype, value, tb)
559     filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_tb(tb))
560     sys.stderr.write("\nscons: *** %s\n" % value)
561     sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
562     sys.exit(2)
563
564 def _scons_user_warning(e):
565     """Handle user warnings. Print out a message and a description of
566     the warning, along with the line number and routine where it occured.
567     The file and line number will be the deepest stack frame that is
568     not part of SCons itself.
569     """
570     etype, value, tb = sys.exc_info()
571     filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_tb(tb))
572     sys.stderr.write("\nscons: warning: %s\n" % e)
573     sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
574
575 def _scons_internal_warning(e):
576     """Slightly different from _scons_user_warning in that we use the
577     *current call stack* rather than sys.exc_info() to get our stack trace.
578     This is used by the warnings framework to print warnings."""
579     filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_stack())
580     sys.stderr.write("\nscons: warning: %s\n" % e[0])
581     sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
582
583 def _scons_internal_error():
584     """Handle all errors but user errors. Print out a message telling
585     the user what to do in this case and print a normal trace.
586     """
587     print 'internal error'
588     traceback.print_exc()
589     sys.exit(2)
590
591 def _SConstruct_exists(dirname='', repositories=[]):
592     """This function checks that an SConstruct file exists in a directory.
593     If so, it returns the path of the file. By default, it checks the
594     current directory.
595     """
596     for file in ['SConstruct', 'Sconstruct', 'sconstruct']:
597         sfile = os.path.join(dirname, file)
598         if os.path.isfile(sfile):
599             return sfile
600         if not os.path.isabs(sfile):
601             for rep in repositories:
602                 if os.path.isfile(os.path.join(rep, sfile)):
603                     return sfile
604     return None
605
606 def _set_debug_values(options):
607     global print_memoizer, print_objects, print_stacktrace, print_time
608
609     debug_values = options.debug
610
611     if "count" in debug_values:
612         # All of the object counts are within "if __debug__:" blocks,
613         # which get stripped when running optimized (with python -O or
614         # from compiled *.pyo files).  Provide a warning if __debug__ is
615         # stripped, so it doesn't just look like --debug=count is broken.
616         enable_count = False
617         if __debug__: enable_count = True
618         if enable_count:
619             count_stats.enable(sys.stdout)
620         else:
621             msg = "--debug=count is not supported when running SCons\n" + \
622                   "\twith the python -O option or optimized (.pyo) modules."
623             SCons.Warnings.warn(SCons.Warnings.NoObjectCountWarning, msg)
624     if "dtree" in debug_values:
625         options.tree_printers.append(TreePrinter(derived=True))
626     options.debug_explain = ("explain" in debug_values)
627     if "findlibs" in debug_values:
628         SCons.Scanner.Prog.print_find_libs = "findlibs"
629     options.debug_includes = ("includes" in debug_values)
630     print_memoizer = ("memoizer" in debug_values)
631     if "memory" in debug_values:
632         memory_stats.enable(sys.stdout)
633     print_objects = ("objects" in debug_values)
634     if "presub" in debug_values:
635         SCons.Action.print_actions_presub = 1
636     if "stacktrace" in debug_values:
637         print_stacktrace = 1
638     if "stree" in debug_values:
639         options.tree_printers.append(TreePrinter(status=True))
640     if "time" in debug_values:
641         print_time = 1
642     if "tree" in debug_values:
643         options.tree_printers.append(TreePrinter())
644
645 def _create_path(plist):
646     path = '.'
647     for d in plist:
648         if os.path.isabs(d):
649             path = d
650         else:
651             path = path + '/' + d
652     return path
653
654 def _load_site_scons_dir(topdir, site_dir_name=None):
655     """Load the site_scons dir under topdir.
656     Adds site_scons to sys.path, imports site_scons/site_init.py,
657     and adds site_scons/site_tools to default toolpath."""
658     if site_dir_name:
659         err_if_not_found = True       # user specified: err if missing
660     else:
661         site_dir_name = "site_scons"
662         err_if_not_found = False
663         
664     site_dir = os.path.join(topdir.path, site_dir_name)
665     if not os.path.exists(site_dir):
666         if err_if_not_found:
667             raise SCons.Errors.UserError, "site dir %s not found."%site_dir
668         return
669
670     site_init_filename = "site_init.py"
671     site_init_modname = "site_init"
672     site_tools_dirname = "site_tools"
673     sys.path = [os.path.abspath(site_dir)] + sys.path
674     site_init_file = os.path.join(site_dir, site_init_filename)
675     site_tools_dir = os.path.join(site_dir, site_tools_dirname)
676     if os.path.exists(site_init_file):
677         import imp
678         try:
679             fp, pathname, description = imp.find_module(site_init_modname,
680                                                         [site_dir])
681             try:
682                 imp.load_module(site_init_modname, fp, pathname, description)
683             finally:
684                 if fp:
685                     fp.close()
686         except ImportError, e:
687             sys.stderr.write("Can't import site init file '%s': %s\n"%(site_init_file, e))
688             raise
689         except Exception, e:
690             sys.stderr.write("Site init file '%s' raised exception: %s\n"%(site_init_file, e))
691             raise
692     if os.path.exists(site_tools_dir):
693         SCons.Tool.DefaultToolpath.append(os.path.abspath(site_tools_dir))
694
695 def version_string(label, module):
696     version = module.__version__
697     build = module.__build__
698     if build:
699         if build[0] != '.':
700             build = '.' + build
701         version = version + build
702     fmt = "\t%s: v%s, %s, by %s on %s\n"
703     return fmt % (label,
704                   version,
705                   module.__date__,
706                   module.__developer__,
707                   module.__buildsys__)
708
709 def _main(parser):
710     global exit_status
711
712     options = parser.values
713
714     # Here's where everything really happens.
715
716     # First order of business:  set up default warnings and then
717     # handle the user's warning options, so that we can issue (or
718     # suppress) appropriate warnings about anything that might happen,
719     # as configured by the user.
720
721     default_warnings = [ SCons.Warnings.CorruptSConsignWarning,
722                          SCons.Warnings.DeprecatedWarning,
723                          SCons.Warnings.DuplicateEnvironmentWarning,
724                          SCons.Warnings.MissingSConscriptWarning,
725                          SCons.Warnings.NoMD5ModuleWarning,
726                          SCons.Warnings.NoMetaclassSupportWarning,
727                          SCons.Warnings.NoObjectCountWarning,
728                          SCons.Warnings.NoParallelSupportWarning,
729                          SCons.Warnings.MisleadingKeywordsWarning,
730                          SCons.Warnings.StackSizeWarning, ]
731     for warning in default_warnings:
732         SCons.Warnings.enableWarningClass(warning)
733     SCons.Warnings._warningOut = _scons_internal_warning
734     SCons.Warnings.process_warn_strings(options.warn)
735
736     # Now that we have the warnings configuration set up, we can actually
737     # issue (or suppress) any warnings about warning-worthy things that
738     # occurred while the command-line options were getting parsed.
739     try:
740         dw = options.delayed_warnings
741     except AttributeError:
742         pass
743     else:
744         delayed_warnings.extend(dw)
745     for warning_type, message in delayed_warnings:
746         SCons.Warnings.warn(warning_type, message)
747
748     if options.diskcheck:
749         SCons.Node.FS.set_diskcheck(options.diskcheck)
750
751     # Next, we want to create the FS object that represents the outside
752     # world's file system, as that's central to a lot of initialization.
753     # To do this, however, we need to be in the directory from which we
754     # want to start everything, which means first handling any relevant
755     # options that might cause us to chdir somewhere (-C, -D, -U, -u).
756     if options.directory:
757         cdir = _create_path(options.directory)
758         try:
759             os.chdir(cdir)
760         except OSError:
761             sys.stderr.write("Could not change directory to %s\n" % cdir)
762
763     target_top = None
764     if options.climb_up:
765         target_top = '.'  # directory to prepend to targets
766         script_dir = os.getcwd()  # location of script
767         while script_dir and not _SConstruct_exists(script_dir, options.repository):
768             script_dir, last_part = os.path.split(script_dir)
769             if last_part:
770                 target_top = os.path.join(last_part, target_top)
771             else:
772                 script_dir = ''
773         if script_dir:
774             display("scons: Entering directory `%s'" % script_dir)
775             os.chdir(script_dir)
776
777     # Now that we're in the top-level SConstruct directory, go ahead
778     # and initialize the FS object that represents the file system,
779     # and make it the build engine default.
780     fs = SCons.Node.FS.get_default_fs()
781
782     for rep in options.repository:
783         fs.Repository(rep)
784
785     # Now that we have the FS object, the next order of business is to
786     # check for an SConstruct file (or other specified config file).
787     # If there isn't one, we can bail before doing any more work.
788     scripts = []
789     if options.file:
790         scripts.extend(options.file)
791     if not scripts:
792         sfile = _SConstruct_exists(repositories=options.repository)
793         if sfile:
794             scripts.append(sfile)
795
796     if not scripts:
797         if options.help:
798             # There's no SConstruct, but they specified -h.
799             # Give them the options usage now, before we fail
800             # trying to read a non-existent SConstruct file.
801             raise SConsPrintHelpException
802         raise SCons.Errors.UserError, "No SConstruct file found."
803
804     if scripts[0] == "-":
805         d = fs.getcwd()
806     else:
807         d = fs.File(scripts[0]).dir
808     fs.set_SConstruct_dir(d)
809
810     _set_debug_values(options)
811     SCons.Node.implicit_cache = options.implicit_cache
812     SCons.Node.implicit_deps_changed = options.implicit_deps_changed
813     SCons.Node.implicit_deps_unchanged = options.implicit_deps_unchanged
814
815     if options.no_exec:
816         SCons.SConf.dryrun = 1
817         SCons.Action.execute_actions = None
818     if options.question:
819         SCons.SConf.dryrun = 1
820     if options.clean:
821         SCons.SConf.SetBuildType('clean')
822     if options.help:
823         SCons.SConf.SetBuildType('help')
824     SCons.SConf.SetCacheMode(options.config)
825     SCons.SConf.SetProgressDisplay(progress_display)
826
827     if options.no_progress or options.silent:
828         progress_display.set_mode(0)
829
830     if options.site_dir:
831         _load_site_scons_dir(d, options.site_dir)
832     elif not options.no_site_dir:
833         _load_site_scons_dir(d)
834         
835     if options.include_dir:
836         sys.path = options.include_dir + sys.path
837
838     # That should cover (most of) the options.  Next, set up the variables
839     # that hold command-line arguments, so the SConscript files that we
840     # read and execute have access to them.
841     targets = []
842     xmit_args = []
843     for a in parser.largs:
844         if a[0] == '-':
845             continue
846         if '=' in a:
847             xmit_args.append(a)
848         else:
849             targets.append(a)
850     SCons.Script._Add_Targets(targets + parser.rargs)
851     SCons.Script._Add_Arguments(xmit_args)
852
853     # If stdout is not a tty, replace it with a wrapper object to call flush
854     # after every write.
855     #
856     # Tty devices automatically flush after every newline, so the replacement
857     # isn't necessary.  Furthermore, if we replace sys.stdout, the readline
858     # module will no longer work.  This affects the behavior during
859     # --interactive mode.  --interactive should only be used when stdin and
860     # stdout refer to a tty.
861     if not sys.stdout.isatty():
862         sys.stdout = SCons.Util.Unbuffered(sys.stdout)
863     if not sys.stderr.isatty():
864         sys.stderr = SCons.Util.Unbuffered(sys.stderr)
865
866     memory_stats.append('before reading SConscript files:')
867     count_stats.append(('pre-', 'read'))
868
869     # And here's where we (finally) read the SConscript files.
870
871     progress_display("scons: Reading SConscript files ...")
872
873     start_time = time.time()
874     try:
875         for script in scripts:
876             SCons.Script._SConscript._SConscript(fs, script)
877     except SCons.Errors.StopError, e:
878         # We had problems reading an SConscript file, such as it
879         # couldn't be copied in to the VariantDir.  Since we're just
880         # reading SConscript files and haven't started building
881         # things yet, stop regardless of whether they used -i or -k
882         # or anything else.
883         sys.stderr.write("scons: *** %s  Stop.\n" % e)
884         exit_status = 2
885         sys.exit(exit_status)
886     global sconscript_time
887     sconscript_time = time.time() - start_time
888
889     progress_display("scons: done reading SConscript files.")
890
891     memory_stats.append('after reading SConscript files:')
892     count_stats.append(('post-', 'read'))
893
894     # Re-{enable,disable} warnings in case they disabled some in
895     # the SConscript file.
896     #
897     # We delay enabling the PythonVersionWarning class until here so that,
898     # if they explicity disabled it in either in the command line or in
899     # $SCONSFLAGS, or in the SConscript file, then the search through
900     # the list of deprecated warning classes will find that disabling
901     # first and not issue the warning.
902     SCons.Warnings.enableWarningClass(SCons.Warnings.PythonVersionWarning)
903     SCons.Warnings.process_warn_strings(options.warn)
904
905     # Now that we've read the SConscript files, we can check for the
906     # warning about deprecated Python versions--delayed until here
907     # in case they disabled the warning in the SConscript files.
908     if python_version_deprecated():
909         msg = "Support for pre-2.2 Python (%s) is deprecated.\n" + \
910               "    If this will cause hardship, contact dev@scons.tigris.org."
911         SCons.Warnings.warn(SCons.Warnings.PythonVersionWarning,
912                             msg % python_version_string())
913
914     if not options.help:
915         SCons.SConf.CreateConfigHBuilder(SCons.Defaults.DefaultEnvironment())
916
917     # Now re-parse the command-line options (any to the left of a '--'
918     # argument, that is) with any user-defined command-line options that
919     # the SConscript files may have added to the parser object.  This will
920     # emit the appropriate error message and exit if any unknown option
921     # was specified on the command line.
922
923     parser.preserve_unknown_options = False
924     parser.parse_args(parser.largs, options)
925
926     if options.help:
927         help_text = SCons.Script.help_text
928         if help_text is None:
929             # They specified -h, but there was no Help() inside the
930             # SConscript files.  Give them the options usage.
931             raise SConsPrintHelpException
932         else:
933             print help_text
934             print "Use scons -H for help about command-line options."
935         exit_status = 0
936         return
937
938     # Change directory to the top-level SConstruct directory, then tell
939     # the Node.FS subsystem that we're all done reading the SConscript
940     # files and calling Repository() and VariantDir() and changing
941     # directories and the like, so it can go ahead and start memoizing
942     # the string values of file system nodes.
943
944     fs.chdir(fs.Top)
945
946     SCons.Node.FS.save_strings(1)
947
948     # Now that we've read the SConscripts we can set the options
949     # that are SConscript settable:
950     SCons.Node.implicit_cache = options.implicit_cache
951     SCons.Node.FS.set_duplicate(options.duplicate)
952     fs.set_max_drift(options.max_drift)
953     if not options.stack_size is None:
954         SCons.Job.stack_size = options.stack_size
955
956     platform = SCons.Platform.platform_module()
957
958     if options.interactive:
959         SCons.Script.Interactive.interact(fs, OptionsParser, options,
960                                           targets, target_top)
961
962     else:
963
964         # Build the targets
965         nodes = _build_targets(fs, options, targets, target_top)
966         if not nodes:
967             exit_status = 2
968
969 def _build_targets(fs, options, targets, target_top):
970
971     progress_display.set_mode(not (options.no_progress or options.silent))
972     display.set_mode(not options.silent)
973     SCons.Action.print_actions          = not options.silent
974     SCons.Action.execute_actions        = not options.no_exec
975     SCons.SConf.dryrun                  = options.no_exec
976
977     if options.diskcheck:
978         SCons.Node.FS.set_diskcheck(options.diskcheck)
979
980     _set_debug_values(options)
981     SCons.Node.implicit_cache = options.implicit_cache
982     SCons.Node.implicit_deps_changed = options.implicit_deps_changed
983     SCons.Node.implicit_deps_unchanged = options.implicit_deps_unchanged
984
985     SCons.CacheDir.cache_enabled = not options.cache_disable
986     SCons.CacheDir.cache_debug = options.cache_debug
987     SCons.CacheDir.cache_force = options.cache_force
988     SCons.CacheDir.cache_show = options.cache_show
989
990     if options.no_exec:
991         CleanTask.execute = CleanTask.show
992     else:
993         CleanTask.execute = CleanTask.remove
994
995     lookup_top = None
996     if targets or SCons.Script.BUILD_TARGETS != SCons.Script._build_plus_default:
997         # They specified targets on the command line or modified
998         # BUILD_TARGETS in the SConscript file(s), so if they used -u,
999         # -U or -D, we have to look up targets relative to the top,
1000         # but we build whatever they specified.
1001         if target_top:
1002             lookup_top = fs.Dir(target_top)
1003             target_top = None
1004
1005         targets = SCons.Script.BUILD_TARGETS
1006     else:
1007         # There are no targets specified on the command line,
1008         # so if they used -u, -U or -D, we may have to restrict
1009         # what actually gets built.
1010         d = None
1011         if target_top:
1012             if options.climb_up == 1:
1013                 # -u, local directory and below
1014                 target_top = fs.Dir(target_top)
1015                 lookup_top = target_top
1016             elif options.climb_up == 2:
1017                 # -D, all Default() targets
1018                 target_top = None
1019                 lookup_top = None
1020             elif options.climb_up == 3:
1021                 # -U, local SConscript Default() targets
1022                 target_top = fs.Dir(target_top)
1023                 def check_dir(x, target_top=target_top):
1024                     if hasattr(x, 'cwd') and not x.cwd is None:
1025                         cwd = x.cwd.srcnode()
1026                         return cwd == target_top
1027                     else:
1028                         # x doesn't have a cwd, so it's either not a target,
1029                         # or not a file, so go ahead and keep it as a default
1030                         # target and let the engine sort it out:
1031                         return 1                
1032                 d = filter(check_dir, SCons.Script.DEFAULT_TARGETS)
1033                 SCons.Script.DEFAULT_TARGETS[:] = d
1034                 target_top = None
1035                 lookup_top = None
1036
1037         targets = SCons.Script._Get_Default_Targets(d, fs)
1038
1039     if not targets:
1040         sys.stderr.write("scons: *** No targets specified and no Default() targets found.  Stop.\n")
1041         return None
1042
1043     def Entry(x, ltop=lookup_top, ttop=target_top, fs=fs):
1044         if isinstance(x, SCons.Node.Node):
1045             node = x
1046         else:
1047             node = None
1048             # Why would ltop be None? Unfortunately this happens.
1049             if ltop == None: ltop = ''
1050             # Curdir becomes important when SCons is called with -u, -C,
1051             # or similar option that changes directory, and so the paths
1052             # of targets given on the command line need to be adjusted.
1053             curdir = os.path.join(os.getcwd(), str(ltop))
1054             for lookup in SCons.Node.arg2nodes_lookups:
1055                 node = lookup(x, curdir=curdir)
1056                 if node != None:
1057                     break
1058             if node is None:
1059                 node = fs.Entry(x, directory=ltop, create=1)
1060         if ttop and not node.is_under(ttop):
1061             if isinstance(node, SCons.Node.FS.Dir) and ttop.is_under(node):
1062                 node = ttop
1063             else:
1064                 node = None
1065         return node
1066
1067     nodes = filter(None, map(Entry, targets))
1068
1069     task_class = BuildTask      # default action is to build targets
1070     opening_message = "Building targets ..."
1071     closing_message = "done building targets."
1072     if options.keep_going:
1073         failure_message = "done building targets (errors occurred during build)."
1074     else:
1075         failure_message = "building terminated because of errors."
1076     if options.question:
1077         task_class = QuestionTask
1078     try:
1079         if options.clean:
1080             task_class = CleanTask
1081             opening_message = "Cleaning targets ..."
1082             closing_message = "done cleaning targets."
1083             if options.keep_going:
1084                 failure_message = "done cleaning targets (errors occurred during clean)."
1085             else:
1086                 failure_message = "cleaning terminated because of errors."
1087     except AttributeError:
1088         pass
1089
1090     task_class.progress = ProgressObject
1091
1092     if options.random:
1093         def order(dependencies):
1094             """Randomize the dependencies."""
1095             import random
1096             # This is cribbed from the implementation of
1097             # random.shuffle() in Python 2.X.
1098             d = dependencies
1099             for i in xrange(len(d)-1, 0, -1):
1100                 j = int(random.random() * (i+1))
1101                 d[i], d[j] = d[j], d[i]
1102             return d
1103     else:
1104         def order(dependencies):
1105             """Leave the order of dependencies alone."""
1106             return dependencies
1107
1108     if options.taskmastertrace_file == '-':
1109         tmtrace = sys.stdout
1110     elif options.taskmastertrace_file:
1111         tmtrace = open(options.taskmastertrace_file, 'wb')
1112     else:
1113         tmtrace = None
1114     taskmaster = SCons.Taskmaster.Taskmaster(nodes, task_class, order, tmtrace)
1115
1116     # Let the BuildTask objects get at the options to respond to the
1117     # various print_* settings, tree_printer list, etc.
1118     BuildTask.options = options
1119
1120     global num_jobs
1121     num_jobs = options.num_jobs
1122     jobs = SCons.Job.Jobs(num_jobs, taskmaster)
1123     if num_jobs > 1:
1124         msg = None
1125         if jobs.num_jobs == 1:
1126             msg = "parallel builds are unsupported by this version of Python;\n" + \
1127                   "\tignoring -j or num_jobs option.\n"
1128         elif sys.platform == 'win32':
1129             msg = fetch_win32_parallel_msg()
1130         if msg:
1131             SCons.Warnings.warn(SCons.Warnings.NoParallelSupportWarning, msg)
1132
1133     memory_stats.append('before building targets:')
1134     count_stats.append(('pre-', 'build'))
1135
1136     try:
1137         progress_display("scons: " + opening_message)
1138         try:
1139             jobs.run()
1140         except KeyboardInterrupt:
1141             # If we are in interactive mode, a KeyboardInterrupt
1142             # interrupts only this current run.  Return 'nodes' normally
1143             # so that the outer loop can clean up the nodes and continue.
1144             if options.interactive:
1145                 print "Build interrupted."
1146                 # Continue and return normally
1147     finally:
1148         jobs.cleanup()
1149         if exit_status:
1150             progress_display("scons: " + failure_message)
1151         else:
1152             progress_display("scons: " + closing_message)
1153         if not options.no_exec:
1154             SCons.SConsign.write()
1155
1156     memory_stats.append('after building targets:')
1157     count_stats.append(('post-', 'build'))
1158
1159     return nodes
1160
1161 def _exec_main(parser, values):
1162     sconsflags = os.environ.get('SCONSFLAGS', '')
1163     all_args = string.split(sconsflags) + sys.argv[1:]
1164
1165     options, args = parser.parse_args(all_args, values)
1166
1167     if type(options.debug) == type([]) and "pdb" in options.debug:
1168         import pdb
1169         pdb.Pdb().runcall(_main, parser)
1170     elif options.profile_file:
1171         from profile import Profile
1172
1173         # Some versions of Python 2.4 shipped a profiler that had the
1174         # wrong 'c_exception' entry in its dispatch table.  Make sure
1175         # we have the right one.  (This may put an unnecessary entry
1176         # in the table in earlier versions of Python, but its presence
1177         # shouldn't hurt anything).
1178         try:
1179             dispatch = Profile.dispatch
1180         except AttributeError:
1181             pass
1182         else:
1183             dispatch['c_exception'] = Profile.trace_dispatch_return
1184
1185         prof = Profile()
1186         try:
1187             prof.runcall(_main, parser)
1188         except SConsPrintHelpException, e:
1189             prof.dump_stats(options.profile_file)
1190             raise e
1191         except SystemExit:
1192             pass
1193         prof.dump_stats(options.profile_file)
1194     else:
1195         _main(parser)
1196
1197 def main():
1198     global OptionsParser
1199     global exit_status
1200     global first_command_start
1201
1202     # Check up front for a Python version we do not support.  We
1203     # delay the check for deprecated Python versions until later,
1204     # after the SConscript files have been read, in case they
1205     # disable that warning.
1206     if python_version_unsupported():
1207         msg = "scons: *** SCons version %s does not run under Python version %s.\n"
1208         sys.stderr.write(msg % (SCons.__version__, python_version_string()))
1209         sys.exit(1)
1210
1211     parts = ["SCons by Steven Knight et al.:\n"]
1212     try:
1213         parts.append(version_string("script", __main__))
1214     except KeyboardInterrupt:
1215         raise
1216     except:
1217         # On Windows there is no scons.py, so there is no
1218         # __main__.__version__, hence there is no script version.
1219         pass 
1220     parts.append(version_string("engine", SCons))
1221     parts.append("__COPYRIGHT__")
1222     version = string.join(parts, '')
1223
1224     import SConsOptions
1225     parser = SConsOptions.Parser(version)
1226     values = SConsOptions.SConsValues(parser.get_default_values())
1227
1228     OptionsParser = parser
1229     
1230     try:
1231         _exec_main(parser, values)
1232     except SystemExit, s:
1233         if s:
1234             exit_status = s
1235     except KeyboardInterrupt:
1236         print "Build interrupted."
1237         sys.exit(2)
1238     except SyntaxError, e:
1239         _scons_syntax_error(e)
1240     except SCons.Errors.InternalError:
1241         _scons_internal_error()
1242     except SCons.Errors.UserError, e:
1243         _scons_user_error(e)
1244     except SConsPrintHelpException:
1245         parser.print_help()
1246         exit_status = 0
1247     except:
1248         # An exception here is likely a builtin Python exception Python
1249         # code in an SConscript file.  Show them precisely what the
1250         # problem was and where it happened.
1251         SCons.Script._SConscript.SConscript_exception()
1252         sys.exit(2)
1253
1254     memory_stats.print_stats()
1255     count_stats.print_stats()
1256
1257     if print_objects:
1258         SCons.Debug.listLoggedInstances('*')
1259         #SCons.Debug.dumpLoggedInstances('*')
1260
1261     if print_memoizer:
1262         SCons.Memoize.Dump("Memoizer (memory cache) hits and misses:")
1263
1264     # Dump any development debug info that may have been enabled.
1265     # These are purely for internal debugging during development, so
1266     # there's no need to control them with --debug= options; they're
1267     # controlled by changing the source code.
1268     SCons.Debug.dump_caller_counts()
1269     SCons.Taskmaster.dump_stats()
1270
1271     if print_time:
1272         total_time = time.time() - SCons.Script.start_time
1273         if num_jobs == 1:
1274             ct = cumulative_command_time
1275         else:
1276             if last_command_end is None or first_command_start is None:
1277                 ct = 0.0
1278             else:
1279                 ct = last_command_end - first_command_start
1280         scons_time = total_time - sconscript_time - ct
1281         print "Total build time: %f seconds"%total_time
1282         print "Total SConscript file execution time: %f seconds"%sconscript_time
1283         print "Total SCons execution time: %f seconds"%scons_time
1284         print "Total command execution time: %f seconds"%ct
1285
1286     sys.exit(exit_status)