Make exception handling conform to Pythonic standards. (Kevin Quick)
[scons.git] / src / engine / SCons / Script / __init__.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 time
40 start_time = time.time()
41
42 import os
43 import os.path
44 import random
45 import string
46 import sys
47 import traceback
48
49 # Strip the script directory from sys.path() so on case-insensitive
50 # (WIN32) systems Python doesn't think that the "scons" script is the
51 # "SCons" package.  Replace it with our own version directory so, if
52 # if they're there, we pick up the right version of the build engine
53 # modules.
54 #sys.path = [os.path.join(sys.prefix,
55 #                         'lib',
56 #                         'scons-%d' % SCons.__version__)] + sys.path[1:]
57
58 import SCons.Debug
59 import SCons.Defaults
60 import SCons.Environment
61 import SCons.Errors
62 import SCons.Job
63 import SCons.Node
64 import SCons.Node.FS
65 from SCons.Optik import OptionParser, SUPPRESS_HELP, OptionValueError
66 import SCons.Script.SConscript
67 import SCons.Sig
68 import SCons.Taskmaster
69 import SCons.Util
70 import SCons.Warnings
71
72 #
73 import __builtin__
74 try:
75     __builtin__.zip
76 except AttributeError:
77     def zip(l1, l2):
78         result = []
79         for i in xrange(len(l1)):
80            result.append((l1[i], l2[i]))
81         return result
82     __builtin__.zip = zip
83
84 #
85 display = SCons.Util.display
86 progress_display = SCons.Util.DisplayEngine()
87
88 # Task control.
89 #
90 class BuildTask(SCons.Taskmaster.Task):
91     """An SCons build task."""
92     def display(self, message):
93         display('scons: ' + message)
94
95     def execute(self):
96         target = self.targets[0]
97         if target.get_state() == SCons.Node.up_to_date:
98             if self.top and target.has_builder():
99                 display("scons: `%s' is up to date." % str(self.node))
100         elif target.has_builder() and not hasattr(target.builder, 'status'):
101             if print_time:
102                 start_time = time.time()
103             SCons.Taskmaster.Task.execute(self)
104             if print_time:
105                 finish_time = time.time()
106                 global command_time
107                 command_time = command_time+finish_time-start_time
108                 print "Command execution time: %f seconds"%(finish_time-start_time)
109
110     def do_failed(self, status=2):
111         global exit_status
112         if ignore_errors:
113             SCons.Taskmaster.Task.executed(self)
114         elif keep_going_on_error:
115             SCons.Taskmaster.Task.fail_continue(self)
116             exit_status = status
117         else:
118             SCons.Taskmaster.Task.fail_stop(self)
119             exit_status = status
120             
121     def executed(self):
122         t = self.targets[0]
123         if self.top and not t.has_builder() and not t.side_effect:
124             if not t.exists():
125                 sys.stderr.write("scons: *** Do not know how to make target `%s'." % t)
126                 if not keep_going_on_error:
127                     sys.stderr.write("  Stop.")
128                 sys.stderr.write("\n")
129                 self.do_failed()
130             else:
131                 print "scons: Nothing to be done for `%s'." % t
132                 SCons.Taskmaster.Task.executed(self)
133         else:
134             SCons.Taskmaster.Task.executed(self)
135
136         # print the tree here instead of in execute() because
137         # this method is serialized, but execute isn't:
138         if print_tree and self.top:
139             print
140             print SCons.Util.render_tree(self.targets[0], get_all_children)
141         if print_dtree and self.top:
142             print
143             print SCons.Util.render_tree(self.targets[0], get_derived_children)
144         if print_includes and self.top:
145             t = self.targets[0]
146             tree = t.render_include_tree()
147             if tree:
148                 print
149                 print tree
150
151     def failed(self):
152         # Handle the failure of a build task.  The primary purpose here
153         # is to display the various types of Errors and Exceptions
154         # appropriately.
155         status = 2
156         e = sys.exc_value
157         t = sys.exc_type
158         tb = None
159         if t is SCons.Errors.TaskmasterException:
160             # The Taskmaster received an Error or Exception while trying
161             # to process or build the Nodes and dependencies, which it
162             # wrapped up for us in the object recorded as the value of
163             # the Exception, so process the wrapped info instead of the
164             # TaskmasterException itself.
165             t = e.type
166             tb = e.traceback
167             e = e.value
168
169         if t == SCons.Errors.BuildError:
170             sys.stderr.write("scons: *** [%s] %s\n" % (e.node, e.errstr))
171             if e.errstr == 'Exception':
172                 traceback.print_exception(e.args[0], e.args[1], e.args[2])
173         elif t == SCons.Errors.ExplicitExit:
174             status = e.status
175             sys.stderr.write("scons: *** [%s] Explicit exit, status %s\n" % (e.node, e.status))
176         else:
177             if e is None:
178                 e = t
179             s = str(e)
180             if t == SCons.Errors.StopError and not keep_going_on_error:
181                 s = s + '  Stop.'
182             sys.stderr.write("scons: *** %s\n" % s)
183
184             if tb:
185                 sys.stderr.write("scons: internal stack trace:\n")
186                 traceback.print_tb(tb, file=sys.stderr)
187
188         self.do_failed(status)
189
190     def make_ready(self):
191         """Make a task ready for execution"""
192         SCons.Taskmaster.Task.make_ready(self)
193         if self.out_of_date and print_explanations:
194             explanation = self.out_of_date[0].explain()
195             if explanation:
196                 sys.stdout.write("scons: " + explanation)
197
198 class CleanTask(SCons.Taskmaster.Task):
199     """An SCons clean task."""
200     def show(self):
201         if (self.targets[0].has_builder() or self.targets[0].side_effect) \
202            and not os.path.isdir(str(self.targets[0])):
203             display("Removed " + str(self.targets[0]))
204         if SCons.Environment.CleanTargets.has_key(self.targets[0]):
205             files = SCons.Environment.CleanTargets[self.targets[0]]
206             for f in files:
207                 SCons.Util.fs_delete(str(f), 0)
208
209     def remove(self):
210         if self.targets[0].has_builder() or self.targets[0].side_effect:
211             for t in self.targets:
212                 try:
213                     removed = t.remove()
214                 except OSError, e:
215                     print "scons: Could not remove '%s':" % str(t), e.strerror
216                 else:
217                     if removed:
218                         display("Removed " + str(t))
219         if SCons.Environment.CleanTargets.has_key(self.targets[0]):
220             files = SCons.Environment.CleanTargets[self.targets[0]]
221             for f in files:
222                 SCons.Util.fs_delete(str(f))
223
224     execute = remove
225
226     # Have the taskmaster arrange to "execute" all of the targets, because
227     # we'll figure out ourselves (in remove() or show() above) whether
228     # anything really needs to be done.
229     make_ready = SCons.Taskmaster.Task.make_ready_all
230
231     def prepare(self):
232         pass
233
234 class QuestionTask(SCons.Taskmaster.Task):
235     """An SCons task for the -q (question) option."""
236     def prepare(self):
237         pass
238     
239     def execute(self):
240         if self.targets[0].get_state() != SCons.Node.up_to_date:
241             global exit_status
242             exit_status = 1
243             self.tm.stop()
244
245     def executed(self):
246         pass
247
248 # Global variables
249
250 keep_going_on_error = 0
251 print_count = 0
252 print_dtree = 0
253 print_explanations = 0
254 print_includes = 0
255 print_objects = 0
256 print_time = 0
257 print_tree = 0
258 memory_stats = None
259 ignore_errors = 0
260 sconscript_time = 0
261 command_time = 0
262 exit_status = 0 # exit status, assume success by default
263 profiling = 0
264 repositories = []
265 num_jobs = 1 # this is modifed by SConscript.SetJobs()
266
267 # Exceptions for this module
268 class PrintHelp(Exception):
269     pass
270
271 # utility functions
272
273 def get_all_children(node): return node.all_children(None)
274
275 def get_derived_children(node):
276     children = node.all_children(None)
277     return filter(lambda x: x.has_builder(), children)
278
279 def _scons_syntax_error(e):
280     """Handle syntax errors. Print out a message and show where the error
281     occurred.
282     """
283     etype, value, tb = sys.exc_info()
284     lines = traceback.format_exception_only(etype, value)
285     for line in lines:
286         sys.stderr.write(line+'\n')
287     sys.exit(2)
288
289 def find_deepest_user_frame(tb):
290     """
291     Find the deepest stack frame that is not part of SCons.
292
293     Input is a "pre-processed" stack trace in the form
294     returned by traceback.extract_tb() or traceback.extract_stack()
295     """
296     
297     tb.reverse()
298
299     # find the deepest traceback frame that is not part
300     # of SCons:
301     for frame in tb:
302         filename = frame[0]
303         if string.find(filename, os.sep+'SCons'+os.sep) == -1:
304             return frame
305     return tb[0]
306
307 def _scons_user_error(e):
308     """Handle user errors. Print out a message and a description of the
309     error, along with the line number and routine where it occured. 
310     The file and line number will be the deepest stack frame that is
311     not part of SCons itself.
312     """
313     etype, value, tb = sys.exc_info()
314     filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_tb(tb))
315     sys.stderr.write("\nscons: *** %s\n" % value)
316     sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
317     sys.exit(2)
318
319 def _scons_user_warning(e):
320     """Handle user warnings. Print out a message and a description of
321     the warning, along with the line number and routine where it occured.
322     The file and line number will be the deepest stack frame that is
323     not part of SCons itself.
324     """
325     etype, value, tb = sys.exc_info()
326     filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_tb(tb))
327     sys.stderr.write("\nscons: warning: %s\n" % e)
328     sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
329
330 def _scons_internal_warning(e):
331     """Slightly different from _scons_user_warning in that we use the
332     *current call stack* rather than sys.exc_info() to get our stack trace.
333     This is used by the warnings framework to print warnings."""
334     filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_stack())
335     sys.stderr.write("\nscons: warning: %s\n" % e[0])
336     sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
337
338 def _scons_internal_error():
339     """Handle all errors but user errors. Print out a message telling
340     the user what to do in this case and print a normal trace.
341     """
342     print 'internal error'
343     traceback.print_exc()
344     sys.exit(2)
345
346 def _varargs(option, parser):
347     value = None
348     if parser.rargs:
349         arg = parser.rargs[0]
350         if arg[0] != "-":
351             value = arg
352             del parser.rargs[0]
353     return value
354
355 def _setup_warn(arg):
356     """The --warn option.  An argument to this option
357     should be of the form <warning-class> or no-<warning-class>.
358     The warning class is munged in order to get an actual class
359     name from the SCons.Warnings module to enable or disable.
360     The supplied <warning-class> is split on hyphens, each element
361     is captialized, then smushed back together.  Then the string
362     "SCons.Warnings." is added to the front and "Warning" is added
363     to the back to get the fully qualified class name.
364
365     For example, --warn=deprecated will enable the
366     SCons.Warnings.DeprecatedWarning class.
367
368     --warn=no-dependency will disable the
369     SCons.Warnings.DependencyWarning class.
370
371     As a special case, --warn=all and --warn=no-all
372     will enable or disable (respectively) the base
373     class of all warnings, which is SCons.Warning.Warning."""
374
375     elems = string.split(string.lower(arg), '-')
376     enable = 1
377     if elems[0] == 'no':
378         enable = 0
379         del elems[0]
380
381     if len(elems) == 1 and elems[0] == 'all':
382         class_name = "Warning"
383     else:
384         def _capitalize(s):
385             if s[:5] == "scons":
386                 return "SCons" + s[5:]
387             else:
388                 return string.capitalize(s)
389         class_name = string.join(map(_capitalize, elems), '') + "Warning"
390     try:
391         clazz = getattr(SCons.Warnings, class_name)
392     except AttributeError:
393         sys.stderr.write("No warning type: '%s'\n" % arg)
394     else:
395         if enable:
396             SCons.Warnings.enableWarningClass(clazz)
397         else:
398             SCons.Warnings.suppressWarningClass(clazz)
399
400 def _SConstruct_exists(dirname=''):
401     """This function checks that an SConstruct file exists in a directory.
402     If so, it returns the path of the file. By default, it checks the
403     current directory.
404     """
405     global repositories
406     for file in ['SConstruct', 'Sconstruct', 'sconstruct']:
407         sfile = os.path.join(dirname, file)
408         if os.path.isfile(sfile):
409             return sfile
410         if not os.path.isabs(sfile):
411             for rep in repositories:
412                 if os.path.isfile(os.path.join(rep, sfile)):
413                     return sfile
414     return None
415
416 def _set_globals(options):
417     global repositories, keep_going_on_error, ignore_errors
418     global print_count, print_dtree
419     global print_explanations, print_includes
420     global print_objects, print_time, print_tree
421     global memory_outf, memory_stats
422
423     if options.repository:
424         repositories.extend(options.repository)
425     keep_going_on_error = options.keep_going
426     try:
427         if options.debug:
428             if options.debug == "count":
429                 print_count = 1
430             elif options.debug == "dtree":
431                 print_dtree = 1
432             elif options.debug == "explain":
433                 print_explanations = 1
434             elif options.debug == "includes":
435                 print_includes = 1
436             elif options.debug == "memory":
437                 memory_stats = []
438                 memory_outf = sys.stdout
439             elif options.debug == "objects":
440                 print_objects = 1
441             elif options.debug == "presub":
442                 SCons.Action.print_actions_presub = 1
443             elif options.debug == "time":
444                 print_time = 1
445             elif options.debug == "tree":
446                 print_tree = 1
447     except AttributeError:
448         pass
449     ignore_errors = options.ignore_errors
450
451 def _create_path(plist):
452     path = '.'
453     for d in plist:
454         if os.path.isabs(d):
455             path = d
456         else:
457             path = path + '/' + d
458     return path
459
460
461 class OptParser(OptionParser):
462     def __init__(self):
463         import __main__
464         import SCons
465         parts = ["SCons by Steven Knight et al.:\n"]
466         try:
467             parts.append("\tscript: v%s.%s, %s, by %s on %s\n" % (__main__.__version__,
468                                                                   __main__.__build__,
469                                                                   __main__.__date__,
470                                                                   __main__.__developer__,
471                                                                   __main__.__buildsys__))
472         except KeyboardInterrupt:
473             raise
474         except:
475             # On win32 there is no scons.py, so there is no __main__.__version__,
476             # hence there is no script version.
477             pass 
478         parts.append("\tengine: v%s.%s, %s, by %s on %s\n" % (SCons.__version__,
479                                                               SCons.__build__,
480                                                               SCons.__date__,
481                                                               SCons.__developer__,
482                                                               SCons.__buildsys__))
483         parts.append("__COPYRIGHT__")
484         OptionParser.__init__(self, version=string.join(parts, ''),
485                               usage="usage: scons [OPTION] [TARGET] ...")
486
487         # options ignored for compatibility
488         def opt_ignore(option, opt, value, parser):
489             sys.stderr.write("Warning:  ignoring %s option\n" % opt)
490         self.add_option("-b", "-m", "-S", "-t", "--no-keep-going", "--stop",
491                         "--touch", action="callback", callback=opt_ignore,
492                         help="Ignored for compatibility.")
493
494         self.add_option('-c', '--clean', '--remove', action="store_true",
495                         dest="clean",
496                         help="Remove specified targets and dependencies.")
497
498         self.add_option('-C', '--directory', type="string", action = "append",
499                         metavar="DIR",
500                         help="Change to DIR before doing anything.")
501
502         self.add_option('--cache-disable', '--no-cache',
503                         action="store_true", dest='cache_disable', default=0,
504                         help="Do not retrieve built targets from CacheDir.")
505
506         self.add_option('--cache-force', '--cache-populate',
507                         action="store_true", dest='cache_force', default=0,
508                         help="Copy already-built targets into the CacheDir.")
509
510         self.add_option('--cache-show',
511                         action="store_true", dest='cache_show', default=0,
512                         help="Print build actions for files from CacheDir.")
513
514         def opt_not_yet(option, opt, value, parser):
515             sys.stderr.write("Warning:  the %s option is not yet implemented\n" % opt)
516             sys.exit(0)
517         self.add_option('-d', action="callback",
518                         callback=opt_not_yet,
519                         help = "Print file dependency information.")
520         
521         self.add_option('-D', action="store_const", const=2, dest="climb_up",
522                         help="Search up directory tree for SConstruct,       "
523                              "build all Default() targets.")
524
525         debug_options = ["count", "dtree", "explain",
526                          "includes", "memory", "objects",
527                          "pdb", "presub", "time", "tree"]
528
529         def opt_debug(option, opt, value, parser, debug_options=debug_options):
530             if value in debug_options:
531                 parser.values.debug = value
532             else:
533                 raise OptionValueError("Warning:  %s is not a valid debug type" % value)
534         self.add_option('--debug', action="callback", type="string",
535                         callback=opt_debug, nargs=1, dest="debug",
536                         metavar="TYPE",
537                         help="Print various types of debugging information: "
538                              "%s." % string.join(debug_options, ", "))
539
540         def opt_duplicate(option, opt, value, parser):
541             if not value in SCons.Node.FS.Valid_Duplicates:
542                 raise OptionValueError("`%s' is not a valid duplication style." % value)
543             parser.values.duplicate = value
544             # Set the duplicate style right away so it can affect linking
545             # of SConscript files.
546             SCons.Node.FS.set_duplicate(value)
547         self.add_option('--duplicate', action="callback", type="string",
548                         callback=opt_duplicate, nargs=1, dest="duplicate",
549                         help="Set the preferred duplication methods. Must be one of "
550                         + string.join(SCons.Node.FS.Valid_Duplicates, ", "))
551
552         self.add_option('-f', '--file', '--makefile', '--sconstruct',
553                         action="append", nargs=1,
554                         help="Read FILE as the top-level SConstruct file.")
555
556         self.add_option('-h', '--help', action="store_true", default=0,
557                         dest="help_msg",
558                         help="Print defined help message, or this one.")
559
560         self.add_option("-H", "--help-options",
561                         action="help",
562                         help="Print this message and exit.")
563
564         self.add_option('-i', '--ignore-errors', action="store_true",
565                         default=0, dest='ignore_errors',
566                         help="Ignore errors from build actions.")
567
568         self.add_option('-I', '--include-dir', action="append",
569                         dest='include_dir', metavar="DIR",
570                         help="Search DIR for imported Python modules.")
571
572         self.add_option('--implicit-cache', action="store_true",
573                         dest='implicit_cache',
574                         help="Cache implicit dependencies")
575
576         self.add_option('--implicit-deps-changed', action="store_true",
577                         default=0, dest='implicit_deps_changed',
578                         help="Ignore cached implicit dependencies.")
579         self.add_option('--implicit-deps-unchanged', action="store_true",
580                         default=0, dest='implicit_deps_unchanged',
581                         help="Ignore changes in implicit dependencies.")
582
583         def opt_j(option, opt, value, parser):
584             value = int(value)
585             parser.values.num_jobs = value
586         self.add_option('-j', '--jobs', action="callback", type="int",
587                         callback=opt_j, metavar="N",
588                         help="Allow N jobs at once.")
589
590         self.add_option('-k', '--keep-going', action="store_true", default=0,
591                         dest='keep_going',
592                         help="Keep going when a target can't be made.")
593
594         self.add_option('--max-drift', type="int", action="store",
595                         dest='max_drift', metavar="N",
596                         help="Set maximum system clock drift to N seconds.")
597
598         self.add_option('-n', '--no-exec', '--just-print', '--dry-run',
599                         '--recon', action="store_true", dest='noexec',
600                         default=0, help="Don't build; just print commands.")
601
602         def opt_profile(option, opt, value, parser):
603             global profiling
604             if not profiling:
605                 profiling = 1
606                 import profile
607                 profile.run('SCons.Script.main()', value)
608                 sys.exit(exit_status)
609         self.add_option('--profile', nargs=1, action="callback",
610                         callback=opt_profile, type="string", dest="profile",
611                         metavar="FILE",
612                         help="Profile SCons and put results in FILE.")
613
614         self.add_option('-q', '--question', action="store_true", default=0,
615                         help="Don't build; exit status says if up to date.")
616
617         self.add_option('-Q', dest='no_progress', action="store_true",
618                         default=0,
619                         help="Suppress \"Reading/Building\" progress messages.")
620
621         self.add_option('--random', dest="random", action="store_true",
622                         default=0, help="Build dependencies in random order.")
623
624         self.add_option('-s', '--silent', '--quiet', action="store_true",
625                         default=0, help="Don't print commands.")
626
627         self.add_option('--save-explain-info', type="int", action="store",
628                         dest='save_explain_info', metavar='0|1',
629                         help="(Don't) save --debug=explain information")
630
631         self.add_option('-u', '--up', '--search-up', action="store_const",
632                         dest="climb_up", default=0, const=1,
633                         help="Search up directory tree for SConstruct,       "
634                              "build targets at or below current directory.")
635         self.add_option('-U', action="store_const", dest="climb_up",
636                         default=0, const=3,
637                         help="Search up directory tree for SConstruct,       "
638                              "build Default() targets from local SConscript.")
639
640         self.add_option("-v", "--version",
641                         action="version",
642                         help="Print the SCons version number and exit.")
643
644         self.add_option('--warn', '--warning', nargs=1, action="store",
645                         metavar="WARNING-SPEC",
646                         help="Enable or disable warnings.")
647
648         self.add_option('-Y', '--repository', nargs=1, action="append",
649                         help="Search REPOSITORY for source and target files.")
650
651         self.add_option('-e', '--environment-overrides', action="callback",
652                         callback=opt_not_yet,
653                         # help="Environment variables override makefiles."
654                         help=SUPPRESS_HELP)
655         self.add_option('-l', '--load-average', '--max-load', action="callback",
656                         callback=opt_not_yet, type="int", dest="load_average",
657                         # action="store",
658                         # help="Don't start multiple jobs unless load is below "
659                         #      "LOAD-AVERAGE."
660                         # type="int",
661                         help=SUPPRESS_HELP)
662         self.add_option('--list-derived', action="callback",
663                         callback=opt_not_yet,
664                         # help="Don't build; list files that would be built."
665                         help=SUPPRESS_HELP)
666         self.add_option('--list-actions', action="callback",
667                         callback=opt_not_yet,
668                         # help="Don't build; list files and build actions."
669                         help=SUPPRESS_HELP)
670         self.add_option('--list-where', action="callback",
671                         callback=opt_not_yet,
672                         # help="Don't build; list files and where defined."
673                         help=SUPPRESS_HELP)
674         self.add_option('-o', '--old-file', '--assume-old', action="callback",
675                         callback=opt_not_yet, type="string", dest="old_file",
676                         # help = "Consider FILE to be old; don't rebuild it."
677                         help=SUPPRESS_HELP)
678         self.add_option('--override', action="callback", dest="override",
679                         callback=opt_not_yet, type="string",
680                         # help="Override variables as specified in FILE."
681                         help=SUPPRESS_HELP)
682         self.add_option('-p', action="callback",
683                         callback=opt_not_yet,
684                         # help="Print internal environments/objects."
685                         help=SUPPRESS_HELP)
686         self.add_option('-r', '-R', '--no-builtin-rules',
687                         '--no-builtin-variables', action="callback",
688                         callback=opt_not_yet,
689                         # help="Clear default environments and variables."
690                         help=SUPPRESS_HELP)
691         self.add_option('-w', '--print-directory', action="callback",
692                         callback=opt_not_yet,
693                         # help="Print the current directory."
694                         help=SUPPRESS_HELP)
695         self.add_option('--no-print-directory', action="callback",
696                         callback=opt_not_yet,
697                         # help="Turn off -w, even if it was turned on implicitly."
698                         help=SUPPRESS_HELP)
699         self.add_option('--write-filenames', action="callback",
700                         callback=opt_not_yet, type="string", dest="write_filenames",
701                         # help="Write all filenames examined into FILE."
702                         help=SUPPRESS_HELP)
703         self.add_option('-W', '--what-if', '--new-file', '--assume-new',
704                         dest="new_file",
705                         action="callback", callback=opt_not_yet, type="string",
706                         # help="Consider FILE to be changed."
707                         help=SUPPRESS_HELP)
708         self.add_option('--warn-undefined-variables', action="callback",
709                         callback=opt_not_yet,
710                         # help="Warn when an undefined variable is referenced."
711                         help=SUPPRESS_HELP)
712
713     def parse_args(self, args=None, values=None):
714         opt, arglist = OptionParser.parse_args(self, args, values)
715         if opt.implicit_deps_changed or opt.implicit_deps_unchanged:
716             opt.implicit_cache = 1
717         return opt, arglist
718
719 class SConscriptSettableOptions:
720     """This class wraps an OptParser instance and provides
721     uniform access to options that can be either set on the command
722     line or from a SConscript file. A value specified on the command
723     line always overrides a value set in a SConscript file.
724     Not all command line options are SConscript settable, and the ones
725     that are must be explicitly added to settable dictionary and optionally
726     validated and coerced in the set() method."""
727     
728     def __init__(self, options):
729         self.options = options
730
731         # This dictionary stores the defaults for all the SConscript
732         # settable options, as well as indicating which options
733         # are SConscript settable. 
734         self.settable = {'num_jobs':1,
735                          'max_drift':SCons.Sig.default_max_drift,
736                          'implicit_cache':0,
737                          'clean':0,
738                          'duplicate':'hard-soft-copy',
739                          'save_explain_info':1}
740
741     def get(self, name):
742         if not self.settable.has_key(name):
743             raise SCons.Error.UserError, "This option is not settable from a SConscript file: %s"%name
744         if hasattr(self.options, name) and getattr(self.options, name) is not None:
745             return getattr(self.options, name)
746         else:
747             return self.settable[name]
748
749     def set(self, name, value):
750         if not self.settable.has_key(name):
751             raise SCons.Error.UserError, "This option is not settable from a SConscript file: %s"%name
752
753         if name == 'num_jobs':
754             try:
755                 value = int(value)
756                 if value < 1:
757                     raise ValueError
758             except ValueError:
759                 raise SCons.Errors.UserError, "A positive integer is required: %s"%repr(value)
760         elif name == 'max_drift':
761             try:
762                 value = int(value)
763             except ValueError:
764                 raise SCons.Errors.UserError, "An integer is required: %s"%repr(value)
765         elif name == 'duplicate':
766             try:
767                 value = str(value)
768             except ValueError:
769                 raise SCons.Errors.UserError, "A string is required: %s"%repr(value)
770             if not value in SCons.Node.FS.Valid_Duplicates:
771                 raise SCons.Errors.UserError, "Not a valid duplication style: %s" % value
772             # Set the duplicate stye right away so it can affect linking
773             # of SConscript files.
774             SCons.Node.FS.set_duplicate(value)
775
776         self.settable[name] = value
777     
778
779 def _main(args, parser):
780     targets = []
781     fs = SCons.Node.FS.default_fs
782
783     # Enable deprecated warnings by default.
784     SCons.Warnings._warningOut = _scons_internal_warning
785     SCons.Warnings.enableWarningClass(SCons.Warnings.CorruptSConsignWarning)
786     SCons.Warnings.enableWarningClass(SCons.Warnings.DeprecatedWarning)
787     SCons.Warnings.enableWarningClass(SCons.Warnings.DuplicateEnvironmentWarning)
788     SCons.Warnings.enableWarningClass(SCons.Warnings.MissingSConscriptWarning)
789     SCons.Warnings.enableWarningClass(SCons.Warnings.NoParallelSupportWarning)
790     # This is good for newbies, and hopefully most everyone else too.
791     SCons.Warnings.enableWarningClass(SCons.Warnings.MisleadingKeywordsWarning)
792
793     global ssoptions
794     ssoptions = SConscriptSettableOptions(options)
795
796     if options.help_msg:
797         def raisePrintHelp(text):
798             raise PrintHelp, text
799         SCons.Script.SConscript.HelpFunction = raisePrintHelp
800
801     _set_globals(options)
802     SCons.Node.implicit_cache = options.implicit_cache
803     SCons.Node.implicit_deps_changed = options.implicit_deps_changed
804     SCons.Node.implicit_deps_unchanged = options.implicit_deps_unchanged
805     if options.warn:
806         _setup_warn(options.warn)
807     if options.noexec:
808         SCons.SConf.dryrun = 1
809         SCons.Action.execute_actions = None
810         CleanTask.execute = CleanTask.show
811     if options.question:
812         SCons.SConf.dryrun = 1
813
814     if options.no_progress or options.silent:
815         progress_display.set_mode(0)
816     if options.silent:
817         display.set_mode(0)
818     if options.silent:
819         SCons.Action.print_actions = None
820     if options.cache_disable:
821         def disable(self): pass
822         fs.CacheDir = disable
823     if options.cache_force:
824         fs.cache_force = 1
825     if options.cache_show:
826         fs.cache_show = 1
827     if options.directory:
828         cdir = _create_path(options.directory)
829         try:
830             os.chdir(cdir)
831         except OSError:
832             sys.stderr.write("Could not change directory to %s\n" % cdir)
833
834     xmit_args = []
835     for a in args:
836         if '=' in a:
837             xmit_args.append(a)
838         else:
839             targets.append(a)
840     SCons.Script.SConscript._scons_add_args(xmit_args)
841     SCons.Script.SConscript._scons_add_targets(targets)
842
843     target_top = None
844     if options.climb_up:
845         target_top = '.'  # directory to prepend to targets
846         script_dir = os.getcwd()  # location of script
847         while script_dir and not _SConstruct_exists(script_dir):
848             script_dir, last_part = os.path.split(script_dir)
849             if last_part:
850                 target_top = os.path.join(last_part, target_top)
851             else:
852                 script_dir = ''
853         if script_dir:
854             display("scons: Entering directory `%s'" % script_dir)
855             os.chdir(script_dir)
856         else:
857             raise SCons.Errors.UserError, "No SConstruct file found."
858
859     fs.set_toplevel_dir(os.getcwd())
860
861     scripts = []
862     if options.file:
863         scripts.extend(options.file)
864     if not scripts:
865         sfile = _SConstruct_exists()
866         if sfile:
867             scripts.append(sfile)
868
869     if options.help_msg:
870         if not scripts:
871             # There's no SConstruct, but they specified -h.
872             # Give them the options usage now, before we fail
873             # trying to read a non-existent SConstruct file.
874             parser.print_help()
875             sys.exit(0)
876         SCons.Script.SConscript.print_help = 1
877
878     if not scripts:
879         raise SCons.Errors.UserError, "No SConstruct file found."
880
881     if scripts[0] == "-":
882         d = fs.getcwd()
883     else:
884         d = fs.File(scripts[0]).dir
885     fs.set_SConstruct_dir(d)
886
887     class Unbuffered:
888         def __init__(self, file):
889             self.file = file
890         def write(self, arg):
891             self.file.write(arg)
892             self.file.flush()
893         def __getattr__(self, attr):
894             return getattr(self.file, attr)
895
896     sys.stdout = Unbuffered(sys.stdout)
897
898     if options.include_dir:
899         sys.path = options.include_dir + sys.path
900
901     global repositories
902     for rep in repositories:
903         fs.Repository(rep)
904
905     if not memory_stats is None: memory_stats.append(SCons.Debug.memory())
906
907     progress_display("scons: Reading SConscript files ...")
908     try:
909         start_time = time.time()
910         try:
911             for script in scripts:
912                 SCons.Script.SConscript._SConscript(fs, script)
913         except SCons.Errors.StopError, e:
914             # We had problems reading an SConscript file, such as it
915             # couldn't be copied in to the BuildDir.  Since we're just
916             # reading SConscript files and haven't started building
917             # things yet, stop regardless of whether they used -i or -k
918             # or anything else, but don't say "Stop." on the message.
919             global exit_status
920             sys.stderr.write("scons: *** %s\n" % e)
921             exit_status = 2
922             sys.exit(exit_status)
923         global sconscript_time
924         sconscript_time = time.time() - start_time
925     except PrintHelp, text:
926         progress_display("scons: done reading SConscript files.")
927         print text
928         print "Use scons -H for help about command-line options."
929         sys.exit(0)
930     progress_display("scons: done reading SConscript files.")
931
932     # Tell the Node.FS subsystem that we're all done reading the
933     # SConscript files and calling Repository() and BuildDir() and the
934     # like, so it can go ahead and start memoizing the string values of
935     # file system nodes.
936     SCons.Node.FS.save_strings(1)
937
938     if not memory_stats is None: memory_stats.append(SCons.Debug.memory())
939
940     fs.chdir(fs.Top)
941
942     if options.help_msg:
943         # They specified -h, but there was no Help() inside the
944         # SConscript files.  Give them the options usage.
945         parser.print_help(sys.stdout)
946         sys.exit(0)
947
948     # Now that we've read the SConscripts we can set the options
949     # that are SConscript settable:
950     SCons.Node.implicit_cache = ssoptions.get('implicit_cache')
951     SCons.Node.FS.set_duplicate(ssoptions.get('duplicate'))
952     SCons.Node.Save_Explain_Info = ssoptions.get('save_explain_info') or print_explanations
953
954     lookup_top = None
955     if targets:
956         # They specified targets on the command line, so if they
957         # used -u, -U or -D, we have to look up targets relative
958         # to the top, but we build whatever they specified.
959         if target_top:
960             lookup_top = fs.Dir(target_top)
961             target_top = None
962     else:
963         # There are no targets specified on the command line,
964         # so if they used -u, -U or -D, we may have to restrict
965         # what actually gets built.
966         d = None
967         if target_top:
968             if options.climb_up == 1:
969                 # -u, local directory and below
970                 target_top = fs.Dir(target_top)
971                 lookup_top = target_top
972             elif options.climb_up == 2:
973                 # -D, all Default() targets
974                 target_top = None
975                 lookup_top = None
976             elif options.climb_up == 3:
977                 # -U, local SConscript Default() targets
978                 target_top = fs.Dir(target_top)
979                 def check_dir(x, target_top=target_top):
980                     if hasattr(x, 'cwd') and not x.cwd is None:
981                         cwd = x.cwd.srcnode()
982                         return cwd == target_top
983                     else:
984                         # x doesn't have a cwd, so it's either not a target,
985                         # or not a file, so go ahead and keep it as a default
986                         # target and let the engine sort it out:
987                         return 1                
988                 d = filter(check_dir, SCons.Script.SConscript.DefaultTargets)
989                 SCons.Script.SConscript.DefaultTargets[:] = d
990                 target_top = None
991                 lookup_top = None
992
993         if SCons.Script.SConscript.DefaultCalled:
994             targets = SCons.Script.SConscript.DefaultTargets
995         else:
996             if d is None:
997                 d = [fs.Dir('.')]
998             targets = d
999
1000
1001     if not targets:
1002         sys.stderr.write("scons: *** No targets specified and no Default() targets found.  Stop.\n")
1003         sys.exit(2)
1004
1005     def Entry(x, ltop=lookup_top, ttop=target_top, fs=fs):
1006         if isinstance(x, SCons.Node.Node):
1007             node = x
1008         else:
1009             node = SCons.Node.Alias.default_ans.lookup(x)
1010             if node is None:
1011                 node = fs.Entry(x, directory=ltop, create=1)
1012         if ttop and not node.is_under(ttop):
1013             if isinstance(node, SCons.Node.FS.Dir) and ttop.is_under(node):
1014                 node = ttop
1015             else:
1016                 node = None
1017         return node
1018
1019     nodes = filter(lambda x: x is not None, map(Entry, targets))
1020
1021     task_class = BuildTask      # default action is to build targets
1022     opening_message = "Building targets ..."
1023     closing_message = "done building targets."
1024     failure_message = "building terminated because of errors."
1025     if options.question:
1026         task_class = QuestionTask
1027     try:
1028         if ssoptions.get('clean'):
1029             task_class = CleanTask
1030             opening_message = "Cleaning targets ..."
1031             closing_message = "done cleaning targets."
1032             failure_message = "cleaning terminated because of errors."
1033     except AttributeError:
1034         pass
1035
1036     SCons.Environment.CalculatorArgs['max_drift'] = ssoptions.get('max_drift')
1037
1038     if options.random:
1039         def order(dependencies):
1040             """Randomize the dependencies."""
1041             # This is cribbed from the implementation of
1042             # random.shuffle() in Python 2.X.
1043             d = dependencies
1044             for i in xrange(len(d)-1, 0, -1):
1045                 j = int(random.random() * (i+1))
1046                 d[i], d[j] = d[j], d[i]
1047             return d
1048     else:
1049         def order(dependencies):
1050             """Leave the order of dependencies alone."""
1051             return dependencies
1052
1053     progress_display("scons: " + opening_message)
1054     taskmaster = SCons.Taskmaster.Taskmaster(nodes, task_class, order)
1055
1056     nj = ssoptions.get('num_jobs')
1057     jobs = SCons.Job.Jobs(nj, taskmaster)
1058     if nj > 1 and jobs.num_jobs == 1:
1059         msg = "parallel builds are unsupported by this version of Python;\n" + \
1060               "\tignoring -j or num_jobs option.\n"
1061         SCons.Warnings.warn(SCons.Warnings.NoParallelSupportWarning, msg)
1062
1063     if not memory_stats is None: memory_stats.append(SCons.Debug.memory())
1064
1065     try:
1066         jobs.run()
1067     finally:
1068         if exit_status:
1069             progress_display("scons: " + failure_message)
1070         else:
1071             progress_display("scons: " + closing_message)
1072         if not options.noexec:
1073             SCons.SConsign.write()
1074
1075     if not memory_stats is None:
1076         memory_stats.append(SCons.Debug.memory())
1077         when = [
1078             'before SConscript files',
1079             'after SConscript files',
1080             'before building',
1081             'after building',
1082         ]
1083         for i in xrange(len(when)):
1084             memory_outf.write('Memory %s:  %d\n' % (when[i], memory_stats[i]))
1085
1086     if print_count:
1087         SCons.Debug.countLoggedInstances('*')
1088
1089     if print_objects:
1090         SCons.Debug.listLoggedInstances('*')
1091         #SCons.Debug.dumpLoggedInstances('*')
1092
1093 def _exec_main():
1094     all_args = sys.argv[1:]
1095     try:
1096         all_args = string.split(os.environ['SCONSFLAGS']) + all_args
1097     except KeyError:
1098             # it's OK if there's no SCONSFLAGS
1099             pass
1100     parser = OptParser()
1101     global options
1102     options, args = parser.parse_args(all_args)
1103     if options.debug == "pdb":
1104         import pdb
1105         pdb.Pdb().runcall(_main, args, parser)
1106     else:
1107         _main(args, parser)
1108
1109 def main():
1110     global exit_status
1111     
1112     try:
1113         _exec_main()
1114     except SystemExit, s:
1115         if s:
1116             exit_status = s
1117     except KeyboardInterrupt:
1118         print "Build interrupted."
1119         sys.exit(2)
1120     except SyntaxError, e:
1121         _scons_syntax_error(e)
1122     except SCons.Errors.InternalError:
1123         _scons_internal_error()
1124     except SCons.Errors.UserError, e:
1125         _scons_user_error(e)
1126     except SCons.Errors.ConfigureDryRunError, e:
1127         _scons_configure_dryrun_error(e)
1128     except:
1129         # An exception here is likely a builtin Python exception Python
1130         # code in an SConscript file.  Show them precisely what the
1131         # problem was and where it happened.
1132         SCons.Script.SConscript.SConscript_exception()
1133         sys.exit(2)
1134
1135     if print_time:
1136         total_time = time.time()-start_time
1137         scons_time = total_time-sconscript_time-command_time
1138         print "Total build time: %f seconds"%total_time
1139         print "Total SConscript file execution time: %f seconds"%sconscript_time
1140         print "Total SCons execution time: %f seconds"%scons_time
1141         print "Total command execution time: %f seconds"%command_time
1142
1143     sys.exit(exit_status)