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