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