From c5735b35d93afed9962dd721f1bc2a7613174375 Mon Sep 17 00:00:00 2001 From: Thaddeus Crews Date: Wed, 13 Nov 2024 13:10:12 -0600 Subject: [PATCH] Integrate `from __future__ import annotations` --- CHANGES.txt | 6 +++ RELEASE.txt | 6 +++ SCons/Action.py | 86 +++++++++++++++--------------- SCons/ActionTests.py | 12 +++-- SCons/Builder.py | 6 +-- SCons/BuilderTests.py | 11 ++-- SCons/Defaults.py | 10 ++-- SCons/Environment.py | 20 ++++--- SCons/Errors.py | 10 ++-- SCons/Executor.py | 10 ++-- SCons/Node/FS.py | 7 +-- SCons/Node/FSTests.py | 14 +++-- SCons/Node/NodeTests.py | 10 ++-- SCons/Node/__init__.py | 8 +-- SCons/SConf.py | 5 +- SCons/Script/Main.py | 11 ++-- SCons/Script/SConsOptions.py | 5 +- SCons/Script/SConscript.py | 7 +-- SCons/Subst.py | 7 +-- SCons/Tool/FortranCommon.py | 11 ++-- SCons/Tool/JavaCommon.py | 7 +-- SCons/Tool/__init__.py | 5 +- SCons/Tool/jar.py | 5 +- SCons/Tool/lex.py | 7 +-- SCons/Tool/ninja/Methods.py | 12 +++-- SCons/Tool/ninja/Utils.py | 11 ++-- SCons/Tool/yacc.py | 11 ++-- SCons/Util/UtilTests.py | 19 +++---- SCons/Util/__init__.py | 22 ++++---- SCons/Util/envs.py | 18 ++++--- SCons/Util/filelock.py | 11 ++-- SCons/Util/hashes.py | 3 +- SCons/Util/sctypes.py | 24 +++++---- SCons/Util/sctyping.py | 33 ------------ SCons/Variables/BoolVariable.py | 8 +-- SCons/Variables/EnumVariable.py | 10 ++-- SCons/Variables/ListVariable.py | 16 +++--- SCons/Variables/PackageVariable.py | 10 ++-- SCons/Variables/PathVariable.py | 7 +-- SCons/Variables/__init__.py | 26 ++++----- SCons/Warnings.py | 6 ++- pyproject.toml | 9 ++++ runtest.py | 10 ++-- test/Variables/PackageVariable.py | 5 +- testing/framework/TestCmd.py | 24 +++++---- testing/framework/TestCommon.py | 44 +++++++-------- testing/framework/TestSCons.py | 7 +-- 47 files changed, 351 insertions(+), 281 deletions(-) delete mode 100644 SCons/Util/sctyping.py diff --git a/CHANGES.txt b/CHANGES.txt index b75f8560a7..4461f1bdf3 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -76,6 +76,12 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER - Change the attempted conversion of a define expansion from using int() to a constant expression evaluation. + From Thaddeus Crews: + - Ruff: Linter includes new rules - `FA`, `UP006`, `UP007`, and `UP037` - to + detect and upgrade legacy type-hint syntax. + - Removed "SCons.Util.sctyping.py", as the functionality can now be substituted + via top-level `from __future__ import annotations`. + From Alex James: - On Darwin, PermissionErrors are now handled while trying to access /etc/paths.d. This may occur if SCons is invoked in a sandboxed diff --git a/RELEASE.txt b/RELEASE.txt index 2cb6638066..f2b2b09d5f 100644 --- a/RELEASE.txt +++ b/RELEASE.txt @@ -173,6 +173,12 @@ DEVELOPMENT - List visible changes in the way SCons is developed +- Ruff: Linter includes new rules - `FA`, `UP006`, `UP007`, and `UP037` - to + detect and upgrade legacy type-hint syntax. + +- Removed "SCons.Util.sctyping.py", as the functionality can now be substituted + via top-level `from __future__ import annotations`. + Thanks to the following contributors listed below for their contributions to this release. ========================================================================================== .. code-block:: text diff --git a/SCons/Action.py b/SCons/Action.py index 567f66cef4..6022b19b38 100644 --- a/SCons/Action.py +++ b/SCons/Action.py @@ -100,6 +100,8 @@ """ +from __future__ import annotations + import inspect import os import pickle @@ -109,7 +111,7 @@ from abc import ABC, abstractmethod from collections import OrderedDict from subprocess import DEVNULL, PIPE -from typing import List, Optional, Tuple +from typing import TYPE_CHECKING import SCons.Debug import SCons.Errors @@ -120,7 +122,9 @@ from SCons.Debug import logInstanceCreation from SCons.Subst import SUBST_CMD, SUBST_RAW, SUBST_SIG from SCons.Util import is_String, is_List -from SCons.Util.sctyping import ExecutorType + +if TYPE_CHECKING: + from SCons.Executor import Executor class _null: pass @@ -481,9 +485,7 @@ def _do_create_action(act, kw): return None -# TODO: from __future__ import annotations once we get to Python 3.7 base, -# to avoid quoting the defined-later classname -def _do_create_list_action(act, kw) -> "ListAction": +def _do_create_list_action(act, kw) -> ListAction: """A factory for list actions. Convert the input list *act* into Actions and then wrap them in a @@ -529,7 +531,7 @@ def __call__( show=_null, execute=_null, chdir=_null, - executor: Optional[ExecutorType] = None, + executor: Executor | None = None, ): raise NotImplementedError @@ -541,15 +543,15 @@ def no_batch_key(self, env, target, source): batch_key = no_batch_key - def genstring(self, target, source, env, executor: Optional[ExecutorType] = None) -> str: + def genstring(self, target, source, env, executor: Executor | None = None) -> str: return str(self) @abstractmethod - def get_presig(self, target, source, env, executor: Optional[ExecutorType] = None): + def get_presig(self, target, source, env, executor: Executor | None = None): raise NotImplementedError @abstractmethod - def get_implicit_deps(self, target, source, env, executor: Optional[ExecutorType] = None): + def get_implicit_deps(self, target, source, env, executor: Executor | None = None): raise NotImplementedError def get_contents(self, target, source, env): @@ -601,10 +603,10 @@ def presub_lines(self, env): self.presub_env = None # don't need this any more return lines - def get_varlist(self, target, source, env, executor: Optional[ExecutorType] = None): + def get_varlist(self, target, source, env, executor: Executor | None = None): return self.varlist - def get_targets(self, env, executor: Optional[ExecutorType]): + def get_targets(self, env, executor: Executor | None): """ Returns the type of targets ($TARGETS, $CHANGED_TARGETS) used by this action. @@ -658,7 +660,7 @@ def __call__(self, target, source, env, show=_null, execute=_null, chdir=_null, - executor: Optional[ExecutorType] = None): + executor: Executor | None = None): if not is_List(target): target = [target] if not is_List(source): @@ -742,10 +744,10 @@ def __call__(self, target, source, env, # 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: Optional[ExecutorType] = None): + def get_presig(self, target, source, env, executor: Executor | None = None): raise NotImplementedError - def get_implicit_deps(self, target, source, env, executor: Optional[ExecutorType] = None): + def get_implicit_deps(self, target, source, env, executor: Executor | None = None): raise NotImplementedError @@ -1010,7 +1012,7 @@ def __str__(self) -> str: return str(self.cmd_list) - def process(self, target, source, env, executor=None, overrides: Optional[dict] = None) -> Tuple[List, bool, bool]: + def process(self, target, source, env, executor: Executor | None = None, overrides: dict | None = None) -> tuple[list, bool, bool]: if executor: result = env.subst_list(self.cmd_list, SUBST_CMD, executor=executor, overrides=overrides) else: @@ -1031,7 +1033,7 @@ def process(self, target, source, env, executor=None, overrides: Optional[dict] pass return result, ignore, silent - def strfunction(self, target, source, env, executor: Optional[ExecutorType] = None, overrides: Optional[dict] = None) -> str: + def strfunction(self, target, source, env, executor: Executor | None = None, overrides: dict | None = None) -> str: if self.cmdstr is None: return None if self.cmdstr is not _null: @@ -1046,7 +1048,7 @@ def strfunction(self, target, source, env, executor: Optional[ExecutorType] = No return '' return _string_from_cmd_list(cmd_list[0]) - def execute(self, target, source, env, executor: Optional[ExecutorType] = None): + def execute(self, target, source, env, executor: Executor | None = None): """Execute a command action. This will handle lists of commands as well as individual commands, @@ -1108,7 +1110,7 @@ def execute(self, target, source, env, executor: Optional[ExecutorType] = None): command=cmd_line) return 0 - def get_presig(self, target, source, env, executor: Optional[ExecutorType] = None): + def get_presig(self, target, source, env, executor: Executor | None = None): """Return the signature contents of this action's command line. This strips $(-$) and everything in between the string, @@ -1123,7 +1125,7 @@ def get_presig(self, target, source, env, executor: Optional[ExecutorType] = Non 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: Optional[ExecutorType] = None): + def get_implicit_deps(self, target, source, env, executor: Executor | None = 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 @@ def get_implicit_deps(self, target, source, env, executor: Optional[ExecutorType # lightweight dependency scanning. return self._get_implicit_deps_lightweight(target, source, env, executor) - def _get_implicit_deps_lightweight(self, target, source, env, executor: Optional[ExecutorType]): + def _get_implicit_deps_lightweight(self, target, source, env, executor: Executor | None): """ Lightweight dependency scanning involves only scanning the first entry in an action string, even if it contains &&. @@ -1166,7 +1168,7 @@ def _get_implicit_deps_lightweight(self, target, source, env, executor: Optional res.append(env.fs.File(d)) return res - def _get_implicit_deps_heavyweight(self, target, source, env, executor: Optional[ExecutorType], + def _get_implicit_deps_heavyweight(self, target, source, env, executor: Executor | None, icd_int): """ Heavyweight dependency scanning involves scanning more than just the @@ -1234,7 +1236,7 @@ def __init__(self, generator, kw) -> None: self.varlist = kw.get('varlist', ()) self.targets = kw.get('targets', '$TARGETS') - def _generate(self, target, source, env, for_signature, executor: Optional[ExecutorType] = None): + def _generate(self, target, source, env, for_signature, executor: Executor | None = None): # ensure that target is a list, to make it easier to write # generator functions: if not is_List(target): @@ -1265,11 +1267,11 @@ def __str__(self) -> str: 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: Optional[ExecutorType] = None) -> str: + def genstring(self, target, source, env, executor: Executor | None = 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: Optional[ExecutorType] = None): + show=_null, execute=_null, chdir=_null, executor: Executor | None = None): act = self._generate(target, source, env, 0, executor) if act is None: raise SCons.Errors.UserError( @@ -1281,7 +1283,7 @@ def __call__(self, target, source, env, exitstatfunc=_null, presub=_null, target, source, env, exitstatfunc, presub, show, execute, chdir, executor ) - def get_presig(self, target, source, env, executor: Optional[ExecutorType] = None): + def get_presig(self, target, source, env, executor: Executor | None = None): """Return the signature contents of this action's command line. This strips $(-$) and everything in between the string, @@ -1289,13 +1291,13 @@ def get_presig(self, target, source, env, executor: Optional[ExecutorType] = Non """ return self._generate(target, source, env, 1, executor).get_presig(target, source, env) - def get_implicit_deps(self, target, source, env, executor: Optional[ExecutorType] = None): + def get_implicit_deps(self, target, source, env, executor: Executor | None = None): return self._generate(target, source, env, 1, executor).get_implicit_deps(target, source, env) - def get_varlist(self, target, source, env, executor: Optional[ExecutorType] = None): + def get_varlist(self, target, source, env, executor: Executor | None = None): return self._generate(target, source, env, 1, executor).get_varlist(target, source, env, executor) - def get_targets(self, env, executor: Optional[ExecutorType]): + def get_targets(self, env, executor: Executor | None): return self._generate(None, None, env, 1, executor).get_targets(env, executor) @@ -1341,22 +1343,22 @@ def _generate_cache(self, env): 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: Optional[ExecutorType] = None): + def _generate(self, target, source, env, for_signature, executor: Executor | None = 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: Optional[ExecutorType] = None): + def get_presig(self, target, source, env, executor: Executor | None = None): c = self.get_parent_class(env) return c.get_presig(self, target, source, env) - def get_implicit_deps(self, target, source, env, executor: Optional[ExecutorType] = None): + def get_implicit_deps(self, target, source, env, executor: Executor | None = None): c = self.get_parent_class(env) return c.get_implicit_deps(self, target, source, env) - def get_varlist(self, target, source, env, executor: Optional[ExecutorType] = None): + def get_varlist(self, target, source, env, executor: Executor | None = None): c = self.get_parent_class(env) return c.get_varlist(self, target, source, env, executor) @@ -1389,7 +1391,7 @@ def function_name(self): except AttributeError: return "unknown_python_function" - def strfunction(self, target, source, env, executor: Optional[ExecutorType] = None): + def strfunction(self, target, source, env, executor: Executor | None = None): if self.cmdstr is None: return None if self.cmdstr is not _null: @@ -1430,7 +1432,7 @@ def __str__(self) -> str: return str(self.execfunction) return "%s(target, source, env)" % name - def execute(self, target, source, env, executor: Optional[ExecutorType] = None): + def execute(self, target, source, env, executor: Executor | None = None): exc_info = (None,None,None) try: if executor: @@ -1461,14 +1463,14 @@ def execute(self, target, source, env, executor: Optional[ExecutorType] = None): # more information about this issue. del exc_info - def get_presig(self, target, source, env, executor: Optional[ExecutorType] = None): + def get_presig(self, target, source, env, executor: Executor | None = 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: Optional[ExecutorType] = None): + def get_implicit_deps(self, target, source, env, executor: Executor | None = None): return [] class ListAction(ActionBase): @@ -1485,7 +1487,7 @@ def list_of_actions(x): self.varlist = () self.targets = '$TARGETS' - def genstring(self, target, source, env, executor: Optional[ExecutorType] = None) -> str: + def genstring(self, target, source, env, executor: Executor | None = None) -> str: return '\n'.join([a.genstring(target, source, env) for a in self.list]) def __str__(self) -> str: @@ -1495,7 +1497,7 @@ def presub_lines(self, env): return SCons.Util.flatten_sequence( [a.presub_lines(env) for a in self.list]) - def get_presig(self, target, source, env, executor: Optional[ExecutorType] = None): + def get_presig(self, target, source, env, executor: Executor | None = None): """Return the signature contents of this action list. Simple concatenation of the signatures of the elements. @@ -1503,7 +1505,7 @@ def get_presig(self, target, source, env, executor: Optional[ExecutorType] = Non 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: Optional[ExecutorType] = None): + show=_null, execute=_null, chdir=_null, executor: Executor | None = None): if executor: target = executor.get_all_targets() source = executor.get_all_sources() @@ -1514,13 +1516,13 @@ def __call__(self, target, source, env, exitstatfunc=_null, presub=_null, return stat return 0 - def get_implicit_deps(self, target, source, env, executor: Optional[ExecutorType] = None): + def get_implicit_deps(self, target, source, env, executor: Executor | None = 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: Optional[ExecutorType] = None): + def get_varlist(self, target, source, env, executor: Executor | None = None): result = OrderedDict() for act in self.list: for var in act.get_varlist(target, source, env, executor): @@ -1586,7 +1588,7 @@ def subst_kw(self, target, source, env): kw[key] = self.subst(self.kw[key], target, source, env) return kw - def __call__(self, target, source, env, executor: Optional[ExecutorType] = None): + def __call__(self, target, source, env, executor: Executor | None = 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 39798809e9..497de1869d 100644 --- a/SCons/ActionTests.py +++ b/SCons/ActionTests.py @@ -27,6 +27,8 @@ # contents, so try to minimize changes by defining them here, before we # even import anything. +from __future__ import annotations + def GlobalFunc() -> None: pass @@ -43,13 +45,15 @@ def __call__(self) -> None: import unittest from unittest import mock from subprocess import PIPE -from typing import Optional +from typing import TYPE_CHECKING import SCons.Action import SCons.Environment import SCons.Errors from SCons.Action import scons_subproc_run -from SCons.Util.sctyping import ExecutorType + +if TYPE_CHECKING: + from SCons.Executor import Executor import TestCmd @@ -1701,11 +1705,11 @@ def __call__(self, target, source, env) -> int: c = test.read(outfile, 'r') assert c == "class1b\n", c - def build_it(target, source, env, executor: Optional[ExecutorType] = None, self=self) -> int: + def build_it(target, source, env, executor: Executor | None = None, self=self) -> int: self.build_it = 1 return 0 - def string_it(target, source, env, executor: Optional[ExecutorType] = None, self=self): + def string_it(target, source, env, executor: Executor | None = None, self=self): self.string_it = 1 return None diff --git a/SCons/Builder.py b/SCons/Builder.py index 3efcc8271d..1ed1543c8a 100644 --- a/SCons/Builder.py +++ b/SCons/Builder.py @@ -99,10 +99,11 @@ """ +from __future__ import annotations + import os from collections import UserDict, UserList from contextlib import suppress -from typing import Optional import SCons.Action import SCons.Debug @@ -112,7 +113,6 @@ import SCons.Warnings from SCons.Debug import logInstanceCreation from SCons.Errors import InternalError, UserError -from SCons.Util.sctyping import ExecutorType class _Null: pass @@ -591,7 +591,7 @@ def _execute(self, env, target, source, overwarn={}, executor_kw={}): # build this particular list of targets from this particular list of # sources. - executor: Optional[ExecutorType] = None + executor: Executor | None = None key = None if self.multi: diff --git a/SCons/BuilderTests.py b/SCons/BuilderTests.py index b66f52439e..adfa648280 100644 --- a/SCons/BuilderTests.py +++ b/SCons/BuilderTests.py @@ -21,6 +21,8 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +from __future__ import annotations + import SCons.compat # Define a null function for use as a builder action. @@ -31,6 +33,7 @@ def Func() -> None: pass from collections import UserList +from typing import TYPE_CHECKING import io import os.path import re @@ -45,7 +48,9 @@ def Func() -> None: import SCons.Errors import SCons.Subst import SCons.Util -from SCons.Util.sctyping import ExecutorType + +if TYPE_CHECKING: + from SCons.Executor import Executor sys.stdout = io.StringIO() @@ -185,9 +190,9 @@ def generate_build_env(self, env): return env def get_build_env(self): return self.executor.get_build_env() - def set_executor(self, executor: ExecutorType) -> None: + def set_executor(self, executor: Executor) -> None: self.executor = executor - def get_executor(self, create: int=1) -> ExecutorType: + def get_executor(self, create: int=1) -> Executor: return self.executor class MyNode(MyNode_without_target_from_source): diff --git a/SCons/Defaults.py b/SCons/Defaults.py index a5d49fc76f..d971d060f6 100644 --- a/SCons/Defaults.py +++ b/SCons/Defaults.py @@ -31,12 +31,14 @@ from distutils.msvccompiler. """ +from __future__ import annotations + import os import shutil import stat import sys import time -from typing import List, Callable +from typing import Callable import SCons.Action import SCons.Builder @@ -467,8 +469,8 @@ def _stripixes( prefix: str, items, suffix: str, - stripprefixes: List[str], - stripsuffixes: List[str], + stripprefixes: list[str], + stripsuffixes: list[str], env, literal_prefix: str = "", c: Callable[[list], list] = None, @@ -547,7 +549,7 @@ def _stripixes( return c(prefix, stripped, suffix, env) -def processDefines(defs) -> List[str]: +def processDefines(defs) -> list[str]: """Return list of strings for preprocessor defines from *defs*. Resolves the different forms ``CPPDEFINES`` can be assembled in: diff --git a/SCons/Environment.py b/SCons/Environment.py index ad5045633b..9e28fbdf5c 100644 --- a/SCons/Environment.py +++ b/SCons/Environment.py @@ -30,6 +30,8 @@ are construction variables used to initialize the Environment. """ +from __future__ import annotations + import copy import os import sys @@ -37,7 +39,7 @@ import shlex from collections import UserDict, UserList, deque from subprocess import PIPE, DEVNULL -from typing import Callable, Collection, Optional, Sequence, Union +from typing import TYPE_CHECKING, Callable, Collection, Sequence import SCons.Action import SCons.Builder @@ -76,7 +78,9 @@ to_String_for_subst, uniquer_hashables, ) -from SCons.Util.sctyping import ExecutorType + +if TYPE_CHECKING: + from SCons.Executor import Executor class _Null: pass @@ -698,7 +702,7 @@ def gvars(self): def lvars(self): return {} - def subst(self, string, raw: int=0, target=None, source=None, conv=None, executor: Optional[ExecutorType] = None, overrides: Optional[dict] = None): + def subst(self, string, raw: int=0, target=None, source=None, conv=None, executor: Executor | None = None, overrides: dict | None = None): """Recursively interpolates construction variables from the Environment into the specified string, returning the expanded result. Construction variables are specified by a $ prefix @@ -724,7 +728,7 @@ def subst_kw(self, kw, raw: int=0, target=None, source=None): nkw[k] = v return nkw - def subst_list(self, string, raw: int=0, target=None, source=None, conv=None, executor: Optional[ExecutorType] = None, overrides: Optional[dict] = None): + def subst_list(self, string, raw: int=0, target=None, source=None, conv=None, executor: Executor | None = None, overrides: dict | None = None): """Calls through to SCons.Subst.scons_subst_list(). See the documentation for that function. @@ -901,7 +905,7 @@ def ParseFlags(self, *flags) -> dict: 'RPATH' : [], } - def do_parse(arg: Union[str, Sequence]) -> None: + def do_parse(arg: str | Sequence) -> None: if not arg: return @@ -1791,7 +1795,7 @@ def default(self, obj): raise ValueError("Unsupported serialization format: %s." % fmt) - def FindIxes(self, paths: Sequence[str], prefix: str, suffix: str) -> Optional[str]: + def FindIxes(self, paths: Sequence[str], prefix: str, suffix: str) -> str | None: """Search *paths* for a path that has *prefix* and *suffix*. Returns on first match. @@ -2072,7 +2076,7 @@ def _find_toolpath_dir(self, tp): return self.fs.Dir(self.subst(tp)).srcnode().get_abspath() def Tool( - self, tool: Union[str, Callable], toolpath: Optional[Collection[str]] = None, **kwargs + self, tool: str | Callable, toolpath: Collection[str] | None = None, **kwargs ) -> Callable: """Find and run tool module *tool*. @@ -2608,7 +2612,7 @@ class OverrideEnvironment(Base): ``OverrideEnvironment``. """ - def __init__(self, subject, overrides: Optional[dict] = None) -> None: + def __init__(self, subject, overrides: dict | None = None) -> None: if SCons.Debug.track_instances: logInstanceCreation(self, 'Environment.OverrideEnvironment') overrides = {} if overrides is None else overrides # set these directly via __dict__ to avoid trapping by __setattr__ diff --git a/SCons/Errors.py b/SCons/Errors.py index a2efc97088..af77971032 100644 --- a/SCons/Errors.py +++ b/SCons/Errors.py @@ -26,11 +26,15 @@ Used to handle internal and user errors in SCons. """ +from __future__ import annotations + import shutil -from typing import Optional +from typing import TYPE_CHECKING from SCons.Util.sctypes import to_String, is_String -from SCons.Util.sctyping import ExecutorType + +if TYPE_CHECKING: + from SCons.Executor import Executor # Note that not all Errors are defined here, some are at the point of use @@ -75,7 +79,7 @@ class BuildError(Exception): def __init__(self, node=None, errstr: str="Unknown error", status: int=2, exitstatus: int=2, - filename=None, executor: Optional[ExecutorType] = None, action=None, command=None, + filename=None, executor: Executor | None = 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 1b054b4ba8..53eb5cbb2f 100644 --- a/SCons/Executor.py +++ b/SCons/Executor.py @@ -23,8 +23,9 @@ """Execute actions with specific lists of target and source Nodes.""" +from __future__ import annotations + import collections -from typing import Dict import SCons.Errors import SCons.Memoize @@ -32,7 +33,6 @@ 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 @@ -550,12 +550,12 @@ def get_implicit_deps(self): -_batch_executors: Dict[str, ExecutorType] = {} +_batch_executors: dict[str, Executor] = {} -def GetBatchExecutor(key: str) -> ExecutorType: +def GetBatchExecutor(key: str) -> Executor: return _batch_executors[key] -def AddBatchExecutor(key: str, executor: ExecutorType) -> None: +def AddBatchExecutor(key: str, executor: Executor) -> None: assert key not in _batch_executors _batch_executors[key] = executor diff --git a/SCons/Node/FS.py b/SCons/Node/FS.py index 3cd7720c0f..bdecffcfbd 100644 --- a/SCons/Node/FS.py +++ b/SCons/Node/FS.py @@ -30,6 +30,8 @@ that can be used by scripts or modules looking for the canonical default. """ +from __future__ import annotations + import codecs import fnmatch import importlib.util @@ -40,7 +42,6 @@ import sys import time from itertools import chain -from typing import Optional import SCons.Action import SCons.Debug @@ -1492,7 +1493,7 @@ def Repository(self, *dirs) -> None: d = self.Dir(d) self.Top.addRepository(d) - def PyPackageDir(self, modulename) -> Optional[Dir]: + def PyPackageDir(self, modulename) -> Dir | None: r"""Locate the directory of Python module *modulename*. For example 'SCons' might resolve to @@ -3190,7 +3191,7 @@ def exists(self): # SIGNATURE SUBSYSTEM # - def get_max_drift_csig(self) -> Optional[str]: + def get_max_drift_csig(self) -> str | None: """ Returns the content signature currently stored for this node if it's been unmodified longer than the max_drift value, or the diff --git a/SCons/Node/FSTests.py b/SCons/Node/FSTests.py index 83ceef28c7..9ae8c03e10 100644 --- a/SCons/Node/FSTests.py +++ b/SCons/Node/FSTests.py @@ -21,6 +21,8 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +from __future__ import annotations + import SCons.compat import os import os.path @@ -29,7 +31,7 @@ import unittest import shutil import stat -from typing import Optional +from typing import TYPE_CHECKING from TestCmd import TestCmd, IS_WINDOWS, IS_ROOT @@ -38,7 +40,9 @@ import SCons.Util import SCons.Warnings import SCons.Environment -from SCons.Util.sctyping import ExecutorType + +if TYPE_CHECKING: + from SCons.Executor import Executor built_it = None @@ -320,7 +324,7 @@ class MkdirAction(Action): def __init__(self, dir_made) -> None: self.dir_made = dir_made - def __call__(self, target, source, env, executor: Optional[ExecutorType] = None) -> None: + def __call__(self, target, source, env, executor: Executor | None = None) -> None: if executor: target = executor.get_all_targets() source = executor.get_all_sources() @@ -2491,7 +2495,7 @@ def collect(self, args): result += a return result - def signature(self, executor: ExecutorType): + def signature(self, executor: Executor): return self.val + 222 self.module = M(val) @@ -3582,7 +3586,7 @@ class MkdirAction(Action): def __init__(self, dir_made) -> None: self.dir_made = dir_made - def __call__(self, target, source, env, executor: Optional[ExecutorType] = None) -> None: + def __call__(self, target, source, env, executor: Executor | None = 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 70c8551b13..6c7437d600 100644 --- a/SCons/Node/NodeTests.py +++ b/SCons/Node/NodeTests.py @@ -21,17 +21,21 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +from __future__ import annotations + import SCons.compat import collections import re import unittest -from typing import Optional +from typing import TYPE_CHECKING import SCons.Errors import SCons.Node import SCons.Util -from SCons.Util.sctyping import ExecutorType + +if TYPE_CHECKING: + from SCons.Executor import Executor built_it = None @@ -64,7 +68,7 @@ class MyAction(MyActionBase): def __init__(self) -> None: self.order = 0 - def __call__(self, target, source, env, executor: Optional[ExecutorType] = None) -> int: + def __call__(self, target, source, env, executor: Executor | None = 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 00bf4ac3b1..267a40b9bc 100644 --- a/SCons/Node/__init__.py +++ b/SCons/Node/__init__.py @@ -40,10 +40,11 @@ """ +from __future__ import annotations + import collections import copy from itertools import chain, zip_longest -from typing import Optional import SCons.Debug import SCons.Executor @@ -51,7 +52,6 @@ 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 @@ -636,11 +636,11 @@ def get_build_scanner_path(self, scanner): """Fetch the appropriate scanner path for this node.""" return self.get_executor().get_build_scanner_path(scanner) - def set_executor(self, executor: ExecutorType) -> None: + def set_executor(self, executor: Executor) -> None: """Set the action executor for this node.""" self.executor = executor - def get_executor(self, create: int=1) -> ExecutorType: + def get_executor(self, create: int=1) -> Executor: """Fetch the action executor for this node. Create one if there isn't already one, and requested to do so.""" try: diff --git a/SCons/SConf.py b/SCons/SConf.py index d2e09be34e..c22355665c 100644 --- a/SCons/SConf.py +++ b/SCons/SConf.py @@ -31,6 +31,8 @@ libraries are installed, if some command line options are supported etc. """ +from __future__ import annotations + import SCons.compat import atexit @@ -39,7 +41,6 @@ import re import sys import traceback -from typing import Tuple import SCons.Action import SCons.Builder @@ -265,7 +266,7 @@ def failed(self): sys.excepthook(*self.exc_info()) return SCons.Taskmaster.Task.failed(self) - def collect_node_states(self) -> Tuple[bool, bool, bool]: + def collect_node_states(self) -> tuple[bool, bool, bool]: # returns (is_up_to_date, cached_error, cachable) # where is_up_to_date is True if the node(s) are up_to_date # cached_error is True if the node(s) are up_to_date, but the diff --git a/SCons/Script/Main.py b/SCons/Script/Main.py index 04b420a3bf..7f04d00243 100644 --- a/SCons/Script/Main.py +++ b/SCons/Script/Main.py @@ -31,6 +31,8 @@ it goes here. """ +from __future__ import annotations + import SCons.compat import importlib.util @@ -42,7 +44,6 @@ import traceback import platform import threading -from typing import Optional, List, TYPE_CHECKING import SCons.CacheDir import SCons.Debug @@ -552,7 +553,7 @@ def SetOption(name: str, value): """Set the value of an option - Public API.""" return OptionsParser.values.set_option(name, value) -def DebugOptions(json: Optional[str] = None) -> None: +def DebugOptions(json: str | None = None) -> None: """Specify options to SCons debug logic - Public API. Currently only *json* is supported, which changes the JSON file @@ -681,8 +682,8 @@ def _scons_internal_error() -> None: sys.exit(2) def _SConstruct_exists( - dirname: str, repositories: List[str], filelist: List[str] -) -> Optional[str]: + dirname: str, repositories: list[str], filelist: list[str] +) -> str | None: """Check that an SConstruct file exists in a directory. Arguments: @@ -1424,7 +1425,7 @@ def _exec_main(parser, values) -> None: class SConsPdb(pdb.Pdb): """Specialization of Pdb to help find SConscript files.""" - def lookupmodule(self, filename: str) -> Optional[str]: + def lookupmodule(self, filename: str) -> str | None: """Helper function for break/clear parsing -- SCons version. Translates (possibly incomplete) file or module name diff --git a/SCons/Script/SConsOptions.py b/SCons/Script/SConsOptions.py index 08531814f5..ef27b70669 100644 --- a/SCons/Script/SConsOptions.py +++ b/SCons/Script/SConsOptions.py @@ -21,13 +21,14 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +from __future__ import annotations + import gettext import optparse import re import shutil import sys import textwrap -from typing import Optional import SCons.Node.FS import SCons.Platform.virtualenv @@ -318,7 +319,7 @@ class SConsBadOptionError(optparse.BadOptionError): """ # TODO why is 'parser' needed? Not called in current code base. - def __init__(self, opt_str: str, parser: Optional["SConsOptionParser"] = None) -> None: + def __init__(self, opt_str: str, parser: SConsOptionParser | None = None) -> None: self.opt_str = opt_str self.parser = parser diff --git a/SCons/Script/SConscript.py b/SCons/Script/SConscript.py index a2ef3b9d57..7cc9bea5af 100644 --- a/SCons/Script/SConscript.py +++ b/SCons/Script/SConscript.py @@ -23,6 +23,8 @@ """This module defines the Python API provided to SConscript files.""" +from __future__ import annotations + import SCons import SCons.Action import SCons.Builder @@ -45,7 +47,6 @@ import sys import traceback import time -from typing import Tuple class SConscriptReturn(Exception): pass @@ -386,7 +387,7 @@ class SConsEnvironment(SCons.Environment.Base): # Private methods of an SConsEnvironment. # @staticmethod - def _get_major_minor_revision(version_string: str) -> Tuple[int, int, int]: + def _get_major_minor_revision(version_string: str) -> tuple[int, int, int]: """Split a version string into major, minor and (optionally) revision parts. @@ -485,7 +486,7 @@ def Default(self, *targets) -> None: SCons.Script._Set_Default_Targets(self, targets) @staticmethod - def GetSConsVersion() -> Tuple[int, int, int]: + def GetSConsVersion() -> tuple[int, int, int]: """Return the current SCons version. .. versionadded:: 4.8.0 diff --git a/SCons/Subst.py b/SCons/Subst.py index b04ebe50cd..4d6b249c6c 100644 --- a/SCons/Subst.py +++ b/SCons/Subst.py @@ -23,10 +23,11 @@ """SCons string substitution.""" +from __future__ import annotations + import collections import re from inspect import signature, Parameter -from typing import Optional import SCons.Errors from SCons.Util import is_String, is_Sequence @@ -807,7 +808,7 @@ def _remove_list(list): _space_sep = re.compile(r'[\t ]+(?![^{]*})') -def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None, overrides: Optional[dict] = None): +def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None, overrides: dict | None = None): """Expand a string or list containing construction variable substitutions. @@ -889,7 +890,7 @@ def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={ return result -def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None, overrides: Optional[dict] = None): +def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None, overrides: dict | None = None): """Substitute construction variables in a string (or list or other object) and separate the arguments into a command list. diff --git a/SCons/Tool/FortranCommon.py b/SCons/Tool/FortranCommon.py index f221d15ec7..dc9dcddf6e 100644 --- a/SCons/Tool/FortranCommon.py +++ b/SCons/Tool/FortranCommon.py @@ -23,9 +23,10 @@ """Routines for setting up Fortran, common to all dialects.""" +from __future__ import annotations + import re import os.path -from typing import Tuple, List import SCons.Scanner.Fortran import SCons.Tool @@ -96,7 +97,7 @@ def ShFortranEmitter(target, source, env) -> Tuple: return SharedObjectEmitter(target, source, env) -def ComputeFortranSuffixes(suffixes: List[str], ppsuffixes: List[str]) -> None: +def ComputeFortranSuffixes(suffixes: list[str], ppsuffixes: list[str]) -> None: """Update the suffix lists to reflect the platform requirements. If upper-cased suffixes can be distinguished from lower, those are @@ -119,7 +120,7 @@ def ComputeFortranSuffixes(suffixes: List[str], ppsuffixes: List[str]) -> None: def CreateDialectActions( dialect: str, -) -> Tuple[CommandAction, CommandAction, CommandAction, CommandAction]: +) -> tuple[CommandAction, CommandAction, CommandAction, CommandAction]: """Create dialect specific actions.""" CompAction = Action(f'${dialect}COM ', cmdstr=f'${dialect}COMSTR') CompPPAction = Action(f'${dialect}PPCOM ', cmdstr=f'${dialect}PPCOMSTR') @@ -131,8 +132,8 @@ def CreateDialectActions( def DialectAddToEnv( env, dialect: str, - suffixes: List[str], - ppsuffixes: List[str], + suffixes: list[str], + ppsuffixes: list[str], support_mods: bool = False, ) -> None: """Add dialect specific construction variables. diff --git a/SCons/Tool/JavaCommon.py b/SCons/Tool/JavaCommon.py index c7e62b88ce..0bcb0eaa5e 100644 --- a/SCons/Tool/JavaCommon.py +++ b/SCons/Tool/JavaCommon.py @@ -23,11 +23,12 @@ """Common routines for processing Java. """ +from __future__ import annotations + import os import re import glob from pathlib import Path -from typing import List import SCons.Util @@ -491,7 +492,7 @@ def parse_java_file(fn, version=default_java_version): return os.path.split(fn) -def get_java_install_dirs(platform, version=None) -> List[str]: +def get_java_install_dirs(platform, version=None) -> list[str]: """ Find possible java jdk installation directories. Returns a list for use as `default_paths` when looking up actual @@ -540,7 +541,7 @@ def win32getvnum(java): return [] -def get_java_include_paths(env, javac, version) -> List[str]: +def get_java_include_paths(env, javac, version) -> list[str]: """Find java include paths for JNI building. Cannot be called in isolation - `javac` refers to an already detected diff --git a/SCons/Tool/__init__.py b/SCons/Tool/__init__.py index faa92a78b2..a7bc927ebc 100644 --- a/SCons/Tool/__init__.py +++ b/SCons/Tool/__init__.py @@ -33,10 +33,11 @@ tool specifications. """ +from __future__ import annotations + import sys import os import importlib.util -from typing import Optional import SCons.Builder import SCons.Errors @@ -824,7 +825,7 @@ def tool_list(platform, env): return [x for x in tools if x] -def find_program_path(env, key_program, default_paths=None, add_path: bool=False) -> Optional[str]: +def find_program_path(env, key_program, default_paths=None, add_path: bool=False) -> str | None: """ Find the location of a tool using various means. diff --git a/SCons/Tool/jar.py b/SCons/Tool/jar.py index 1967294f08..13bdca0518 100644 --- a/SCons/Tool/jar.py +++ b/SCons/Tool/jar.py @@ -28,8 +28,9 @@ selection method. """ +from __future__ import annotations + import os -from typing import List import SCons.Node import SCons.Node.FS @@ -41,7 +42,7 @@ from SCons.Tool.JavaCommon import get_java_install_dirs -def jarSources(target, source, env, for_signature) -> List[str]: +def jarSources(target, source, env, for_signature) -> list[str]: """Only include sources that are not a manifest file.""" try: env['JARCHDIR'] diff --git a/SCons/Tool/lex.py b/SCons/Tool/lex.py index 527f91c294..5e77ea692d 100644 --- a/SCons/Tool/lex.py +++ b/SCons/Tool/lex.py @@ -31,9 +31,10 @@ selection method. """ +from __future__ import annotations + import os.path import sys -from typing import Optional import SCons.Action import SCons.Tool @@ -95,7 +96,7 @@ def lexEmitter(target, source, env) -> tuple: return target, source -def get_lex_path(env, append_paths: bool=False) -> Optional[str]: +def get_lex_path(env, append_paths: bool=False) -> str | None: """ Returns the path to the lex tool, searching several possible names. @@ -162,7 +163,7 @@ def generate(env) -> None: env['_LEX_TABLES'] = '${LEX_TABLES_FILE and "--tables-file=" + str(LEX_TABLES_FILE)}' -def exists(env) -> Optional[str]: +def exists(env) -> str | None: if sys.platform == 'win32': return get_lex_path(env) else: diff --git a/SCons/Tool/ninja/Methods.py b/SCons/Tool/ninja/Methods.py index 5c56e49085..ff006c072e 100644 --- a/SCons/Tool/ninja/Methods.py +++ b/SCons/Tool/ninja/Methods.py @@ -21,10 +21,12 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +from __future__ import annotations + import os import shlex import textwrap -from typing import Optional +from typing import TYPE_CHECKING import SCons from SCons.Subst import SUBST_CMD @@ -32,7 +34,9 @@ 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 + +if TYPE_CHECKING: + from SCons.Executor import Executor def register_custom_handler(env, name, handler) -> None: @@ -78,7 +82,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: Optional[ExecutorType] = None): +def get_generic_shell_command(env, node, action, targets, sources, executor: Executor | None = None): return ( "GENERATED_CMD", { @@ -231,7 +235,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: Optional[ExecutorType] = None): + def get_response_file_command(env, node, action, targets, sources, executor: Executor | None = 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 cdce92895c..24d439ef5e 100644 --- a/SCons/Tool/ninja/Utils.py +++ b/SCons/Tool/ninja/Utils.py @@ -20,17 +20,22 @@ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +from __future__ import annotations + import os import shutil from os.path import join as joinpath from collections import OrderedDict -from typing import Optional +from typing import TYPE_CHECKING 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 + +if TYPE_CHECKING: + from SCons.Executor import Executor class NinjaExperimentalWarning(SCons.Warnings.WarningOnByDefault): pass @@ -349,7 +354,7 @@ def get_comstr(env, action, targets, sources): return action.genstring(targets, sources, env) -def generate_command(env, node, action, targets, sources, executor: Optional[ExecutorType] = None): +def generate_command(env, node, action, targets, sources, executor: Executor | None = 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/Tool/yacc.py b/SCons/Tool/yacc.py index 7a4ddfc54e..bfd82f6053 100644 --- a/SCons/Tool/yacc.py +++ b/SCons/Tool/yacc.py @@ -34,9 +34,10 @@ selection method. """ +from __future__ import annotations + import os.path import sys -from typing import Optional import SCons.Defaults import SCons.Tool @@ -68,7 +69,7 @@ def _yaccEmitter(target, source, env, ysuf, hsuf) -> tuple: # If -d is specified on the command line, yacc will emit a .h # or .hpp file with the same base name as the .c or .cpp output file. - # if '-d' in flags: + # if '-d' in flags: # or bison options -H, --header, --defines (obsolete) if "-d" in flags or "-H" in flags or "--header" in flags or "--defines" in flags: target.append(targetBase + env.subst(hsuf, target=target, source=source)) @@ -76,7 +77,7 @@ def _yaccEmitter(target, source, env, ysuf, hsuf) -> tuple: # If -g is specified on the command line, yacc will emit a graph # file with the same base name as the .c or .cpp output file. # TODO: should this be handled like -v? i.e. a side effect, not target - # if "-g" in flags: + # if "-g" in flags: # or bison option --graph if "-g" in flags or "--graph" in flags: target.append(targetBase + env.subst("$YACC_GRAPH_FILE_SUFFIX")) @@ -134,7 +135,7 @@ def yyEmitter(target, source, env) -> tuple: return _yaccEmitter(target, source, env, ['.yy'], '$YACCHXXFILESUFFIX') -def get_yacc_path(env, append_paths: bool=False) -> Optional[str]: +def get_yacc_path(env, append_paths: bool=False) -> str | None: """ Returns the path to the yacc tool, searching several possible names. @@ -200,7 +201,7 @@ def generate(env) -> None: env['_YACC_GRAPH'] = '${YACC_GRAPH_FILE and "--graph=" + str(YACC_GRAPH_FILE)}' -def exists(env) -> Optional[str]: +def exists(env) -> str | None: if 'YACC' in env: return env.Detect(env['YACC']) diff --git a/SCons/Util/UtilTests.py b/SCons/Util/UtilTests.py index b1c01086e4..0f457c3ce0 100644 --- a/SCons/Util/UtilTests.py +++ b/SCons/Util/UtilTests.py @@ -21,6 +21,8 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +from __future__ import annotations + import functools import hashlib import io @@ -30,7 +32,6 @@ import unittest.mock import warnings from collections import UserDict, UserList, UserString, namedtuple -from typing import Union import TestCmd @@ -533,8 +534,8 @@ def test_get_native_path(self) -> None: def test_PrependPath(self) -> None: """Test prepending to a path""" - p1: Union[list, str] = r'C:\dir\num\one;C:\dir\num\two' - p2: Union[list, str] = r'C:\mydir\num\one;C:\mydir\num\two' + p1: list | str = r'C:\dir\num\one;C:\dir\num\two' + p2: list | str = r'C:\mydir\num\one;C:\mydir\num\two' # have to include the pathsep here so that the test will work on UNIX too. p1 = PrependPath(p1, r'C:\dir\num\two', sep=';') p1 = PrependPath(p1, r'C:\dir\num\three', sep=';') @@ -545,14 +546,14 @@ def test_PrependPath(self) -> None: assert p2 == r'C:\mydir\num\one;C:\mydir\num\three;C:\mydir\num\two', p2 # check (only) first one is kept if there are dupes in new - p3: Union[list, str] = r'C:\dir\num\one' + p3: list | str = r'C:\dir\num\one' p3 = PrependPath(p3, r'C:\dir\num\two;C:\dir\num\three;C:\dir\num\two', sep=';') assert p3 == r'C:\dir\num\two;C:\dir\num\three;C:\dir\num\one', p3 def test_AppendPath(self) -> None: """Test appending to a path.""" - p1: Union[list, str] = r'C:\dir\num\one;C:\dir\num\two' - p2: Union[list, str] = r'C:\mydir\num\one;C:\mydir\num\two' + p1: list | str = r'C:\dir\num\one;C:\dir\num\two' + p2: list | str = r'C:\mydir\num\one;C:\mydir\num\two' # have to include the pathsep here so that the test will work on UNIX too. p1 = AppendPath(p1, r'C:\dir\num\two', sep=';') p1 = AppendPath(p1, r'C:\dir\num\three', sep=';') @@ -563,13 +564,13 @@ def test_AppendPath(self) -> None: assert p2 == r'C:\mydir\num\two;C:\mydir\num\three;C:\mydir\num\one', p2 # check (only) last one is kept if there are dupes in new - p3: Union[list, str] = r'C:\dir\num\one' + p3: list | str = r'C:\dir\num\one' p3 = AppendPath(p3, r'C:\dir\num\two;C:\dir\num\three;C:\dir\num\two', sep=';') assert p3 == r'C:\dir\num\one;C:\dir\num\three;C:\dir\num\two', p3 def test_PrependPathPreserveOld(self) -> None: """Test prepending to a path while preserving old paths""" - p1: Union[list, str] = r'C:\dir\num\one;C:\dir\num\two' + p1: list | str = r'C:\dir\num\one;C:\dir\num\two' # have to include the pathsep here so that the test will work on UNIX too. p1 = PrependPath(p1, r'C:\dir\num\two', sep=';', delete_existing=False) p1 = PrependPath(p1, r'C:\dir\num\three', sep=';') @@ -577,7 +578,7 @@ def test_PrependPathPreserveOld(self) -> None: def test_AppendPathPreserveOld(self) -> None: """Test appending to a path while preserving old paths""" - p1: Union[list, str] = r'C:\dir\num\one;C:\dir\num\two' + p1: list | str = r'C:\dir\num\one;C:\dir\num\two' # have to include the pathsep here so that the test will work on UNIX too. p1 = AppendPath(p1, r'C:\dir\num\one', sep=';', delete_existing=False) p1 = AppendPath(p1, r'C:\dir\num\three', sep=';') diff --git a/SCons/Util/__init__.py b/SCons/Util/__init__.py index 28565da797..0398c1fad7 100644 --- a/SCons/Util/__init__.py +++ b/SCons/Util/__init__.py @@ -49,6 +49,8 @@ # ) # (issue filed on this upstream, for now just be aware) +from __future__ import annotations + import copy import hashlib import logging @@ -59,7 +61,7 @@ from collections import UserDict, UserList, deque from contextlib import suppress from types import MethodType, FunctionType -from typing import Optional, Union, Any, List +from typing import Any from logging import Formatter # Util split into a package. Make sure things that used to work @@ -203,11 +205,11 @@ def __str__(self) -> str: def __iter__(self): return iter(self.data) - def __call__(self, *args, **kwargs) -> 'NodeList': + def __call__(self, *args, **kwargs) -> NodeList: result = [x(*args, **kwargs) for x in self.data] return self.__class__(result) - def __getattr__(self, name) -> 'NodeList': + def __getattr__(self, name) -> NodeList: """Returns a NodeList of `name` from each member.""" result = [getattr(x, name) for x in self.data] return self.__class__(result) @@ -254,8 +256,8 @@ def render_tree( root, child_func, prune: bool = False, - margin: List[bool] = [False], - visited: Optional[dict] = None, + margin: list[bool] = [False], + visited: dict | None = None, ) -> str: """Render a tree of nodes into an ASCII tree view. @@ -323,8 +325,8 @@ def print_tree( child_func, prune: bool = False, showtags: int = 0, - margin: List[bool] = [False], - visited: Optional[dict] = None, + margin: list[bool] = [False], + visited: dict | None = None, lastChild: bool = False, singleLineDraw: bool = False, ) -> None: @@ -694,7 +696,7 @@ def RegOpenKeyEx(root, key): if sys.platform == 'win32': - def WhereIs(file, path=None, pathext=None, reject=None) -> Optional[str]: + def WhereIs(file, path=None, pathext=None, reject=None) -> str | None: if path is None: try: path = os.environ['PATH'] @@ -731,7 +733,7 @@ def WhereIs(file, path=None, pathext=None, reject=None) -> Optional[str]: elif os.name == 'os2': - def WhereIs(file, path=None, pathext=None, reject=None) -> Optional[str]: + def WhereIs(file, path=None, pathext=None, reject=None) -> str | None: if path is None: try: path = os.environ['PATH'] @@ -763,7 +765,7 @@ def WhereIs(file, path=None, pathext=None, reject=None) -> Optional[str]: else: - def WhereIs(file, path=None, pathext=None, reject=None) -> Optional[str]: + def WhereIs(file, path=None, pathext=None, reject=None) -> str | None: import stat # pylint: disable=import-outside-toplevel if path is None: diff --git a/SCons/Util/envs.py b/SCons/Util/envs.py index 2640ef5c19..9c97da6e9e 100644 --- a/SCons/Util/envs.py +++ b/SCons/Util/envs.py @@ -9,10 +9,12 @@ that don't need the specifics of the Environment class. """ +from __future__ import annotations + import re import os from types import MethodType, FunctionType -from typing import Union, Callable, Optional, Any +from typing import Callable, Any from .sctypes import is_List, is_Tuple, is_String @@ -22,8 +24,8 @@ def PrependPath( newpath, sep=os.pathsep, delete_existing: bool = True, - canonicalize: Optional[Callable] = None, -) -> Union[list, str]: + canonicalize: Callable | None = None, +) -> list | str: """Prepend *newpath* path elements to *oldpath*. Will only add any particular path once (leaving the first one it @@ -112,8 +114,8 @@ def AppendPath( newpath, sep=os.pathsep, delete_existing: bool = True, - canonicalize: Optional[Callable] = None, -) -> Union[list, str]: + canonicalize: Callable | None = None, +) -> list | str: """Append *newpath* path elements to *oldpath*. Will only add any particular path once (leaving the last one it @@ -239,7 +241,7 @@ class MethodWrapper: a new underlying object being copied (without which we wouldn't need to save that info). """ - def __init__(self, obj: Any, method: Callable, name: Optional[str] = None) -> None: + def __init__(self, obj: Any, method: Callable, name: str | None = None) -> None: if name is None: name = method.__name__ self.object = obj @@ -275,7 +277,7 @@ def clone(self, new_object): # is not needed, the remaining bit is now used inline in AddMethod. -def AddMethod(obj, function: Callable, name: Optional[str] = None) -> None: +def AddMethod(obj, function: Callable, name: str | None = None) -> None: """Add a method to an object. Adds *function* to *obj* if *obj* is a class object. @@ -314,7 +316,7 @@ def AddMethod(obj, function: Callable, name: Optional[str] = None) -> None: function.__code__, function.__globals__, name, function.__defaults__ ) - method: Union[MethodType, MethodWrapper, Callable] + method: MethodType | MethodWrapper | Callable if hasattr(obj, '__class__') and obj.__class__ is not type: # obj is an instance, so it gets a bound method. diff --git a/SCons/Util/filelock.py b/SCons/Util/filelock.py index 8ebf3889fe..730f48607b 100644 --- a/SCons/Util/filelock.py +++ b/SCons/Util/filelock.py @@ -30,9 +30,10 @@ # The lock attributes could probably be made opaque. Showed one visible # in the example above, but not sure the benefit of that. +from __future__ import annotations + import os import time -from typing import Optional class SConsLockFailure(Exception): @@ -75,8 +76,8 @@ class FileLock: def __init__( self, file: str, - timeout: Optional[int] = None, - delay: Optional[float] = 0.05, + timeout: int | None = None, + delay: float | None = 0.05, writer: bool = False, ) -> None: if timeout is not None and delay is None: @@ -90,7 +91,7 @@ def __init__( # Our simple first guess is just put it where the file is. self.file = file self.lockfile = f"{file}.lock" - self.lock: Optional[int] = None + self.lock: int | None = None self.timeout = 999999 if timeout == 0 else timeout self.delay = 0.0 if delay is None else delay self.writer = writer @@ -128,7 +129,7 @@ def release_lock(self) -> None: os.unlink(self.lockfile) self.lock = None - def __enter__(self) -> "FileLock": + def __enter__(self) -> FileLock: """Context manager entry: acquire lock if not holding.""" if not self.lock: self.acquire_lock() diff --git a/SCons/Util/hashes.py b/SCons/Util/hashes.py index 566897abbe..016354c6e5 100644 --- a/SCons/Util/hashes.py +++ b/SCons/Util/hashes.py @@ -8,10 +8,11 @@ Routines for working with content and signature hashes. """ +from __future__ import annotations + import functools import hashlib import sys -from typing import Optional, Union from .sctypes import to_bytes diff --git a/SCons/Util/sctypes.py b/SCons/Util/sctypes.py index 765458e130..b95e395c80 100644 --- a/SCons/Util/sctypes.py +++ b/SCons/Util/sctypes.py @@ -6,13 +6,13 @@ Routines which check types and do type conversions. """ +from __future__ import annotations import codecs import os import pprint import re import sys -from typing import Optional, Union from collections import UserDict, UserList, UserString, deque from collections.abc import MappingView, Iterable @@ -56,20 +56,24 @@ if sys.version_info >= (3, 13): from typing import TypeAlias, TypeIs - DictTypeRet: TypeAlias = TypeIs[Union[dict, UserDict]] - ListTypeRet: TypeAlias = TypeIs[Union[list, UserList, deque]] - SequenceTypeRet: TypeAlias = TypeIs[Union[list, tuple, deque, UserList, MappingView]] + DictTypeRet: TypeAlias = TypeIs[dict | UserDict] + ListTypeRet: TypeAlias = TypeIs[list | UserList | deque] + SequenceTypeRet: TypeAlias = TypeIs[list | tuple | deque | UserList | MappingView] TupleTypeRet: TypeAlias = TypeIs[tuple] - StringTypeRet: TypeAlias = TypeIs[Union[str, UserString]] + StringTypeRet: TypeAlias = TypeIs[str | UserString] elif sys.version_info >= (3, 10): from typing import TypeAlias, TypeGuard - DictTypeRet: TypeAlias = TypeGuard[Union[dict, UserDict]] - ListTypeRet: TypeAlias = TypeGuard[Union[list, UserList, deque]] - SequenceTypeRet: TypeAlias = TypeGuard[Union[list, tuple, deque, UserList, MappingView]] + DictTypeRet: TypeAlias = TypeGuard[dict | UserDict] + ListTypeRet: TypeAlias = TypeGuard[list | UserList | deque] + SequenceTypeRet: TypeAlias = TypeGuard[list | tuple | deque | UserList | MappingView] TupleTypeRet: TypeAlias = TypeGuard[tuple] - StringTypeRet: TypeAlias = TypeGuard[Union[str, UserString]] + StringTypeRet: TypeAlias = TypeGuard[str | UserString] else: + # Because we have neither `TypeAlias` class nor `type` keyword pre-3.10, + # the boolean fallback type has to be wrapped in the legacy `Union` class. + from typing import Union + DictTypeRet = Union[bool, bool] ListTypeRet = Union[bool, bool] SequenceTypeRet = Union[bool, bool] @@ -354,7 +358,7 @@ def get_os_env_bool(name: str, default: bool=False) -> bool: _get_env_var = re.compile(r'^\$([_a-zA-Z]\w*|{[_a-zA-Z]\w*})$') -def get_environment_var(varstr) -> Optional[str]: +def get_environment_var(varstr) -> str | None: """Return undecorated construction variable string. Determine if *varstr* looks like a reference diff --git a/SCons/Util/sctyping.py b/SCons/Util/sctyping.py deleted file mode 100644 index 5da5eb9fe7..0000000000 --- a/SCons/Util/sctyping.py +++ /dev/null @@ -1,33 +0,0 @@ -# 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: diff --git a/SCons/Variables/BoolVariable.py b/SCons/Variables/BoolVariable.py index 815a4b7865..573a0244e7 100644 --- a/SCons/Variables/BoolVariable.py +++ b/SCons/Variables/BoolVariable.py @@ -32,7 +32,9 @@ ... """ -from typing import Callable, Tuple, Union +from __future__ import annotations + +from typing import Callable import SCons.Errors @@ -42,7 +44,7 @@ FALSE_STRINGS = ('n', 'no', 'false', 'f', '0', 'off', 'none') -def _text2bool(val: Union[str, bool]) -> bool: +def _text2bool(val: str | bool) -> bool: """Convert boolean-like string to boolean. If *val* looks like it expresses a bool-like value, based on @@ -83,7 +85,7 @@ def _validator(key: str, val, env) -> None: raise SCons.Errors.UserError(msg) from None # lint: W0622: Redefining built-in 'help' (redefined-builtin) -def BoolVariable(key, help: str, default) -> Tuple[str, str, str, Callable, Callable]: +def BoolVariable(key, help: str, default) -> tuple[str, str, str, Callable, Callable]: """Return a tuple describing a boolean SCons Variable. The input parameters describe a boolean variable, using a string diff --git a/SCons/Variables/EnumVariable.py b/SCons/Variables/EnumVariable.py index 3698e470dc..f154a133b7 100644 --- a/SCons/Variables/EnumVariable.py +++ b/SCons/Variables/EnumVariable.py @@ -43,7 +43,9 @@ ... """ -from typing import Callable, List, Optional, Tuple +from __future__ import annotations + +from typing import Callable import SCons.Errors @@ -69,10 +71,10 @@ def EnumVariable( key, help: str, default: str, - allowed_values: List[str], - map: Optional[dict] = None, + allowed_values: list[str], + map: dict | None = None, ignorecase: int = 0, -) -> Tuple[str, str, str, Callable, Callable]: +) -> tuple[str, str, str, Callable, Callable]: """Return a tuple describing an enumaration SCons Variable. The input parameters describe a variable with only predefined values diff --git a/SCons/Variables/ListVariable.py b/SCons/Variables/ListVariable.py index 2c79ee7f15..4ea7dc304c 100644 --- a/SCons/Variables/ListVariable.py +++ b/SCons/Variables/ListVariable.py @@ -53,9 +53,11 @@ # Known Bug: This should behave like a Set-Type, but does not really, # since elements can occur twice. +from __future__ import annotations + import collections import functools -from typing import Callable, List, Optional, Tuple, Union +from typing import Callable import SCons.Util @@ -75,7 +77,7 @@ class _ListVariable(collections.UserList): """ def __init__( - self, initlist: Optional[list] = None, allowedElems: Optional[list] = None + self, initlist: list | None = None, allowedElems: list | None = None ) -> None: if initlist is None: initlist = [] @@ -179,11 +181,11 @@ def _validator(key, val, env) -> None: def ListVariable( key, help: str, - default: Union[str, List[str]], - names: List[str], - map: Optional[dict] = None, - validator: Optional[Callable] = None, -) -> Tuple[str, str, str, None, Callable]: + default: str | list[str], + names: list[str], + map: dict | None = None, + validator: Callable | None = None, +) -> tuple[str, str, str, None, Callable]: """Return a tuple describing a list variable. The input parameters describe a list variable, where the values diff --git a/SCons/Variables/PackageVariable.py b/SCons/Variables/PackageVariable.py index 7271cfbf8f..2ecedfe77a 100644 --- a/SCons/Variables/PackageVariable.py +++ b/SCons/Variables/PackageVariable.py @@ -50,9 +50,11 @@ ... # build with x11 ... """ +from __future__ import annotations + import os import functools -from typing import Callable, Optional, Tuple, Union +from typing import Callable import SCons.Errors @@ -61,7 +63,7 @@ ENABLE_STRINGS = ('1', 'yes', 'true', 'on', 'enable', 'search') DISABLE_STRINGS = ('0', 'no', 'false', 'off', 'disable') -def _converter(val: Union[str, bool], default: str) -> Union[str, bool]: +def _converter(val: str | bool, default: str) -> str | bool: """Convert a package variable. Returns *val* if it looks like a path string, and ``False`` if it @@ -108,8 +110,8 @@ def _validator(key: str, val, env, searchfunc) -> None: # lint: W0622: Redefining built-in 'help' (redefined-builtin) def PackageVariable( - key: str, help: str, default, searchfunc: Optional[Callable] = None -) -> Tuple[str, str, str, Callable, Callable]: + key: str, help: str, default, searchfunc: Callable | None = None +) -> tuple[str, str, str, Callable, Callable]: """Return a tuple describing a package list SCons Variable. The input parameters describe a 'package list' variable. Returns diff --git a/SCons/Variables/PathVariable.py b/SCons/Variables/PathVariable.py index 43904e62c7..f840a95d26 100644 --- a/SCons/Variables/PathVariable.py +++ b/SCons/Variables/PathVariable.py @@ -71,10 +71,11 @@ ) """ +from __future__ import annotations import os import os.path -from typing import Callable, Optional, Tuple +from typing import Callable import SCons.Errors import SCons.Util @@ -141,8 +142,8 @@ def PathExists(key: str, val, env) -> None: # lint: W0622: Redefining built-in 'help' (redefined-builtin) def __call__( - self, key: str, help: str, default, validator: Optional[Callable] = None - ) -> Tuple[str, str, str, Callable, None]: + self, key: str, help: str, default, validator: Callable | None = None + ) -> tuple[str, str, str, Callable, None]: """Return a tuple describing a path list SCons Variable. The input parameters describe a 'path list' variable. Returns diff --git a/SCons/Variables/__init__.py b/SCons/Variables/__init__.py index 28325266fb..2d160072a5 100644 --- a/SCons/Variables/__init__.py +++ b/SCons/Variables/__init__.py @@ -23,10 +23,12 @@ """Adds user-friendly customizable variables to an SCons build.""" +from __future__ import annotations + import os.path import sys from functools import cmp_to_key -from typing import Callable, Dict, List, Optional, Sequence, Union +from typing import Callable, Sequence import SCons.Errors import SCons.Util @@ -90,16 +92,16 @@ class Variables: def __init__( self, - files: Optional[Union[str, Sequence[str]]] = None, - args: Optional[dict] = None, + files: str | Sequence[str | None] = None, + args: dict | None = None, is_global: bool = False, ) -> None: - self.options: List[Variable] = [] + self.options: list[Variable] = [] self.args = args if args is not None else {} if not SCons.Util.is_Sequence(files): files = [files] if files else [] self.files: Sequence[str] = files - self.unknown: Dict[str, str] = {} + self.unknown: dict[str, str] = {} def __str__(self) -> str: """Provide a way to "print" a Variables object.""" @@ -113,11 +115,11 @@ def __str__(self) -> str: # lint: W0622: Redefining built-in 'help' def _do_add( self, - key: Union[str, List[str]], + key: str | list[str], help: str = "", default=None, - validator: Optional[Callable] = None, - converter: Optional[Callable] = None, + validator: Callable | None = None, + converter: Callable | None = None, **kwargs, ) -> None: """Create a Variable and add it to the list. @@ -162,7 +164,7 @@ def keys(self) -> list: yield option.key def Add( - self, key: Union[str, Sequence], *args, **kwargs, + self, key: str | Sequence, *args, **kwargs, ) -> None: """Add a Build Variable. @@ -218,7 +220,7 @@ def AddVariables(self, *optlist) -> None: for opt in optlist: self._do_add(*opt) - def Update(self, env, args: Optional[dict] = None) -> None: + def Update(self, env, args: dict | None = None) -> None: """Update an environment with the Build Variables. Args: @@ -362,7 +364,7 @@ def Save(self, filename, env) -> None: msg = f'Error writing options to file: {filename}\n{exc}' raise SCons.Errors.UserError(msg) from exc - def GenerateHelpText(self, env, sort: Union[bool, Callable] = False) -> str: + def GenerateHelpText(self, env, sort: bool | Callable = False) -> str: """Generate the help text for the Variables object. Args: @@ -403,7 +405,7 @@ def FormatVariableHelpText( help: str, default, actual, - aliases: Optional[List[str]] = None, + aliases: list[str | None] = None, ) -> str: """Format the help text for a single variable. diff --git a/SCons/Warnings.py b/SCons/Warnings.py index d604659c40..b9f9cc16f4 100644 --- a/SCons/Warnings.py +++ b/SCons/Warnings.py @@ -61,8 +61,10 @@ framework and it will behave like an ordinary exception. """ +from __future__ import annotations + import sys -from typing import Callable, Sequence, Optional +from typing import Callable, Sequence import SCons.Errors @@ -75,7 +77,7 @@ # Function to emit the warning. Initialized by SCons/Main.py for regular use; # the unit test will set to a capturing version for testing. -_warningOut: Optional[Callable] = None +_warningOut: Callable | None = None class SConsWarning(SCons.Errors.UserError): diff --git a/pyproject.toml b/pyproject.toml index cb824b7ba9..be261fa3fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -86,6 +86,15 @@ extend-exclude = [ "runtest.py", ] +[tool.ruff.lint] +extend-select = [ + "FA", # Future annotations + "UP006", # Use {to} instead of {from} for type annotation + "UP007", # Use `X | Y` for type annotations + "UP037", # Remove quotes from type annotation +] +extend-safe-fixes = ["FA", "UP006", "UP007"] + [tool.ruff.format] quote-style = "preserve" # Equivalent to black's "skip-string-normalization" diff --git a/runtest.py b/runtest.py index 220b490333..3408d68681 100755 --- a/runtest.py +++ b/runtest.py @@ -15,6 +15,8 @@ performs test discovery and processes tests according to options. """ +from __future__ import annotations + import argparse import itertools import os @@ -27,11 +29,11 @@ from io import StringIO from pathlib import Path, PurePath, PureWindowsPath from queue import Queue -from typing import List, TextIO, Optional +from typing import TextIO cwd = os.getcwd() -debug: Optional[str] = None -scons: Optional[str] = None +debug: str | None = None +scons: str | None = None catch_output: bool = False suppress_output: bool = False script = PurePath(sys.argv[0]).name @@ -43,7 +45,7 @@ """ # this is currently expected to be global, maybe refactor later? -unittests: List[str] +unittests: list[str] parser = argparse.ArgumentParser( usage=usagestr, diff --git a/test/Variables/PackageVariable.py b/test/Variables/PackageVariable.py index bc447dd58e..77c04e6088 100644 --- a/test/Variables/PackageVariable.py +++ b/test/Variables/PackageVariable.py @@ -27,8 +27,9 @@ Test the PackageVariable canned Variable type. """ +from __future__ import annotations + import os -from typing import List import TestSCons @@ -36,7 +37,7 @@ SConstruct_path = test.workpath('SConstruct') -def check(expect: List[str]) -> None: +def check(expect: list[str]) -> None: result = test.stdout().split('\n') # skip first line and any lines beyond the length of expect assert result[1:len(expect)+1] == expect, (result[1:len(expect)+1], expect) diff --git a/testing/framework/TestCmd.py b/testing/framework/TestCmd.py index 243745d020..7307078c5e 100644 --- a/testing/framework/TestCmd.py +++ b/testing/framework/TestCmd.py @@ -295,6 +295,8 @@ TestCmd.where_is('foo', 'PATH1;PATH2', '.suffix3;.suffix4') """ +from __future__ import annotations + __author__ = "Steven Knight " __revision__ = "TestCmd.py 1.3.D001 2010/06/03 12:58:27 knight" __version__ = "1.3" @@ -323,7 +325,7 @@ from collections import UserList, UserString from pathlib import Path from subprocess import PIPE, STDOUT -from typing import Callable, Dict, Optional, Union +from typing import Callable IS_WINDOWS = sys.platform == 'win32' IS_MACOS = sys.platform == 'darwin' @@ -437,7 +439,7 @@ def clean_up_ninja_daemon(self, result_type) -> None: def fail_test( self=None, condition: bool = True, - function: Optional[Callable] = None, + function: Callable | None = None, skip: int = 0, message: str = "", ) -> None: @@ -1044,8 +1046,8 @@ def __init__( diff_stdout=None, diff_stderr=None, combine: bool = False, - universal_newlines: Optional[bool] = True, - timeout: Optional[float] = None, + universal_newlines: bool | None = True, + timeout: float | None = None, ) -> None: self.external = os.environ.get('SCONS_EXTERNAL_TEST', 0) self._cwd = os.getcwd() @@ -1060,7 +1062,7 @@ def __init__( self.verbose_set(verbose) self.combine = combine self.universal_newlines = universal_newlines - self.process: Optional[Popen] = None + self.process: Popen | None = None # Two layers of timeout: one at the test class instance level, # one set on an individual start() call (usually via a run() call) self.timeout = timeout @@ -1068,7 +1070,7 @@ def __init__( self.set_match_function(match, match_stdout, match_stderr) self.set_diff_function(diff, diff_stdout, diff_stderr) self._dirlist = [] - self._preserve: Dict[str, Union[str, bool]] = { + self._preserve: dict[str, str | bool] = { 'pass_test': False, 'fail_test': False, 'no_result': False, @@ -1084,9 +1086,9 @@ def __init__( self._preserve['no_result'] = os.environ.get('PRESERVE_NO_RESULT', False) self._stdout = [] self._stderr = [] - self.status: Optional[int] = None + self.status: int | None = None self.condition = 'no_result' - self.workdir: Optional[str] + self.workdir: str | None self.workdir_set(workdir) self.subdir(subdir) @@ -1254,7 +1256,7 @@ def diff_stdout(self, a, b, *args, **kw): def fail_test( self, condition: bool = True, - function: Optional[Callable] = None, + function: Callable | None = None, skip: int = 0, message: str = "", )-> None: @@ -1738,7 +1740,7 @@ def sleep(self, seconds=default_sleep_seconds) -> None: """ time.sleep(seconds) - def stderr(self, run=None) -> Optional[str]: + def stderr(self, run=None) -> str | None: """Returns the stored standard error output from a given run. Args: @@ -1760,7 +1762,7 @@ def stderr(self, run=None) -> Optional[str]: except IndexError: return None - def stdout(self, run=None) -> Optional[str]: + def stdout(self, run=None) -> str | None: """Returns the stored standard output from a given run. Args: diff --git a/testing/framework/TestCommon.py b/testing/framework/TestCommon.py index f5bb084e68..470dbcb208 100644 --- a/testing/framework/TestCommon.py +++ b/testing/framework/TestCommon.py @@ -110,6 +110,8 @@ """ +from __future__ import annotations + __author__ = "Steven Knight " __revision__ = "TestCommon.py 1.3.D001 2010/06/03 12:58:27 knight" __version__ = "1.3" @@ -121,7 +123,7 @@ import sysconfig from collections import UserList -from typing import Callable, List, Optional, Union +from typing import Callable from TestCmd import * from TestCmd import __all__ @@ -226,14 +228,14 @@ def separate_files(flist): missing.append(f) return existing, missing -def contains(seq, subseq, find: Optional[Callable] = None) -> bool: +def contains(seq, subseq, find: Callable | None = None) -> bool: if find is None: return subseq in seq else: f = find(seq, subseq) return f not in (None, -1) and f is not False -def find_index(seq, subseq, find: Optional[Callable] = None) -> Optional[int]: +def find_index(seq, subseq, find: Callable | None = None) -> int | None: # Returns either an index of the subseq within the seq, or None. # Accepts a function find(seq, subseq), which returns an integer on success # and either: None, False, or -1, on failure. @@ -280,8 +282,8 @@ def __init__(self, **kw) -> None: def options_arguments( self, - options: Union[str, List[str]], - arguments: Union[str, List[str]], + options: str | list[str], + arguments: str | list[str], ): """Merges the "options" keyword argument with the arguments.""" # TODO: this *doesn't* split unless both are non-empty strings. @@ -323,7 +325,7 @@ def must_contain( file: str, required: str, mode: str = 'rb', - find: Optional[Callable] = None, + find: Callable | None = None, ) -> None: """Ensures specified file contains the required text. @@ -353,7 +355,7 @@ def must_contain( print(file_contents) self.fail_test() - def must_contain_all(self, output, input, title: str = "", find: Optional[Callable] = None)-> None: + def must_contain_all(self, output, input, title: str = "", find: Callable | None = None)-> None: """Ensures that the specified output string (first argument) contains all of the specified input as a block (second argument). @@ -376,7 +378,7 @@ def must_contain_all(self, output, input, title: str = "", find: Optional[Callab print(output) self.fail_test() - def must_contain_all_lines(self, output, lines, title: str = "", find: Optional[Callable] = None) -> None: + def must_contain_all_lines(self, output, lines, title: str = "", find: Callable | None = None) -> None: """Ensures that the specified output string (first argument) contains all of the specified lines (second argument). @@ -427,7 +429,7 @@ def must_contain_single_instance_of(self, output, lines, title: str = "") -> Non sys.stdout.write(output) self.fail_test() - def must_contain_any_line(self, output, lines, title: str = "", find: Optional[Callable] = None) -> None: + def must_contain_any_line(self, output, lines, title: str = "", find: Callable | None = None) -> None: """Ensures that the specified output string (first argument) contains at least one of the specified lines (second argument). @@ -451,7 +453,7 @@ def must_contain_any_line(self, output, lines, title: str = "", find: Optional[C sys.stdout.write(output) self.fail_test() - def must_contain_exactly_lines(self, output, expect, title: str = "", find: Optional[Callable] = None) -> None: + def must_contain_exactly_lines(self, output, expect, title: str = "", find: Callable | None = None) -> None: """Ensures that the specified output string (first argument) contains all of the lines in the expected string (second argument) with none left over. @@ -499,7 +501,7 @@ def must_contain_exactly_lines(self, output, expect, title: str = "", find: Opti sys.stdout.flush() self.fail_test() - def must_contain_lines(self, lines, output, title: str = "", find: Optional[Callable] = None) -> None: + def must_contain_lines(self, lines, output, title: str = "", find: Callable | None = None) -> None: # Deprecated; retain for backwards compatibility. self.must_contain_all_lines(output, lines, title, find) @@ -540,7 +542,7 @@ def must_match( file, expect, mode: str = 'rb', - match: Optional[Callable] = None, + match: Callable | None = None, message: str = "", newline=None, ): @@ -569,7 +571,7 @@ def must_match_file( file, golden_file, mode: str = 'rb', - match: Optional[Callable] = None, + match: Callable | None = None, message: str = "", newline=None, ) -> None: @@ -609,7 +611,7 @@ def must_not_contain(self, file, banned, mode: str = 'rb', find = None) -> None: print(file_contents) self.fail_test() - def must_not_contain_any_line(self, output, lines, title: str = "", find: Optional[Callable] = None) -> None: + def must_not_contain_any_line(self, output, lines, title: str = "", find: Callable | None = None) -> None: """Ensures that the specified output string (first argument) does not contain any of the specified lines (second argument). @@ -635,7 +637,7 @@ def must_not_contain_any_line(self, output, lines, title: str = "", find: Option sys.stdout.write(output) self.fail_test() - def must_not_contain_lines(self, lines, output, title: str = "", find: Optional[Callable] = None) -> None: + def must_not_contain_lines(self, lines, output, title: str = "", find: Callable | None = None) -> None: self.must_not_contain_any_line(output, lines, title, find) def must_not_exist(self, *files) -> None: @@ -768,9 +770,9 @@ def start(self, program = None, def finish( self, popen, - stdout: Optional[str] = None, - stderr: Optional[str] = '', - status: Optional[int] = 0, + stdout: str | None = None, + stderr: str | None = '', + status: int | None = 0, **kw, ) -> None: """Finish and wait for the process being run. @@ -800,9 +802,9 @@ def run( self, options=None, arguments=None, - stdout: Optional[str] = None, - stderr: Optional[str] = '', - status: Optional[int] = 0, + stdout: str | None = None, + stderr: str | None = '', + status: int | None = 0, **kw, ) -> None: """Runs the program under test, checking that the test succeeded. diff --git a/testing/framework/TestSCons.py b/testing/framework/TestSCons.py index 243be753a4..806b596be0 100644 --- a/testing/framework/TestSCons.py +++ b/testing/framework/TestSCons.py @@ -34,6 +34,8 @@ attributes defined in this subclass. """ +from __future__ import annotations + import os import re import shutil @@ -42,7 +44,6 @@ import subprocess as sp import zipfile from collections import namedtuple -from typing import Optional, Tuple from TestCommon import * from TestCommon import __all__, _python_ @@ -865,7 +866,7 @@ def java_where_includes(self, version=None): result.append(os.path.join(d, 'linux')) return result - def java_where_java_home(self, version=None) -> Optional[str]: + def java_where_java_home(self, version=None) -> str | None: """ Find path to what would be JAVA_HOME. SCons does not read JAVA_HOME from the environment, so deduce it. @@ -980,7 +981,7 @@ def java_where_java(self, version=None) -> str: return where_java - def java_where_javac(self, version=None) -> Tuple[str, str]: + def java_where_javac(self, version=None) -> tuple[str, str]: """ Find java compiler. Args: