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