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