From 531cbfda04263a234238bf58fb6bfa27f8f12ea7 Mon Sep 17 00:00:00 2001 From: Thaddeus Crews Date: Mon, 15 Jan 2024 10:19:35 -0600 Subject: Implement framework for hinting complex types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit • Showcase potential with `ExecutorType` --- CHANGES.txt | 12 ++++--- RELEASE.txt | 2 ++ SCons/Action.py | 76 +++++++++++++++++++++++---------------------- SCons/ActionTests.py | 6 ++-- SCons/Builder.py | 4 ++- SCons/BuilderTests.py | 5 +-- SCons/Environment.py | 5 +-- SCons/Errors.py | 4 ++- SCons/Executor.py | 8 +++-- SCons/Node/FSTests.py | 8 +++-- SCons/Node/NodeTests.py | 5 +-- SCons/Node/__init__.py | 8 +++-- SCons/Tool/ninja/Methods.py | 6 ++-- SCons/Tool/ninja/Utils.py | 4 ++- SCons/Util/sctyping.py | 33 ++++++++++++++++++++ 15 files changed, 123 insertions(+), 63 deletions(-) create mode 100644 SCons/Util/sctyping.py diff --git a/CHANGES.txt b/CHANGES.txt index adb955c..09d0d07 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -43,6 +43,10 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER method so that the generated function argument list matches the function's prototype when including a header file. + From Thaddeus Crews: + - Implemented SCons.Util.sctyping as a safe means of hinting complex types. Currently + only implemented for `Executor` as a proof-of-concept. + From William Deegan: - Fix sphinx config to handle SCons versions with post such as: 4.6.0.post1 @@ -1402,12 +1406,12 @@ RELEASE 3.1.0 - Mon, 20 Jul 2019 16:59:23 -0700 scons: rebuilding `file3' because: the dependency order changed: ->Sources - Old:xxx New:zzz - Old:yyy New:yyy - Old:zzz New:xxx + Old:xxx New:zzz + Old:yyy New:yyy + Old:zzz New:xxx ->Depends ->Implicit - Old:/usr/bin/python New:/usr/bin/python + Old:/usr/bin/python New:/usr/bin/python - Fix Issue #3350 - SCons Exception EnvironmentError is conflicting with Python's EnvironmentError. - Fix spurious rebuilds on second build for cases where builder has > 1 target and the source file is generated. This was causing the > 1th target to not have it's implicit list cleared when the source diff --git a/RELEASE.txt b/RELEASE.txt index 22922d5..04003cd 100644 --- a/RELEASE.txt +++ b/RELEASE.txt @@ -72,6 +72,8 @@ DEVELOPMENT ----------- - Fix sphinx config to handle SCons versions with post such as: 4.6.0.post1 +- Created SCons.Util.sctyping to contain complex type information, allowing + for repo-wide type hinting without causing cyclical dependencies. Thanks to the following contributors listed below for their contributions to this release. ========================================================================================== diff --git a/SCons/Action.py b/SCons/Action.py index 0dd02cc..8dcc1a4 100644 --- a/SCons/Action.py +++ b/SCons/Action.py @@ -109,6 +109,7 @@ import sys from abc import ABC, abstractmethod from collections import OrderedDict from subprocess import DEVNULL, PIPE +from typing import Optional import SCons.Debug import SCons.Errors @@ -119,6 +120,7 @@ import SCons.Util from SCons.Debug import logInstanceCreation from SCons.Subst import SUBST_SIG, SUBST_RAW from SCons.Util import is_String, is_List +from SCons.Util.sctyping import ExecutorType class _null: pass @@ -530,7 +532,7 @@ class ActionBase(ABC): show=_null, execute=_null, chdir=_null, - executor=None, + executor: Optional[ExecutorType] = None, ): raise NotImplementedError @@ -542,15 +544,15 @@ class ActionBase(ABC): batch_key = no_batch_key - def genstring(self, target, source, env, executor=None) -> str: + def genstring(self, target, source, env, executor: Optional[ExecutorType] = None) -> str: return str(self) @abstractmethod - def get_presig(self, target, source, env, executor=None): + def get_presig(self, target, source, env, executor: Optional[ExecutorType] = None): raise NotImplementedError @abstractmethod - def get_implicit_deps(self, target, source, env, executor=None): + def get_implicit_deps(self, target, source, env, executor: Optional[ExecutorType] = None): raise NotImplementedError def get_contents(self, target, source, env): @@ -602,10 +604,10 @@ class ActionBase(ABC): self.presub_env = None # don't need this any more return lines - def get_varlist(self, target, source, env, executor=None): + def get_varlist(self, target, source, env, executor: Optional[ExecutorType] = None): return self.varlist - def get_targets(self, env, executor): + def get_targets(self, env, executor: Optional[ExecutorType]): """ Returns the type of targets ($TARGETS, $CHANGED_TARGETS) used by this action. @@ -659,7 +661,7 @@ class _ActionAction(ActionBase): show=_null, execute=_null, chdir=_null, - executor=None): + executor: Optional[ExecutorType] = None): if not is_List(target): target = [target] if not is_List(source): @@ -743,10 +745,10 @@ class _ActionAction(ActionBase): # an ABC like parent ActionBase, but things reach in and use it. It's # not just unittests or we could fix it up with a concrete subclass there. - def get_presig(self, target, source, env, executor=None): + def get_presig(self, target, source, env, executor: Optional[ExecutorType] = None): raise NotImplementedError - def get_implicit_deps(self, target, source, env, executor=None): + def get_implicit_deps(self, target, source, env, executor: Optional[ExecutorType] = None): raise NotImplementedError @@ -1010,7 +1012,7 @@ class CommandAction(_ActionAction): return ' '.join(map(str, self.cmd_list)) return str(self.cmd_list) - def process(self, target, source, env, executor=None, overrides: bool=False): + def process(self, target, source, env, executor: Optional[ExecutorType] = None, overrides: bool=False): if executor: result = env.subst_list(self.cmd_list, 0, executor=executor, overrides=overrides) else: @@ -1031,7 +1033,7 @@ class CommandAction(_ActionAction): pass return result, ignore, silent - def strfunction(self, target, source, env, executor=None, overrides: bool=False): + def strfunction(self, target, source, env, executor: Optional[ExecutorType] = None, overrides: bool=False): if self.cmdstr is None: return None if self.cmdstr is not _null: @@ -1046,7 +1048,7 @@ class CommandAction(_ActionAction): return '' return _string_from_cmd_list(cmd_list[0]) - def execute(self, target, source, env, executor=None): + def execute(self, target, source, env, executor: Optional[ExecutorType] = None): """Execute a command action. This will handle lists of commands as well as individual commands, @@ -1108,7 +1110,7 @@ class CommandAction(_ActionAction): command=cmd_line) return 0 - def get_presig(self, target, source, env, executor=None): + def get_presig(self, target, source, env, executor: Optional[ExecutorType] = None): """Return the signature contents of this action's command line. This strips $(-$) and everything in between the string, @@ -1123,7 +1125,7 @@ class CommandAction(_ActionAction): return env.subst_target_source(cmd, SUBST_SIG, executor=executor) return env.subst_target_source(cmd, SUBST_SIG, target, source) - def get_implicit_deps(self, target, source, env, executor=None): + def get_implicit_deps(self, target, source, env, executor: Optional[ExecutorType] = None): """Return the implicit dependencies of this action's command line.""" icd = env.get('IMPLICIT_COMMAND_DEPENDENCIES', True) if is_String(icd) and icd[:1] == '$': @@ -1145,7 +1147,7 @@ class CommandAction(_ActionAction): # lightweight dependency scanning. return self._get_implicit_deps_lightweight(target, source, env, executor) - def _get_implicit_deps_lightweight(self, target, source, env, executor): + def _get_implicit_deps_lightweight(self, target, source, env, executor: Optional[ExecutorType]): """ Lightweight dependency scanning involves only scanning the first entry in an action string, even if it contains &&. @@ -1166,7 +1168,7 @@ class CommandAction(_ActionAction): res.append(env.fs.File(d)) return res - def _get_implicit_deps_heavyweight(self, target, source, env, executor, + def _get_implicit_deps_heavyweight(self, target, source, env, executor: Optional[ExecutorType], icd_int): """ Heavyweight dependency scanning involves scanning more than just the @@ -1234,7 +1236,7 @@ class CommandGeneratorAction(ActionBase): self.varlist = kw.get('varlist', ()) self.targets = kw.get('targets', '$TARGETS') - def _generate(self, target, source, env, for_signature, executor=None): + def _generate(self, target, source, env, for_signature, executor: Optional[ExecutorType] = None): # ensure that target is a list, to make it easier to write # generator functions: if not is_List(target): @@ -1265,11 +1267,11 @@ class CommandGeneratorAction(ActionBase): def batch_key(self, env, target, source): return self._generate(target, source, env, 1).batch_key(env, target, source) - def genstring(self, target, source, env, executor=None) -> str: + def genstring(self, target, source, env, executor: Optional[ExecutorType] = None) -> str: return self._generate(target, source, env, 1, executor).genstring(target, source, env) def __call__(self, target, source, env, exitstatfunc=_null, presub=_null, - show=_null, execute=_null, chdir=_null, executor=None): + show=_null, execute=_null, chdir=_null, executor: Optional[ExecutorType] = None): act = self._generate(target, source, env, 0, executor) if act is None: raise SCons.Errors.UserError( @@ -1281,7 +1283,7 @@ class CommandGeneratorAction(ActionBase): target, source, env, exitstatfunc, presub, show, execute, chdir, executor ) - def get_presig(self, target, source, env, executor=None): + def get_presig(self, target, source, env, executor: Optional[ExecutorType] = None): """Return the signature contents of this action's command line. This strips $(-$) and everything in between the string, @@ -1289,13 +1291,13 @@ class CommandGeneratorAction(ActionBase): """ return self._generate(target, source, env, 1, executor).get_presig(target, source, env) - def get_implicit_deps(self, target, source, env, executor=None): + def get_implicit_deps(self, target, source, env, executor: Optional[ExecutorType] = None): return self._generate(target, source, env, 1, executor).get_implicit_deps(target, source, env) - def get_varlist(self, target, source, env, executor=None): + def get_varlist(self, target, source, env, executor: Optional[ExecutorType] = None): return self._generate(target, source, env, 1, executor).get_varlist(target, source, env, executor) - def get_targets(self, env, executor): + def get_targets(self, env, executor: Optional[ExecutorType]): return self._generate(None, None, env, 1, executor).get_targets(env, executor) @@ -1341,22 +1343,22 @@ class LazyAction(CommandGeneratorAction, CommandAction): raise SCons.Errors.UserError("$%s value %s cannot be used to create an Action." % (self.var, repr(c))) return gen_cmd - def _generate(self, target, source, env, for_signature, executor=None): + def _generate(self, target, source, env, for_signature, executor: Optional[ExecutorType] = None): return self._generate_cache(env) def __call__(self, target, source, env, *args, **kw): c = self.get_parent_class(env) return c.__call__(self, target, source, env, *args, **kw) - def get_presig(self, target, source, env, executor=None): + def get_presig(self, target, source, env, executor: Optional[ExecutorType] = None): c = self.get_parent_class(env) return c.get_presig(self, target, source, env) - def get_implicit_deps(self, target, source, env, executor=None): + def get_implicit_deps(self, target, source, env, executor: Optional[ExecutorType] = None): c = self.get_parent_class(env) return c.get_implicit_deps(self, target, source, env) - def get_varlist(self, target, source, env, executor=None): + def get_varlist(self, target, source, env, executor: Optional[ExecutorType] = None): c = self.get_parent_class(env) return c.get_varlist(self, target, source, env, executor) @@ -1389,7 +1391,7 @@ class FunctionAction(_ActionAction): except AttributeError: return "unknown_python_function" - def strfunction(self, target, source, env, executor=None): + def strfunction(self, target, source, env, executor: Optional[ExecutorType] = None): if self.cmdstr is None: return None if self.cmdstr is not _null: @@ -1430,7 +1432,7 @@ class FunctionAction(_ActionAction): return str(self.execfunction) return "%s(target, source, env)" % name - def execute(self, target, source, env, executor=None): + def execute(self, target, source, env, executor: Optional[ExecutorType] = None): exc_info = (None,None,None) try: if executor: @@ -1471,14 +1473,14 @@ class FunctionAction(_ActionAction): # more information about this issue. del exc_info - def get_presig(self, target, source, env, executor=None): + def get_presig(self, target, source, env, executor: Optional[ExecutorType] = None): """Return the signature contents of this callable action.""" try: return self.gc(target, source, env) except AttributeError: return self.funccontents - def get_implicit_deps(self, target, source, env, executor=None): + def get_implicit_deps(self, target, source, env, executor: Optional[ExecutorType] = None): return [] class ListAction(ActionBase): @@ -1495,7 +1497,7 @@ class ListAction(ActionBase): self.varlist = () self.targets = '$TARGETS' - def genstring(self, target, source, env, executor=None) -> str: + def genstring(self, target, source, env, executor: Optional[ExecutorType] = None) -> str: return '\n'.join([a.genstring(target, source, env) for a in self.list]) def __str__(self) -> str: @@ -1505,7 +1507,7 @@ class ListAction(ActionBase): return SCons.Util.flatten_sequence( [a.presub_lines(env) for a in self.list]) - def get_presig(self, target, source, env, executor=None): + def get_presig(self, target, source, env, executor: Optional[ExecutorType] = None): """Return the signature contents of this action list. Simple concatenation of the signatures of the elements. @@ -1513,7 +1515,7 @@ class ListAction(ActionBase): return b"".join([bytes(x.get_contents(target, source, env)) for x in self.list]) def __call__(self, target, source, env, exitstatfunc=_null, presub=_null, - show=_null, execute=_null, chdir=_null, executor=None): + show=_null, execute=_null, chdir=_null, executor: Optional[ExecutorType] = None): if executor: target = executor.get_all_targets() source = executor.get_all_sources() @@ -1524,13 +1526,13 @@ class ListAction(ActionBase): return stat return 0 - def get_implicit_deps(self, target, source, env, executor=None): + def get_implicit_deps(self, target, source, env, executor: Optional[ExecutorType] = None): result = [] for act in self.list: result.extend(act.get_implicit_deps(target, source, env)) return result - def get_varlist(self, target, source, env, executor=None): + def get_varlist(self, target, source, env, executor: Optional[ExecutorType] = None): result = OrderedDict() for act in self.list: for var in act.get_varlist(target, source, env, executor): @@ -1596,7 +1598,7 @@ class ActionCaller: kw[key] = self.subst(self.kw[key], target, source, env) return kw - def __call__(self, target, source, env, executor=None): + def __call__(self, target, source, env, executor: Optional[ExecutorType] = None): args = self.subst_args(target, source, env) kw = self.subst_kw(target, source, env) return self.parent.actfunc(*args, **kw) diff --git a/SCons/ActionTests.py b/SCons/ActionTests.py index e0e15b0..3979880 100644 --- a/SCons/ActionTests.py +++ b/SCons/ActionTests.py @@ -43,11 +43,13 @@ import types import unittest from unittest import mock from subprocess import PIPE +from typing import Optional import SCons.Action import SCons.Environment import SCons.Errors from SCons.Action import scons_subproc_run +from SCons.Util.sctyping import ExecutorType import TestCmd @@ -1699,11 +1701,11 @@ class FunctionActionTestCase(unittest.TestCase): c = test.read(outfile, 'r') assert c == "class1b\n", c - def build_it(target, source, env, executor=None, self=self) -> int: + def build_it(target, source, env, executor: Optional[ExecutorType] = None, self=self) -> int: self.build_it = 1 return 0 - def string_it(target, source, env, executor=None, self=self): + def string_it(target, source, env, executor: Optional[ExecutorType] = None, self=self): self.string_it = 1 return None diff --git a/SCons/Builder.py b/SCons/Builder.py index 16b0cc2..75cebc0 100644 --- a/SCons/Builder.py +++ b/SCons/Builder.py @@ -102,6 +102,7 @@ There are the following methods for internal use within this module: import os from collections import UserDict, UserList from contextlib import suppress +from typing import Optional import SCons.Action import SCons.Debug @@ -111,6 +112,7 @@ import SCons.Util import SCons.Warnings from SCons.Debug import logInstanceCreation from SCons.Errors import InternalError, UserError +from SCons.Util.sctyping import ExecutorType class _Null: pass @@ -589,7 +591,7 @@ class BuilderBase: # build this particular list of targets from this particular list of # sources. - executor = None + executor: Optional[ExecutorType] = None key = None if self.multi: diff --git a/SCons/BuilderTests.py b/SCons/BuilderTests.py index 344eef4..b66f524 100644 --- a/SCons/BuilderTests.py +++ b/SCons/BuilderTests.py @@ -45,6 +45,7 @@ import SCons.Environment import SCons.Errors import SCons.Subst import SCons.Util +from SCons.Util.sctyping import ExecutorType sys.stdout = io.StringIO() @@ -184,9 +185,9 @@ class MyNode_without_target_from_source: return env def get_build_env(self): return self.executor.get_build_env() - def set_executor(self, executor) -> None: + def set_executor(self, executor: ExecutorType) -> None: self.executor = executor - def get_executor(self, create: int=1): + def get_executor(self, create: int=1) -> ExecutorType: return self.executor class MyNode(MyNode_without_target_from_source): diff --git a/SCons/Environment.py b/SCons/Environment.py index 64d38b0..9d3fce6 100644 --- a/SCons/Environment.py +++ b/SCons/Environment.py @@ -76,6 +76,7 @@ from SCons.Util import ( to_String_for_subst, uniquer_hashables, ) +from SCons.Util.sctyping import ExecutorType class _Null: pass @@ -680,7 +681,7 @@ class SubstitutionEnvironment: def lvars(self): return {} - def subst(self, string, raw: int=0, target=None, source=None, conv=None, executor=None, overrides: Optional[dict] = None): + def subst(self, string, raw: int=0, target=None, source=None, conv=None, executor: Optional[ExecutorType] = None, overrides: Optional[dict] = None): """Recursively interpolates construction variables from the Environment into the specified string, returning the expanded result. Construction variables are specified by a $ prefix @@ -706,7 +707,7 @@ class SubstitutionEnvironment: nkw[k] = v return nkw - def subst_list(self, string, raw: int=0, target=None, source=None, conv=None, executor=None, overrides: Optional[dict] = None): + def subst_list(self, string, raw: int=0, target=None, source=None, conv=None, executor: Optional[ExecutorType] = None, overrides: Optional[dict] = None): """Calls through to SCons.Subst.scons_subst_list(). See the documentation for that function. diff --git a/SCons/Errors.py b/SCons/Errors.py index 012d1c6..e489c58 100644 --- a/SCons/Errors.py +++ b/SCons/Errors.py @@ -27,8 +27,10 @@ Used to handle internal and user errors in SCons. """ import shutil +from typing import Optional from SCons.Util.sctypes import to_String, is_String +from SCons.Util.sctyping import ExecutorType # Note that not all Errors are defined here, some are at the point of use @@ -73,7 +75,7 @@ class BuildError(Exception): def __init__(self, node=None, errstr: str="Unknown error", status: int=2, exitstatus: int=2, - filename=None, executor=None, action=None, command=None, + filename=None, executor: Optional[ExecutorType] = None, action=None, command=None, exc_info=(None, None, None)) -> None: # py3: errstr should be string and not bytes. diff --git a/SCons/Executor.py b/SCons/Executor.py index 02be2c9..1b054b4 100644 --- a/SCons/Executor.py +++ b/SCons/Executor.py @@ -24,6 +24,7 @@ """Execute actions with specific lists of target and source Nodes.""" import collections +from typing import Dict import SCons.Errors import SCons.Memoize @@ -31,6 +32,7 @@ import SCons.Util from SCons.compat import NoSlotsPyPy import SCons.Debug from SCons.Debug import logInstanceCreation +from SCons.Util.sctyping import ExecutorType class Batch: """Remembers exact association between targets @@ -548,12 +550,12 @@ class Executor(metaclass=NoSlotsPyPy): -_batch_executors = {} +_batch_executors: Dict[str, ExecutorType] = {} -def GetBatchExecutor(key): +def GetBatchExecutor(key: str) -> ExecutorType: return _batch_executors[key] -def AddBatchExecutor(key, executor) -> None: +def AddBatchExecutor(key: str, executor: ExecutorType) -> None: assert key not in _batch_executors _batch_executors[key] = executor diff --git a/SCons/Node/FSTests.py b/SCons/Node/FSTests.py index e2eb0af..17a1dc7 100644 --- a/SCons/Node/FSTests.py +++ b/SCons/Node/FSTests.py @@ -29,6 +29,7 @@ import time import unittest import shutil import stat +from typing import Optional from TestCmd import TestCmd, IS_WINDOWS @@ -37,6 +38,7 @@ import SCons.Node.FS import SCons.Util import SCons.Warnings import SCons.Environment +from SCons.Util.sctyping import ExecutorType built_it = None @@ -319,7 +321,7 @@ class VariantDirTestCase(unittest.TestCase): def __init__(self, dir_made) -> None: self.dir_made = dir_made - def __call__(self, target, source, env, executor=None) -> None: + def __call__(self, target, source, env, executor: Optional[ExecutorType] = None) -> None: if executor: target = executor.get_all_targets() source = executor.get_all_sources() @@ -2485,7 +2487,7 @@ class EntryTestCase(_tempdirTestCase): result += a return result - def signature(self, executor): + def signature(self, executor: ExecutorType): return self.val + 222 self.module = M(val) @@ -3576,7 +3578,7 @@ class prepareTestCase(unittest.TestCase): def __init__(self, dir_made) -> None: self.dir_made = dir_made - def __call__(self, target, source, env, executor=None) -> None: + def __call__(self, target, source, env, executor: Optional[ExecutorType] = None) -> None: if executor: target = executor.get_all_targets() source = executor.get_all_sources() diff --git a/SCons/Node/NodeTests.py b/SCons/Node/NodeTests.py index 42fae01..70c8551 100644 --- a/SCons/Node/NodeTests.py +++ b/SCons/Node/NodeTests.py @@ -26,11 +26,12 @@ import SCons.compat import collections import re import unittest +from typing import Optional import SCons.Errors import SCons.Node import SCons.Util - +from SCons.Util.sctyping import ExecutorType built_it = None @@ -63,7 +64,7 @@ class MyAction(MyActionBase): def __init__(self) -> None: self.order = 0 - def __call__(self, target, source, env, executor=None) -> int: + def __call__(self, target, source, env, executor: Optional[ExecutorType] = None) -> int: global built_it, built_target, built_source, built_args, built_order if executor: target = executor.get_all_targets() diff --git a/SCons/Node/__init__.py b/SCons/Node/__init__.py index 3da4faf..aff6133 100644 --- a/SCons/Node/__init__.py +++ b/SCons/Node/__init__.py @@ -43,6 +43,7 @@ be able to depend on any other type of "thing." import collections import copy from itertools import chain, zip_longest +from typing import Optional import SCons.Debug import SCons.Executor @@ -50,6 +51,7 @@ import SCons.Memoize from SCons.compat import NoSlotsPyPy from SCons.Debug import logInstanceCreation, Trace from SCons.Util import hash_signature, is_List, UniqueList, render_tree +from SCons.Util.sctyping import ExecutorType print_duplicate = 0 @@ -634,11 +636,11 @@ class Node(metaclass=NoSlotsPyPy): """Fetch the appropriate scanner path for this node.""" return self.get_executor().get_build_scanner_path(scanner) - def set_executor(self, executor) -> None: + def set_executor(self, executor: ExecutorType) -> None: """Set the action executor for this node.""" self.executor = executor - def get_executor(self, create: int=1): + def get_executor(self, create: int=1) -> ExecutorType: """Fetch the action executor for this node. Create one if there isn't already one, and requested to do so.""" try: @@ -649,7 +651,7 @@ class Node(metaclass=NoSlotsPyPy): try: act = self.builder.action except AttributeError: - executor = SCons.Executor.Null(targets=[self]) + executor = SCons.Executor.Null(targets=[self]) # type: ignore else: executor = SCons.Executor.Executor(act, self.env or self.builder.env, diff --git a/SCons/Tool/ninja/Methods.py b/SCons/Tool/ninja/Methods.py index 899f761..5c56e49 100644 --- a/SCons/Tool/ninja/Methods.py +++ b/SCons/Tool/ninja/Methods.py @@ -24,6 +24,7 @@ import os import shlex import textwrap +from typing import Optional import SCons from SCons.Subst import SUBST_CMD @@ -31,6 +32,7 @@ from SCons.Tool.ninja import NINJA_CUSTOM_HANDLERS, NINJA_RULES, NINJA_POOLS from SCons.Tool.ninja.Globals import __NINJA_RULE_MAPPING from SCons.Tool.ninja.Utils import get_targets_sources, get_dependencies, get_order_only, get_outputs, get_inputs, \ get_rule, get_path, generate_command, get_command_env, get_comstr +from SCons.Util.sctyping import ExecutorType def register_custom_handler(env, name, handler) -> None: @@ -76,7 +78,7 @@ def set_build_node_callback(env, node, callback) -> None: node.attributes.ninja_build_callback = callback -def get_generic_shell_command(env, node, action, targets, sources, executor=None): +def get_generic_shell_command(env, node, action, targets, sources, executor: Optional[ExecutorType] = None): return ( "GENERATED_CMD", { @@ -229,7 +231,7 @@ def gen_get_response_file_command(env, rule, tool, tool_is_dynamic: bool=False, if "$" in tool: tool_is_dynamic = True - def get_response_file_command(env, node, action, targets, sources, executor=None): + def get_response_file_command(env, node, action, targets, sources, executor: Optional[ExecutorType] = None): if hasattr(action, "process"): cmd_list, _, _ = action.process(targets, sources, env, executor=executor) cmd_list = [str(c).replace("$", "$$") for c in cmd_list[0]] diff --git a/SCons/Tool/ninja/Utils.py b/SCons/Tool/ninja/Utils.py index 470599f..126d5de 100644 --- a/SCons/Tool/ninja/Utils.py +++ b/SCons/Tool/ninja/Utils.py @@ -24,11 +24,13 @@ import os import shutil from os.path import join as joinpath from collections import OrderedDict +from typing import Optional import SCons from SCons.Action import get_default_ENV, _string_from_cmd_list from SCons.Script import AddOption from SCons.Util import is_List, flatten_sequence +from SCons.Util.sctyping import ExecutorType class NinjaExperimentalWarning(SCons.Warnings.WarningOnByDefault): pass @@ -344,7 +346,7 @@ def get_comstr(env, action, targets, sources): return action.genstring(targets, sources, env) -def generate_command(env, node, action, targets, sources, executor=None): +def generate_command(env, node, action, targets, sources, executor: Optional[ExecutorType] = None): # Actions like CommandAction have a method called process that is # used by SCons to generate the cmd_line they need to run. So # check if it's a thing like CommandAction and call it if we can. diff --git a/SCons/Util/sctyping.py b/SCons/Util/sctyping.py new file mode 100644 index 0000000..5da5eb9 --- /dev/null +++ b/SCons/Util/sctyping.py @@ -0,0 +1,33 @@ +# SPDX-License-Identifier: MIT +# +# Copyright The SCons Foundation + +"""Various SCons type aliases. + +For representing complex types across the entire repo without risking +circular dependencies, we take advantage of TYPE_CHECKING to import +modules in an tool-only environment. This allows us to introduce +hinting that resolves as expected in IDEs without clashing at runtime. + +For consistency, it's recommended to ALWAYS use these aliases in a +type-hinting context, even if the type is actually expected to be +resolved in a given file. +""" + +from typing import Union, TYPE_CHECKING + +if TYPE_CHECKING: + import SCons.Executor + + +# Because we don't have access to TypeAlias until 3.10, we have to utilize +# 'Union' for all aliases. As it expects at least two entries, anything that +# is only represented with a single type needs to list itself twice. +ExecutorType = Union["SCons.Executor.Executor", "SCons.Executor.Executor"] + + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: -- cgit v0.12