Document the -f option correctly, support building a parallel tree by pointing to...
[scons.git] / src / engine / SCons / Script / __init__.py
1 """SCons.Script
2
3 This file implements the main() function used by the scons script.
4
5 Architecturally, this *is* the scons script, and will likely only be
6 called from the external "scons" wrapper.  Consequently, anything here
7 should not be, or be considered, part of the build engine.  If it's
8 something that we expect other software to want to use, it should go in
9 some other module.  If it's specific to the "scons" script invocation,
10 it goes here.
11
12 """
13
14 #
15 # __COPYRIGHT__
16 #
17 # Permission is hereby granted, free of charge, to any person obtaining
18 # a copy of this software and associated documentation files (the
19 # "Software"), to deal in the Software without restriction, including
20 # without limitation the rights to use, copy, modify, merge, publish,
21 # distribute, sublicense, and/or sell copies of the Software, and to
22 # permit persons to whom the Software is furnished to do so, subject to
23 # the following conditions:
24 #
25 # The above copyright notice and this permission notice shall be included
26 # in all copies or substantial portions of the Software.
27 #
28 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
29 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
30 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
31 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
32 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
33 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
34 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
35 #
36
37 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
38
39 import time
40 start_time = time.time()
41
42 import os
43 import os.path
44 import random
45 import string
46 import sys
47 import traceback
48
49 # Strip the script directory from sys.path() so on case-insensitive
50 # (WIN32) systems Python doesn't think that the "scons" script is the
51 # "SCons" package.  Replace it with our own version directory so, if
52 # if they're there, we pick up the right version of the build engine
53 # modules.
54 #sys.path = [os.path.join(sys.prefix,
55 #                         'lib',
56 #                         'scons-%d' % SCons.__version__)] + sys.path[1:]
57
58 import SCons.Errors
59 import SCons.Job
60 import SCons.Node
61 import SCons.Node.FS
62 from SCons.Optik import OptionParser, SUPPRESS_HELP, OptionValueError
63 import SCons.Script.SConscript
64 import SCons.Sig
65 import SCons.Taskmaster
66 from SCons.Util import display
67 import SCons.Warnings
68
69 #
70 # Task control.
71 #
72 class BuildTask(SCons.Taskmaster.Task):
73     """An SCons build task."""
74     def execute(self):
75         target = self.targets[0]
76         if target.get_state() == SCons.Node.up_to_date:
77             if self.top and target.has_builder():
78                 display('scons: "%s" is up to date.' % str(target))
79         elif target.has_builder() and not hasattr(target.builder, 'status'):
80             if print_time:
81                 start_time = time.time()
82             SCons.Taskmaster.Task.execute(self)
83             if print_time:
84                 finish_time = time.time()
85                 global command_time
86                 command_time = command_time+finish_time-start_time
87                 print "Command execution time: %f seconds"%(finish_time-start_time)
88
89     def do_failed(self, status=2):
90         global exit_status
91         if ignore_errors:
92             SCons.Taskmaster.Task.executed(self)
93         elif keep_going_on_error:
94             SCons.Taskmaster.Task.fail_continue(self)
95             exit_status = status
96         else:
97             SCons.Taskmaster.Task.fail_stop(self)
98             exit_status = status
99             
100     def executed(self):
101         t = self.targets[0]
102         if self.top and not t.has_builder() and not t.side_effect:
103             if not t.exists():
104                 sys.stderr.write("scons: *** Do not know how to make target `%s'." % t)
105                 if not keep_going_on_error:
106                     sys.stderr.write("  Stop.")
107                 sys.stderr.write("\n")
108                 self.do_failed()
109             else:
110                 print "scons: Nothing to be done for `%s'." % t
111                 SCons.Taskmaster.Task.executed(self)
112         else:
113             SCons.Taskmaster.Task.executed(self)
114
115         # print the tree here instead of in execute() because
116         # this method is serialized, but execute isn't:
117         if print_tree and self.top:
118             print
119             print SCons.Util.render_tree(self.targets[0], get_all_children)
120         if print_dtree and self.top:
121             print
122             print SCons.Util.render_tree(self.targets[0], get_derived_children)
123         if print_includes and self.top:
124             t = self.targets[0]
125             tree = t.render_include_tree()
126             if tree:
127                 print
128                 print tree
129
130     def failed(self):
131         e = sys.exc_value
132         status = 2
133         if sys.exc_type == SCons.Errors.BuildError:
134             sys.stderr.write("scons: *** [%s] %s\n" % (e.node, e.errstr))
135             if e.errstr == 'Exception':
136                 traceback.print_exception(e.args[0], e.args[1], e.args[2])
137         elif sys.exc_type == SCons.Errors.UserError:
138             # We aren't being called out of a user frame, so
139             # don't try to walk the stack, just print the error.
140             sys.stderr.write("\nscons: *** %s\n" % e)
141         elif sys.exc_type == SCons.Errors.StopError:
142             s = str(e)
143             if not keep_going_on_error:
144                 s = s + '  Stop.'
145             sys.stderr.write("scons: *** %s\n" % s)
146         elif sys.exc_type == SCons.Errors.ExplicitExit:
147             status = e.status
148             sys.stderr.write("scons: *** [%s] Explicit exit, status %s\n" % (e.node, e.status))
149         else:
150             if e is None:
151                 e = sys.exc_type
152             sys.stderr.write("scons: *** %s\n" % e)
153
154         self.do_failed(status)
155
156 class CleanTask(SCons.Taskmaster.Task):
157     """An SCons clean task."""
158     def show(self):
159         if (self.targets[0].has_builder() or self.targets[0].side_effect) \
160            and not os.path.isdir(str(self.targets[0])):
161             display("Removed " + str(self.targets[0]))
162         if SCons.Script.SConscript.clean_targets.has_key(self.targets[0]):
163             files = SCons.Script.SConscript.clean_targets[self.targets[0]]
164             for f in files:
165                 SCons.Util.fs_delete(str(f), 0)
166
167     def remove(self):
168         if self.targets[0].has_builder() or self.targets[0].side_effect:
169             for t in self.targets:
170                 try:
171                     removed = t.remove()
172                 except OSError, e:
173                     print "scons: Could not remove '%s':" % str(t), e.strerror
174                 else:
175                     if removed:
176                         display("Removed " + str(t))
177         if SCons.Script.SConscript.clean_targets.has_key(self.targets[0]):
178             files = SCons.Script.SConscript.clean_targets[self.targets[0]]
179             for f in files:
180                 SCons.Util.fs_delete(str(f))
181
182     execute = remove
183
184     def prepare(self):
185         pass
186
187 class QuestionTask(SCons.Taskmaster.Task):
188     """An SCons task for the -q (question) option."""
189     def prepare(self):
190         pass
191     
192     def execute(self):
193         if self.targets[0].get_state() != SCons.Node.up_to_date:
194             global exit_status
195             exit_status = 1
196             self.tm.stop()
197
198     def executed(self):
199         pass
200
201 # Global variables
202
203 keep_going_on_error = 0
204 print_tree = 0
205 print_dtree = 0
206 print_time = 0
207 print_includes = 0
208 ignore_errors = 0
209 sconscript_time = 0
210 command_time = 0
211 exit_status = 0 # exit status, assume success by default
212 profiling = 0
213 repositories = []
214 sig_module = None
215 num_jobs = 1 # this is modifed by SConscript.SetJobs()
216
217 # Exceptions for this module
218 class PrintHelp(Exception):
219     pass
220
221 # utility functions
222
223 def get_num_jobs(options):
224     if hasattr(options, 'num_jobs'):
225         return options.num_jobs
226     else:
227         return num_jobs
228
229 def get_all_children(node): return node.all_children(None)
230
231 def get_derived_children(node):
232     children = node.all_children(None)
233     return filter(lambda x: x.has_builder(), children)
234
235 def _scons_syntax_error(e):
236     """Handle syntax errors. Print out a message and show where the error
237     occurred.
238     """
239     etype, value, tb = sys.exc_info()
240     lines = traceback.format_exception_only(etype, value)
241     for line in lines:
242         sys.stderr.write(line+'\n')
243     sys.exit(2)
244
245 def find_deepest_user_frame(tb):
246     """
247     Find the deepest stack frame that is not part of SCons.
248
249     Input is a "pre-processed" stack trace in the form
250     returned by traceback.extract_tb() or traceback.extract_stack()
251     """
252     
253     tb.reverse()
254
255     # find the deepest traceback frame that is not part
256     # of SCons:
257     for frame in tb:
258         filename = frame[0]
259         if string.find(filename, os.sep+'SCons'+os.sep) == -1:
260             return frame
261     return tb[0]
262
263 def _scons_user_error(e):
264     """Handle user errors. Print out a message and a description of the
265     error, along with the line number and routine where it occured. 
266     The file and line number will be the deepest stack frame that is
267     not part of SCons itself.
268     """
269     etype, value, tb = sys.exc_info()
270     filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_tb(tb))
271     sys.stderr.write("\nscons: *** %s\n" % value)
272     sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
273     sys.exit(2)
274
275 def _scons_user_warning(e):
276     """Handle user warnings. Print out a message and a description of
277     the warning, along with the line number and routine where it occured.
278     The file and line number will be the deepest stack frame that is
279     not part of SCons itself.
280     """
281     etype, value, tb = sys.exc_info()
282     filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_tb(tb))
283     sys.stderr.write("\nscons: warning: %s\n" % e)
284     sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
285
286 def _scons_internal_warning(e):
287     """Slightly different from _scons_user_warning in that we use the
288     *current call stack* rather than sys.exc_info() to get our stack trace.
289     This is used by the warnings framework to print warnings."""
290     filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_stack())
291     sys.stderr.write("\nscons: warning: %s\n" % e)
292     sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
293
294 def _scons_other_errors():
295     """Handle all errors but user errors. Print out a message telling
296     the user what to do in this case and print a normal trace.
297     """
298     print 'other errors'
299     traceback.print_exc()
300     sys.exit(2)
301
302 def _varargs(option, parser):
303     value = None
304     if parser.rargs:
305         arg = parser.rargs[0]
306         if arg[0] != "-":
307             value = arg
308             del parser.rargs[0]
309     return value
310
311 def _setup_warn(arg):
312     """The --warn option.  An argument to this option
313     should be of the form <warning-class> or no-<warning-class>.
314     The warning class is munged in order to get an actual class
315     name from the SCons.Warnings module to enable or disable.
316     The supplied <warning-class> is split on hyphens, each element
317     is captialized, then smushed back together.  Then the string
318     "SCons.Warnings." is added to the front and "Warning" is added
319     to the back to get the fully qualified class name.
320
321     For example, --warn=deprecated will enable the
322     SCons.Warnings.DeprecatedWarning class.
323
324     --warn=no-dependency will disable the
325     SCons.Warnings.DependencyWarning class.
326
327     As a special case, --warn=all and --warn=no-all
328     will enable or disable (respectively) the base
329     class of all warnings, which is SCons.Warning.Warning."""
330
331     elems = string.split(string.lower(arg), '-')
332     enable = 1
333     if elems[0] == 'no':
334         enable = 0
335         del elems[0]
336
337     if len(elems) == 1 and elems[0] == 'all':
338         class_name = "Warning"
339     else:
340         class_name = string.join(map(string.capitalize, elems), '') + \
341                      "Warning"
342     try:
343         clazz = getattr(SCons.Warnings, class_name)
344     except AttributeError:
345         sys.stderr.write("No warning type: '%s'\n" % arg)
346     else:
347         if enable:
348             SCons.Warnings.enableWarningClass(clazz)
349         else:
350             SCons.Warnings.suppressWarningClass(clazz)
351
352 def _SConstruct_exists(dirname=''):
353     """This function checks that an SConstruct file exists in a directory.
354     If so, it returns the path of the file. By default, it checks the
355     current directory.
356     """
357     global repositories
358     for file in ['SConstruct', 'Sconstruct', 'sconstruct']:
359         sfile = os.path.join(dirname, file)
360         if os.path.isfile(sfile):
361             return sfile
362         if not os.path.isabs(sfile):
363             for rep in repositories:
364                 if os.path.isfile(os.path.join(rep, sfile)):
365                     return sfile
366     return None
367
368 def _set_globals(options):
369     global repositories, keep_going_on_error, print_tree, print_dtree
370     global print_time, ignore_errors, print_includes
371
372     if options.repository:
373         repositories.extend(options.repository)
374     keep_going_on_error = options.keep_going
375     try:
376         if options.debug:
377             if options.debug == "tree":
378                 print_tree = 1
379             elif options.debug == "dtree":
380                 print_dtree = 1
381             elif options.debug == "time":
382                 print_time = 1
383             elif options.debug == "includes":
384                 print_includes = 1
385     except AttributeError:
386         pass
387     ignore_errors = options.ignore_errors
388
389 def _create_path(plist):
390     path = '.'
391     for d in plist:
392         if os.path.isabs(d):
393             path = d
394         else:
395             path = path + '/' + d
396     return path
397
398
399 class OptParser(OptionParser):
400     def __init__(self):
401         import __main__
402         import SCons
403         parts = ["SCons by Steven Knight et al.:\n"]
404         try:
405             parts.append("\tscript: v%s.%s, %s, by %s on %s\n" % (__main__.__version__,
406                                                                   __main__.__build__,
407                                                                   __main__.__date__,
408                                                                   __main__.__developer__,
409                                                                   __main__.__buildsys__))
410         except:
411             # On win32 there is no scons.py, so there is no __main__.__version__,
412             # hence there is no script version.
413             pass 
414         parts.append("\tengine: v%s.%s, %s, by %s on %s\n" % (SCons.__version__,
415                                                               SCons.__build__,
416                                                               SCons.__date__,
417                                                               SCons.__developer__,
418                                                               SCons.__buildsys__))
419         parts.append("__COPYRIGHT__")
420         OptionParser.__init__(self, version=string.join(parts, ''),
421                               usage="usage: scons [OPTION] [TARGET] ...")
422
423         # options ignored for compatibility
424         def opt_ignore(option, opt, value, parser):
425             sys.stderr.write("Warning:  ignoring %s option\n" % opt)
426         self.add_option("-b", "-m", "-S", "-t", "--no-keep-going", "--stop",
427                         "--touch", action="callback", callback=opt_ignore,
428                         help="Ignored for compatibility.")
429
430         self.add_option('-c', '--clean', '--remove', action="store_true",
431                         default=0, dest="clean",
432                         help="Remove specified targets and dependencies.")
433
434         self.add_option('-C', '--directory', type="string", action = "append",
435                         help="Change to DIRECTORY before doing anything.")
436
437         self.add_option('--cache-disable', '--no-cache',
438                         action="store_true", dest='cache_disable', default=0,
439                         help="Do not retrieve built targets from CacheDir.")
440
441         self.add_option('--cache-force', '--cache-populate',
442                         action="store_true", dest='cache_force', default=0,
443                         help="Copy already-built targets into the CacheDir.")
444
445         self.add_option('--cache-show',
446                         action="store_true", dest='cache_show', default=0,
447                         help="Print build actions for files from CacheDir.")
448
449         def opt_not_yet(option, opt, value, parser):
450             sys.stderr.write("Warning:  the %s option is not yet implemented\n" % opt)
451             sys.exit(0)
452         self.add_option('-d', action="callback",
453                         callback=opt_not_yet,
454                         help = "Print file dependency information.")
455         
456         self.add_option('-D', action="store_const", const=2, dest="climb_up",
457                         help="Search up directory tree for SConstruct, "
458                              "build all Default() targets.")
459
460         def opt_debug(option, opt, value, parser):
461             if value == "pdb":
462                 if os.name == 'java':
463                     python = os.path.join(sys.prefix, 'jython')
464                 else:
465                     python = sys.executable
466                 args = [ python, "pdb.py" ] + \
467                        filter(lambda x: x != "--debug=pdb", sys.argv)
468                 if sys.platform == 'win32':
469                     args[1] = os.path.join(sys.prefix, "lib", "pdb.py")
470                     sys.exit(os.spawnve(os.P_WAIT, args[0], args, os.environ))
471                 else:
472                     args[1] = os.path.join(sys.prefix,
473                                            "lib",
474                                            "python" + sys.version[0:3],
475                                            "pdb.py")
476                 os.execvpe(args[0], args, os.environ)
477             elif value in ["tree", "dtree", "time", "includes"]:
478                 setattr(parser.values, 'debug', value)
479             else:
480                 raise OptionValueError("Warning:  %s is not a valid debug type" % value)
481         self.add_option('--debug', action="callback", type="string",
482                         callback=opt_debug, nargs=1, dest="debug",
483                         help="Print various types of debugging information.")
484
485         self.add_option('-f', '--file', '--makefile', '--sconstruct',
486                         action="append", nargs=1,
487                         help="Read FILE as the top-level SConstruct file.")
488
489         self.add_option('-h', '--help', action="store_true", default=0,
490                         dest="help_msg",
491                         help="Print defined help message, or this one.")
492
493         self.add_option("-H", "--help-options",
494                         action="help",
495                         help="Print this message and exit.")
496
497         self.add_option('-i', '--ignore-errors', action="store_true",
498                         default=0, dest='ignore_errors',
499                         help="Ignore errors from build actions.")
500
501         self.add_option('-I', '--include-dir', action="append",
502                         dest='include_dir', metavar="DIRECTORY",
503                         help="Search DIRECTORY for imported Python modules.")
504
505         self.add_option('--implicit-cache', action="store_true", default=0,
506                         dest='implicit_cache',
507                         help="Cache implicit dependencies")
508
509         self.add_option('--implicit-deps-changed', action="store_true",
510                         default=0, dest='implicit_deps_changed',
511                         help="Ignore the cached implicit deps.")
512         self.add_option('--implicit-deps-unchanged', action="store_true",
513                         default=0, dest='implicit_deps_unchanged',
514                         help="Ignore changes in implicit deps.")
515
516         def opt_j(option, opt, value, parser):
517             value = int(value)
518             setattr(parser.values, 'num_jobs', value)
519         self.add_option('-j', '--jobs', action="callback", type="int",
520                         callback=opt_j, metavar="N",
521                         help="Allow N jobs at once.")
522
523         self.add_option('-k', '--keep-going', action="store_true", default=0,
524                         dest='keep_going',
525                         help="Keep going when a target can't be made.")
526
527         self.add_option('--max-drift', type="int", action="store",
528                         dest='max_drift',
529                         help="Set the maximum system clock drift to be"
530                              " MAX_DRIFT seconds.")
531
532         self.add_option('-n', '--no-exec', '--just-print', '--dry-run',
533                         '--recon', action="store_true", dest='noexec',
534                         default=0, help="Don't build; just print commands.")
535
536         def opt_profile(option, opt, value, parser):
537             global profiling
538             if not profiling:
539                 profiling = 1
540                 import profile
541                 profile.run('SCons.Script.main()', value)
542                 sys.exit(exit_status)
543         self.add_option('--profile', nargs=1, action="callback",
544                         callback=opt_profile, type="string", dest="profile",
545                         help="Profile SCons and put results in PROFILE.")
546
547         self.add_option('-q', '--question', action="store_true", default=0,
548                         help="Don't build; exit status says if up to date.")
549
550         self.add_option('-Q', dest='no_progress', action="store_true",
551                         default=0,
552                         help="Don't print SCons progress messages.")
553
554         self.add_option('--random', dest="random", action="store_true",
555                         default=0, help="Build dependencies in random order.")
556
557         self.add_option('-s', '--silent', '--quiet', action="store_true",
558                         default=0, help="Don't print commands.")
559
560         self.add_option('-u', '--up', '--search-up', action="store_const",
561                         dest="climb_up", default=0, const=1,
562                         help="Search up directory tree for SConstruct, "
563                              "build targets at or below current directory.")
564         self.add_option('-U', action="store_const", dest="climb_up",
565                         default=0, const=3,
566                         help="Search up directory tree for SConstruct, "
567                              "build Default() targets from local SConscript.")
568
569         self.add_option("-v", "--version",
570                         action="version",
571                         help="Print the SCons version number and exit.")
572
573         self.add_option('--warn', '--warning', nargs=1, action="store",
574                         metavar="WARNING-SPEC",
575                         help="Enable or disable warnings.")
576
577         self.add_option('-Y', '--repository', nargs=1, action="append",
578                         help="Search REPOSITORY for source and target files.")
579
580         self.add_option('-e', '--environment-overrides', action="callback",
581                         callback=opt_not_yet,
582                         # help="Environment variables override makefiles."
583                         help=SUPPRESS_HELP)
584         self.add_option('-l', '--load-average', '--max-load', action="callback",
585                         callback=opt_not_yet, type="int", dest="load_average",
586                         # action="store",
587                         # help="Don't start multiple jobs unless load is below "
588                         #      "LOAD-AVERAGE."
589                         # type="int",
590                         help=SUPPRESS_HELP)
591         self.add_option('--list-derived', action="callback",
592                         callback=opt_not_yet,
593                         # help="Don't build; list files that would be built."
594                         help=SUPPRESS_HELP)
595         self.add_option('--list-actions', action="callback",
596                         callback=opt_not_yet,
597                         # help="Don't build; list files and build actions."
598                         help=SUPPRESS_HELP)
599         self.add_option('--list-where', action="callback",
600                         callback=opt_not_yet,
601                         # help="Don't build; list files and where defined."
602                         help=SUPPRESS_HELP)
603         self.add_option('-o', '--old-file', '--assume-old', action="callback",
604                         callback=opt_not_yet, type="string", dest="old_file",
605                         # help = "Consider FILE to be old; don't rebuild it."
606                         help=SUPPRESS_HELP)
607         self.add_option('--override', action="callback", dest="override",
608                         callback=opt_not_yet, type="string",
609                         # help="Override variables as specified in FILE."
610                         help=SUPPRESS_HELP)
611         self.add_option('-p', action="callback",
612                         callback=opt_not_yet,
613                         # help="Print internal environments/objects."
614                         help=SUPPRESS_HELP)
615         self.add_option('-r', '-R', '--no-builtin-rules',
616                         '--no-builtin-variables', action="callback",
617                         callback=opt_not_yet,
618                         # help="Clear default environments and variables."
619                         help=SUPPRESS_HELP)
620         self.add_option('-w', '--print-directory', action="callback",
621                         callback=opt_not_yet,
622                         # help="Print the current directory."
623                         help=SUPPRESS_HELP)
624         self.add_option('--no-print-directory', action="callback",
625                         callback=opt_not_yet,
626                         # help="Turn off -w, even if it was turned on implicitly."
627                         help=SUPPRESS_HELP)
628         self.add_option('--write-filenames', action="callback",
629                         callback=opt_not_yet, type="string", dest="write_filenames",
630                         # help="Write all filenames examined into FILE."
631                         help=SUPPRESS_HELP)
632         self.add_option('-W', '--what-if', '--new-file', '--assume-new',
633                         dest="new_file",
634                         action="callback", callback=opt_not_yet, type="string",
635                         # help="Consider FILE to be changed."
636                         help=SUPPRESS_HELP)
637         self.add_option('--warn-undefined-variables', action="callback",
638                         callback=opt_not_yet,
639                         # help="Warn when an undefined variable is referenced."
640                         help=SUPPRESS_HELP)
641
642     def parse_args(self, args=None, values=None):
643         opt, arglist = OptionParser.parse_args(self, args, values)
644         if opt.implicit_deps_changed or opt.implicit_deps_unchanged:
645             opt.implicit_cache = 1
646         return opt, arglist
647
648
649 def _main():
650     targets = []
651
652     # Enable deprecated warnings by default.
653     SCons.Warnings._warningOut = _scons_internal_warning
654     SCons.Warnings.enableWarningClass(SCons.Warnings.DeprecatedWarning)
655     SCons.Warnings.enableWarningClass(SCons.Warnings.CorruptSConsignWarning)
656
657     all_args = sys.argv[1:]
658     try:
659         all_args = string.split(os.environ['SCONSFLAGS']) + all_args
660     except KeyError:
661             # it's OK if there's no SCONSFLAGS
662             pass
663     parser = OptParser()
664     global options
665     options, args = parser.parse_args(all_args)
666
667     if options.help_msg:
668         def raisePrintHelp(text):
669             raise PrintHelp, text
670         SCons.Script.SConscript.HelpFunction = raisePrintHelp
671
672     _set_globals(options)
673     SCons.Node.implicit_cache = options.implicit_cache
674     SCons.Node.implicit_deps_changed = options.implicit_deps_changed
675     SCons.Node.implicit_deps_unchanged = options.implicit_deps_unchanged
676     if options.warn:
677         _setup_warn(options.warn)
678     if options.noexec:
679         SCons.Action.execute_actions = None
680         CleanTask.execute = CleanTask.show
681     if options.no_progress or options.silent:
682         display.set_mode(0)
683     if options.silent:
684         SCons.Action.print_actions = None
685     if options.cache_disable:
686         def disable(self): pass
687         SCons.Node.FS.default_fs.CacheDir = disable
688     if options.cache_force:
689         SCons.Node.FS.default_fs.cache_force = 1
690     if options.cache_show:
691         SCons.Node.FS.default_fs.cache_show = 1
692     if options.directory:
693         cdir = _create_path(options.directory)
694         try:
695             os.chdir(cdir)
696         except:
697             sys.stderr.write("Could not change directory to %s\n" % cdir)
698
699     xmit_args = []
700     for a in args:
701         if '=' in a:
702             xmit_args.append(a)
703         else:
704             targets.append(a)
705     SCons.Script.SConscript._scons_add_args(xmit_args)
706
707     target_top = None
708     if options.climb_up:
709         target_top = '.'  # directory to prepend to targets
710         script_dir = os.getcwd()  # location of script
711         while script_dir and not _SConstruct_exists(script_dir):
712             script_dir, last_part = os.path.split(script_dir)
713             if last_part:
714                 target_top = os.path.join(last_part, target_top)
715             else:
716                 script_dir = ''
717         if script_dir:
718             display("scons: Entering directory %s" % script_dir)
719             os.chdir(script_dir)
720         else:
721             raise SCons.Errors.UserError, "No SConstruct file found."
722
723     SCons.Node.FS.default_fs.set_toplevel_dir(os.getcwd())
724
725     scripts = []
726     if options.file:
727         scripts.extend(options.file)
728     if not scripts:
729         sfile = _SConstruct_exists()
730         if sfile:
731             scripts.append(sfile)
732
733     if options.help_msg:
734         if not scripts:
735             # There's no SConstruct, but they specified -h.
736             # Give them the options usage now, before we fail
737             # trying to read a non-existent SConstruct file.
738             parser.print_help()
739             sys.exit(0)
740         SCons.Script.SConscript.print_help = 1
741
742     if not scripts:
743         raise SCons.Errors.UserError, "No SConstruct file found."
744
745     SCons.Node.FS.default_fs.set_SConstruct(scripts[0])
746
747     class Unbuffered:
748         def __init__(self, file):
749             self.file = file
750         def write(self, arg):
751             self.file.write(arg)
752             self.file.flush()
753         def __getattr__(self, attr):
754             return getattr(self.file, attr)
755
756     sys.stdout = Unbuffered(sys.stdout)
757
758     if options.include_dir:
759         sys.path = options.include_dir + sys.path
760
761     global repositories
762     for rep in repositories:
763         SCons.Node.FS.default_fs.Repository(rep)
764
765     display("scons: Reading SConscript files ...")
766     try:
767         start_time = time.time()
768         try:
769             for script in scripts:
770                 SCons.Script.SConscript.SConscript(script)
771         except SCons.Errors.StopError, e:
772             # We had problems reading an SConscript file, such as it
773             # couldn't be copied in to the BuildDir.  Since we're just
774             # reading SConscript files and haven't started building
775             # things yet, stop regardless of whether they used -i or -k
776             # or anything else, but don't say "Stop." on the message.
777             global exit_status
778             sys.stderr.write("scons: *** %s\n" % e)
779             exit_status = 2
780             sys.exit(exit_status)
781         global sconscript_time
782         sconscript_time = time.time() - start_time
783     except PrintHelp, text:
784         display("scons: done reading SConscript files.")
785         print text
786         print "Use scons -H for help about command-line options."
787         sys.exit(0)
788     display("scons: done reading SConscript files.")
789
790     SCons.Node.FS.default_fs.chdir(SCons.Node.FS.default_fs.Top)
791
792     if options.help_msg:
793         # They specified -h, but there was no Help() inside the
794         # SConscript files.  Give them the options usage.
795         parser.print_help(sys.stdout)
796         sys.exit(0)
797
798     if target_top:
799         target_top = SCons.Node.FS.default_fs.Dir(target_top)
800         
801         if options.climb_up == 2 and not targets:
802             # -D with default targets
803             target_top = None
804         elif options.climb_up == 3 and not targets:
805             # -U with default targets
806             default_targets = SCons.Script.SConscript.default_targets
807             def check_dir(x, target_top=target_top):
808                 if hasattr(x, 'cwd') and not x.cwd is None:
809                     cwd = x.cwd.srcnode()
810                     return cwd == target_top
811                 else:
812                     # x doesn't have a cwd, so it's either not a target,
813                     # or not a file, so go ahead and keep it as a default
814                     # target and let the engine sort it out:
815                     return 1                
816             default_targets = filter(check_dir, default_targets)
817             SCons.Script.SConscript.default_targets = default_targets
818             target_top = None
819
820     if not targets:
821         targets = SCons.Script.SConscript.default_targets
822         if targets is None:
823             targets = [SCons.Node.FS.default_fs.Dir('.')]
824
825     if not targets:
826         sys.stderr.write("scons: *** No targets specified and no Default() targets found.  Stop.\n")
827         sys.exit(2)
828
829     def Entry(x, top = target_top):
830         if isinstance(x, SCons.Node.Node):
831             node = x
832         else:
833             node = SCons.Node.Alias.default_ans.lookup(x)
834             if node is None:
835                 node = SCons.Node.FS.default_fs.Entry(x,
836                                                       directory = top,
837                                                       create = 1)
838         if top and not node.is_under(top):
839             if isinstance(node, SCons.Node.FS.Dir) and top.is_under(node):
840                 node = top
841             else:
842                 node = None
843         return node
844
845     nodes = filter(lambda x: x is not None, map(Entry, targets))
846
847     calc = None
848     task_class = BuildTask      # default action is to build targets
849     if options.question:
850         task_class = QuestionTask
851     try:
852         if options.clean:
853             task_class = CleanTask
854             class CleanCalculator:
855                 def bsig(self, node):
856                     return None
857                 def csig(self, node):
858                     return None
859                 def current(self, node, sig):
860                     return 0
861                 def write(self):
862                     pass
863             calc = CleanCalculator()
864     except AttributeError:
865         pass
866
867     if not calc:
868         if options.max_drift is not None:
869             if sig_module is not None:
870                 SCons.Sig.default_calc = SCons.Sig.Calculator(module=sig_module,
871                                                               max_drift=options.max_drift)
872             else:
873                 SCons.Sig.default_calc = SCons.Sig.Calculator(max_drift=options.max_drift)
874         elif sig_module is not None:
875             SCons.Sig.default_calc = SCons.Sig.Calculator(module=sig_module)
876
877         calc = SCons.Sig.default_calc
878
879     if options.random:
880         def order(dependencies):
881             """Randomize the dependencies."""
882             # This is cribbed from the implementation of
883             # random.shuffle() in Python 2.X.
884             d = dependencies
885             for i in xrange(len(d)-1, 0, -1):
886                 j = int(random.random() * (i+1))
887                 d[i], d[j] = d[j], d[i]
888             return d
889     else:
890         def order(dependencies):
891             """Leave the order of dependencies alone."""
892             return dependencies
893
894     display("scons: Building targets ...")
895     taskmaster = SCons.Taskmaster.Taskmaster(nodes, task_class, calc, order)
896
897     jobs = SCons.Job.Jobs(get_num_jobs(options), taskmaster)
898
899     try:
900         jobs.run()
901     finally:
902         display("scons: done building targets.")
903         SCons.Sig.write()
904
905 def main():
906     global exit_status
907     
908     try:
909         _main()
910     except SystemExit, s:
911         if s:
912             exit_status = s
913     except KeyboardInterrupt:
914         print "Build interrupted."
915         sys.exit(2)
916     except SyntaxError, e:
917         _scons_syntax_error(e)
918     except SCons.Errors.UserError, e:
919         _scons_user_error(e)
920     except:
921         _scons_other_errors()
922
923     if print_time:
924         total_time = time.time()-start_time
925         scons_time = total_time-sconscript_time-command_time
926         print "Total build time: %f seconds"%total_time
927         print "Total SConscript file execution time: %f seconds"%sconscript_time
928         print "Total SCons execution time: %f seconds"%scons_time
929         print "Total command execution time: %f seconds"%command_time
930
931     sys.exit(exit_status)