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