http://scons.tigris.org/issues/show_bug.cgi?id=2345
[scons.git] / src / engine / SCons / Script / Main.py
index a6b4f88a3b8d04d67956db96c1fc518979341aa2..96fc4b74cafda752b7d74c08ba1794a674decf38 100644 (file)
@@ -33,19 +33,20 @@ it goes here.
 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 #
+from __future__ import generators  ### KEEP FOR COMPATIBILITY FIXERS
 
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
+import SCons.compat
+
 import os
 import os.path
-import random
-import string
 import sys
 import time
 import traceback
 
 # Strip the script directory from sys.path() so on case-insensitive
-# (WIN32) systems Python doesn't think that the "scons" script is the
+# (Windows) systems Python doesn't think that the "scons" script is the
 # "SCons" package.  Replace it with our own version directory so, if
 # if they're there, we pick up the right version of the build engine
 # modules.
@@ -53,6 +54,7 @@ import traceback
 #                         'lib',
 #                         'scons-%d' % SCons.__version__)] + sys.path[1:]
 
+import SCons.CacheDir
 import SCons.Debug
 import SCons.Defaults
 import SCons.Environment
@@ -60,163 +62,321 @@ import SCons.Errors
 import SCons.Job
 import SCons.Node
 import SCons.Node.FS
-from SCons.Optik import OptionParser, SUPPRESS_HELP, OptionValueError
 import SCons.SConf
-import SCons.Sig
+import SCons.Script
 import SCons.Taskmaster
 import SCons.Util
 import SCons.Warnings
 
+import SCons.Script.Interactive
+
+def fetch_win32_parallel_msg():
+    # A subsidiary function that exists solely to isolate this import
+    # so we don't have to pull it in on all platforms, and so that an
+    # in-line "import" statement in the _main() function below doesn't
+    # cause warnings about local names shadowing use of the 'SCons'
+    # globl in nest scopes and UnboundLocalErrors and the like in some
+    # versions (2.1) of Python.
+    import SCons.Platform.win32
+    return SCons.Platform.win32.parallel_msg
+
 #
+
+class SConsPrintHelpException(Exception):
+    pass
+
 display = SCons.Util.display
 progress_display = SCons.Util.DisplayEngine()
 
+first_command_start = None
+last_command_end = None
+
+class Progressor:
+    prev = ''
+    count = 0
+    target_string = '$TARGET'
+
+    def __init__(self, obj, interval=1, file=None, overwrite=False):
+        if file is None:
+            file = sys.stdout
+
+        self.obj = obj
+        self.file = file
+        self.interval = interval
+        self.overwrite = overwrite
+
+        if callable(obj):
+            self.func = obj
+        elif SCons.Util.is_List(obj):
+            self.func = self.spinner
+        elif obj.find(self.target_string) != -1:
+            self.func = self.replace_string
+        else:
+            self.func = self.string
+
+    def write(self, s):
+        self.file.write(s)
+        self.file.flush()
+        self.prev = s
+
+    def erase_previous(self):
+        if self.prev:
+            length = len(self.prev)
+            if self.prev[-1] in ('\n', '\r'):
+                length = length - 1
+            self.write(' ' * length + '\r')
+            self.prev = ''
+
+    def spinner(self, node):
+        self.write(self.obj[self.count % len(self.obj)])
+
+    def string(self, node):
+        self.write(self.obj)
+
+    def replace_string(self, node):
+        self.write(self.obj.replace(self.target_string, str(node)))
+
+    def __call__(self, node):
+        self.count = self.count + 1
+        if (self.count % self.interval) == 0:
+            if self.overwrite:
+                self.erase_previous()
+            self.func(node)
+
+ProgressObject = SCons.Util.Null()
+
+def Progress(*args, **kw):
+    global ProgressObject
+    ProgressObject = Progressor(*args, **kw)
+
 # Task control.
 #
-class BuildTask(SCons.Taskmaster.Task):
+
+_BuildFailures = []
+
+def GetBuildFailures():
+    return _BuildFailures
+
+class BuildTask(SCons.Taskmaster.OutOfDateTask):
     """An SCons build task."""
+    progress = ProgressObject
+
     def display(self, message):
         display('scons: ' + message)
 
+    def prepare(self):
+        self.progress(self.targets[0])
+        return SCons.Taskmaster.OutOfDateTask.prepare(self)
+
+    def needs_execute(self):
+        if SCons.Taskmaster.OutOfDateTask.needs_execute(self):
+            return True
+        if self.top and self.targets[0].has_builder():
+            display("scons: `%s' is up to date." % str(self.node))
+        return False
+
     def execute(self):
-        target = self.targets[0]
-        if target.get_state() == SCons.Node.up_to_date:
-            if self.top and target.has_builder():
-                display("scons: `%s' is up to date." % str(self.node))
-        elif target.has_builder() and not hasattr(target.builder, 'status'):
-            if print_time:
-                start_time = time.time()
-            SCons.Taskmaster.Task.execute(self)
-            if print_time:
-                finish_time = time.time()
-                global command_time
-                command_time = command_time+finish_time-start_time
-                print "Command execution time: %f seconds"%(finish_time-start_time)
+        if print_time:
+            start_time = time.time()
+            global first_command_start
+            if first_command_start is None:
+                first_command_start = start_time
+        SCons.Taskmaster.OutOfDateTask.execute(self)
+        if print_time:
+            global cumulative_command_time
+            global last_command_end
+            finish_time = time.time()
+            last_command_end = finish_time
+            cumulative_command_time = cumulative_command_time+finish_time-start_time
+            sys.stdout.write("Command execution time: %f seconds\n"%(finish_time-start_time))
 
     def do_failed(self, status=2):
+        _BuildFailures.append(self.exception[1])
         global exit_status
-        if ignore_errors:
-            SCons.Taskmaster.Task.executed(self)
-        elif keep_going_on_error:
-            SCons.Taskmaster.Task.fail_continue(self)
+        global this_build_status
+        if self.options.ignore_errors:
+            SCons.Taskmaster.OutOfDateTask.executed(self)
+        elif self.options.keep_going:
+            SCons.Taskmaster.OutOfDateTask.fail_continue(self)
             exit_status = status
+            this_build_status = status
         else:
-            SCons.Taskmaster.Task.fail_stop(self)
+            SCons.Taskmaster.OutOfDateTask.fail_stop(self)
             exit_status = status
+            this_build_status = status
             
     def executed(self):
         t = self.targets[0]
         if self.top and not t.has_builder() and not t.side_effect:
             if not t.exists():
-                sys.stderr.write("scons: *** Do not know how to make target `%s'." % t)
-                if not keep_going_on_error:
+                def classname(obj):
+                    return str(obj.__class__).split('.')[-1]
+                if classname(t) in ('File', 'Dir', 'Entry'):
+                    errstr="Do not know how to make %s target `%s' (%s)." % (classname(t), t, t.abspath)
+                else: # Alias or Python or ...
+                    errstr="Do not know how to make %s target `%s'." % (classname(t), t)
+                sys.stderr.write("scons: *** " + errstr)
+                if not self.options.keep_going:
                     sys.stderr.write("  Stop.")
                 sys.stderr.write("\n")
+                try:
+                    raise SCons.Errors.BuildError(t, errstr)
+                except KeyboardInterrupt:
+                    raise
+                except:
+                    self.exception_set()
                 self.do_failed()
             else:
                 print "scons: Nothing to be done for `%s'." % t
-                SCons.Taskmaster.Task.executed(self)
+                SCons.Taskmaster.OutOfDateTask.executed(self)
         else:
-            SCons.Taskmaster.Task.executed(self)
+            SCons.Taskmaster.OutOfDateTask.executed(self)
 
     def failed(self):
         # Handle the failure of a build task.  The primary purpose here
         # is to display the various types of Errors and Exceptions
         # appropriately.
-        status = 2
         exc_info = self.exc_info()
         try:
             t, e, tb = exc_info
         except ValueError:
             t, e = exc_info
             tb = None
+
         if t is None:
             # The Taskmaster didn't record an exception for this Task;
             # see if the sys module has one.
-            t, e = sys.exc_info()[:2]
-
-        if t == SCons.Errors.BuildError:
-            fname = e.node
-            if SCons.Util.is_List(e.node):
-                fname = string.join(map(str, e.node), ', ')
-            sys.stderr.write("scons: *** [%s] %s\n" % (fname, e.errstr))
-            if e.errstr == 'Exception':
-                traceback.print_exception(e.args[0], e.args[1], e.args[2])
-        elif t == SCons.Errors.ExplicitExit:
-            status = e.status
-            sys.stderr.write("scons: *** [%s] Explicit exit, status %s\n" % (e.node, e.status))
-        else:
-            if e is None:
-                e = t
-            s = str(e)
-            if t == SCons.Errors.StopError and not keep_going_on_error:
-                s = s + '  Stop.'
-            sys.stderr.write("scons: *** %s\n" % s)
-
-            if tb and print_stacktrace:
-                sys.stderr.write("scons: internal stack trace:\n")
-                traceback.print_tb(tb, file=sys.stderr)
-
-        self.do_failed(status)
+            try:
+                t, e, tb = sys.exc_info()[:]
+            except ValueError:
+                t, e = exc_info
+                tb = None
+                
+        # Deprecated string exceptions will have their string stored
+        # in the first entry of the tuple.
+        if e is None:
+            e = t
+
+        buildError = SCons.Errors.convert_to_BuildError(e)
+        if not buildError.node:
+            buildError.node = self.node
+
+        node = buildError.node
+        if not SCons.Util.is_List(node):
+                node = [ node ]
+        nodename = ', '.join(map(str, node))
+
+        errfmt = "scons: *** [%s] %s\n"
+        sys.stderr.write(errfmt % (nodename, buildError))
+
+        if (buildError.exc_info[2] and buildError.exc_info[1] and 
+           # TODO(1.5)
+           #not isinstance(
+           #    buildError.exc_info[1], 
+           #    (EnvironmentError, SCons.Errors.StopError, SCons.Errors.UserError))):
+           not isinstance(buildError.exc_info[1], EnvironmentError) and
+           not isinstance(buildError.exc_info[1], SCons.Errors.StopError) and
+           not isinstance(buildError.exc_info[1], SCons.Errors.UserError)):
+            type, value, trace = buildError.exc_info
+            traceback.print_exception(type, value, trace)
+        elif tb and print_stacktrace:
+            sys.stderr.write("scons: internal stack trace:\n")
+            traceback.print_tb(tb, file=sys.stderr)
+
+        self.exception = (e, buildError, tb) # type, value, traceback
+        self.do_failed(buildError.exitstatus)
 
         self.exc_clear()
 
     def postprocess(self):
         if self.top:
             t = self.targets[0]
-            if print_tree:
-                print
-                SCons.Util.print_tree(t, get_all_children)
-            if print_stree:
-                print
-                SCons.Util.print_tree(t, get_all_children, showtags=2)
-            if print_dtree:
-                print
-                SCons.Util.print_tree(t, get_derived_children)
-            if print_includes:
+            for tp in self.options.tree_printers:
+                tp.display(t)
+            if self.options.debug_includes:
                 tree = t.render_include_tree()
                 if tree:
                     print
                     print tree
-        SCons.Taskmaster.Task.postprocess(self)
+        SCons.Taskmaster.OutOfDateTask.postprocess(self)
 
     def make_ready(self):
         """Make a task ready for execution"""
-        SCons.Taskmaster.Task.make_ready(self)
-        if self.out_of_date and print_explanations:
+        SCons.Taskmaster.OutOfDateTask.make_ready(self)
+        if self.out_of_date and self.options.debug_explain:
             explanation = self.out_of_date[0].explain()
             if explanation:
                 sys.stdout.write("scons: " + explanation)
 
-class CleanTask(SCons.Taskmaster.Task):
+class CleanTask(SCons.Taskmaster.AlwaysTask):
     """An SCons clean task."""
+    def fs_delete(self, path, pathstr, remove=1):
+        try:
+            if os.path.lexists(path):
+                if os.path.isfile(path) or os.path.islink(path):
+                    if remove: os.unlink(path)
+                    display("Removed " + pathstr)
+                elif os.path.isdir(path) and not os.path.islink(path):
+                    # delete everything in the dir
+                    for e in sorted(os.listdir(path)):
+                        p = os.path.join(path, e)
+                        s = os.path.join(pathstr, e)
+                        if os.path.isfile(p):
+                            if remove: os.unlink(p)
+                            display("Removed " + s)
+                        else:
+                            self.fs_delete(p, s, remove)
+                    # then delete dir itself
+                    if remove: os.rmdir(path)
+                    display("Removed directory " + pathstr)
+                else:
+                    errstr = "Path '%s' exists but isn't a file or directory."
+                    raise SCons.Errors.UserError(errstr % (pathstr))
+        except SCons.Errors.UserError, e:
+            print e
+        except (IOError, OSError), e:
+            print "scons: Could not remove '%s':" % pathstr, e.strerror
+
     def show(self):
         target = self.targets[0]
-        if (target.has_builder() or target.side_effect) and not target.isdir():
-            display("Removed " + str(target))
-        if SCons.Environment.CleanTargets.has_key(target):
+        if (target.has_builder() or target.side_effect) and not target.noclean:
+            for t in self.targets:
+                if not t.isdir():
+                    display("Removed " + str(t))
+        if target in SCons.Environment.CleanTargets:
             files = SCons.Environment.CleanTargets[target]
             for f in files:
-                SCons.Util.fs_delete(str(f), 0)
+                self.fs_delete(f.abspath, str(f), 0)
 
     def remove(self):
         target = self.targets[0]
-        if target.has_builder() or target.side_effect:
+        if (target.has_builder() or target.side_effect) and not target.noclean:
             for t in self.targets:
                 try:
                     removed = t.remove()
                 except OSError, e:
+                    # An OSError may indicate something like a permissions
+                    # issue, an IOError would indicate something like
+                    # the file not existing.  In either case, print a
+                    # message and keep going to try to remove as many
+                    # targets aa possible.
                     print "scons: Could not remove '%s':" % str(t), e.strerror
                 else:
                     if removed:
                         display("Removed " + str(t))
-        if SCons.Environment.CleanTargets.has_key(target):
+        if target in SCons.Environment.CleanTargets:
             files = SCons.Environment.CleanTargets[target]
             for f in files:
-                SCons.Util.fs_delete(str(f))
+                self.fs_delete(f.abspath, str(f))
 
     execute = remove
 
+    # We want the Taskmaster to update the Node states (and therefore
+    # handle reference counts, etc.), but we don't want to call
+    # back to the Node's post-build methods, which would do things
+    # we don't want, like store .sconsign information.
+    executed = SCons.Taskmaster.Task.executed_without_callbacks
+
     # Have the taskmaster arrange to "execute" all of the targets, because
     # we'll figure out ourselves (in remove() or show() above) whether
     # anything really needs to be done.
@@ -225,59 +385,99 @@ class CleanTask(SCons.Taskmaster.Task):
     def prepare(self):
         pass
 
-class QuestionTask(SCons.Taskmaster.Task):
+class QuestionTask(SCons.Taskmaster.AlwaysTask):
     """An SCons task for the -q (question) option."""
     def prepare(self):
         pass
-    
+
     def execute(self):
-        if self.targets[0].get_state() != SCons.Node.up_to_date:
+        if self.targets[0].get_state() != SCons.Node.up_to_date or \
+           (self.top and not self.targets[0].exists()):
             global exit_status
+            global this_build_status
             exit_status = 1
+            this_build_status = 1
             self.tm.stop()
 
     def executed(self):
         pass
 
+
+class TreePrinter:
+    def __init__(self, derived=False, prune=False, status=False):
+        self.derived = derived
+        self.prune = prune
+        self.status = status
+    def get_all_children(self, node):
+        return node.all_children()
+    def get_derived_children(self, node):
+        children = node.all_children(None)
+        return [x for x in children if x.has_builder()]
+    def display(self, t):
+        if self.derived:
+            func = self.get_derived_children
+        else:
+            func = self.get_all_children
+        s = self.status and 2 or 0
+        SCons.Util.print_tree(t, func, prune=self.prune, showtags=s)
+
+
+def python_version_string():
+    return sys.version.split()[0]
+
+def python_version_unsupported(version=sys.version_info):
+    return version < (1, 5, 2)
+
+def python_version_deprecated(version=sys.version_info):
+    return version < (2, 4, 0)
+
+
 # Global variables
 
-keep_going_on_error = 0
-print_dtree = 0
-print_explanations = 0
-print_includes = 0
 print_objects = 0
 print_memoizer = 0
 print_stacktrace = 0
-print_stree = 0
 print_time = 0
-print_tree = 0
-ignore_errors = 0
 sconscript_time = 0
-command_time = 0
-exit_status = 0 # exit status, assume success by default
-repositories = []
-num_jobs = 1 # this is modifed by SConscript.SetJobs()
-
-diskcheck_all = SCons.Node.FS.diskcheck_types()
-diskcheck_option_set = None
-
-def diskcheck_convert(value):
-    if value is None:
-        return []
-    if not SCons.Util.is_List(value):
-        value = string.split(value, ',')
-    result = []
-    for v in map(string.lower, value):
-        if v == 'all':
-            result = diskcheck_all
-        elif v == 'none':
-            result = []
-        elif v in diskcheck_all:
-            result.append(v)
-        else:
-            raise ValueError, v
+cumulative_command_time = 0
+exit_status = 0 # final exit status, assume success by default
+this_build_status = 0 # "exit status" of an individual build
+num_jobs = None
+delayed_warnings = []
+
+class FakeOptionParser:
+    """
+    A do-nothing option parser, used for the initial OptionsParser variable.
+
+    During normal SCons operation, the OptionsParser is created right
+    away by the main() function.  Certain tests scripts however, can
+    introspect on different Tool modules, the initialization of which
+    can try to add a new, local option to an otherwise uninitialized
+    OptionsParser object.  This allows that introspection to happen
+    without blowing up.
+
+    """
+    class FakeOptionValues:
+        def __getattr__(self, attr):
+            return None
+    values = FakeOptionValues()
+    def add_local_option(self, *args, **kw):
+        pass
+
+OptionsParser = FakeOptionParser()
+
+def AddOption(*args, **kw):
+    if 'default' not in kw:
+        kw['default'] = None
+    result = OptionsParser.add_local_option(*args, **kw)
     return result
 
+def GetOption(name):
+    return getattr(OptionsParser.values, name)
+
+def SetOption(name, value):
+    return OptionsParser.values.set_option(name, value)
+
 #
 class Stats:
     def __init__(self):
@@ -299,26 +499,24 @@ class CountStats(Stats):
     def do_print(self):
         stats_table = {}
         for s in self.stats:
-            for n in map(lambda t: t[0], s):
+            for n in [t[0] for t in s]:
                 stats_table[n] = [0, 0, 0, 0]
         i = 0
         for s in self.stats:
             for n, c in s:
                 stats_table[n][i] = c
             i = i + 1
-        keys = stats_table.keys()
-        keys.sort()
         self.outfp.write("Object counts:\n")
         pre = ["   "]
         post = ["   %s\n"]
         l = len(self.stats)
-        fmt1 = string.join(pre + [' %7s']*l + post, '')
-        fmt2 = string.join(pre + [' %7d']*l + post, '')
+        fmt1 = ''.join(pre + [' %7s']*l + post)
+        fmt2 = ''.join(pre + [' %7d']*l + post)
         labels = self.labels[:l]
         labels.append(("", "Class"))
-        self.outfp.write(fmt1 % tuple(map(lambda x: x[0], labels)))
-        self.outfp.write(fmt1 % tuple(map(lambda x: x[1], labels)))
-        for k in keys:
+        self.outfp.write(fmt1 % tuple([x[0] for x in labels]))
+        self.outfp.write(fmt1 % tuple([x[1] for x in labels]))
+        for k in sorted(stats_table.keys()):
             r = stats_table[k][:l] + [k]
             self.outfp.write(fmt2 % tuple(r))
 
@@ -337,12 +535,6 @@ memory_stats = MemStats()
 
 # utility functions
 
-def get_all_children(node): return node.all_children()
-
-def get_derived_children(node):
-    children = node.all_children(None)
-    return filter(lambda x: x.has_builder(), children)
-
 def _scons_syntax_error(e):
     """Handle syntax errors. Print out a message and show where the error
     occurred.
@@ -367,7 +559,7 @@ def find_deepest_user_frame(tb):
     # of SCons:
     for frame in tb:
         filename = frame[0]
-        if string.find(filename, os.sep+'SCons'+os.sep) == -1:
+        if filename.find(os.sep+'SCons'+os.sep) == -1:
             return frame
     return tb[0]
 
@@ -377,7 +569,10 @@ def _scons_user_error(e):
     The file and line number will be the deepest stack frame that is
     not part of SCons itself.
     """
+    global print_stacktrace
     etype, value, tb = sys.exc_info()
+    if print_stacktrace:
+        traceback.print_exception(etype, value, tb)
     filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_tb(tb))
     sys.stderr.write("\nscons: *** %s\n" % value)
     sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
@@ -410,67 +605,14 @@ def _scons_internal_error():
     traceback.print_exc()
     sys.exit(2)
 
-def _varargs(option, parser):
-    value = None
-    if parser.rargs:
-        arg = parser.rargs[0]
-        if arg[0] != "-":
-            value = arg
-            del parser.rargs[0]
-    return value
-
-def _setup_warn(arg):
-    """The --warn option.  An argument to this option
-    should be of the form <warning-class> or no-<warning-class>.
-    The warning class is munged in order to get an actual class
-    name from the SCons.Warnings module to enable or disable.
-    The supplied <warning-class> is split on hyphens, each element
-    is captialized, then smushed back together.  Then the string
-    "SCons.Warnings." is added to the front and "Warning" is added
-    to the back to get the fully qualified class name.
-
-    For example, --warn=deprecated will enable the
-    SCons.Warnings.DeprecatedWarning class.
-
-    --warn=no-dependency will disable the
-    SCons.Warnings.DependencyWarning class.
-
-    As a special case, --warn=all and --warn=no-all
-    will enable or disable (respectively) the base
-    class of all warnings, which is SCons.Warning.Warning."""
-
-    elems = string.split(string.lower(arg), '-')
-    enable = 1
-    if elems[0] == 'no':
-        enable = 0
-        del elems[0]
-
-    if len(elems) == 1 and elems[0] == 'all':
-        class_name = "Warning"
-    else:
-        def _capitalize(s):
-            if s[:5] == "scons":
-                return "SCons" + s[5:]
-            else:
-                return string.capitalize(s)
-        class_name = string.join(map(_capitalize, elems), '') + "Warning"
-    try:
-        clazz = getattr(SCons.Warnings, class_name)
-    except AttributeError:
-        sys.stderr.write("No warning type: '%s'\n" % arg)
-    else:
-        if enable:
-            SCons.Warnings.enableWarningClass(clazz)
-        else:
-            SCons.Warnings.suppressWarningClass(clazz)
-
-def _SConstruct_exists(dirname=''):
+def _SConstruct_exists(dirname='', repositories=[], filelist=None):
     """This function checks that an SConstruct file exists in a directory.
     If so, it returns the path of the file. By default, it checks the
     current directory.
     """
-    global repositories
-    for file in ['SConstruct', 'Sconstruct', 'sconstruct']:
+    if not filelist:
+        filelist = ['SConstruct', 'Sconstruct', 'sconstruct']
+    for file in filelist:
         sfile = os.path.join(dirname, file)
         if os.path.isfile(sfile):
             return sfile
@@ -480,49 +622,44 @@ def _SConstruct_exists(dirname=''):
                     return sfile
     return None
 
-def _set_globals(options):
-    global keep_going_on_error, ignore_errors
-    global count_stats, print_dtree
-    global print_explanations, print_includes, print_memoizer
-    global print_objects, print_stacktrace, print_stree
-    global print_time, print_tree
-    global memory_stats
+def _set_debug_values(options):
+    global print_memoizer, print_objects, print_stacktrace, print_time
 
-    keep_going_on_error = options.keep_going
-    try:
-        debug_values = options.debug
-        if debug_values is None:
-            debug_values = []
-    except AttributeError:
-        pass
-    else:
-        if "count" in debug_values:
+    debug_values = options.debug
+
+    if "count" in debug_values:
+        # All of the object counts are within "if __debug__:" blocks,
+        # which get stripped when running optimized (with python -O or
+        # from compiled *.pyo files).  Provide a warning if __debug__ is
+        # stripped, so it doesn't just look like --debug=count is broken.
+        enable_count = False
+        if __debug__: enable_count = True
+        if enable_count:
             count_stats.enable(sys.stdout)
-        if "dtree" in debug_values:
-            print_dtree = 1
-        if "explain" in debug_values:
-            print_explanations = 1
-        if "findlibs" in debug_values:
-            SCons.Scanner.Prog.print_find_libs = "findlibs"
-        if "includes" in debug_values:
-            print_includes = 1
-        if "memoizer" in debug_values:
-            print_memoizer = 1
-        if "memory" in debug_values:
-            memory_stats.enable(sys.stdout)
-        if "objects" in debug_values:
-            print_objects = 1
-        if "presub" in debug_values:
-            SCons.Action.print_actions_presub = 1
-        if "stacktrace" in debug_values:
-            print_stacktrace = 1
-        if "stree" in debug_values:
-            print_stree = 1
-        if "time" in debug_values:
-            print_time = 1
-        if "tree" in debug_values:
-            print_tree = 1
-    ignore_errors = options.ignore_errors
+        else:
+            msg = "--debug=count is not supported when running SCons\n" + \
+                  "\twith the python -O option or optimized (.pyo) modules."
+            SCons.Warnings.warn(SCons.Warnings.NoObjectCountWarning, msg)
+    if "dtree" in debug_values:
+        options.tree_printers.append(TreePrinter(derived=True))
+    options.debug_explain = ("explain" in debug_values)
+    if "findlibs" in debug_values:
+        SCons.Scanner.Prog.print_find_libs = "findlibs"
+    options.debug_includes = ("includes" in debug_values)
+    print_memoizer = ("memoizer" in debug_values)
+    if "memory" in debug_values:
+        memory_stats.enable(sys.stdout)
+    print_objects = ("objects" in debug_values)
+    if "presub" in debug_values:
+        SCons.Action.print_actions_presub = 1
+    if "stacktrace" in debug_values:
+        print_stacktrace = 1
+    if "stree" in debug_values:
+        options.tree_printers.append(TreePrinter(status=True))
+    if "time" in debug_values:
+        print_time = 1
+    if "tree" in debug_values:
+        options.tree_printers.append(TreePrinter())
 
 def _create_path(plist):
     path = '.'
@@ -533,371 +670,131 @@ def _create_path(plist):
             path = path + '/' + d
     return path
 
-
-class OptParser(OptionParser):
-    def __init__(self):
-        import __main__
-        import SCons
-        parts = ["SCons by Steven Knight et al.:\n"]
-        try:
-            parts.append("\tscript: v%s.%s, %s, by %s on %s\n" % (__main__.__version__,
-                                                                  __main__.__build__,
-                                                                  __main__.__date__,
-                                                                  __main__.__developer__,
-                                                                  __main__.__buildsys__))
-        except KeyboardInterrupt:
-            raise
-        except:
-            # On win32 there is no scons.py, so there is no __main__.__version__,
-            # hence there is no script version.
-            pass 
-        parts.append("\tengine: v%s.%s, %s, by %s on %s\n" % (SCons.__version__,
-                                                              SCons.__build__,
-                                                              SCons.__date__,
-                                                              SCons.__developer__,
-                                                              SCons.__buildsys__))
-        parts.append("__COPYRIGHT__")
-        OptionParser.__init__(self, version=string.join(parts, ''),
-                              usage="usage: scons [OPTION] [TARGET] ...")
-
-        # options ignored for compatibility
-        def opt_ignore(option, opt, value, parser):
-            sys.stderr.write("Warning:  ignoring %s option\n" % opt)
-        self.add_option("-b", "-m", "-S", "-t", "--no-keep-going", "--stop",
-                        "--touch", action="callback", callback=opt_ignore,
-                        help="Ignored for compatibility.")
-
-        self.add_option('-c', '--clean', '--remove', action="store_true",
-                        dest="clean",
-                        help="Remove specified targets and dependencies.")
-
-        self.add_option('-C', '--directory', type="string", action = "append",
-                        metavar="DIR",
-                        help="Change to DIR before doing anything.")
-
-        self.add_option('--cache-disable', '--no-cache',
-                        action="store_true", dest='cache_disable', default=0,
-                        help="Do not retrieve built targets from CacheDir.")
-
-        self.add_option('--cache-force', '--cache-populate',
-                        action="store_true", dest='cache_force', default=0,
-                        help="Copy already-built targets into the CacheDir.")
-
-        self.add_option('--cache-show',
-                        action="store_true", dest='cache_show', default=0,
-                        help="Print build actions for files from CacheDir.")
-
-        config_options = ["auto", "force" ,"cache"]
-
-        def opt_config(option, opt, value, parser, c_options=config_options):
-            if value in c_options:
-                parser.values.config = value
-            else:
-                raise OptionValueError("Warning:  %s is not a valid config type" % value)
-        self.add_option('--config', action="callback", type="string",
-                        callback=opt_config, nargs=1, dest="config",
-                        metavar="MODE", default="auto",
-                        help="Controls Configure subsystem: "
-                             "%s." % string.join(config_options, ", "))
-
-        def opt_not_yet(option, opt, value, parser):
-            sys.stderr.write("Warning:  the %s option is not yet implemented\n" % opt)
-            sys.exit(0)
-        self.add_option('-d', action="callback",
-                        callback=opt_not_yet,
-                        help = "Print file dependency information.")
+def _load_site_scons_dir(topdir, site_dir_name=None):
+    """Load the site_scons dir under topdir.
+    Adds site_scons to sys.path, imports site_scons/site_init.py,
+    and adds site_scons/site_tools to default toolpath."""
+    if site_dir_name:
+        err_if_not_found = True       # user specified: err if missing
+    else:
+        site_dir_name = "site_scons"
+        err_if_not_found = False
         
-        self.add_option('-D', action="store_const", const=2, dest="climb_up",
-                        help="Search up directory tree for SConstruct,       "
-                             "build all Default() targets.")
-
-        debug_options = ["count", "dtree", "explain", "findlibs",
-                         "includes", "memoizer", "memory",
-                         "nomemoizer", "objects",
-                         "pdb", "presub", "stacktrace", "stree",
-                         "time", "tree"]
-
-        def opt_debug(option, opt, value, parser, debug_options=debug_options):
-            if value in debug_options:
-                try:
-                    if parser.values.debug is None:
-                        parser.values.debug = []
-                except AttributeError:
-                    parser.values.debug = []
-                parser.values.debug.append(value)
-            else:
-                raise OptionValueError("Warning:  %s is not a valid debug type" % value)
-        self.add_option('--debug', action="callback", type="string",
-                        callback=opt_debug, nargs=1, dest="debug",
-                        metavar="TYPE",
-                        help="Print various types of debugging information: "
-                             "%s." % string.join(debug_options, ", "))
-
-        def opt_diskcheck(option, opt, value, parser):
-            try:
-                global diskcheck_option_set
-                diskcheck_option_set = diskcheck_convert(value)
-                SCons.Node.FS.set_diskcheck(diskcheck_option_set)
-            except ValueError, e:
-                raise OptionValueError("Warning: `%s' is not a valid diskcheck type" % e)
-
-            
-        self.add_option('--diskcheck', action="callback", type="string",
-                        callback=opt_diskcheck, dest='diskcheck',
-                        metavar="TYPE",
-                        help="Enable specific on-disk checks.")
-
-        def opt_duplicate(option, opt, value, parser):
-            if not value in SCons.Node.FS.Valid_Duplicates:
-                raise OptionValueError("`%s' is not a valid duplication style." % value)
-            parser.values.duplicate = value
-            # Set the duplicate style right away so it can affect linking
-            # of SConscript files.
-            SCons.Node.FS.set_duplicate(value)
-        self.add_option('--duplicate', action="callback", type="string",
-                        callback=opt_duplicate, nargs=1, dest="duplicate",
-                        help="Set the preferred duplication methods. Must be one of "
-                        + string.join(SCons.Node.FS.Valid_Duplicates, ", "))
-
-        self.add_option('-f', '--file', '--makefile', '--sconstruct',
-                        action="append", nargs=1,
-                        help="Read FILE as the top-level SConstruct file.")
-
-        self.add_option('-h', '--help', action="store_true", default=0,
-                        dest="help_msg",
-                        help="Print defined help message, or this one.")
-
-        self.add_option("-H", "--help-options",
-                        action="help",
-                        help="Print this message and exit.")
-
-        self.add_option('-i', '--ignore-errors', action="store_true",
-                        default=0, dest='ignore_errors',
-                        help="Ignore errors from build actions.")
-
-        self.add_option('-I', '--include-dir', action="append",
-                        dest='include_dir', metavar="DIR",
-                        help="Search DIR for imported Python modules.")
-
-        self.add_option('--implicit-cache', action="store_true",
-                        dest='implicit_cache',
-                        help="Cache implicit dependencies")
-
-        self.add_option('--implicit-deps-changed', action="store_true",
-                        default=0, dest='implicit_deps_changed',
-                        help="Ignore cached implicit dependencies.")
-        self.add_option('--implicit-deps-unchanged', action="store_true",
-                        default=0, dest='implicit_deps_unchanged',
-                        help="Ignore changes in implicit dependencies.")
-
-        def opt_j(option, opt, value, parser):
-            value = int(value)
-            parser.values.num_jobs = value
-        self.add_option('-j', '--jobs', action="callback", type="int",
-                        callback=opt_j, metavar="N",
-                        help="Allow N jobs at once.")
-
-        self.add_option('-k', '--keep-going', action="store_true", default=0,
-                        dest='keep_going',
-                        help="Keep going when a target can't be made.")
-
-        self.add_option('--max-drift', type="int", action="store",
-                        dest='max_drift', metavar="N",
-                        help="Set maximum system clock drift to N seconds.")
-
-        self.add_option('-n', '--no-exec', '--just-print', '--dry-run',
-                        '--recon', action="store_true", dest='noexec',
-                        default=0, help="Don't build; just print commands.")
-
-        self.add_option('--profile', action="store",
-                        dest="profile_file", metavar="FILE",
-                        help="Profile SCons and put results in FILE.")
-
-        self.add_option('-q', '--question', action="store_true", default=0,
-                        help="Don't build; exit status says if up to date.")
-
-        self.add_option('-Q', dest='no_progress', action="store_true",
-                        default=0,
-                        help="Suppress \"Reading/Building\" progress messages.")
-
-        self.add_option('--random', dest="random", action="store_true",
-                        default=0, help="Build dependencies in random order.")
-
-        self.add_option('-s', '--silent', '--quiet', action="store_true",
-                        default=0, help="Don't print commands.")
-
-        self.add_option('-u', '--up', '--search-up', action="store_const",
-                        dest="climb_up", default=0, const=1,
-                        help="Search up directory tree for SConstruct,       "
-                             "build targets at or below current directory.")
-        self.add_option('-U', action="store_const", dest="climb_up",
-                        default=0, const=3,
-                        help="Search up directory tree for SConstruct,       "
-                             "build Default() targets from local SConscript.")
-
-        self.add_option("-v", "--version",
-                        action="version",
-                        help="Print the SCons version number and exit.")
-
-        self.add_option('--warn', '--warning', nargs=1, action="store",
-                        metavar="WARNING-SPEC",
-                        help="Enable or disable warnings.")
-
-        self.add_option('-Y', '--repository', nargs=1, action="append",
-                        help="Search REPOSITORY for source and target files.")
-
-        self.add_option('-e', '--environment-overrides', action="callback",
-                        callback=opt_not_yet,
-                        # help="Environment variables override makefiles."
-                        help=SUPPRESS_HELP)
-        self.add_option('-l', '--load-average', '--max-load', action="callback",
-                        callback=opt_not_yet, type="int", dest="load_average",
-                        # action="store",
-                        # help="Don't start multiple jobs unless load is below "
-                        #      "LOAD-AVERAGE."
-                        # type="int",
-                        help=SUPPRESS_HELP)
-        self.add_option('--list-derived', action="callback",
-                        callback=opt_not_yet,
-                        # help="Don't build; list files that would be built."
-                        help=SUPPRESS_HELP)
-        self.add_option('--list-actions', action="callback",
-                        callback=opt_not_yet,
-                        # help="Don't build; list files and build actions."
-                        help=SUPPRESS_HELP)
-        self.add_option('--list-where', action="callback",
-                        callback=opt_not_yet,
-                        # help="Don't build; list files and where defined."
-                        help=SUPPRESS_HELP)
-        self.add_option('-o', '--old-file', '--assume-old', action="callback",
-                        callback=opt_not_yet, type="string", dest="old_file",
-                        # help = "Consider FILE to be old; don't rebuild it."
-                        help=SUPPRESS_HELP)
-        self.add_option('--override', action="callback", dest="override",
-                        callback=opt_not_yet, type="string",
-                        # help="Override variables as specified in FILE."
-                        help=SUPPRESS_HELP)
-        self.add_option('-p', action="callback",
-                        callback=opt_not_yet,
-                        # help="Print internal environments/objects."
-                        help=SUPPRESS_HELP)
-        self.add_option('-r', '-R', '--no-builtin-rules',
-                        '--no-builtin-variables', action="callback",
-                        callback=opt_not_yet,
-                        # help="Clear default environments and variables."
-                        help=SUPPRESS_HELP)
-        self.add_option('-w', '--print-directory', action="callback",
-                        callback=opt_not_yet,
-                        # help="Print the current directory."
-                        help=SUPPRESS_HELP)
-        self.add_option('--no-print-directory', action="callback",
-                        callback=opt_not_yet,
-                        # help="Turn off -w, even if it was turned on implicitly."
-                        help=SUPPRESS_HELP)
-        self.add_option('--write-filenames', action="callback",
-                        callback=opt_not_yet, type="string", dest="write_filenames",
-                        # help="Write all filenames examined into FILE."
-                        help=SUPPRESS_HELP)
-        self.add_option('-W', '--what-if', '--new-file', '--assume-new',
-                        dest="new_file",
-                        action="callback", callback=opt_not_yet, type="string",
-                        # help="Consider FILE to be changed."
-                        help=SUPPRESS_HELP)
-        self.add_option('--warn-undefined-variables', action="callback",
-                        callback=opt_not_yet,
-                        # help="Warn when an undefined variable is referenced."
-                        help=SUPPRESS_HELP)
-
-    def parse_args(self, args=None, values=None):
-        opt, arglist = OptionParser.parse_args(self, args, values)
-        if opt.implicit_deps_changed or opt.implicit_deps_unchanged:
-            opt.implicit_cache = 1
-        return opt, arglist
-
-class SConscriptSettableOptions:
-    """This class wraps an OptParser instance and provides
-    uniform access to options that can be either set on the command
-    line or from a SConscript file. A value specified on the command
-    line always overrides a value set in a SConscript file.
-    Not all command line options are SConscript settable, and the ones
-    that are must be explicitly added to settable dictionary and optionally
-    validated and coerced in the set() method."""
-    
-    def __init__(self, options):
-        self.options = options
-
-        # This dictionary stores the defaults for all the SConscript
-        # settable options, as well as indicating which options
-        # are SConscript settable. 
-        self.settable = {'num_jobs':1,
-                         'max_drift':SCons.Sig.default_max_drift,
-                         'implicit_cache':0,
-                         'clean':0,
-                         'duplicate':'hard-soft-copy',
-                         'diskcheck':diskcheck_all}
-
-    def get(self, name):
-        if not self.settable.has_key(name):
-            raise SCons.Error.UserError, "This option is not settable from a SConscript file: %s"%name
-        if hasattr(self.options, name) and getattr(self.options, name) is not None:
-            return getattr(self.options, name)
-        else:
-            return self.settable[name]
-
-    def set(self, name, value):
-        if not self.settable.has_key(name):
-            raise SCons.Error.UserError, "This option is not settable from a SConscript file: %s"%name
-
-        if name == 'num_jobs':
-            try:
-                value = int(value)
-                if value < 1:
-                    raise ValueError
-            except ValueError:
-                raise SCons.Errors.UserError, "A positive integer is required: %s"%repr(value)
-        elif name == 'max_drift':
-            try:
-                value = int(value)
-            except ValueError:
-                raise SCons.Errors.UserError, "An integer is required: %s"%repr(value)
-        elif name == 'duplicate':
-            try:
-                value = str(value)
-            except ValueError:
-                raise SCons.Errors.UserError, "A string is required: %s"%repr(value)
-            if not value in SCons.Node.FS.Valid_Duplicates:
-                raise SCons.Errors.UserError, "Not a valid duplication style: %s" % value
-            # Set the duplicate stye right away so it can affect linking
-            # of SConscript files.
-            SCons.Node.FS.set_duplicate(value)
-        elif name == 'diskcheck':
+    site_dir = os.path.join(topdir.path, site_dir_name)
+    if not os.path.exists(site_dir):
+        if err_if_not_found:
+            raise SCons.Errors.UserError("site dir %s not found."%site_dir)
+        return
+
+    site_init_filename = "site_init.py"
+    site_init_modname = "site_init"
+    site_tools_dirname = "site_tools"
+    sys.path = [os.path.abspath(site_dir)] + sys.path
+    site_init_file = os.path.join(site_dir, site_init_filename)
+    site_tools_dir = os.path.join(site_dir, site_tools_dirname)
+    if os.path.exists(site_init_file):
+        import imp
+        # TODO(2.4): turn this into try:-except:-finally:
+        try:
             try:
-                value = diskcheck_convert(value)
-            except ValueError, v:
-                raise SCons.Errors.UserError, "Not a valid diskcheck value: %s"%v
-            if not diskcheck_option_set:
-                SCons.Node.FS.set_diskcheck(value)
+                fp, pathname, description = imp.find_module(site_init_modname,
+                                                            [site_dir])
+                # Load the file into SCons.Script namespace.  This is
+                # opaque and clever; m is the module object for the
+                # SCons.Script module, and the exec ... in call executes a
+                # file (or string containing code) in the context of the
+                # module's dictionary, so anything that code defines ends
+                # up adding to that module.  This is really short, but all
+                # the error checking makes it longer.
+                try:
+                    m = sys.modules['SCons.Script']
+                except Exception, e:
+                    fmt = 'cannot import site_init.py: missing SCons.Script module %s'
+                    raise SCons.Errors.InternalError(fmt % repr(e))
+                try:
+                    # This is the magic.
+                    exec fp in m.__dict__
+                except KeyboardInterrupt:
+                    raise
+                except Exception, e:
+                    fmt = '*** Error loading site_init file %s:\n'
+                    sys.stderr.write(fmt % repr(site_init_file))
+                    raise
+            except KeyboardInterrupt:
+                raise
+            except ImportError, e:
+                fmt = '*** cannot import site init file %s:\n'
+                sys.stderr.write(fmt % repr(site_init_file))
+                raise
+        finally:
+            if fp:
+                fp.close()
+    if os.path.exists(site_tools_dir):
+        SCons.Tool.DefaultToolpath.append(os.path.abspath(site_tools_dir))
+
+def version_string(label, module):
+    version = module.__version__
+    build = module.__build__
+    if build:
+        if build[0] != '.':
+            build = '.' + build
+        version = version + build
+    fmt = "\t%s: v%s, %s, by %s on %s\n"
+    return fmt % (label,
+                  version,
+                  module.__date__,
+                  module.__developer__,
+                  module.__buildsys__)
+
+def _main(parser):
+    global exit_status
+    global this_build_status
 
-        self.settable[name] = value
-    
+    options = parser.values
 
-def _main(args, parser):
     # Here's where everything really happens.
 
-    # First order of business:  set up default warnings and and then
-    # handle the user's warning options, so we can warn about anything
-    # that happens appropriately.
+    # First order of business:  set up default warnings and then
+    # handle the user's warning options, so that we can issue (or
+    # suppress) appropriate warnings about anything that might happen,
+    # as configured by the user.
+
     default_warnings = [ SCons.Warnings.CorruptSConsignWarning,
                          SCons.Warnings.DeprecatedWarning,
                          SCons.Warnings.DuplicateEnvironmentWarning,
+                         SCons.Warnings.FutureReservedVariableWarning,
+                         SCons.Warnings.LinkWarning,
                          SCons.Warnings.MissingSConscriptWarning,
+                         SCons.Warnings.NoMD5ModuleWarning,
+                         SCons.Warnings.NoMetaclassSupportWarning,
+                         SCons.Warnings.NoObjectCountWarning,
                          SCons.Warnings.NoParallelSupportWarning,
-                         SCons.Warnings.MisleadingKeywordsWarning, ]
+                         SCons.Warnings.MisleadingKeywordsWarning,
+                         SCons.Warnings.ReservedVariableWarning,
+                         SCons.Warnings.StackSizeWarning,
+                         SCons.Warnings.VisualVersionMismatch,
+                         SCons.Warnings.VisualCMissingWarning,
+                       ]
+
     for warning in default_warnings:
         SCons.Warnings.enableWarningClass(warning)
     SCons.Warnings._warningOut = _scons_internal_warning
-    if options.warn:
-        _setup_warn(options.warn)
+    SCons.Warnings.process_warn_strings(options.warn)
+
+    # Now that we have the warnings configuration set up, we can actually
+    # issue (or suppress) any warnings about warning-worthy things that
+    # occurred while the command-line options were getting parsed.
+    try:
+        dw = options.delayed_warnings
+    except AttributeError:
+        pass
+    else:
+        delayed_warnings.extend(dw)
+    for warning_type, message in delayed_warnings:
+        SCons.Warnings.warn(warning_type, message)
+
+    if options.diskcheck:
+        SCons.Node.FS.set_diskcheck(options.diskcheck)
 
     # Next, we want to create the FS object that represents the outside
     # world's file system, as that's central to a lot of initialization.
@@ -905,38 +802,35 @@ def _main(args, parser):
     # want to start everything, which means first handling any relevant
     # options that might cause us to chdir somewhere (-C, -D, -U, -u).
     if options.directory:
-        cdir = _create_path(options.directory)
-        try:
-            os.chdir(cdir)
-        except OSError:
-            sys.stderr.write("Could not change directory to %s\n" % cdir)
-
-    # The SConstruct file may be in a repository, so initialize those
-    # before we start the search up our path for one.
-    global repositories
-    if options.repository:
-        repositories.extend(options.repository)
+        script_dir = os.path.abspath(_create_path(options.directory))
+    else:
+        script_dir = os.getcwd()
 
     target_top = None
     if options.climb_up:
         target_top = '.'  # directory to prepend to targets
-        script_dir = os.getcwd()  # location of script
-        while script_dir and not _SConstruct_exists(script_dir):
+        while script_dir and not _SConstruct_exists(script_dir,
+                                                    options.repository,
+                                                    options.file):
             script_dir, last_part = os.path.split(script_dir)
             if last_part:
                 target_top = os.path.join(last_part, target_top)
             else:
                 script_dir = ''
-        if script_dir:
-            display("scons: Entering directory `%s'" % script_dir)
+
+    if script_dir and script_dir != os.getcwd():
+        display("scons: Entering directory `%s'" % script_dir)
+        try:
             os.chdir(script_dir)
+        except OSError:
+            sys.stderr.write("Could not change directory to %s\n" % script_dir)
 
     # Now that we're in the top-level SConstruct directory, go ahead
     # and initialize the FS object that represents the file system,
     # and make it the build engine default.
-    fs = SCons.Node.FS.default_fs = SCons.Node.FS.FS()
+    fs = SCons.Node.FS.get_default_fs()
 
-    for rep in repositories:
+    for rep in options.repository:
         fs.Repository(rep)
 
     # Now that we have the FS object, the next order of business is to
@@ -946,18 +840,18 @@ def _main(args, parser):
     if options.file:
         scripts.extend(options.file)
     if not scripts:
-        sfile = _SConstruct_exists()
+        sfile = _SConstruct_exists(repositories=options.repository,
+                                   filelist=options.file)
         if sfile:
             scripts.append(sfile)
 
     if not scripts:
-        if options.help_msg:
+        if options.help:
             # There's no SConstruct, but they specified -h.
             # Give them the options usage now, before we fail
             # trying to read a non-existent SConstruct file.
-            parser.print_help()
-            sys.exit(0)
-        raise SCons.Errors.UserError, "No SConstruct file found."
+            raise SConsPrintHelpException
+        raise SCons.Errors.UserError("No SConstruct file found.")
 
     if scripts[0] == "-":
         d = fs.getcwd()
@@ -965,38 +859,31 @@ def _main(args, parser):
         d = fs.File(scripts[0]).dir
     fs.set_SConstruct_dir(d)
 
-    # Now that we have the FS object and it's intialized, set up (most
-    # of) the rest of the options.
-    global ssoptions
-    ssoptions = SConscriptSettableOptions(options)
-
-    _set_globals(options)
+    _set_debug_values(options)
     SCons.Node.implicit_cache = options.implicit_cache
     SCons.Node.implicit_deps_changed = options.implicit_deps_changed
     SCons.Node.implicit_deps_unchanged = options.implicit_deps_unchanged
-    if options.noexec:
+
+    if options.no_exec:
         SCons.SConf.dryrun = 1
         SCons.Action.execute_actions = None
-        CleanTask.execute = CleanTask.show
     if options.question:
         SCons.SConf.dryrun = 1
+    if options.clean:
+        SCons.SConf.SetBuildType('clean')
+    if options.help:
+        SCons.SConf.SetBuildType('help')
     SCons.SConf.SetCacheMode(options.config)
     SCons.SConf.SetProgressDisplay(progress_display)
 
     if options.no_progress or options.silent:
         progress_display.set_mode(0)
-    if options.silent:
-        display.set_mode(0)
-    if options.silent:
-        SCons.Action.print_actions = None
-    if options.cache_disable:
-        def disable(self): pass
-        fs.CacheDir = disable
-    if options.cache_force:
-        fs.cache_force = 1
-    if options.cache_show:
-        fs.cache_show = 1
 
+    if options.site_dir:
+        _load_site_scons_dir(d, options.site_dir)
+    elif not options.no_site_dir:
+        _load_site_scons_dir(d)
+        
     if options.include_dir:
         sys.path = options.include_dir + sys.path
 
@@ -1005,28 +892,34 @@ def _main(args, parser):
     # read and execute have access to them.
     targets = []
     xmit_args = []
-    for a in args:
+    for a in parser.largs:
+        if a[:1] == '-':
+            continue
         if '=' in a:
             xmit_args.append(a)
         else:
             targets.append(a)
-    SCons.Script._Add_Targets(targets)
+    SCons.Script._Add_Targets(targets + parser.rargs)
     SCons.Script._Add_Arguments(xmit_args)
 
-    class Unbuffered:
-        def __init__(self, file):
-            self.file = file
-        def write(self, arg):
-            self.file.write(arg)
-            self.file.flush()
-        def __getattr__(self, attr):
-            return getattr(self.file, attr)
-
-    sys.stdout = Unbuffered(sys.stdout)
+    # If stdout is not a tty, replace it with a wrapper object to call flush
+    # after every write.
+    #
+    # Tty devices automatically flush after every newline, so the replacement
+    # isn't necessary.  Furthermore, if we replace sys.stdout, the readline
+    # module will no longer work.  This affects the behavior during
+    # --interactive mode.  --interactive should only be used when stdin and
+    # stdout refer to a tty.
+    if not hasattr(sys.stdout, 'isatty') or not sys.stdout.isatty():
+        sys.stdout = SCons.Util.Unbuffered(sys.stdout)
+    if not hasattr(sys.stderr, 'isatty') or not sys.stderr.isatty():
+        sys.stderr = SCons.Util.Unbuffered(sys.stderr)
 
     memory_stats.append('before reading SConscript files:')
     count_stats.append(('pre-', 'read'))
 
+    # And here's where we (finally) read the SConscript files.
+
     progress_display("scons: Reading SConscript files ...")
 
     start_time = time.time()
@@ -1035,54 +928,135 @@ def _main(args, parser):
             SCons.Script._SConscript._SConscript(fs, script)
     except SCons.Errors.StopError, e:
         # We had problems reading an SConscript file, such as it
-        # couldn't be copied in to the BuildDir.  Since we're just
+        # couldn't be copied in to the VariantDir.  Since we're just
         # reading SConscript files and haven't started building
         # things yet, stop regardless of whether they used -i or -k
         # or anything else.
-        global exit_status
         sys.stderr.write("scons: *** %s  Stop.\n" % e)
         exit_status = 2
         sys.exit(exit_status)
     global sconscript_time
     sconscript_time = time.time() - start_time
-    SCons.SConf.CreateConfigHBuilder(SCons.Defaults.DefaultEnvironment())
-    progress_display("scons: done reading SConscript files.")
 
-    # Tell the Node.FS subsystem that we're all done reading the
-    # SConscript files and calling Repository() and BuildDir() and the
-    # like, so it can go ahead and start memoizing the string values of
-    # file system nodes.
-    SCons.Node.FS.save_strings(1)
+    progress_display("scons: done reading SConscript files.")
 
     memory_stats.append('after reading SConscript files:')
     count_stats.append(('post-', 'read'))
 
-    fs.chdir(fs.Top)
-
-    if options.help_msg:
+    # Re-{enable,disable} warnings in case they disabled some in
+    # the SConscript file.
+    #
+    # We delay enabling the PythonVersionWarning class until here so that,
+    # if they explicity disabled it in either in the command line or in
+    # $SCONSFLAGS, or in the SConscript file, then the search through
+    # the list of deprecated warning classes will find that disabling
+    # first and not issue the warning.
+    SCons.Warnings.enableWarningClass(SCons.Warnings.PythonVersionWarning)
+    SCons.Warnings.process_warn_strings(options.warn)
+
+    # Now that we've read the SConscript files, we can check for the
+    # warning about deprecated Python versions--delayed until here
+    # in case they disabled the warning in the SConscript files.
+    if python_version_deprecated():
+        msg = "Support for pre-2.4 Python (%s) is deprecated.\n" + \
+              "    If this will cause hardship, contact dev@scons.tigris.org."
+        SCons.Warnings.warn(SCons.Warnings.PythonVersionWarning,
+                            msg % python_version_string())
+
+    if not options.help:
+        SCons.SConf.CreateConfigHBuilder(SCons.Defaults.DefaultEnvironment())
+
+    # Now re-parse the command-line options (any to the left of a '--'
+    # argument, that is) with any user-defined command-line options that
+    # the SConscript files may have added to the parser object.  This will
+    # emit the appropriate error message and exit if any unknown option
+    # was specified on the command line.
+
+    parser.preserve_unknown_options = False
+    parser.parse_args(parser.largs, options)
+
+    if options.help:
         help_text = SCons.Script.help_text
         if help_text is None:
             # They specified -h, but there was no Help() inside the
             # SConscript files.  Give them the options usage.
-            parser.print_help(sys.stdout)
+            raise SConsPrintHelpException
         else:
             print help_text
             print "Use scons -H for help about command-line options."
-        sys.exit(0)
+        exit_status = 0
+        return
+
+    # Change directory to the top-level SConstruct directory, then tell
+    # the Node.FS subsystem that we're all done reading the SConscript
+    # files and calling Repository() and VariantDir() and changing
+    # directories and the like, so it can go ahead and start memoizing
+    # the string values of file system nodes.
+
+    fs.chdir(fs.Top)
+
+    SCons.Node.FS.save_strings(1)
 
     # Now that we've read the SConscripts we can set the options
     # that are SConscript settable:
-    SCons.Node.implicit_cache = ssoptions.get('implicit_cache')
-    SCons.Node.FS.set_duplicate(ssoptions.get('duplicate'))
+    SCons.Node.implicit_cache = options.implicit_cache
+    SCons.Node.FS.set_duplicate(options.duplicate)
+    fs.set_max_drift(options.max_drift)
+
+    SCons.Job.explicit_stack_size = options.stack_size
+
+    if options.md5_chunksize:
+        SCons.Node.FS.File.md5_chunksize = options.md5_chunksize
+
+    platform = SCons.Platform.platform_module()
+
+    if options.interactive:
+        SCons.Script.Interactive.interact(fs, OptionsParser, options,
+                                          targets, target_top)
+
+    else:
+
+        # Build the targets
+        nodes = _build_targets(fs, options, targets, target_top)
+        if not nodes:
+            exit_status = 2
+
+def _build_targets(fs, options, targets, target_top):
+
+    global this_build_status
+    this_build_status = 0
+
+    progress_display.set_mode(not (options.no_progress or options.silent))
+    display.set_mode(not options.silent)
+    SCons.Action.print_actions          = not options.silent
+    SCons.Action.execute_actions        = not options.no_exec
+    SCons.Node.FS.do_store_info         = not options.no_exec
+    SCons.SConf.dryrun                  = options.no_exec
+
+    if options.diskcheck:
+        SCons.Node.FS.set_diskcheck(options.diskcheck)
+
+    SCons.CacheDir.cache_enabled = not options.cache_disable
+    SCons.CacheDir.cache_debug = options.cache_debug
+    SCons.CacheDir.cache_force = options.cache_force
+    SCons.CacheDir.cache_show = options.cache_show
+
+    if options.no_exec:
+        CleanTask.execute = CleanTask.show
+    else:
+        CleanTask.execute = CleanTask.remove
 
     lookup_top = None
-    if targets:
-        # They specified targets on the command line, so if they
-        # used -u, -U or -D, we have to look up targets relative
-        # to the top, but we build whatever they specified.
+    if targets or SCons.Script.BUILD_TARGETS != SCons.Script._build_plus_default:
+        # They specified targets on the command line or modified
+        # BUILD_TARGETS in the SConscript file(s), so if they used -u,
+        # -U or -D, we have to look up targets relative to the top,
+        # but we build whatever they specified.
         if target_top:
             lookup_top = fs.Dir(target_top)
             target_top = None
+
+        targets = SCons.Script.BUILD_TARGETS
     else:
         # There are no targets specified on the command line,
         # so if they used -u, -U or -D, we may have to restrict
@@ -1109,7 +1083,7 @@ def _main(args, parser):
                         # or not a file, so go ahead and keep it as a default
                         # target and let the engine sort it out:
                         return 1                
-                d = filter(check_dir, SCons.Script.DEFAULT_TARGETS)
+                d = list(filter(check_dir, SCons.Script.DEFAULT_TARGETS))
                 SCons.Script.DEFAULT_TARGETS[:] = d
                 target_top = None
                 lookup_top = None
@@ -1118,7 +1092,7 @@ def _main(args, parser):
 
     if not targets:
         sys.stderr.write("scons: *** No targets specified and no Default() targets found.  Stop.\n")
-        sys.exit(2)
+        return None
 
     def Entry(x, ltop=lookup_top, ttop=target_top, fs=fs):
         if isinstance(x, SCons.Node.Node):
@@ -1126,14 +1100,14 @@ def _main(args, parser):
         else:
             node = None
             # Why would ltop be None? Unfortunately this happens.
-            if ltop == None: ltop = ''
+            if ltop is None: ltop = ''
             # Curdir becomes important when SCons is called with -u, -C,
             # or similar option that changes directory, and so the paths
             # of targets given on the command line need to be adjusted.
             curdir = os.path.join(os.getcwd(), str(ltop))
             for lookup in SCons.Node.arg2nodes_lookups:
                 node = lookup(x, curdir=curdir)
-                if node != None:
+                if node is not None:
                     break
             if node is None:
                 node = fs.Entry(x, directory=ltop, create=1)
@@ -1144,38 +1118,39 @@ def _main(args, parser):
                 node = None
         return node
 
-    nodes = filter(None, map(Entry, targets))
+    nodes = [_f for _f in map(Entry, targets) if _f]
 
-    task_class = BuildTask     # default action is to build targets
+    task_class = BuildTask      # default action is to build targets
     opening_message = "Building targets ..."
     closing_message = "done building targets."
-    if keep_going_on_error:
+    if options.keep_going:
         failure_message = "done building targets (errors occurred during build)."
     else:
         failure_message = "building terminated because of errors."
     if options.question:
         task_class = QuestionTask
     try:
