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