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