Fix --debug=memoizer so it actually prints some stats on Python 2.x.
[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         else:
872             raise SCons.Errors.UserError, "No SConstruct file found."
873
874     fs.set_toplevel_dir(os.getcwd())
875
876     scripts = []
877     if options.file:
878         scripts.extend(options.file)
879     if not scripts:
880         sfile = _SConstruct_exists()
881         if sfile:
882             scripts.append(sfile)
883
884     if options.help_msg:
885         if not scripts:
886             # There's no SConstruct, but they specified -h.
887             # Give them the options usage now, before we fail
888             # trying to read a non-existent SConstruct file.
889             parser.print_help()
890             sys.exit(0)
891
892     if not scripts:
893         raise SCons.Errors.UserError, "No SConstruct file found."
894
895     if scripts[0] == "-":
896         d = fs.getcwd()
897     else:
898         d = fs.File(scripts[0]).dir
899     fs.set_SConstruct_dir(d)
900
901     class Unbuffered:
902         def __init__(self, file):
903             self.file = file
904         def write(self, arg):
905             self.file.write(arg)
906             self.file.flush()
907         def __getattr__(self, attr):
908             return getattr(self.file, attr)
909
910     sys.stdout = Unbuffered(sys.stdout)
911
912     if options.include_dir:
913         sys.path = options.include_dir + sys.path
914
915     global repositories
916     for rep in repositories:
917         fs.Repository(rep)
918
919     if not memory_stats is None: memory_stats.append(SCons.Debug.memory())
920     if not count_stats is None: count_stats.append(SCons.Debug.fetchLoggedInstances())
921
922     progress_display("scons: Reading SConscript files ...")
923
924     start_time = time.time()
925     try:
926         for script in scripts:
927             SCons.Script._SConscript._SConscript(fs, script)
928     except SCons.Errors.StopError, e:
929         # We had problems reading an SConscript file, such as it
930         # couldn't be copied in to the BuildDir.  Since we're just
931         # reading SConscript files and haven't started building
932         # things yet, stop regardless of whether they used -i or -k
933         # or anything else.
934         global exit_status
935         sys.stderr.write("scons: *** %s  Stop.\n" % e)
936         exit_status = 2
937         sys.exit(exit_status)
938     global sconscript_time
939     sconscript_time = time.time() - start_time
940     SCons.SConf.CreateConfigHBuilder(SCons.Defaults.DefaultEnvironment())
941     progress_display("scons: done reading SConscript files.")
942
943     # Tell the Node.FS subsystem that we're all done reading the
944     # SConscript files and calling Repository() and BuildDir() and the
945     # like, so it can go ahead and start memoizing the string values of
946     # file system nodes.
947     SCons.Node.FS.save_strings(1)
948
949     if not memory_stats is None: memory_stats.append(SCons.Debug.memory())
950     if not count_stats is None: count_stats.append(SCons.Debug.fetchLoggedInstances())
951
952     fs.chdir(fs.Top)
953
954     if options.help_msg:
955         help_text = SCons.Script.help_text
956         if help_text is None:
957             # They specified -h, but there was no Help() inside the
958             # SConscript files.  Give them the options usage.
959             parser.print_help(sys.stdout)
960         else:
961             print help_text
962             print "Use scons -H for help about command-line options."
963         sys.exit(0)
964
965     # Now that we've read the SConscripts we can set the options
966     # that are SConscript settable:
967     SCons.Node.implicit_cache = ssoptions.get('implicit_cache')
968     SCons.Node.FS.set_duplicate(ssoptions.get('duplicate'))
969
970     lookup_top = None
971     if targets:
972         # They specified targets on the command line, so if they
973         # used -u, -U or -D, we have to look up targets relative
974         # to the top, but we build whatever they specified.
975         if target_top:
976             lookup_top = fs.Dir(target_top)
977             target_top = None
978     else:
979         # There are no targets specified on the command line,
980         # so if they used -u, -U or -D, we may have to restrict
981         # what actually gets built.
982         d = None
983         if target_top:
984             if options.climb_up == 1:
985                 # -u, local directory and below
986                 target_top = fs.Dir(target_top)
987                 lookup_top = target_top
988             elif options.climb_up == 2:
989                 # -D, all Default() targets
990                 target_top = None
991                 lookup_top = None
992             elif options.climb_up == 3:
993                 # -U, local SConscript Default() targets
994                 target_top = fs.Dir(target_top)
995                 def check_dir(x, target_top=target_top):
996                     if hasattr(x, 'cwd') and not x.cwd is None:
997                         cwd = x.cwd.srcnode()
998                         return cwd == target_top
999                     else:
1000                         # x doesn't have a cwd, so it's either not a target,
1001                         # or not a file, so go ahead and keep it as a default
1002                         # target and let the engine sort it out:
1003                         return 1                
1004                 d = filter(check_dir, SCons.Script.DEFAULT_TARGETS)
1005                 SCons.Script.DEFAULT_TARGETS[:] = d
1006                 target_top = None
1007                 lookup_top = None
1008
1009         targets = SCons.Script._Get_Default_Targets(d, fs)
1010
1011     if not targets:
1012         sys.stderr.write("scons: *** No targets specified and no Default() targets found.  Stop.\n")
1013         sys.exit(2)
1014
1015     def Entry(x, ltop=lookup_top, ttop=target_top, fs=fs):
1016         if isinstance(x, SCons.Node.Node):
1017             node = x
1018         else:
1019             node = SCons.Node.Alias.default_ans.lookup(x)
1020             if node is None:
1021                 node = fs.Entry(x, directory=ltop, create=1)
1022         if ttop and not node.is_under(ttop):
1023             if isinstance(node, SCons.Node.FS.Dir) and ttop.is_under(node):
1024                 node = ttop
1025             else:
1026                 node = None
1027         return node
1028
1029     nodes = filter(lambda x: x is not None, map(Entry, targets))
1030
1031     task_class = BuildTask      # default action is to build targets
1032     opening_message = "Building targets ..."
1033     closing_message = "done building targets."
1034     if keep_going_on_error:
1035         failure_message = "done building targets (errors occurred during build)."
1036     else:
1037         failure_message = "building terminated because of errors."
1038     if options.question:
1039         task_class = QuestionTask
1040     try:
1041         if ssoptions.get('clean'):
1042             task_class = CleanTask
1043             opening_message = "Cleaning targets ..."
1044             closing_message = "done cleaning targets."
1045             if keep_going_on_error:
1046                 closing_message = "done cleaning targets (errors occurred during clean)."
1047             else:
1048                 failure_message = "cleaning terminated because of errors."
1049     except AttributeError:
1050         pass
1051
1052     SCons.Environment.CalculatorArgs['max_drift'] = ssoptions.get('max_drift')
1053
1054     if options.random:
1055         def order(dependencies):
1056             """Randomize the dependencies."""
1057             # This is cribbed from the implementation of
1058             # random.shuffle() in Python 2.X.
1059             d = dependencies
1060             for i in xrange(len(d)-1, 0, -1):
1061                 j = int(random.random() * (i+1))
1062                 d[i], d[j] = d[j], d[i]
1063             return d
1064     else:
1065         def order(dependencies):
1066             """Leave the order of dependencies alone."""
1067             return dependencies
1068
1069     progress_display("scons: " + opening_message)
1070     taskmaster = SCons.Taskmaster.Taskmaster(nodes, task_class, order)
1071
1072     nj = ssoptions.get('num_jobs')
1073     jobs = SCons.Job.Jobs(nj, taskmaster)
1074     if nj > 1 and jobs.num_jobs == 1:
1075         msg = "parallel builds are unsupported by this version of Python;\n" + \
1076               "\tignoring -j or num_jobs option.\n"
1077         SCons.Warnings.warn(SCons.Warnings.NoParallelSupportWarning, msg)
1078
1079     if not memory_stats is None: memory_stats.append(SCons.Debug.memory())
1080     if not count_stats is None: count_stats.append(SCons.Debug.fetchLoggedInstances())
1081
1082     try:
1083         jobs.run()
1084     finally:
1085         if exit_status:
1086             progress_display("scons: " + failure_message)
1087         else:
1088             progress_display("scons: " + closing_message)
1089         if not options.noexec:
1090             SCons.SConsign.write()
1091
1092     if not memory_stats is None:
1093         memory_stats.append(SCons.Debug.memory())
1094         when = [
1095             'before reading SConscript files',
1096             'after reading SConscript files',
1097             'before building targets',
1098             'after building targets',
1099         ]
1100         for i in xrange(len(when)):
1101             memory_outf.write('Memory %-32s %12d\n' % (when[i]+':', memory_stats[i]))
1102
1103     if not count_stats is None:
1104         count_stats.append(SCons.Debug.fetchLoggedInstances())
1105         stats_table = {}
1106         for cs in count_stats:
1107             for n in map(lambda t: t[0], cs):
1108                 stats_table[n] = [0, 0, 0, 0]
1109         i = 0
1110         for cs in count_stats:
1111             for n, c in cs:
1112                 stats_table[n][i] = c
1113             i = i + 1
1114         keys = stats_table.keys()
1115         keys.sort()
1116         print "Object counts:"
1117         fmt = "    %7s %7s %7s %7s   %s"
1118         print fmt % ("pre-", "post-", "pre-", "post-", "")
1119         print fmt % ("read", "read", "build", "build", "Class")
1120         for k in keys:
1121             r = stats_table[k]
1122             print "    %7d %7d %7d %7d   %s" % (r[0], r[1], r[2], r[3], k)
1123
1124     if print_objects:
1125         SCons.Debug.listLoggedInstances('*')
1126         #SCons.Debug.dumpLoggedInstances('*')
1127
1128     if print_memoizer:
1129         print "Memoizer (memory cache) hits and misses:"
1130         SCons.Memoize.Dump()
1131
1132 def _exec_main():
1133     all_args = sys.argv[1:]
1134     try:
1135         all_args = string.split(os.environ['SCONSFLAGS']) + all_args
1136     except KeyError:
1137             # it's OK if there's no SCONSFLAGS
1138             pass
1139     parser = OptParser()
1140     global options
1141     options, args = parser.parse_args(all_args)
1142     if type(options.debug) == type([]) and "pdb" in options.debug:
1143         import pdb
1144         pdb.Pdb().runcall(_main, args, parser)
1145     else:
1146         _main(args, parser)
1147
1148 def main():
1149     global exit_status
1150     
1151     try:
1152         _exec_main()
1153     except SystemExit, s:
1154         if s:
1155             exit_status = s
1156     except KeyboardInterrupt:
1157         print "Build interrupted."
1158         sys.exit(2)
1159     except SyntaxError, e:
1160         _scons_syntax_error(e)
1161     except SCons.Errors.InternalError:
1162         _scons_internal_error()
1163     except SCons.Errors.UserError, e:
1164         _scons_user_error(e)
1165     except:
1166         # An exception here is likely a builtin Python exception Python
1167         # code in an SConscript file.  Show them precisely what the
1168         # problem was and where it happened.
1169         SCons.Script._SConscript.SConscript_exception()
1170         sys.exit(2)
1171
1172     if print_time:
1173         total_time = time.time()-SCons.Script.start_time
1174         scons_time = total_time-sconscript_time-command_time
1175         print "Total build time: %f seconds"%total_time
1176         print "Total SConscript file execution time: %f seconds"%sconscript_time
1177         print "Total SCons execution time: %f seconds"%scons_time
1178         print "Total command execution time: %f seconds"%command_time
1179
1180     sys.exit(exit_status)