summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWilliam Deegan <bill@baddogconsulting.com>2024-11-16 23:43:22 (GMT)
committerGitHub <noreply@github.com>2024-11-16 23:43:22 (GMT)
commit45a4d5748b540483919f6f9d4ec4cd828edc1117 (patch)
treee9b8eb069658fb95d3ac18fec269b5a62526bee0
parent630ee1c6e34272a69bff31cdc3d56e95e27b7c0d (diff)
parent3829f4551edebee3e42864b19e6332b24af3eb93 (diff)
downloadSCons-45a4d5748b540483919f6f9d4ec4cd828edc1117.zip
SCons-45a4d5748b540483919f6f9d4ec4cd828edc1117.tar.gz
SCons-45a4d5748b540483919f6f9d4ec4cd828edc1117.tar.bz2
Merge pull request #4643 from Repiteo/future-annotations
Integrate `from __future__ import annotations`
-rw-r--r--CHANGES.txt4
-rw-r--r--RELEASE.txt6
-rw-r--r--SCons/Action.py86
-rw-r--r--SCons/ActionTests.py12
-rw-r--r--SCons/Builder.py7
-rw-r--r--SCons/BuilderTests.py11
-rw-r--r--SCons/Defaults.py10
-rw-r--r--SCons/Environment.py20
-rw-r--r--SCons/Errors.py10
-rw-r--r--SCons/Executor.py10
-rw-r--r--SCons/Node/FS.py7
-rw-r--r--SCons/Node/FSTests.py14
-rw-r--r--SCons/Node/NodeTests.py10
-rw-r--r--SCons/Node/__init__.py9
-rw-r--r--SCons/SConf.py5
-rw-r--r--SCons/Script/Main.py11
-rw-r--r--SCons/Script/SConsOptions.py5
-rw-r--r--SCons/Script/SConscript.py7
-rw-r--r--SCons/Subst.py7
-rw-r--r--SCons/Tool/FortranCommon.py11
-rw-r--r--SCons/Tool/JavaCommon.py7
-rw-r--r--SCons/Tool/__init__.py5
-rw-r--r--SCons/Tool/jar.py5
-rw-r--r--SCons/Tool/lex.py7
-rw-r--r--SCons/Tool/ninja/Methods.py12
-rw-r--r--SCons/Tool/ninja/Utils.py11
-rw-r--r--SCons/Tool/yacc.py11
-rw-r--r--SCons/Util/UtilTests.py19
-rw-r--r--SCons/Util/__init__.py22
-rw-r--r--SCons/Util/envs.py18
-rw-r--r--SCons/Util/filelock.py11
-rw-r--r--SCons/Util/hashes.py3
-rw-r--r--SCons/Util/sctypes.py24
-rw-r--r--SCons/Util/sctyping.py33
-rw-r--r--SCons/Variables/BoolVariable.py8
-rw-r--r--SCons/Variables/EnumVariable.py10
-rw-r--r--SCons/Variables/ListVariable.py16
-rw-r--r--SCons/Variables/PackageVariable.py10
-rw-r--r--SCons/Variables/PathVariable.py7
-rw-r--r--SCons/Variables/__init__.py26
-rw-r--r--SCons/Warnings.py6
-rw-r--r--pyproject.toml9
-rwxr-xr-xruntest.py10
-rw-r--r--test/Variables/PackageVariable.py5
-rw-r--r--testing/framework/TestCmd.py24
-rw-r--r--testing/framework/TestCommon.py44
-rw-r--r--testing/framework/TestSCons.py7
47 files changed, 351 insertions, 281 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index 9fa60af..ad1c929 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -78,6 +78,10 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER
From Thaddeus Crews:
- Ruff/Mypy: Excluded items now synced.
+ - 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
diff --git a/RELEASE.txt b/RELEASE.txt
index b12a538..1863443 100644
--- a/RELEASE.txt
+++ b/RELEASE.txt
@@ -182,6 +182,12 @@ DEVELOPMENT
- Ruff/Mypy: Excluded items now synced.
+- 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 567f66c..6022b19 100644
--- a/SCons/Action.py
+++ b/SCons/Action.py
@@ -100,6 +100,8 @@ way for wrapping up the functions.
"""
+from __future__ import annotations
+
import inspect
import os
import pickle
@@ -109,7 +111,7 @@ import sys
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 @@ import SCons.Util
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 @@ class ActionBase(ABC):
show=_null,
execute=_null,
chdir=_null,
- executor: Optional[ExecutorType] = None,
+ executor: Executor | None = None,
):
raise NotImplementedError
@@ -541,15 +543,15 @@ class ActionBase(ABC):
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 @@ class ActionBase(ABC):
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 @@ class _ActionAction(ActionBase):
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 @@ 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: 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 @@ class CommandAction(_ActionAction):
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 @@ class CommandAction(_ActionAction):
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 @@ class CommandAction(_ActionAction):
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 @@ class CommandAction(_ActionAction):
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 @@ 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: 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 @@ 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: 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 @@ class CommandAction(_ActionAction):
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 @@ class CommandGeneratorAction(ActionBase):
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 @@ 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: 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 @@ class CommandGeneratorAction(ActionBase):
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 @@ 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: 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 @@ 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: 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 @@ class FunctionAction(_ActionAction):
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 @@ class FunctionAction(_ActionAction):
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 @@ class FunctionAction(_ActionAction):
# 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 @@ class ListAction(ActionBase):
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 @@ 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: 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 @@ 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: 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 @@ class ListAction(ActionBase):
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 @@ class ActionCaller:
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 3979880..497de18 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 @@ import types
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 @@ class FunctionActionTestCase(unittest.TestCase):
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 3efcc82..c81d104 100644
--- a/SCons/Builder.py
+++ b/SCons/Builder.py
@@ -99,10 +99,11 @@ There are the following methods for internal use within this module:
"""
+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,7 @@ import SCons.Util
import SCons.Warnings
from SCons.Debug import logInstanceCreation
from SCons.Errors import InternalError, UserError
-from SCons.Util.sctyping import ExecutorType
+from SCons.Executor import Executor
class _Null:
pass
@@ -591,7 +592,7 @@ class BuilderBase:
# 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 b66f524..adfa648 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 @@ import SCons.Environment
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 @@ class MyNode_without_target_from_source:
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 a5d49fc..d971d06 100644
--- a/SCons/Defaults.py
+++ b/SCons/Defaults.py
@@ -31,12 +31,14 @@ The code that reads the registry to find MSVC components was borrowed
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 0c14468..62926f5 100644
--- a/SCons/Environment.py
+++ b/SCons/Environment.py
@@ -30,6 +30,8 @@ Keyword arguments supplied when the construction Environment is created
are construction variables used to initialize the Environment.
"""
+from __future__ import annotations
+
import copy
import os
import sys
@@ -37,7 +39,7 @@ import re
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 @@ from SCons.Util import (
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 @@ class SubstitutionEnvironment:
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 @@ class SubstitutionEnvironment:
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 @@ class SubstitutionEnvironment:
'RPATH' : [],
}
- def do_parse(arg: Union[str, Sequence]) -> None:
+ def do_parse(arg: str | Sequence) -> None:
if not arg:
return
@@ -1798,7 +1802,7 @@ class Base(SubstitutionEnvironment):
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.
@@ -2079,7 +2083,7 @@ class Base(SubstitutionEnvironment):
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*.
@@ -2615,7 +2619,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 a2efc97..af77971 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 1b054b4..53eb5cb 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 @@ 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
@@ -550,12 +550,12 @@ class Executor(metaclass=NoSlotsPyPy):
-_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 3cd7720..bdecffc 100644
--- a/SCons/Node/FS.py
+++ b/SCons/Node/FS.py
@@ -30,6 +30,8 @@ This holds a "default_fs" variable that should be initialized with an FS
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 stat
import sys
import time
from itertools import chain
-from typing import Optional
import SCons.Action
import SCons.Debug
@@ -1492,7 +1493,7 @@ class FS(LocalFS):
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 @@ class File(Base):
# 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 83ceef2..9ae8c03 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 time
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.Node.FS
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 VariantDirTestCase(unittest.TestCase):
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 @@ class EntryTestCase(_tempdirTestCase):
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 prepareTestCase(unittest.TestCase):
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 70c8551..6c7437d 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 00bf4ac..8ae991e 100644
--- a/SCons/Node/__init__.py
+++ b/SCons/Node/__init__.py
@@ -40,18 +40,19 @@ be able to depend on any other type of "thing."
"""
+from __future__ import annotations
+
import collections
import copy
from itertools import chain, zip_longest
-from typing import Optional
import SCons.Debug
import SCons.Executor
import SCons.Memoize
from SCons.compat import NoSlotsPyPy
from SCons.Debug import logInstanceCreation, Trace
+from SCons.Executor import Executor
from SCons.Util import hash_signature, is_List, UniqueList, render_tree
-from SCons.Util.sctyping import ExecutorType
print_duplicate = 0
@@ -636,11 +637,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: 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 d2e09be..c223556 100644
--- a/SCons/SConf.py
+++ b/SCons/SConf.py
@@ -31,6 +31,8 @@ Tests on the build system can detect if compiler sees header files, if
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 os
import re
import sys
import traceback
-from typing import Tuple
import SCons.Action
import SCons.Builder
@@ -265,7 +266,7 @@ class SConfBuildTask(SCons.Taskmaster.AlwaysTask):
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 04b420a..7f04d00 100644
--- a/SCons/Script/Main.py
+++ b/SCons/Script/Main.py
@@ -31,6 +31,8 @@ some other module. If it's specific to the "scons" script invocation,
it goes here.
"""
+from __future__ import annotations
+
import SCons.compat
import importlib.util
@@ -42,7 +44,6 @@ import time
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 0853181..ef27b70 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 a2ef3b9..7cc9bea 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 re
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 @@ class SConsEnvironment(SCons.Environment.Base):
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 b04ebe5..4d6b249 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 @@ _separate_args = re.compile(r'(%s|\s+|[^\s$]+|\$)' % _dollar_exps_str)
_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 f221d15..dc9dcdd 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 c7e62b8..0bcb0ea 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 @@ else:
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 get_java_install_dirs(platform, version=None) -> List[str]:
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 faa92a7..a7bc927 100644
--- a/SCons/Tool/__init__.py
+++ b/SCons/Tool/__init__.py
@@ -33,10 +33,11 @@ one needs to use or tie in to this subsystem in order to roll their own
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 1967294..13bdca0 100644
--- a/SCons/Tool/jar.py
+++ b/SCons/Tool/jar.py
@@ -28,8 +28,9 @@ It will usually be imported through the generic SCons.Tool.Tool()
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.Node.FS import _my_normcase
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 527f91c..5e77ea6 100644
--- a/SCons/Tool/lex.py
+++ b/SCons/Tool/lex.py
@@ -31,9 +31,10 @@ It will usually be imported through the generic SCons.Tool.Tool()
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 5c56e49..ff006c0 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 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
+
+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 cdce928..24d439e 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 7a4ddfc..bfd82f6 100644
--- a/SCons/Tool/yacc.py
+++ b/SCons/Tool/yacc.py
@@ -34,9 +34,10 @@ It will usually be imported through the generic SCons.Tool.Tool()
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 b1c0108..0f457c3 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
import unittest.mock
import warnings
from collections import UserDict, UserList, UserString, namedtuple
-from typing import Union
import TestCmd
@@ -533,8 +534,8 @@ class UtilTestCase(unittest.TestCase):
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 @@ class UtilTestCase(unittest.TestCase):
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 @@ class UtilTestCase(unittest.TestCase):
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 @@ class UtilTestCase(unittest.TestCase):
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 28565da..0398c1f 100644
--- a/SCons/Util/__init__.py
+++ b/SCons/Util/__init__.py
@@ -49,6 +49,8 @@ in multiple places, rather then being topical only to one module/package.
# )
# (issue filed on this upstream, for now just be aware)
+from __future__ import annotations
+
import copy
import hashlib
import logging
@@ -59,7 +61,7 @@ import time
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 @@ class NodeList(UserList):
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 @@ else:
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 @@ if sys.platform == 'win32':
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 @@ elif os.name == 'os2':
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 2640ef5..9c97da6 100644
--- a/SCons/Util/envs.py
+++ b/SCons/Util/envs.py
@@ -9,10 +9,12 @@ Routines for working with environments and construction variables
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 @@ class MethodWrapper:
# 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 8ebf388..730f486 100644
--- a/SCons/Util/filelock.py
+++ b/SCons/Util/filelock.py
@@ -30,9 +30,10 @@ Usage::
# 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 @@ class FileLock:
# 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 @@ class FileLock:
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 566897a..016354c 100644
--- a/SCons/Util/hashes.py
+++ b/SCons/Util/hashes.py
@@ -8,10 +8,11 @@ SCons hash utility routines.
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 765458e..b95e395 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 @@ BaseStringTypes = str
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 5da5eb9..0000000
--- 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 815a4b7..573a024 100644
--- a/SCons/Variables/BoolVariable.py
+++ b/SCons/Variables/BoolVariable.py
@@ -32,7 +32,9 @@ Usage example::
...
"""
-from typing import Callable, Tuple, Union
+from __future__ import annotations
+
+from typing import Callable
import SCons.Errors
@@ -42,7 +44,7 @@ TRUE_STRINGS = ('y', 'yes', 'true', 't', '1', 'on', 'all')
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 3698e47..f154a13 100644
--- a/SCons/Variables/EnumVariable.py
+++ b/SCons/Variables/EnumVariable.py
@@ -43,7 +43,9 @@ Usage example::
...
"""
-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 2c79ee7..4ea7dc3 100644
--- a/SCons/Variables/ListVariable.py
+++ b/SCons/Variables/ListVariable.py
@@ -53,9 +53,11 @@ Usage example::
# 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 7271cfb..2ecedfe 100644
--- a/SCons/Variables/PackageVariable.py
+++ b/SCons/Variables/PackageVariable.py
@@ -50,9 +50,11 @@ Can be used as a replacement for autoconf's ``--with-xxx=yyy`` ::
... # 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 @@ __all__ = ['PackageVariable',]
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 43904e6..f840a95 100644
--- a/SCons/Variables/PathVariable.py
+++ b/SCons/Variables/PathVariable.py
@@ -71,10 +71,11 @@ Usage example::
)
"""
+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 @@ class _PathVariableClass:
# 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 2832526..2d16007 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 @@ class Variables:
# 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 @@ class Variables:
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 @@ class Variables:
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 @@ class Variables:
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 @@ class Variables:
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 d604659..b9f9cc1 100644
--- a/SCons/Warnings.py
+++ b/SCons/Warnings.py
@@ -61,8 +61,10 @@ Raising directly with an instance of a warning class bypasses the
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 @@ _warningAsException: bool = False
# 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 1e00257..5f3a49e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -84,6 +84,15 @@ extend-exclude = [
"SCons/Tool/docbook/docbook-xsl-1.76.1/",
]
+[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 220b490..3408d68 100755
--- a/runtest.py
+++ b/runtest.py
@@ -15,6 +15,8 @@ This script adds SCons/ and testing/ directories to PYTHONPATH,
performs test discovery and processes tests according to options.
"""
+from __future__ import annotations
+
import argparse
import itertools
import os
@@ -27,11 +29,11 @@ from abc import ABC, abstractmethod
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 @@ Environment Variables:
"""
# 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 bc447dd..77c04e6 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 @@ test = TestSCons.TestSCons()
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 243745d..7307078 100644
--- a/testing/framework/TestCmd.py
+++ b/testing/framework/TestCmd.py
@@ -295,6 +295,8 @@ version.
TestCmd.where_is('foo', 'PATH1;PATH2', '.suffix3;.suffix4')
"""
+from __future__ import annotations
+
__author__ = "Steven Knight <knight at baldmt dot com>"
__revision__ = "TestCmd.py 1.3.D001 2010/06/03 12:58:27 knight"
__version__ = "1.3"
@@ -323,7 +325,7 @@ import traceback
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 @@ class TestCmd:
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 @@ class TestCmd:
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 @@ class TestCmd:
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 @@ class TestCmd:
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 @@ class TestCmd:
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 @@ class TestCmd:
"""
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 @@ class TestCmd:
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 f5bb084..470dbcb 100644
--- a/testing/framework/TestCommon.py
+++ b/testing/framework/TestCommon.py
@@ -110,6 +110,8 @@ The TestCommon module also provides the following variables
"""
+from __future__ import annotations
+
__author__ = "Steven Knight <knight at baldmt dot com>"
__revision__ = "TestCommon.py 1.3.D001 2010/06/03 12:58:27 knight"
__version__ = "1.3"
@@ -121,7 +123,7 @@ import sys
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 @@ class TestCommon(TestCmd):
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 @@ class TestCommon(TestCmd):
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 @@ class TestCommon(TestCmd):
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 @@ class TestCommon(TestCmd):
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 @@ class TestCommon(TestCmd):
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 @@ class TestCommon(TestCmd):
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 @@ class TestCommon(TestCmd):
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 @@ class TestCommon(TestCmd):
file,
expect,
mode: str = 'rb',
- match: Optional[Callable] = None,
+ match: Callable | None = None,
message: str = "",
newline=None,
):
@@ -569,7 +571,7 @@ class TestCommon(TestCmd):
file,
golden_file,
mode: str = 'rb',
- match: Optional[Callable] = None,
+ match: Callable | None = None,
message: str = "",
newline=None,
) -> None:
@@ -609,7 +611,7 @@ class TestCommon(TestCmd):
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 @@ class TestCommon(TestCmd):
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 @@ class TestCommon(TestCmd):
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 @@ class TestCommon(TestCmd):
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 243be75..806b596 100644
--- a/testing/framework/TestSCons.py
+++ b/testing/framework/TestSCons.py
@@ -34,6 +34,8 @@ from those classes, as well as any overridden or additional methods or
attributes defined in this subclass.
"""
+from __future__ import annotations
+
import os
import re
import shutil
@@ -42,7 +44,6 @@ import time
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 @@ class TestSCons(TestCommon):
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 @@ class TestSCons(TestCommon):
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: