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