From e0826d93d1c465e53010dd04bf62d15332d4f467 Mon Sep 17 00:00:00 2001 From: Mats Wichmann Date: Wed, 11 Mar 2026 09:24:56 -0600 Subject: [PATCH 1/5] Do not duplicate Tool class creation If "default" is part of the tool list, the default.py tool will be run. This calls a function to generate the default tool list, which as a side effect creates a Tool instance for each tool examined. The tool then loops through the returned list, creating a Tool instance for each tool. To alleviate this duplication, the Tool instances are returned in the default list, and if an attempt is made to instantiate a Tool with the "name" argument being an instance already, it is returned as-is. In the default tool - use the routine the SCons.Platform module provides for getting the default tool list. It does the same thing as default.generate did directly, but this way "we're using the API". Signed-off-by: Mats Wichmann --- SCons/Tool/__init__.py | 88 ++++++++++++++++++++++++++++++++++-------- SCons/Tool/default.py | 3 +- 2 files changed, 74 insertions(+), 17 deletions(-) diff --git a/SCons/Tool/__init__.py b/SCons/Tool/__init__.py index 72fb26478c..8a5a843301 100644 --- a/SCons/Tool/__init__.py +++ b/SCons/Tool/__init__.py @@ -22,16 +22,31 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -"""SCons tool selection. - -Looks for modules that define a callable object that can modify a -construction environment as appropriate for a given tool (or tool chain). - -Note that because this subsystem just *selects* a callable that can -modify a construction environment, it's possible for people to define -their own "tool specification" in an arbitrary callable function. No -one needs to use or tie in to this subsystem in order to roll their own -tool specifications. +"""SCons tool subsystem. + +Tool specification modules are callable objects that modify construction +environments to dynamically enable specific types of builds. This module +provides the support for handling tool modules: + + - a Tool class to locate and load a tool module. + - lists of default tools for supported platform types and a mechanism to + select the available ones for the current platform. + - various rules for name remapping, special-cased toolchains, etc. + - utility functions for creating common Builders (eliminating duplication + when multiple tool modules could potentially create a Builder). + - create Scanners for common types used by the Builder utilities. + +This is not set up like a traditional Python package: this file implements +the part that other subsystems can import and call. The remaining files +are either dynamically loaded tool modules that present entry points +(``exists()`` and ``generate()``) called through the Tool instance, +or support files/common logic that can be imported by those tool +modules. Neither are expected to be directly imported by any other SCons +subsystem (test code may reach in and do so). + +Tool modules are simply callable objects that modify a construction +environment. You can define custom tool specifications in any callable +without needing to integrate with this subsystem. """ from __future__ import annotations @@ -50,6 +65,7 @@ import SCons.Scanner.LaTeX import SCons.Scanner.Prog import SCons.Scanner.SWIG +import SCons.Util from SCons.Tool.linkCommon import LibSymlinksActionFunction, LibSymlinksStrFun DefaultToolpath = [] @@ -108,7 +124,25 @@ class Tool: + """A class for loading and applying tool modules. + + *name* is looked up using standard paths plus any specified *toolpath*. + To avoid duplicate creation of instances, recognize if *name* + is actually an existing instance, if so, just return ourselves + without further setup. + + .. versionchanged:: NEXT_RELEASE + Accept an exsiting instance at creation time and don't duplicate it. + """ + + def __new__(cls, name, toolpath=None, **kwargs) -> None: + if isinstance(name, Tool): + return name + return super().__new__(cls) + def __init__(self, name, toolpath=None, **kwargs) -> None: + if isinstance(name, Tool): + return if toolpath is None: toolpath = [] @@ -270,6 +304,12 @@ def __call__(self, env, *args, **kw) -> None: def __str__(self) -> str: return self.name + def __eq__(self, other) -> bool: + return str(other) == self.name + + def __hash__(self) -> int: + return hash(self.name) + LibSymlinksAction = SCons.Action.Action(LibSymlinksActionFunction, LibSymlinksStrFun) @@ -671,18 +711,34 @@ def InstallVersionedLib(self, *args, **kw): def FindTool(tools, env): for tool in tools: + if not SCons.Util.is_String(tool): + # Already a Tool instance + if tool.exists(env): + return tool + continue t = Tool(tool) if t.exists(env): - return tool + return t return None def FindAllTools(tools, env): def ToolExists(tool, env=env): - return Tool(tool).exists(env) - - return list(filter(ToolExists, tools)) + if not SCons.Util.is_String(tool): + return tool.exists(env) + t = Tool(tool) + return t.exists(env) + results = [] + for tool in tools: + if not SCons.Util.is_String(tool): + if tool.exists(env): + results.append(tool) + continue + t = Tool(tool) + if t.exists(env): + results.append(t) + return results def tool_list(platform, env): other_plat_tools = [] @@ -773,7 +829,7 @@ def tool_list(platform, env): # XXX this logic about what tool provides what should somehow be # moved into the tool files themselves. - if c_compiler and c_compiler == 'mingw': + if c_compiler and str(c_compiler) == 'mingw': # MinGW contains a linker, C compiler, C++ compiler, # Fortran compiler, archiver and assembler: cxx_compiler = None @@ -783,7 +839,7 @@ def tool_list(platform, env): ar = None else: # Don't use g++ if the C compiler has built-in C++ support: - if c_compiler in ('msvc', 'intelc', 'icc'): + if str(c_compiler) in ('msvc', 'intelc', 'icc'): cxx_compiler = None else: cxx_compiler = FindTool(cxx_compilers, env) or cxx_compilers[0] diff --git a/SCons/Tool/default.py b/SCons/Tool/default.py index 50512c0b43..e05888bd89 100644 --- a/SCons/Tool/default.py +++ b/SCons/Tool/default.py @@ -29,11 +29,12 @@ selection method. """ +import SCons.Platform import SCons.Tool def generate(env) -> None: """Add default tools.""" - for t in SCons.Tool.tool_list(env['PLATFORM'], env): + for t in SCons.Platform.DefaultToolList(env['PLATFORM'], env): SCons.Tool.Tool(t)(env) def exists(env) -> bool: From ec30132538911a3b5cc79eab6324a7677ea98a04 Mon Sep 17 00:00:00 2001 From: Mats Wichmann Date: Sun, 12 Apr 2026 06:34:02 -0600 Subject: [PATCH 2/5] Minor tweaks in tool-init just an import and a precautionary "str" conversion Signed-off-by: Mats Wichmann --- CHANGES.txt | 2 ++ RELEASE.txt | 3 +++ SCons/Tool/__init__.py | 3 ++- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index eda2eac604..781823adae 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -77,6 +77,8 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER on a one-time uuid to make a path to the file. - Clarify VariantDir behavior when switching to not duplicate sources and tweak wording a bit. + - Don't duplicate the cration of Tool objects if "default" is part of + the tool list. RELEASE 4.10.1 - Sun, 16 Nov 2025 10:51:57 -0700 diff --git a/RELEASE.txt b/RELEASE.txt index 19ad44627a..1fa7a77d25 100644 --- a/RELEASE.txt +++ b/RELEASE.txt @@ -77,6 +77,9 @@ long function signature lines, and some linting-insipired cleanups. - Test suite: end to end tests don't use assert in result checks +- Don't duplicate the cration of Tool objects if "default" is part of + the tool list. + PACKAGING --------- diff --git a/SCons/Tool/__init__.py b/SCons/Tool/__init__.py index 8a5a843301..9952d3ec2f 100644 --- a/SCons/Tool/__init__.py +++ b/SCons/Tool/__init__.py @@ -55,6 +55,7 @@ import os import importlib.util +import SCons.Action import SCons.Builder import SCons.Errors import SCons.Node.FS @@ -288,7 +289,7 @@ def __call__(self, env, *args, **kw) -> None: kw.update(call_kw) else: kw = self.init_kw - env.AppendUnique(TOOLS=[self.name]) + env.AppendUnique(TOOLS=[str(self.name)]) if hasattr(self, 'options'): import SCons.Variables if 'options' not in env: From d8f3a737d758830b3af3f9f1a11b0044c4926360 Mon Sep 17 00:00:00 2001 From: William Deegan Date: Sun, 12 Apr 2026 14:13:04 -0700 Subject: [PATCH 3/5] [ci skip] Fix typos in CHANGES and RELEASE --- CHANGES.txt | 2 +- RELEASE.txt | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 781823adae..de36e18d77 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -77,7 +77,7 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER on a one-time uuid to make a path to the file. - Clarify VariantDir behavior when switching to not duplicate sources and tweak wording a bit. - - Don't duplicate the cration of Tool objects if "default" is part of + - Don't duplicate the creation of Tool objects if "default" is part of the tool list. diff --git a/RELEASE.txt b/RELEASE.txt index 1fa7a77d25..7b7a379a20 100644 --- a/RELEASE.txt +++ b/RELEASE.txt @@ -71,13 +71,13 @@ IMPROVEMENTS - Switch remaining "original style" docstring parameter listings to Google style. - Additional small tweaks to Environment.py type hints, fold some overly -long function signature lines, and some linting-insipired cleanups. +long function signature lines, and some linting-inspired cleanups. - Test framework: tweak module docstrings - Test suite: end to end tests don't use assert in result checks -- Don't duplicate the cration of Tool objects if "default" is part of +- Don't duplicate the creation of Tool objects if "default" is part of the tool list. @@ -105,7 +105,7 @@ DOCUMENTATION a warning for years, but now it's a fatal error). Affects only the API doc build. -- Improve covarage of API doc build by ignoring any setting of +- Improve coverage of API doc build by ignoring any setting of __all__ in a package and not showing inherited members from optparse. - All functions/classes/non-dunder methods in Environment now have docstrings. @@ -130,7 +130,7 @@ DEVELOPMENT - Update pyproject.toml to support Python 3.14 and remove restrictions on lxml version install - Unify internal "_null" sentinel usage. - Docbook tests: improve skip message, more clearly indicate which test - need actual installed system programs (add -live suffix). + needs actual installed system programs (add -live suffix). - Implement type hints for Environment and environment utilities. - MSVC: Added a host/target batch file configuration table for Visual From 1dda30623819a04539daa36261f04175b09a8f54 Mon Sep 17 00:00:00 2001 From: William Deegan Date: Sun, 12 Apr 2026 14:15:17 -0700 Subject: [PATCH 4/5] [ci skip] fixed typo --- SCons/Tool/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SCons/Tool/__init__.py b/SCons/Tool/__init__.py index 9952d3ec2f..47f7f7b423 100644 --- a/SCons/Tool/__init__.py +++ b/SCons/Tool/__init__.py @@ -133,7 +133,7 @@ class Tool: without further setup. .. versionchanged:: NEXT_RELEASE - Accept an exsiting instance at creation time and don't duplicate it. + Accept an existing instance at creation time and don't duplicate it. """ def __new__(cls, name, toolpath=None, **kwargs) -> None: From fdbd0eef09c5aefbfffbbca6688eba329fdc3ed8 Mon Sep 17 00:00:00 2001 From: Mats Wichmann Date: Thu, 16 Apr 2026 08:22:06 -0600 Subject: [PATCH 5/5] Remove unneded string convert in Tool Responding to review comment. --- SCons/Tool/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SCons/Tool/__init__.py b/SCons/Tool/__init__.py index 47f7f7b423..75bdd7c60d 100644 --- a/SCons/Tool/__init__.py +++ b/SCons/Tool/__init__.py @@ -289,7 +289,7 @@ def __call__(self, env, *args, **kw) -> None: kw.update(call_kw) else: kw = self.init_kw - env.AppendUnique(TOOLS=[str(self.name)]) + env.AppendUnique(TOOLS=[self.name]) if hasattr(self, 'options'): import SCons.Variables if 'options' not in env: