1# MIT License
2#
3# Copyright The SCons Foundation
4#
5# Permission is hereby granted, free of charge, to any person obtaining
6# a copy of this software and associated documentation files (the
7# "Software"), to deal in the Software without restriction, including
8# without limitation the rights to use, copy, modify, merge, publish,
9# distribute, sublicense, and/or sell copies of the Software, and to
10# permit persons to whom the Software is furnished to do so, subject to
11# the following conditions:
12#
13# The above copyright notice and this permission notice shall be included
14# in all copies or substantial portions of the Software.
15#
16# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
17# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
18# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
24"""The main() function used by the scons script.
25
26Architecturally, this *is* the scons script, and will likely only be
27called from the external "scons" wrapper.  Consequently, anything here
28should not be, or be considered, part of the build engine.  If it's
29something that we expect other software to want to use, it should go in
30some other module.  If it's specific to the "scons" script invocation,
31it goes here.
32"""
33
34# these define the range of versions SCons supports
35unsupported_python_version = (3, 4, 0)
36deprecated_python_version = (3, 5, 0)
37
38import SCons.compat
39
40import atexit
41import importlib.util
42import os
43import re
44import sys
45import time
46import traceback
47import platform
48import threading
49
50import SCons.CacheDir
51import SCons.Debug
52import SCons.Defaults
53import SCons.Environment
54import SCons.Errors
55import SCons.Job
56import SCons.Node
57import SCons.Node.FS
58import SCons.Platform
59import SCons.Platform.virtualenv
60import SCons.SConf
61import SCons.Script
62import SCons.Taskmaster
63import SCons.Util
64import SCons.Warnings
65import SCons.Script.Interactive
66
67# Global variables
68first_command_start = None
69last_command_end = None
70print_objects = False
71print_memoizer = False
72print_stacktrace = False
73print_time = False
74print_action_timestamps = False
75sconscript_time = 0
76cumulative_command_time = 0
77exit_status = 0   # final exit status, assume success by default
78this_build_status = 0   # "exit status" of an individual build
79num_jobs = None
80delayed_warnings = []
81
82
83def revert_io():
84    # This call is added to revert stderr and stdout to the original
85    # ones just in case some build rule or something else in the system
86    # has redirected them elsewhere.
87    sys.stderr = sys.__stderr__
88    sys.stdout = sys.__stdout__
89
90
91class SConsPrintHelpException(Exception):
92    pass
93
94
95display = SCons.Util.display
96progress_display = SCons.Util.DisplayEngine()
97
98
99class Progressor:
100    prev = ''
101    count = 0
102    target_string = '$TARGET'
103
104    def __init__(self, obj, interval=1, file=None, overwrite=False):
105        if file is None:
106            file = sys.stdout
107
108        self.obj = obj
109        self.file = file
110        self.interval = interval
111        self.overwrite = overwrite
112
113        if callable(obj):
114            self.func = obj
115        elif SCons.Util.is_List(obj):
116            self.func = self.spinner
117        elif obj.find(self.target_string) != -1:
118            self.func = self.replace_string
119        else:
120            self.func = self.string
121
122    def write(self, s):
123        self.file.write(s)
124        self.file.flush()
125        self.prev = s
126
127    def erase_previous(self):
128        if self.prev:
129            length = len(self.prev)
130            if self.prev[-1] in ('\n', '\r'):
131                length = length - 1
132            self.write(' ' * length + '\r')
133            self.prev = ''
134
135    def spinner(self, node):
136        self.write(self.obj[self.count % len(self.obj)])
137
138    def string(self, node):
139        self.write(self.obj)
140
141    def replace_string(self, node):
142        self.write(self.obj.replace(self.target_string, str(node)))
143
144    def __call__(self, node):
145        self.count = self.count + 1
146        if (self.count % self.interval) == 0:
147            if self.overwrite:
148                self.erase_previous()
149            self.func(node)
150
151ProgressObject = SCons.Util.Null()
152
153def Progress(*args, **kw):
154    global ProgressObject
155    ProgressObject = Progressor(*args, **kw)
156
157# Task control.
158#
159
160_BuildFailures = []
161
162
163def GetBuildFailures():
164    return _BuildFailures
165
166
167class BuildTask(SCons.Taskmaster.OutOfDateTask):
168    """An SCons build task."""
169    progress = ProgressObject
170
171    def display(self, message):
172        display('scons: ' + message)
173
174    def prepare(self):
175        if not isinstance(self.progress, SCons.Util.Null):
176            for target in self.targets:
177                self.progress(target)
178        return SCons.Taskmaster.OutOfDateTask.prepare(self)
179
180    def needs_execute(self):
181        if SCons.Taskmaster.OutOfDateTask.needs_execute(self):
182            return True
183        if self.top and self.targets[0].has_builder():
184            display("scons: `%s' is up to date." % str(self.node))
185        return False
186
187    def execute(self):
188        if print_time:
189            start_time = time.time()
190            global first_command_start
191            if first_command_start is None:
192                first_command_start = start_time
193        SCons.Taskmaster.OutOfDateTask.execute(self)
194        if print_time:
195            global cumulative_command_time
196            global last_command_end
197            finish_time = time.time()
198            last_command_end = finish_time
199            cumulative_command_time += finish_time - start_time
200            if print_action_timestamps:
201                sys.stdout.write(
202                    "Command execution start timestamp: %s: %f\n"
203                    % (str(self.node), start_time)
204                )
205                sys.stdout.write(
206                    "Command execution end timestamp: %s: %f\n"
207                    % (str(self.node), finish_time)
208                )
209            sys.stdout.write(
210                "Command execution time: %s: %f seconds\n"
211                % (str(self.node), (finish_time - start_time))
212            )
213
214    def do_failed(self, status=2):
215        _BuildFailures.append(self.exception[1])
216        global exit_status
217        global this_build_status
218        if self.options.ignore_errors:
219            SCons.Taskmaster.OutOfDateTask.executed(self)
220        elif self.options.keep_going:
221            SCons.Taskmaster.OutOfDateTask.fail_continue(self)
222            exit_status = status
223            this_build_status = status
224        else:
225            SCons.Taskmaster.OutOfDateTask.fail_stop(self)
226            exit_status = status
227            this_build_status = status
228
229    def executed(self):
230        t = self.targets[0]
231        if self.top and not t.has_builder() and not t.side_effect:
232            if not t.exists():
233                if t.__class__.__name__ in ('File', 'Dir', 'Entry'):
234                    errstr="Do not know how to make %s target `%s' (%s)." % (t.__class__.__name__, t, t.get_abspath())
235                else: # Alias or Python or ...
236                    errstr="Do not know how to make %s target `%s'." % (t.__class__.__name__, t)
237                sys.stderr.write("scons: *** " + errstr)
238                if not self.options.keep_going:
239                    sys.stderr.write("  Stop.")
240                sys.stderr.write("\n")
241                try:
242                    raise SCons.Errors.BuildError(t, errstr)
243                except KeyboardInterrupt:
244                    raise
245                except:
246                    self.exception_set()
247                self.do_failed()
248            else:
249                print("scons: Nothing to be done for `%s'." % t)
250                SCons.Taskmaster.OutOfDateTask.executed(self)
251        else:
252            SCons.Taskmaster.OutOfDateTask.executed(self)
253
254    def failed(self):
255        # Handle the failure of a build task.  The primary purpose here
256        # is to display the various types of Errors and Exceptions
257        # appropriately.
258        exc_info = self.exc_info()
259        try:
260            t, e, tb = exc_info
261        except ValueError:
262            t, e = exc_info
263            tb = None
264
265        if t is None:
266            # The Taskmaster didn't record an exception for this Task;
267            # see if the sys module has one.
268            try:
269                t, e, tb = sys.exc_info()[:]
270            except ValueError:
271                t, e = exc_info
272                tb = None
273
274        # Deprecated string exceptions will have their string stored
275        # in the first entry of the tuple.
276        if e is None:
277            e = t
278
279        buildError = SCons.Errors.convert_to_BuildError(e)
280        if not buildError.node:
281            buildError.node = self.node
282
283        node = buildError.node
284        if not SCons.Util.is_List(node):
285                node = [ node ]
286        nodename = ', '.join(map(str, node))
287
288        errfmt = "scons: *** [%s] %s\n"
289        sys.stderr.write(errfmt % (nodename, buildError))
290
291        if (buildError.exc_info[2] and buildError.exc_info[1] and
292           not isinstance(
293               buildError.exc_info[1],
294               (EnvironmentError, SCons.Errors.StopError,
295                            SCons.Errors.UserError))):
296            type, value, trace = buildError.exc_info
297            if tb and print_stacktrace:
298                sys.stderr.write("scons: internal stack trace:\n")
299                traceback.print_tb(tb, file=sys.stderr)
300            traceback.print_exception(type, value, trace)
301        elif tb and print_stacktrace:
302            sys.stderr.write("scons: internal stack trace:\n")
303            traceback.print_tb(tb, file=sys.stderr)
304
305        self.exception = (e, buildError, tb) # type, value, traceback
306        self.do_failed(buildError.exitstatus)
307
308        self.exc_clear()
309
310    def postprocess(self):
311        if self.top:
312            t = self.targets[0]
313            for tp in self.options.tree_printers:
314                tp.display(t)
315            if self.options.debug_includes:
316                tree = t.render_include_tree()
317                if tree:
318                    print()
319                    print(tree)
320        SCons.Taskmaster.OutOfDateTask.postprocess(self)
321
322    def make_ready(self):
323        """Make a task ready for execution"""
324        SCons.Taskmaster.OutOfDateTask.make_ready(self)
325        if self.out_of_date and self.options.debug_explain:
326            explanation = self.out_of_date[0].explain()
327            if explanation:
328                sys.stdout.write("scons: " + explanation)
329
330
331class CleanTask(SCons.Taskmaster.AlwaysTask):
332    """An SCons clean task."""
333    def fs_delete(self, path, pathstr, remove=True):
334        try:
335            if os.path.lexists(path):
336                if os.path.isfile(path) or os.path.islink(path):
337                    if remove: os.unlink(path)
338                    display("Removed " + pathstr)
339                elif os.path.isdir(path) and not os.path.islink(path):
340                    # delete everything in the dir
341                    for e in sorted(os.listdir(path)):
342                        p = os.path.join(path, e)
343                        s = os.path.join(pathstr, e)
344                        if os.path.isfile(p):
345                            if remove: os.unlink(p)
346                            display("Removed " + s)
347                        else:
348                            self.fs_delete(p, s, remove)
349                    # then delete dir itself
350                    if remove: os.rmdir(path)
351                    display("Removed directory " + pathstr)
352                else:
353                    errstr = "Path '%s' exists but isn't a file or directory."
354                    raise SCons.Errors.UserError(errstr % pathstr)
355        except SCons.Errors.UserError as e:
356            print(e)
357        except (IOError, OSError) as e:
358            print("scons: Could not remove '%s':" % pathstr, e.strerror)
359
360    def _get_files_to_clean(self):
361        result = []
362        target = self.targets[0]
363        if target.has_builder() or target.side_effect:
364            result = [t for t in self.targets if not t.noclean]
365        return result
366
367    def _clean_targets(self, remove=True):
368        target = self.targets[0]
369        if target in SCons.Environment.CleanTargets:
370            files = SCons.Environment.CleanTargets[target]
371            for f in files:
372                self.fs_delete(f.get_abspath(), str(f), remove)
373
374    def show(self):
375        for t in self._get_files_to_clean():
376            if not t.isdir():
377                display("Removed " + str(t))
378        self._clean_targets(remove=False)
379
380    def remove(self):
381        for t in self._get_files_to_clean():
382            try:
383                removed = t.remove()
384            except OSError as e:
385                # An OSError may indicate something like a permissions
386                # issue, an IOError would indicate something like
387                # the file not existing.  In either case, print a
388                # message and keep going to try to remove as many
389                # targets as possible.
390                print("scons: Could not remove '{0}'".format(str(t)), e.strerror)
391            else:
392                if removed:
393                    display("Removed " + str(t))
394        self._clean_targets(remove=True)
395
396    execute = remove
397
398    # We want the Taskmaster to update the Node states (and therefore
399    # handle reference counts, etc.), but we don't want to call
400    # back to the Node's post-build methods, which would do things
401    # we don't want, like store .sconsign information.
402    executed = SCons.Taskmaster.Task.executed_without_callbacks
403
404    # Have the Taskmaster arrange to "execute" all of the targets, because
405    # we'll figure out ourselves (in remove() or show() above) whether
406    # anything really needs to be done.
407    make_ready = SCons.Taskmaster.Task.make_ready_all
408
409    def prepare(self):
410        pass
411
412class QuestionTask(SCons.Taskmaster.AlwaysTask):
413    """An SCons task for the -q (question) option."""
414    def prepare(self):
415        pass
416
417    def execute(self):
418        if self.targets[0].get_state() != SCons.Node.up_to_date or \
419           (self.top and not self.targets[0].exists()):
420            global exit_status
421            global this_build_status
422            exit_status = 1
423            this_build_status = 1
424            self.tm.stop()
425
426    def executed(self):
427        pass
428
429
430class TreePrinter:
431    def __init__(self, derived=False, prune=False, status=False, sLineDraw=False):
432        self.derived = derived
433        self.prune = prune
434        self.status = status
435        self.sLineDraw = sLineDraw
436    def get_all_children(self, node):
437        return node.all_children()
438    def get_derived_children(self, node):
439        children = node.all_children(None)
440        return [x for x in children if x.has_builder()]
441    def display(self, t):
442        if self.derived:
443            func = self.get_derived_children
444        else:
445            func = self.get_all_children
446        s = self.status and 2 or 0
447        SCons.Util.print_tree(t, func, prune=self.prune, showtags=s, lastChild=True, singleLineDraw=self.sLineDraw)
448
449
450def python_version_string():
451    return sys.version.split()[0]
452
453def python_version_unsupported(version=sys.version_info):
454    return version < unsupported_python_version
455
456def python_version_deprecated(version=sys.version_info):
457    return version < deprecated_python_version
458
459
460class FakeOptionParser:
461    """
462    A do-nothing option parser, used for the initial OptionsParser variable.
463
464    During normal SCons operation, the OptionsParser is created right
465    away by the main() function.  Certain tests scripts however, can
466    introspect on different Tool modules, the initialization of which
467    can try to add a new, local option to an otherwise uninitialized
468    OptionsParser object.  This allows that introspection to happen
469    without blowing up.
470
471    """
472    class FakeOptionValues:
473        def __getattr__(self, attr):
474            return None
475    values = FakeOptionValues()
476    def add_local_option(self, *args, **kw):
477        pass
478
479OptionsParser = FakeOptionParser()
480
481def AddOption(*args, **kw):
482    if 'default' not in kw:
483        kw['default'] = None
484    result = OptionsParser.add_local_option(*args, **kw)
485    return result
486
487def GetOption(name):
488    return getattr(OptionsParser.values, name)
489
490def SetOption(name, value):
491    return OptionsParser.values.set_option(name, value)
492
493def PrintHelp(file=None):
494    OptionsParser.print_help(file=file)
495
496class Stats:
497    def __init__(self):
498        self.stats = []
499        self.labels = []
500        self.append = self.do_nothing
501        self.print_stats = self.do_nothing
502    def enable(self, outfp):
503        self.outfp = outfp
504        self.append = self.do_append
505        self.print_stats = self.do_print
506    def do_nothing(self, *args, **kw):
507        pass
508
509class CountStats(Stats):
510    def do_append(self, label):
511        self.labels.append(label)
512        self.stats.append(SCons.Debug.fetchLoggedInstances())
513    def do_print(self):
514        stats_table = {}
515        for s in self.stats:
516            for n in [t[0] for t in s]:
517                stats_table[n] = [0, 0, 0, 0]
518        i = 0
519        for s in self.stats:
520            for n, c in s:
521                stats_table[n][i] = c
522            i = i + 1
523        self.outfp.write("Object counts:\n")
524        pre = ["   "]
525        post = ["   %s\n"]
526        l = len(self.stats)
527        fmt1 = ''.join(pre + [' %7s']*l + post)
528        fmt2 = ''.join(pre + [' %7d']*l + post)
529        labels = self.labels[:l]
530        labels.append(("", "Class"))
531        self.outfp.write(fmt1 % tuple([x[0] for x in labels]))
532        self.outfp.write(fmt1 % tuple([x[1] for x in labels]))
533        for k in sorted(stats_table.keys()):
534            r = stats_table[k][:l] + [k]
535            self.outfp.write(fmt2 % tuple(r))
536
537count_stats = CountStats()
538
539class MemStats(Stats):
540    def do_append(self, label):
541        self.labels.append(label)
542        self.stats.append(SCons.Debug.memory())
543    def do_print(self):
544        fmt = 'Memory %-32s %12d\n'
545        for label, stats in zip(self.labels, self.stats):
546            self.outfp.write(fmt % (label, stats))
547
548memory_stats = MemStats()
549
550# utility functions
551
552def _scons_syntax_error(e):
553    """Handle syntax errors. Print out a message and show where the error
554    occurred.
555    """
556    etype, value, tb = sys.exc_info()
557    lines = traceback.format_exception_only(etype, value)
558    for line in lines:
559        sys.stderr.write(line+'\n')
560    sys.exit(2)
561
562def find_deepest_user_frame(tb):
563    """
564    Find the deepest stack frame that is not part of SCons.
565
566    Input is a "pre-processed" stack trace in the form
567    returned by traceback.extract_tb() or traceback.extract_stack()
568    """
569
570    tb.reverse()
571
572    # find the deepest traceback frame that is not part
573    # of SCons:
574    for frame in tb:
575        filename = frame[0]
576        if filename.find(os.sep+'SCons'+os.sep) == -1:
577            return frame
578    return tb[0]
579
580def _scons_user_error(e):
581    """Handle user errors. Print out a message and a description of the
582    error, along with the line number and routine where it occured.
583    The file and line number will be the deepest stack frame that is
584    not part of SCons itself.
585    """
586    global print_stacktrace
587    etype, value, tb = sys.exc_info()
588    if print_stacktrace:
589        traceback.print_exception(etype, value, tb)
590    filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_tb(tb))
591    sys.stderr.write("\nscons: *** %s\n" % value)
592    sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
593    sys.exit(2)
594
595def _scons_user_warning(e):
596    """Handle user warnings. Print out a message and a description of
597    the warning, along with the line number and routine where it occured.
598    The file and line number will be the deepest stack frame that is
599    not part of SCons itself.
600    """
601    etype, value, tb = sys.exc_info()
602    filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_tb(tb))
603    sys.stderr.write("\nscons: warning: %s\n" % e)
604    sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
605
606def _scons_internal_warning(e):
607    """Slightly different from _scons_user_warning in that we use the
608    *current call stack* rather than sys.exc_info() to get our stack trace.
609    This is used by the warnings framework to print warnings."""
610    filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_stack())
611    sys.stderr.write("\nscons: warning: %s\n" % e.args[0])
612    sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
613
614def _scons_internal_error():
615    """Handle all errors but user errors. Print out a message telling
616    the user what to do in this case and print a normal trace.
617    """
618    print('internal error')
619    traceback.print_exc()
620    sys.exit(2)
621
622def _SConstruct_exists(dirname='', repositories=[], filelist=None):
623    """This function checks that an SConstruct file exists in a directory.
624    If so, it returns the path of the file. By default, it checks the
625    current directory.
626    """
627    if not filelist:
628        filelist = ['SConstruct', 'Sconstruct', 'sconstruct', 'SConstruct.py', 'Sconstruct.py', 'sconstruct.py']
629    for file in filelist:
630        sfile = os.path.join(dirname, file)
631        if os.path.isfile(sfile):
632            return sfile
633        if not os.path.isabs(sfile):
634            for rep in repositories:
635                if os.path.isfile(os.path.join(rep, sfile)):
636                    return sfile
637    return None
638
639def _set_debug_values(options):
640    global print_memoizer, print_objects, print_stacktrace, print_time, print_action_timestamps
641
642    debug_values = options.debug
643
644    if "count" in debug_values:
645        # All of the object counts are within "if track_instances:" blocks,
646        # which get stripped when running optimized (with python -O or
647        # from compiled *.pyo files).  Provide a warning if __debug__ is
648        # stripped, so it doesn't just look like --debug=count is broken.
649        enable_count = False
650        if __debug__: enable_count = True
651        if enable_count:
652            count_stats.enable(sys.stdout)
653            SCons.Debug.track_instances = True
654        else:
655            msg = "--debug=count is not supported when running SCons\n" + \
656                  "\twith the python -O option or optimized (.pyo) modules."
657            SCons.Warnings.warn(SCons.Warnings.NoObjectCountWarning, msg)
658    if "dtree" in debug_values:
659        options.tree_printers.append(TreePrinter(derived=True))
660    options.debug_explain = ("explain" in debug_values)
661    if "findlibs" in debug_values:
662        SCons.Scanner.Prog.print_find_libs = "findlibs"
663    options.debug_includes = ("includes" in debug_values)
664    print_memoizer = ("memoizer" in debug_values)
665    if "memory" in debug_values:
666        memory_stats.enable(sys.stdout)
667    print_objects = ("objects" in debug_values)
668    if print_objects:
669        SCons.Debug.track_instances = True
670    if "presub" in debug_values:
671        SCons.Action.print_actions_presub = True
672    if "stacktrace" in debug_values:
673        print_stacktrace = True
674    if "stree" in debug_values:
675        options.tree_printers.append(TreePrinter(status=True))
676    if "time" in debug_values:
677        print_time = True
678    if "action-timestamps" in debug_values:
679        print_time = True
680        print_action_timestamps = True
681    if "tree" in debug_values:
682        options.tree_printers.append(TreePrinter())
683    if "prepare" in debug_values:
684        SCons.Taskmaster.print_prepare = True
685    if "duplicate" in debug_values:
686        SCons.Node.print_duplicate = True
687
688def _create_path(plist):
689    path = '.'
690    for d in plist:
691        if os.path.isabs(d):
692            path = d
693        else:
694            path = path + '/' + d
695    return path
696
697def _load_site_scons_dir(topdir, site_dir_name=None):
698    """Load the site directory under topdir.
699
700    If a site dir name is supplied use it, else use default "site_scons"
701    Prepend site dir to sys.path.
702    If a "site_tools" subdir exists, prepend to toolpath.
703    Import "site_init.py" from site dir if it exists.
704    """
705    if site_dir_name:
706        err_if_not_found = True       # user specified: err if missing
707    else:
708        site_dir_name = "site_scons"
709        err_if_not_found = False      # scons default: okay to be missing
710    site_dir = os.path.join(topdir, site_dir_name)
711
712    if not os.path.exists(site_dir):
713        if err_if_not_found:
714            raise SCons.Errors.UserError("site dir %s not found." % site_dir)
715        return
716    sys.path.insert(0, os.path.abspath(site_dir))
717
718    site_init_filename = "site_init.py"
719    site_init_modname = "site_init"
720    site_tools_dirname = "site_tools"
721    site_init_file = os.path.join(site_dir, site_init_filename)
722    site_tools_dir = os.path.join(site_dir, site_tools_dirname)
723
724    if os.path.exists(site_tools_dir):
725        SCons.Tool.DefaultToolpath.insert(0, os.path.abspath(site_tools_dir))
726
727    if not os.path.exists(site_init_file):
728        return
729
730    # "import" the site_init.py file into the SCons.Script namespace.
731    # This is a variant on the basic Python import flow in that the globals
732    # dict for the compile step is prepopulated from the SCons.Script
733    # module object; on success the SCons.Script globals are refilled
734    # from the site_init globals so it all appears in SCons.Script
735    # instead of as a separate module.
736    try:
737        try:
738            m = sys.modules['SCons.Script']
739        except KeyError:
740            fmt = 'cannot import {}: missing SCons.Script module'
741            raise SCons.Errors.InternalError(fmt.format(site_init_file))
742
743        spec = importlib.util.spec_from_file_location(site_init_modname, site_init_file)
744        site_m = {
745            "__file__": spec.origin,
746            "__name__": spec.name,
747            "__doc__": None,
748        }
749        re_dunder = re.compile(r"__[^_]+__")
750        # update site dict with all but magic (dunder) methods
751        for k, v in m.__dict__.items():
752            if not re_dunder.match(k):
753                site_m[k] = v
754
755        with open(spec.origin, 'r') as f:
756            code = f.read()
757        try:
758            codeobj = compile(code, spec.name, "exec")
759            exec(codeobj, site_m)
760        except KeyboardInterrupt:
761            raise
762        except Exception:
763            fmt = "*** Error loading site_init file {}:\n"
764            sys.stderr.write(fmt.format(site_init_file))
765            raise
766        else:
767            # now refill globals with site_init's symbols
768            for k, v in site_m.items():
769                if not re_dunder.match(k):
770                    m.__dict__[k] = v
771    except KeyboardInterrupt:
772        raise
773    except Exception:
774        fmt = "*** cannot import site init file {}:\n"
775        sys.stderr.write(fmt.format(site_init_file))
776        raise
777
778
779def _load_all_site_scons_dirs(topdir, verbose=False):
780    """Load all of the predefined site_scons dir.
781    Order is significant; we load them in order from most generic
782    (machine-wide) to most specific (topdir).
783    The verbose argument is only for testing.
784    """
785    platform = SCons.Platform.platform_default()
786
787    def homedir(d):
788        return os.path.expanduser('~/'+d)
789
790    if platform == 'win32' or platform == 'cygwin':
791        # Note we use $ here instead of %...% because older
792        # pythons (prior to 2.6?) didn't expand %...% on Windows.
793        # This set of dirs should work on XP, Vista, 7 and later.
794        sysdirs=[
795            os.path.expandvars('$ALLUSERSPROFILE\\Application Data\\scons'),
796            os.path.expandvars('$USERPROFILE\\Local Settings\\Application Data\\scons')]
797        appdatadir = os.path.expandvars('$APPDATA\\scons')
798        if appdatadir not in sysdirs:
799            sysdirs.append(appdatadir)
800        sysdirs.append(homedir('.scons'))
801
802    elif platform == 'darwin':  # MacOS X
803        sysdirs=['/Library/Application Support/SCons',
804                 '/opt/local/share/scons', # (for MacPorts)
805                 '/sw/share/scons', # (for Fink)
806                  homedir('Library/Application Support/SCons'),
807                  homedir('.scons')]
808    elif platform == 'sunos':   # Solaris
809        sysdirs=['/opt/sfw/scons',
810                 '/usr/share/scons',
811                 homedir('.scons')]
812    else:                       # Linux, HPUX, etc.
813        # assume posix-like, i.e. platform == 'posix'
814        sysdirs=['/usr/share/scons',
815                 homedir('.scons')]
816
817    dirs = sysdirs + [topdir]
818    for d in dirs:
819        if verbose:    # this is used by unit tests.
820            print("Loading site dir ", d)
821        _load_site_scons_dir(d)
822
823def test_load_all_site_scons_dirs(d):
824    _load_all_site_scons_dirs(d, True)
825
826def version_string(label, module):
827    version = module.__version__
828    build = module.__build__
829    if build:
830        if build[0] != '.':
831            build = '.' + build
832        version = version + build
833    fmt = "\t%s: v%s, %s, by %s on %s\n"
834    return fmt % (label,
835                  version,
836                  module.__date__,
837                  module.__developer__,
838                  module.__buildsys__)
839
840def path_string(label, module):
841    path = module.__path__
842    return "\t%s path: %s\n"%(label,path)
843
844def _main(parser):
845    global exit_status
846    global this_build_status
847
848    options = parser.values
849
850    # Here's where everything really happens.
851
852    # First order of business:  set up default warnings and then
853    # handle the user's warning options, so that we can issue (or
854    # suppress) appropriate warnings about anything that might happen,
855    # as configured by the user.
856
857    default_warnings = [ SCons.Warnings.WarningOnByDefault,
858                         SCons.Warnings.DeprecatedWarning,
859                       ]
860
861    for warning in default_warnings:
862        SCons.Warnings.enableWarningClass(warning)
863    SCons.Warnings._warningOut = _scons_internal_warning
864    SCons.Warnings.process_warn_strings(options.warn)
865
866    # Now that we have the warnings configuration set up, we can actually
867    # issue (or suppress) any warnings about warning-worthy things that
868    # occurred while the command-line options were getting parsed.
869    try:
870        dw = options.delayed_warnings
871    except AttributeError:
872        pass
873    else:
874        delayed_warnings.extend(dw)
875    for warning_type, message in delayed_warnings:
876        SCons.Warnings.warn(warning_type, message)
877
878    if not SCons.Platform.virtualenv.virtualenv_enabled_by_default:
879        if options.enable_virtualenv:
880            SCons.Platform.virtualenv.enable_virtualenv = True
881
882    if options.ignore_virtualenv:
883        SCons.Platform.virtualenv.ignore_virtualenv = True
884
885    if options.diskcheck:
886        SCons.Node.FS.set_diskcheck(options.diskcheck)
887
888    # Next, we want to create the FS object that represents the outside
889    # world's file system, as that's central to a lot of initialization.
890    # To do this, however, we need to be in the directory from which we
891    # want to start everything, which means first handling any relevant
892    # options that might cause us to chdir somewhere (-C, -D, -U, -u).
893    if options.directory:
894        script_dir = os.path.abspath(_create_path(options.directory))
895    else:
896        script_dir = os.getcwd()
897
898    target_top = None
899    if options.climb_up:
900        target_top = '.'  # directory to prepend to targets
901        while script_dir and not _SConstruct_exists(script_dir,
902                                                    options.repository,
903                                                    options.file):
904            script_dir, last_part = os.path.split(script_dir)
905            if last_part:
906                target_top = os.path.join(last_part, target_top)
907            else:
908                script_dir = ''
909
910    if script_dir and script_dir != os.getcwd():
911        if not options.silent:
912            display("scons: Entering directory `%s'" % script_dir)
913        try:
914            os.chdir(script_dir)
915        except OSError:
916            sys.stderr.write("Could not change directory to %s\n" % script_dir)
917
918    # Now that we're in the top-level SConstruct directory, go ahead
919    # and initialize the FS object that represents the file system,
920    # and make it the build engine default.
921    fs = SCons.Node.FS.get_default_fs()
922
923    for rep in options.repository:
924        fs.Repository(rep)
925
926    # Now that we have the FS object, the next order of business is to
927    # check for an SConstruct file (or other specified config file).
928    # If there isn't one, we can bail before doing any more work.
929    scripts = []
930    if options.file:
931        scripts.extend(options.file)
932    if not scripts:
933        sfile = _SConstruct_exists(repositories=options.repository,
934                                   filelist=options.file)
935        if sfile:
936            scripts.append(sfile)
937
938    if not scripts:
939        if options.help:
940            # There's no SConstruct, but they specified -h.
941            # Give them the options usage now, before we fail
942            # trying to read a non-existent SConstruct file.
943            raise SConsPrintHelpException
944        raise SCons.Errors.UserError("No SConstruct file found.")
945
946    if scripts[0] == "-":
947        d = fs.getcwd()
948    else:
949        d = fs.File(scripts[0]).dir
950    fs.set_SConstruct_dir(d)
951
952    _set_debug_values(options)
953    SCons.Node.implicit_cache = options.implicit_cache
954    SCons.Node.implicit_deps_changed = options.implicit_deps_changed
955    SCons.Node.implicit_deps_unchanged = options.implicit_deps_unchanged
956
957    if options.no_exec:
958        SCons.SConf.dryrun = 1
959        SCons.Action.execute_actions = None
960    if options.question:
961        SCons.SConf.dryrun = 1
962    if options.clean:
963        SCons.SConf.SetBuildType('clean')
964    if options.help:
965        SCons.SConf.SetBuildType('help')
966    SCons.SConf.SetCacheMode(options.config)
967    SCons.SConf.SetProgressDisplay(progress_display)
968
969    if options.no_progress or options.silent:
970        progress_display.set_mode(0)
971
972    # if site_dir unchanged from default None, neither --site-dir
973    # nor --no-site-dir was seen, use SCons default
974    if options.site_dir is None:
975        _load_all_site_scons_dirs(d.get_internal_path())
976    elif options.site_dir:  # if a dir was set, use it
977        _load_site_scons_dir(d.get_internal_path(), options.site_dir)
978
979    if options.include_dir:
980        sys.path = options.include_dir + sys.path
981
982    # If we're about to start SCons in the interactive mode,
983    # inform the FS about this right here. Else, the release_target_info
984    # method could get called on some nodes, like the used "gcc" compiler,
985    # when using the Configure methods within the SConscripts.
986    # This would then cause subtle bugs, as already happened in #2971.
987    if options.interactive:
988        SCons.Node.interactive = True
989
990    # That should cover (most of) the options.
991    # Next, set up the variables that hold command-line arguments,
992    # so the SConscript files that we read and execute have access to them.
993    # TODO: for options defined via AddOption which take space-separated
994    # option-args, the option-args will collect into targets here,
995    # because we don't yet know to do any different.
996    targets = []
997    xmit_args = []
998    for a in parser.largs:
999        # Skip so-far unrecognized options, and empty string args
1000        if a.startswith('-') or a in ('', '""', "''"):
1001            continue
1002        if '=' in a:
1003            xmit_args.append(a)
1004        else:
1005            targets.append(a)
1006    SCons.Script._Add_Targets(targets + parser.rargs)
1007    SCons.Script._Add_Arguments(xmit_args)
1008
1009    # If stdout is not a tty, replace it with a wrapper object to call flush
1010    # after every write.
1011    #
1012    # Tty devices automatically flush after every newline, so the replacement
1013    # isn't necessary.  Furthermore, if we replace sys.stdout, the readline
1014    # module will no longer work.  This affects the behavior during
1015    # --interactive mode.  --interactive should only be used when stdin and
1016    # stdout refer to a tty.
1017    if not hasattr(sys.stdout, 'isatty') or not sys.stdout.isatty():
1018        sys.stdout = SCons.Util.Unbuffered(sys.stdout)
1019    if not hasattr(sys.stderr, 'isatty') or not sys.stderr.isatty():
1020        sys.stderr = SCons.Util.Unbuffered(sys.stderr)
1021
1022    memory_stats.append('before reading SConscript files:')
1023    count_stats.append(('pre-', 'read'))
1024
1025    # And here's where we (finally) read the SConscript files.
1026
1027    progress_display("scons: Reading SConscript files ...")
1028
1029    if print_time:
1030        start_time = time.time()
1031    try:
1032        for script in scripts:
1033            SCons.Script._SConscript._SConscript(fs, script)
1034    except SCons.Errors.StopError as e:
1035        # We had problems reading an SConscript file, such as it
1036        # couldn't be copied in to the VariantDir.  Since we're just
1037        # reading SConscript files and haven't started building
1038        # things yet, stop regardless of whether they used -i or -k
1039        # or anything else.
1040        revert_io()
1041        sys.stderr.write("scons: *** %s  Stop.\n" % e)
1042        sys.exit(2)
1043    if print_time:
1044        global sconscript_time
1045        sconscript_time = time.time() - start_time
1046
1047    progress_display("scons: done reading SConscript files.")
1048
1049    memory_stats.append('after reading SConscript files:')
1050    count_stats.append(('post-', 'read'))
1051
1052    # Re-{enable,disable} warnings in case they disabled some in
1053    # the SConscript file.
1054    #
1055    # We delay enabling the PythonVersionWarning class until here so that,
1056    # if they explicitly disabled it in either in the command line or in
1057    # $SCONSFLAGS, or in the SConscript file, then the search through
1058    # the list of deprecated warning classes will find that disabling
1059    # first and not issue the warning.
1060    #SCons.Warnings.enableWarningClass(SCons.Warnings.PythonVersionWarning)
1061    SCons.Warnings.process_warn_strings(options.warn)
1062
1063    # Now that we've read the SConscript files, we can check for the
1064    # warning about deprecated Python versions--delayed until here
1065    # in case they disabled the warning in the SConscript files.
1066    if python_version_deprecated():
1067        msg = "Support for pre-%s Python version (%s) is deprecated.\n" + \
1068              "    If this will cause hardship, contact scons-dev@scons.org"
1069        deprecated_version_string = ".".join(map(str, deprecated_python_version))
1070        SCons.Warnings.warn(SCons.Warnings.PythonVersionWarning,
1071                            msg % (deprecated_version_string, python_version_string()))
1072
1073    if not options.help:
1074        # [ ] Clarify why we need to create Builder here at all, and
1075        #     why it is created in DefaultEnvironment
1076        # https://bitbucket.org/scons/scons/commits/d27a548aeee8ad5e67ea75c2d19a7d305f784e30
1077        if SCons.SConf.NeedConfigHBuilder():
1078            SCons.SConf.CreateConfigHBuilder(SCons.Defaults.DefaultEnvironment())
1079
1080    # Now re-parse the command-line options (any to the left of a '--'
1081    # argument, that is) with any user-defined command-line options that
1082    # the SConscript files may have added to the parser object.  This will
1083    # emit the appropriate error message and exit if any unknown option
1084    # was specified on the command line.
1085
1086    parser.preserve_unknown_options = False
1087    parser.parse_args(parser.largs, options)
1088
1089    if options.help:
1090        help_text = SCons.Script.help_text
1091        if help_text is None:
1092            # They specified -h, but there was no Help() inside the
1093            # SConscript files.  Give them the options usage.
1094            raise SConsPrintHelpException
1095        else:
1096            print(help_text)
1097            print("Use scons -H for help about command-line options.")
1098        exit_status = 0
1099        return
1100
1101    # Change directory to the top-level SConstruct directory, then tell
1102    # the Node.FS subsystem that we're all done reading the SConscript
1103    # files and calling Repository() and VariantDir() and changing
1104    # directories and the like, so it can go ahead and start memoizing
1105    # the string values of file system nodes.
1106
1107    fs.chdir(fs.Top)
1108
1109    SCons.Node.FS.save_strings(1)
1110
1111    # Now that we've read the SConscripts we can set the options
1112    # that are SConscript settable:
1113    SCons.Node.implicit_cache = options.implicit_cache
1114    SCons.Node.FS.set_duplicate(options.duplicate)
1115    fs.set_max_drift(options.max_drift)
1116
1117    SCons.Job.explicit_stack_size = options.stack_size
1118
1119    # Hash format and chunksize are set late to support SetOption being called
1120    # in a SConscript or SConstruct file.
1121    SCons.Util.set_hash_format(options.hash_format)
1122    if options.md5_chunksize:
1123        SCons.Node.FS.File.hash_chunksize = options.md5_chunksize * 1024
1124
1125    platform = SCons.Platform.platform_module()
1126
1127    if options.interactive:
1128        SCons.Script.Interactive.interact(fs, OptionsParser, options,
1129                                          targets, target_top)
1130
1131    else:
1132
1133        # Build the targets
1134        nodes = _build_targets(fs, options, targets, target_top)
1135        if not nodes:
1136            revert_io()
1137            print('Found nothing to build')
1138            exit_status = 2
1139
1140def _build_targets(fs, options, targets, target_top):
1141
1142    global this_build_status
1143    this_build_status = 0
1144
1145    progress_display.set_mode(not (options.no_progress or options.silent))
1146    display.set_mode(not options.silent)
1147    SCons.Action.print_actions          = not options.silent
1148    SCons.Action.execute_actions        = not options.no_exec
1149    SCons.Node.do_store_info            = not options.no_exec
1150    SCons.SConf.dryrun                  = options.no_exec
1151
1152    if options.diskcheck:
1153        SCons.Node.FS.set_diskcheck(options.diskcheck)
1154
1155    SCons.CacheDir.cache_enabled = not options.cache_disable
1156    SCons.CacheDir.cache_readonly = options.cache_readonly
1157    SCons.CacheDir.cache_debug = options.cache_debug
1158    SCons.CacheDir.cache_force = options.cache_force
1159    SCons.CacheDir.cache_show = options.cache_show
1160
1161    if options.no_exec:
1162        CleanTask.execute = CleanTask.show
1163    else:
1164        CleanTask.execute = CleanTask.remove
1165
1166    lookup_top = None
1167    if targets or SCons.Script.BUILD_TARGETS != SCons.Script._build_plus_default:
1168        # They specified targets on the command line or modified
1169        # BUILD_TARGETS in the SConscript file(s), so if they used -u,
1170        # -U or -D, we have to look up targets relative to the top,
1171        # but we build whatever they specified.
1172        if target_top:
1173            lookup_top = fs.Dir(target_top)
1174            target_top = None
1175
1176        targets = SCons.Script.BUILD_TARGETS
1177    else:
1178        # There are no targets specified on the command line,
1179        # so if they used -u, -U or -D, we may have to restrict
1180        # what actually gets built.
1181        d = None
1182        if target_top:
1183            if options.climb_up == 1:
1184                # -u, local directory and below
1185                target_top = fs.Dir(target_top)
1186                lookup_top = target_top
1187            elif options.climb_up == 2:
1188                # -D, all Default() targets
1189                target_top = None
1190                lookup_top = None
1191            elif options.climb_up == 3:
1192                # -U, local SConscript Default() targets
1193                target_top = fs.Dir(target_top)
1194                def check_dir(x, target_top=target_top):
1195                    if hasattr(x, 'cwd') and x.cwd is not None:
1196                        cwd = x.cwd.srcnode()
1197                        return cwd == target_top
1198                    else:
1199                        # x doesn't have a cwd, so it's either not a target,
1200                        # or not a file, so go ahead and keep it as a default
1201                        # target and let the engine sort it out:
1202                        return 1
1203                d = [tgt for tgt in SCons.Script.DEFAULT_TARGETS if check_dir(tgt)]
1204                SCons.Script.DEFAULT_TARGETS[:] = d
1205                target_top = None
1206                lookup_top = None
1207
1208        targets = SCons.Script._Get_Default_Targets(d, fs)
1209
1210    if not targets:
1211        sys.stderr.write("scons: *** No targets specified and no Default() targets found.  Stop.\n")
1212        return None
1213
1214    def Entry(x, ltop=lookup_top, ttop=target_top, fs=fs):
1215        if isinstance(x, SCons.Node.Node):
1216            node = x
1217        else:
1218            node = None
1219            # Why would ltop be None? Unfortunately this happens.
1220            if ltop is None: ltop = ''
1221            # Curdir becomes important when SCons is called with -u, -C,
1222            # or similar option that changes directory, and so the paths
1223            # of targets given on the command line need to be adjusted.
1224            curdir = os.path.join(os.getcwd(), str(ltop))
1225            for lookup in SCons.Node.arg2nodes_lookups:
1226                node = lookup(x, curdir=curdir)
1227                if node is not None:
1228                    break
1229            if node is None:
1230                node = fs.Entry(x, directory=ltop, create=1)
1231        if ttop and not node.is_under(ttop):
1232            if isinstance(node, SCons.Node.FS.Dir) and ttop.is_under(node):
1233                node = ttop
1234            else:
1235                node = None
1236        return node
1237
1238    nodes = [_f for _f in map(Entry, targets) if _f]
1239
1240    task_class = BuildTask      # default action is to build targets
1241    opening_message = "Building targets ..."
1242    closing_message = "done building targets."
1243    if options.keep_going:
1244        failure_message = "done building targets (errors occurred during build)."
1245    else:
1246        failure_message = "building terminated because of errors."
1247    if options.question:
1248        task_class = QuestionTask
1249    try:
1250        if options.clean:
1251            task_class = CleanTask
1252            opening_message = "Cleaning targets ..."
1253            closing_message = "done cleaning targets."
1254            if options.keep_going:
1255                failure_message = "done cleaning targets (errors occurred during clean)."
1256            else:
1257                failure_message = "cleaning terminated because of errors."
1258    except AttributeError:
1259        pass
1260
1261    task_class.progress = ProgressObject
1262
1263    if options.random:
1264        def order(dependencies):
1265            """Randomize the dependencies."""
1266            import random
1267            random.shuffle(dependencies)
1268            return dependencies
1269    else:
1270        def order(dependencies):
1271            """Leave the order of dependencies alone."""
1272            return dependencies
1273
1274    def tmtrace_cleanup(tfile):
1275        tfile.close()
1276
1277    if options.taskmastertrace_file == '-':
1278        tmtrace = sys.stdout
1279    elif options.taskmastertrace_file:
1280        tmtrace = open(options.taskmastertrace_file, 'w')
1281        atexit.register(tmtrace_cleanup, tmtrace)
1282    else:
1283        tmtrace = None
1284    taskmaster = SCons.Taskmaster.Taskmaster(nodes, task_class, order, tmtrace)
1285
1286    # Let the BuildTask objects get at the options to respond to the
1287    # various print_* settings, tree_printer list, etc.
1288    BuildTask.options = options
1289
1290
1291    is_pypy = platform.python_implementation() == 'PyPy'
1292    # As of 3.7, python removed support for threadless platforms.
1293    # See https://www.python.org/dev/peps/pep-0011/
1294    is_37_or_later = sys.version_info >= (3, 7)
1295    # python_has_threads = sysconfig.get_config_var('WITH_THREAD') or is_pypy or is_37_or_later
1296
1297    # As of python 3.4 threading has a dummy_threading module for use when there is no threading
1298    # it's get_ident() will allways return -1, while real threading modules get_ident() will
1299    # always return a positive integer
1300    python_has_threads = threading.get_ident() != -1
1301    # to check if python configured with threads.
1302    global num_jobs
1303    num_jobs = options.num_jobs
1304    jobs = SCons.Job.Jobs(num_jobs, taskmaster)
1305    if num_jobs > 1:
1306        msg = None
1307        if jobs.num_jobs == 1 or not python_has_threads:
1308            msg = "parallel builds are unsupported by this version of Python;\n" + \
1309                  "\tignoring -j or num_jobs option.\n"
1310        if msg:
1311            SCons.Warnings.warn(SCons.Warnings.NoParallelSupportWarning, msg)
1312
1313    memory_stats.append('before building targets:')
1314    count_stats.append(('pre-', 'build'))
1315
1316    def jobs_postfunc(
1317        jobs=jobs,
1318        options=options,
1319        closing_message=closing_message,
1320        failure_message=failure_message
1321        ):
1322        if jobs.were_interrupted():
1323            if not options.no_progress and not options.silent:
1324                sys.stderr.write("scons: Build interrupted.\n")
1325            global exit_status
1326            global this_build_status
1327            exit_status = 2
1328            this_build_status = 2
1329
1330        if this_build_status:
1331            progress_display("scons: " + failure_message)
1332        else:
1333            progress_display("scons: " + closing_message)
1334        if not options.no_exec:
1335            if jobs.were_interrupted():
1336                progress_display("scons: writing .sconsign file.")
1337            SCons.SConsign.write()
1338
1339    progress_display("scons: " + opening_message)
1340    jobs.run(postfunc = jobs_postfunc)
1341
1342    memory_stats.append('after building targets:')
1343    count_stats.append(('post-', 'build'))
1344
1345    return nodes
1346
1347def _exec_main(parser, values):
1348    sconsflags = os.environ.get('SCONSFLAGS', '')
1349    all_args = sconsflags.split() + sys.argv[1:]
1350
1351    options, args = parser.parse_args(all_args, values)
1352
1353    if isinstance(options.debug, list) and "pdb" in options.debug:
1354        import pdb
1355        pdb.Pdb().runcall(_main, parser)
1356    elif options.profile_file:
1357        from cProfile import Profile
1358
1359        prof = Profile()
1360        try:
1361            prof.runcall(_main, parser)
1362        finally:
1363            prof.dump_stats(options.profile_file)
1364    else:
1365        _main(parser)
1366
1367def main():
1368    global OptionsParser
1369    global exit_status
1370    global first_command_start
1371
1372    # Check up front for a Python version we do not support.  We
1373    # delay the check for deprecated Python versions until later,
1374    # after the SConscript files have been read, in case they
1375    # disable that warning.
1376    if python_version_unsupported():
1377        msg = "scons: *** SCons version %s does not run under Python version %s.\n"
1378        sys.stderr.write(msg % (SCons.__version__, python_version_string()))
1379        sys.exit(1)
1380
1381    parts = ["SCons by Steven Knight et al.:\n"]
1382    try:
1383        import SCons
1384        parts.append(version_string("SCons", SCons))
1385    except (ImportError, AttributeError):
1386        # On Windows there is no scons.py, so there is no
1387        # __main__.__version__, hence there is no script version.
1388        pass
1389    parts.append(path_string("SCons", SCons))
1390    parts.append(SCons.__copyright__)
1391    version = ''.join(parts)
1392
1393    from . import SConsOptions
1394    parser = SConsOptions.Parser(version)
1395    values = SConsOptions.SConsValues(parser.get_default_values())
1396
1397    OptionsParser = parser
1398
1399    try:
1400        try:
1401            _exec_main(parser, values)
1402        finally:
1403            revert_io()
1404    except SystemExit as s:
1405        if s:
1406            exit_status = s.code
1407    except KeyboardInterrupt:
1408        print("scons: Build interrupted.")
1409        sys.exit(2)
1410    except SyntaxError as e:
1411        _scons_syntax_error(e)
1412    except SCons.Errors.InternalError:
1413        _scons_internal_error()
1414    except SCons.Errors.UserError as e:
1415        _scons_user_error(e)
1416    except SConsPrintHelpException:
1417        parser.print_help()
1418        exit_status = 0
1419    except SCons.Errors.BuildError as e:
1420        print(e)
1421        exit_status = e.exitstatus
1422    except:
1423        # An exception here is likely a builtin Python exception Python
1424        # code in an SConscript file.  Show them precisely what the
1425        # problem was and where it happened.
1426        SCons.Script._SConscript.SConscript_exception()
1427        sys.exit(2)
1428
1429    memory_stats.print_stats()
1430    count_stats.print_stats()
1431
1432    if print_objects:
1433        SCons.Debug.listLoggedInstances('*')
1434        #SCons.Debug.dumpLoggedInstances('*')
1435
1436    if print_memoizer:
1437        SCons.Memoize.Dump("Memoizer (memory cache) hits and misses:")
1438
1439    # Dump any development debug info that may have been enabled.
1440    # These are purely for internal debugging during development, so
1441    # there's no need to control them with --debug= options; they're
1442    # controlled by changing the source code.
1443    SCons.Debug.dump_caller_counts()
1444    SCons.Taskmaster.dump_stats()
1445
1446    if print_time:
1447        total_time = time.time() - SCons.Script.start_time
1448        if num_jobs == 1:
1449            ct = cumulative_command_time
1450        else:
1451            if last_command_end is None or first_command_start is None:
1452                ct = 0.0
1453            else:
1454                ct = last_command_end - first_command_start
1455        scons_time = total_time - sconscript_time - ct
1456        print("Total build time: %f seconds"%total_time)
1457        print("Total SConscript file execution time: %f seconds"%sconscript_time)
1458        print("Total SCons execution time: %f seconds"%scons_time)
1459        print("Total command execution time: %f seconds"%ct)
1460
1461    sys.exit(exit_status)
1462
1463# Local Variables:
1464# tab-width:4
1465# indent-tabs-mode:nil
1466# End:
1467# vim: set expandtab tabstop=4 shiftwidth=4:
1468