diff options
Diffstat (limited to 'SCons/Script/Main.py')
-rw-r--r-- | SCons/Script/Main.py | 193 |
1 files changed, 111 insertions, 82 deletions
diff --git a/SCons/Script/Main.py b/SCons/Script/Main.py index b90284278..c3f31ca3e 100644 --- a/SCons/Script/Main.py +++ b/SCons/Script/Main.py @@ -31,13 +31,8 @@ some other module. If it's specific to the "scons" script invocation, it goes here. """ -# these define the range of versions SCons supports -minimum_python_version = (3, 6, 0) -deprecated_python_version = (3, 6, 0) - import SCons.compat -import atexit import importlib.util import os import re @@ -46,6 +41,7 @@ import time import traceback import platform import threading +from typing import Optional, List import SCons.CacheDir import SCons.Debug @@ -66,6 +62,20 @@ import SCons.Script.Interactive from SCons import __version__ as SConsVersion +# these define the range of versions SCons supports +minimum_python_version = (3, 6, 0) +deprecated_python_version = (3, 6, 0) + +# ordered list of SConsctruct names to look for if there is no -f flag +KNOWN_SCONSTRUCT_NAMES = [ + 'SConstruct', + 'Sconstruct', + 'sconstruct', + 'SConstruct.py', + 'Sconstruct.py', + 'sconstruct.py', +] + # Global variables first_command_start = None last_command_end = None @@ -82,7 +92,7 @@ num_jobs = None delayed_warnings = [] -def revert_io(): +def revert_io() -> None: # This call is added to revert stderr and stdout to the original # ones just in case some build rule or something else in the system # has redirected them elsewhere. @@ -103,7 +113,7 @@ class Progressor: count = 0 target_string = '$TARGET' - def __init__(self, obj, interval=1, file=None, overwrite=False): + def __init__(self, obj, interval: int=1, file=None, overwrite: bool=False) -> None: if file is None: file = sys.stdout @@ -121,12 +131,12 @@ class Progressor: else: self.func = self.string - def write(self, s): + def write(self, s) -> None: self.file.write(s) self.file.flush() self.prev = s - def erase_previous(self): + def erase_previous(self) -> None: if self.prev: length = len(self.prev) if self.prev[-1] in ('\n', '\r'): @@ -134,16 +144,16 @@ class Progressor: self.write(' ' * length + '\r') self.prev = '' - def spinner(self, node): + def spinner(self, node) -> None: self.write(self.obj[self.count % len(self.obj)]) - def string(self, node): + def string(self, node) -> None: self.write(self.obj) - def replace_string(self, node): + def replace_string(self, node) -> None: self.write(self.obj.replace(self.target_string, str(node))) - def __call__(self, node): + def __call__(self, node) -> None: self.count = self.count + 1 if (self.count % self.interval) == 0: if self.overwrite: @@ -152,7 +162,7 @@ class Progressor: ProgressObject = SCons.Util.Null() -def Progress(*args, **kw): +def Progress(*args, **kw) -> None: global ProgressObject ProgressObject = Progressor(*args, **kw) @@ -170,7 +180,7 @@ class BuildTask(SCons.Taskmaster.OutOfDateTask): """An SCons build task.""" progress = ProgressObject - def display(self, message): + def display(self, message) -> None: display('scons: ' + message) def prepare(self): @@ -179,14 +189,14 @@ class BuildTask(SCons.Taskmaster.OutOfDateTask): self.progress(target) return SCons.Taskmaster.OutOfDateTask.prepare(self) - def needs_execute(self): + def needs_execute(self) -> bool: 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): + def execute(self) -> None: if print_time: start_time = time.time() global first_command_start @@ -213,7 +223,7 @@ class BuildTask(SCons.Taskmaster.OutOfDateTask): % (str(self.node), (finish_time - start_time)) ) - def do_failed(self, status=2): + def do_failed(self, status: int=2) -> None: _BuildFailures.append(self.exception[1]) global exit_status global this_build_status @@ -253,7 +263,7 @@ class BuildTask(SCons.Taskmaster.OutOfDateTask): else: SCons.Taskmaster.OutOfDateTask.executed(self) - def failed(self): + def failed(self) -> None: # Handle the failure of a build task. The primary purpose here # is to display the various types of Errors and Exceptions # appropriately. @@ -284,7 +294,7 @@ class BuildTask(SCons.Taskmaster.OutOfDateTask): node = buildError.node if not SCons.Util.is_List(node): - node = [ node ] + node = [node] nodename = ', '.join(map(str, node)) errfmt = "scons: *** [%s] %s\n" @@ -309,7 +319,7 @@ class BuildTask(SCons.Taskmaster.OutOfDateTask): self.exc_clear() - def postprocess(self): + def postprocess(self) -> None: if self.top: t = self.targets[0] for tp in self.options.tree_printers: @@ -321,7 +331,7 @@ class BuildTask(SCons.Taskmaster.OutOfDateTask): print(tree) SCons.Taskmaster.OutOfDateTask.postprocess(self) - def make_ready(self): + def make_ready(self) -> None: """Make a task ready for execution""" SCons.Taskmaster.OutOfDateTask.make_ready(self) if self.out_of_date and self.options.debug_explain: @@ -332,7 +342,7 @@ class BuildTask(SCons.Taskmaster.OutOfDateTask): class CleanTask(SCons.Taskmaster.AlwaysTask): """An SCons clean task.""" - def fs_delete(self, path, pathstr, remove=True): + def fs_delete(self, path, pathstr, remove: bool=True): try: if os.path.lexists(path): if os.path.isfile(path) or os.path.islink(path): @@ -366,20 +376,20 @@ class CleanTask(SCons.Taskmaster.AlwaysTask): result = [t for t in self.targets if not t.noclean] return result - def _clean_targets(self, remove=True): + def _clean_targets(self, remove: bool=True) -> None: target = self.targets[0] if target in SCons.Environment.CleanTargets: files = SCons.Environment.CleanTargets[target] for f in files: self.fs_delete(f.get_abspath(), str(f), remove) - def show(self): + def show(self) -> None: for t in self._get_files_to_clean(): if not t.isdir(): display("Removed " + str(t)) self._clean_targets(remove=False) - def remove(self): + def remove(self) -> None: for t in self._get_files_to_clean(): try: removed = t.remove() @@ -408,15 +418,15 @@ class CleanTask(SCons.Taskmaster.AlwaysTask): # anything really needs to be done. make_ready = SCons.Taskmaster.Task.make_ready_all - def prepare(self): + def prepare(self) -> None: pass class QuestionTask(SCons.Taskmaster.AlwaysTask): """An SCons task for the -q (question) option.""" - def prepare(self): + def prepare(self) -> None: pass - def execute(self): + def execute(self) -> None: if self.targets[0].get_state() != SCons.Node.up_to_date or \ (self.top and not self.targets[0].exists()): global exit_status @@ -425,12 +435,12 @@ class QuestionTask(SCons.Taskmaster.AlwaysTask): this_build_status = 1 self.tm.stop() - def executed(self): + def executed(self) -> None: pass class TreePrinter: - def __init__(self, derived=False, prune=False, status=False, sLineDraw=False): + def __init__(self, derived: bool=False, prune: bool=False, status: bool=False, sLineDraw: bool=False) -> None: self.derived = derived self.prune = prune self.status = status @@ -440,7 +450,7 @@ class TreePrinter: 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): + def display(self, t) -> None: if self.derived: func = self.get_derived_children else: @@ -460,24 +470,29 @@ def python_version_deprecated(version=sys.version_info): class FakeOptionParser: - """ - A do-nothing option parser, used for the initial OptionsParser variable. + """A do-nothing option parser, used for the initial OptionsParser value. During normal SCons operation, the OptionsParser is created right - away by the main() function. Certain tests scripts however, can + away by the main() function. Certain test 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): + + # TODO: to quiet checkers, FakeOptionParser should also define + # raise_exception_on_error, preserve_unknown_options, largs and parse_args + + def add_local_option(self, *args, **kw) -> None: pass + OptionsParser = FakeOptionParser() def AddOption(*args, **kw): @@ -492,47 +507,51 @@ def GetOption(name): def SetOption(name, value): return OptionsParser.values.set_option(name, value) - -def ValidateOptions(throw_exception=False) -> None: +def ValidateOptions(throw_exception: bool=False) -> None: """Validate options passed to SCons on the command line. - If you call this after you set all your command line options with AddOption(), - it will verify that all command line options are valid. - So if you added an option --xyz and you call SCons with --xyy you can cause + Checks that all options given on the command line are known to this + instance of SCons. Call after all of the cli options have been set + up through :func:`AddOption` calls. For example, if you added an + option ``--xyz`` and you call SCons with ``--xyy`` you can cause SCons to issue an error message and exit by calling this function. - :param bool throw_exception: (Optional) Should this function raise an error if there's an invalid option on the command line, or issue a message and exit with error status. + Arguments: + throw_exception: if an invalid option is present on the command line, + raises an exception if this optional parameter evaluates true; + if false (the default), issue a message and exit with error status. - :raises SConsBadOptionError: If throw_exception is True and there are invalid options on command line. + Raises: + SConsBadOptionError: If *throw_exception* is true and there are invalid + options on the command line. - .. versionadded:: 4.4.1 + .. versionadded:: 4.5.0 """ - OptionsParser.raise_exception_on_error = throw_exception OptionsParser.preserve_unknown_options = False OptionsParser.parse_args(OptionsParser.largs, OptionsParser.values) -def PrintHelp(file=None): +def PrintHelp(file=None) -> None: OptionsParser.print_help(file=file) class Stats: - def __init__(self): + def __init__(self) -> None: self.stats = [] self.labels = [] self.append = self.do_nothing self.print_stats = self.do_nothing - def enable(self, outfp): + def enable(self, outfp) -> None: self.outfp = outfp self.append = self.do_append self.print_stats = self.do_print - def do_nothing(self, *args, **kw): + def do_nothing(self, *args, **kw) -> None: pass class CountStats(Stats): - def do_append(self, label): + def do_append(self, label) -> None: self.labels.append(label) self.stats.append(SCons.Debug.fetchLoggedInstances()) - def do_print(self): + def do_print(self) -> None: stats_table = {} for s in self.stats: for n in [t[0] for t in s]: @@ -559,10 +578,10 @@ class CountStats(Stats): count_stats = CountStats() class MemStats(Stats): - def do_append(self, label): + def do_append(self, label) -> None: self.labels.append(label) self.stats.append(SCons.Debug.memory()) - def do_print(self): + def do_print(self) -> None: fmt = 'Memory %-32s %12d\n' for label, stats in zip(self.labels, self.stats): self.outfp.write(fmt % (label, stats)) @@ -571,7 +590,7 @@ memory_stats = MemStats() # utility functions -def _scons_syntax_error(e): +def _scons_syntax_error(e) -> None: """Handle syntax errors. Print out a message and show where the error occurred. """ @@ -599,7 +618,7 @@ def find_deepest_user_frame(tb): return frame return tb[0] -def _scons_user_error(e): +def _scons_user_error(e) -> None: """Handle user errors. Print out a message and a description of the error, along with the line number and routine where it occured. The file and line number will be the deepest stack frame that is @@ -614,7 +633,7 @@ def _scons_user_error(e): sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine)) sys.exit(2) -def _scons_user_warning(e): +def _scons_user_warning(e) -> None: """Handle user warnings. Print out a message and a description of the warning, along with the line number and routine where it occured. The file and line number will be the deepest stack frame that is @@ -625,7 +644,7 @@ def _scons_user_warning(e): sys.stderr.write("\nscons: warning: %s\n" % e) sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine)) -def _scons_internal_warning(e): +def _scons_internal_warning(e) -> None: """Slightly different from _scons_user_warning in that we use the *current call stack* rather than sys.exc_info() to get our stack trace. This is used by the warnings framework to print warnings.""" @@ -633,7 +652,7 @@ def _scons_internal_warning(e): sys.stderr.write("\nscons: warning: %s\n" % e.args[0]) sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine)) -def _scons_internal_error(): +def _scons_internal_error() -> None: """Handle all errors but user errors. Print out a message telling the user what to do in this case and print a normal trace. """ @@ -641,13 +660,24 @@ def _scons_internal_error(): traceback.print_exc() sys.exit(2) -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. +def _SConstruct_exists( + dirname: str, repositories: List[str], filelist: List[str] +) -> Optional[str]: + """Check that an SConstruct file exists in a directory. + + Arguments: + dirname: the directory to search. If empty, look in cwd. + repositories: a list of repositories to search in addition to the + project directory tree. + filelist: names of SConstruct file(s) to search for. + If empty list, use the built-in list of names. + + Returns: + The path to the located SConstruct file, or ``None``. + """ if not filelist: - filelist = ['SConstruct', 'Sconstruct', 'sconstruct', 'SConstruct.py', 'Sconstruct.py', 'sconstruct.py'] + filelist = KNOWN_SCONSTRUCT_NAMES for file in filelist: sfile = os.path.join(dirname, file) if os.path.isfile(sfile): @@ -658,7 +688,7 @@ def _SConstruct_exists(dirname='', repositories=[], filelist=None): return sfile return None -def _set_debug_values(options): +def _set_debug_values(options) -> None: global print_memoizer, print_objects, print_stacktrace, print_time, print_action_timestamps debug_values = options.debug @@ -679,14 +709,14 @@ def _set_debug_values(options): 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) + 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) + 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) + print_objects = "objects" in debug_values if print_objects: SCons.Debug.track_instances = True if "presub" in debug_values: @@ -798,7 +828,7 @@ def _load_site_scons_dir(topdir, site_dir_name=None): raise -def _load_all_site_scons_dirs(topdir, verbose=False): +def _load_all_site_scons_dirs(topdir, verbose: bool=False) -> None: """Load all of the predefined site_scons dir. Order is significant; we load them in order from most generic (machine-wide) to most specific (topdir). @@ -841,7 +871,7 @@ def _load_all_site_scons_dirs(topdir, verbose=False): print("Loading site dir ", d) _load_site_scons_dir(d) -def test_load_all_site_scons_dirs(d): +def test_load_all_site_scons_dirs(d) -> None: _load_all_site_scons_dirs(d, True) def version_string(label, module): @@ -858,7 +888,7 @@ def version_string(label, module): module.__developer__, module.__buildsys__) -def path_string(label, module): +def path_string(label, module) -> str: path = module.__path__ return "\t%s path: %s\n"%(label,path) @@ -919,9 +949,9 @@ def _main(parser): target_top = None if options.climb_up: target_top = '.' # directory to prepend to targets - while script_dir and not _SConstruct_exists(script_dir, - options.repository, - options.file): + 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) @@ -951,8 +981,7 @@ def _main(parser): if options.file: scripts.extend(options.file) if not scripts: - sfile = _SConstruct_exists(repositories=options.repository, - filelist=options.file) + sfile = _SConstruct_exists("", options.repository, options.file) if sfile: scripts.append(sfile) @@ -1011,8 +1040,8 @@ def _main(parser): # Next, set up the variables that hold command-line arguments, # so the SConscript files that we read and execute have access to them. # TODO: for options defined via AddOption which take space-separated - # option-args, the option-args will collect into targets here, - # because we don't yet know to do any different. + # option-args, the option-args will collect into targets here, + # because we don't yet know to do any different. targets = [] xmit_args = [] for a in parser.largs: @@ -1327,7 +1356,7 @@ def _build_targets(fs, options, targets, target_top): options=options, closing_message=closing_message, failure_message=failure_message - ): + ) -> None: if jobs.were_interrupted(): if not options.no_progress and not options.silent: sys.stderr.write("scons: Build interrupted.\n") @@ -1353,7 +1382,7 @@ def _build_targets(fs, options, targets, target_top): return nodes -def _exec_main(parser, values): +def _exec_main(parser, values) -> None: sconsflags = os.environ.get('SCONSFLAGS', '') all_args = sconsflags.split() + sys.argv[1:] @@ -1374,7 +1403,7 @@ def _exec_main(parser, values): _main(parser) -def main(): +def main() -> None: global OptionsParser global exit_status global first_command_start |