-        if ssoptions.get('clean'):
+        if options.clean:
             task_class = CleanTask
             opening_message = "Cleaning targets ..."
             closing_message = "done cleaning targets."
-            if keep_going_on_error:
-                closing_message = "done cleaning targets (errors occurred during clean)."
+            if options.keep_going:
+                failure_message = "done cleaning targets (errors occurred during clean)."
             else:
                 failure_message = "cleaning terminated because of errors."
     except AttributeError:
         pass
 
-    SCons.Environment.CalculatorArgs['max_drift'] = ssoptions.get('max_drift')
+    task_class.progress = ProgressObject
 
     if options.random:
         def order(dependencies):
             """Randomize the dependencies."""
+            import random
             # This is cribbed from the implementation of
             # random.shuffle() in Python 2.X.
             d = dependencies
-            for i in xrange(len(d)-1, 0, -1):
+            for i in range(len(d)-1, 0, -1):
                 j = int(random.random() * (i+1))
                 d[i], d[j] = d[j], d[i]
             return d
@@ -1184,66 +1159,141 @@ def _main(args, parser):
             """Leave the order of dependencies alone."""
             return dependencies
 
-    progress_display("scons: " + opening_message)
-    taskmaster = SCons.Taskmaster.Taskmaster(nodes, task_class, order)
-
-    nj = ssoptions.get('num_jobs')
-    jobs = SCons.Job.Jobs(nj, taskmaster)
-    if nj > 1 and jobs.num_jobs == 1:
-        msg = "parallel builds are unsupported by this version of Python;\n" + \
-              "\tignoring -j or num_jobs option.\n"
-        SCons.Warnings.warn(SCons.Warnings.NoParallelSupportWarning, msg)
+    if options.taskmastertrace_file == '-':
+        tmtrace = sys.stdout
+    elif options.taskmastertrace_file:
+        tmtrace = open(options.taskmastertrace_file, 'wb')
+    else:
+        tmtrace = None
+    taskmaster = SCons.Taskmaster.Taskmaster(nodes, task_class, order, tmtrace)
+
+    # Let the BuildTask objects get at the options to respond to the
+    # various print_* settings, tree_printer list, etc.
+    BuildTask.options = options
+
+    global num_jobs
+    num_jobs = options.num_jobs
+    jobs = SCons.Job.Jobs(num_jobs, taskmaster)
+    if num_jobs > 1:
+        msg = None
+        if jobs.num_jobs == 1:
+            msg = "parallel builds are unsupported by this version of Python;\n" + \
+                  "\tignoring -j or num_jobs option.\n"
+        elif sys.platform == 'win32':
+            msg = fetch_win32_parallel_msg()
+        if msg:
+            SCons.Warnings.warn(SCons.Warnings.NoParallelSupportWarning, msg)
 
     memory_stats.append('before building targets:')
     count_stats.append(('pre-', 'build'))
 
-    try:
-        jobs.run()
-    finally:
-        if exit_status:
+    def jobs_postfunc(
+        jobs=jobs,
+        options=options,
+        closing_message=closing_message,
+        failure_message=failure_message
+        ):
+        if jobs.were_interrupted():
+            if not options.no_progress and not options.silent:
+                sys.stderr.write("scons: Build interrupted.\n")
+            global exit_status
+            global this_build_status
+            exit_status = 2
+            this_build_status = 2
+
+        if this_build_status:
             progress_display("scons: " + failure_message)
         else:
             progress_display("scons: " + closing_message)
-        if not options.noexec:
+        if not options.no_exec:
+            if jobs.were_interrupted():
+                progress_display("scons: writing .sconsign file.")
             SCons.SConsign.write()
 
+    progress_display("scons: " + opening_message)
+    jobs.run(postfunc = jobs_postfunc)
+
     memory_stats.append('after building targets:')
     count_stats.append(('post-', 'build'))
 
-def _exec_main():
-    all_args = sys.argv[1:]
-    try:
-        all_args = string.split(os.environ['SCONSFLAGS']) + all_args
-    except KeyError:
-            # it's OK if there's no SCONSFLAGS
-            pass
-    parser = OptParser()
-    global options
-    options, args = parser.parse_args(all_args)
-    if type(options.debug) == type([]) and "pdb" in options.debug:
+    return nodes
+
+def _exec_main(parser, values):
+    sconsflags = os.environ.get('SCONSFLAGS', '')
+    all_args = sconsflags.split() + sys.argv[1:]
+
+    options, args = parser.parse_args(all_args, values)
+
+    if isinstance(options.debug, list) and "pdb" in options.debug:
         import pdb
-        pdb.Pdb().runcall(_main, args, parser)
+        pdb.Pdb().runcall(_main, parser)
     elif options.profile_file:
-        import profile
-        prof = profile.Profile()
+        # compat layer imports "cProfile" for us if it's available.
+        from profile import Profile
+
+        # Some versions of Python 2.4 shipped a profiler that had the
+        # wrong 'c_exception' entry in its dispatch table.  Make sure
+        # we have the right one.  (This may put an unnecessary entry
+        # in the table in earlier versions of Python, but its presence
+        # shouldn't hurt anything).
+        try:
+            dispatch = Profile.dispatch
+        except AttributeError:
+            pass
+        else:
+            dispatch['c_exception'] = Profile.trace_dispatch_return
+
+        prof = Profile()
         try:
-            prof.runcall(_main, args, parser)
+            prof.runcall(_main, parser)
+        except SConsPrintHelpException, e:
+            prof.dump_stats(options.profile_file)
+            raise e
         except SystemExit:
             pass
         prof.dump_stats(options.profile_file)
     else:
-        _main(args, parser)
+        _main(parser)
 
 def main():
+    global OptionsParser
     global exit_status
+    global first_command_start
+
+    # Check up front for a Python version we do not support.  We
+    # delay the check for deprecated Python versions until later,
+    # after the SConscript files have been read, in case they
+    # disable that warning.
+    if python_version_unsupported():
+        msg = "scons: *** SCons version %s does not run under Python version %s.\n"
+        sys.stderr.write(msg % (SCons.__version__, python_version_string()))
+        sys.exit(1)
+
+    parts = ["SCons by Steven Knight et al.:\n"]
+    try:
+        import __main__
+        parts.append(version_string("script", __main__))
+    except (ImportError, AttributeError):
+        # On Windows there is no scons.py, so there is no
+        # __main__.__version__, hence there is no script version.
+        pass 
+    parts.append(version_string("engine", SCons))
+    parts.append("__COPYRIGHT__")
+    version = ''.join(parts)
+
+    import SConsOptions
+    parser = SConsOptions.Parser(version)
+    values = SConsOptions.SConsValues(parser.get_default_values())
+
+    OptionsParser = parser
     
     try:
-       _exec_main()
+        _exec_main(parser, values)
     except SystemExit, s:
         if s:
             exit_status = s
     except KeyboardInterrupt:
-        print "Build interrupted."
+        print("scons: Build interrupted.")
         sys.exit(2)
     except SyntaxError, e:
         _scons_syntax_error(e)
@@ -1251,6 +1301,11 @@ def main():
         _scons_internal_error()
     except SCons.Errors.UserError, e:
         _scons_user_error(e)
+    except SConsPrintHelpException:
+        parser.print_help()
+        exit_status = 0
+    except SCons.Errors.BuildError, e:
+        exit_status = e.exitstatus
     except:
         # An exception here is likely a builtin Python exception Python
         # code in an SConscript file.  Show them precisely what the
@@ -1266,8 +1321,7 @@ def main():
         #SCons.Debug.dumpLoggedInstances('*')
 
     if print_memoizer:
-        print "Memoizer (memory cache) hits and misses:"
-        SCons.Memoize.Dump()
+        SCons.Memoize.Dump("Memoizer (memory cache) hits and misses:")
 
     # Dump any development debug info that may have been enabled.
     # These are purely for internal debugging during development, so
@@ -1277,11 +1331,24 @@ def main():
     SCons.Taskmaster.dump_stats()
 
     if print_time:
-        total_time = time.time()-SCons.Script.start_time
-        scons_time = total_time-sconscript_time-command_time
+        total_time = time.time() - SCons.Script.start_time
+        if num_jobs == 1:
+            ct = cumulative_command_time
+        else:
+            if last_command_end is None or first_command_start is None:
+                ct = 0.0
+            else:
+                ct = last_command_end - first_command_start
+        scons_time = total_time - sconscript_time - ct
         print "Total build time: %f seconds"%total_time
         print "Total SConscript file execution time: %f seconds"%sconscript_time
         print "Total SCons execution time: %f seconds"%scons_time
-        print "Total command execution time: %f seconds"%command_time
+        print "Total command execution time: %f seconds"%ct
 
     sys.exit(exit_status)
+
+# Local Variables:
+# tab-width:4
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=4 shiftwidth=4: