Upgrade to scons-4.10.1

This commit is contained in:
Artem Pavlenko 2026-01-28 09:34:18 +00:00
parent 48219e570d
commit 855e904280
1535 changed files with 722 additions and 397 deletions

4
scons/scons-README vendored
View File

@ -43,8 +43,10 @@ scons-local package, or any SCons package, at the SCons download page:
EXECUTION REQUIREMENTS
======================
Running SCons requires Python 3.6 or higher. There should be no other
Running SCons requires Python 3.7 or higher. There should be no other
dependencies or requirements to run standard SCons.
The last release to support Python 3.6 was 4.8.1.
The last release to support Python 3.5 was 4.2.0.
The default SCons configuration assumes use of the Microsoft Visual C++

View File

@ -32,15 +32,15 @@ The files are split into directories named by the first few
digits of the signature. The prefix length used for directory
names can be changed by this script.
"""
__revision__ = "scripts/scons-configure-cache.py 39a12f34d532ab2493e78a7b73aeab2250852790 Thu, 27 Mar 2025 11:44:24 -0700 bdbaddog"
__revision__ = "scripts/scons-configure-cache.py 055b01f429d58b686701a56df863a817c36bb103 Sun, 16 Nov 2025 14:18:20 -0700 bdbaddog"
__version__ = "4.9.1"
__version__ = "4.10.1"
__build__ = "39a12f34d532ab2493e78a7b73aeab2250852790"
__build__ = "055b01f429d58b686701a56df863a817c36bb103"
__buildsys__ = "M1Dog2021"
__date__ = "Thu, 27 Mar 2025 11:44:24 -0700"
__date__ = "Sun, 16 Nov 2025 14:18:20 -0700"
__developer__ = "bdbaddog"

View File

@ -114,6 +114,7 @@ import SCons.Warnings
from SCons.Debug import logInstanceCreation
from SCons.Errors import InternalError, UserError
from SCons.Executor import Executor
from SCons.Node import Node
class _Null:
pass
@ -487,10 +488,11 @@ class BuilderBase:
# fspath() is to catch PathLike paths. We avoid the simpler
# str(f) so as not to "lose" files that are already Nodes:
# TypeError: expected str, bytes or os.PathLike object, not File
with suppress(TypeError):
f = os.fspath(f)
if SCons.Util.is_String(f):
f = SCons.Util.adjustixes(f, pre, suf, ensure_suffix)
if not isinstance(f, Node):
with suppress(TypeError):
f = os.fspath(f)
if SCons.Util.is_String(f):
f = SCons.Util.adjustixes(f, pre, suf, ensure_suffix)
result.append(f)
return result
@ -699,7 +701,7 @@ class BuilderBase:
src_suffix = []
elif not SCons.Util.is_List(src_suffix):
src_suffix = [ src_suffix ]
self.src_suffix = [callable(suf) and suf or self.adjust_suffix(suf) for suf in src_suffix]
self.src_suffix = [suf if callable(suf) else self.adjust_suffix(suf) for suf in src_suffix]
def get_src_suffix(self, env):
"""Get the first src_suffix in the list of src_suffixes."""

View File

@ -649,7 +649,7 @@ class SubstitutionEnvironment:
args - filename strings or nodes to convert; nodes are just
added to the list without further processing.
node_factory - optional factory to create the nodes; if not
specified, will use this environment's ``fs.File method.
specified, will use this environment's ``fs.File`` method.
lookup_list - optional list of lookup functions to call to
attempt to find the file referenced by each *args*.
kw - keyword arguments that represent additional nodes to add.
@ -1295,14 +1295,31 @@ class Base(SubstitutionEnvironment):
if parse_flags:
self.MergeFlags(parse_flags)
def __getattr__(self, name):
"""Handle missing attribute in an environment.
Assume this is a builder that's not instantiated, becasue that has
been a common failure mode. Could also be a typo. Emit a
message about this to try to help. We can't get too clever,
other parts of SCons depend on seeing the :exc:`AttributeError` that
triggers this call, so all we do is produce our own message.
.. versionadded:: 4.10.0
"""
raise AttributeError(
f"Builder or other environment method {name!r} not found.\n"
"Check spelling, check external program exists in env['ENV']['PATH'],\n"
"and check that a suitable tool is being loaded"
) from None
#######################################################################
# Utility methods that are primarily for internal use by SCons.
# These begin with lower-case letters.
#######################################################################
def get_builder(self, name):
"""Fetch the builder with the specified name from the environment.
"""
"""Fetch the builder with the specified name from the environment."""
try:
return self._dict['BUILDERS'][name]
except KeyError:
@ -1755,8 +1772,8 @@ class Base(SubstitutionEnvironment):
(pretty-print) or ``<<non-serializable: function>>`` (JSON).
Args:
key: if omitted, format the whole dict of variables,
else format *key*(s) with the corresponding values.
key: variables to format together with their values.
If omitted, format the whole dict of variables,
format: specify the format to serialize to. ``"pretty"`` generates
a pretty-printed string, ``"json"`` a JSON-formatted string.

View File

@ -26,6 +26,7 @@
from __future__ import annotations
import collections
from contextlib import suppress
import SCons.Errors
import SCons.Memoize
@ -163,10 +164,6 @@ class Executor(metaclass=NoSlotsPyPy):
'builder_kw',
'_memo',
'lvars',
'_changed_sources_list',
'_changed_targets_list',
'_unchanged_sources_list',
'_unchanged_targets_list',
'action_list',
'_do_execute',
'_execute_str')
@ -193,49 +190,56 @@ class Executor(metaclass=NoSlotsPyPy):
return self.lvars
except AttributeError:
self.lvars = {
'CHANGED_SOURCES' : TSList(self._get_changed_sources),
'CHANGED_TARGETS' : TSList(self._get_changed_targets),
'SOURCE' : TSObject(self._get_source),
'SOURCES' : TSList(self._get_sources),
'TARGET' : TSObject(self._get_target),
'TARGETS' : TSList(self._get_targets),
'UNCHANGED_SOURCES' : TSList(self._get_unchanged_sources),
'UNCHANGED_TARGETS' : TSList(self._get_unchanged_targets),
'CHANGED_SOURCES': TSList(self._get_changed_sources),
'CHANGED_TARGETS': TSList(self._get_changed_targets),
'SOURCE': TSObject(self._get_source),
'SOURCES': TSList(self._get_sources),
'TARGET': TSObject(self._get_target),
'TARGETS': TSList(self._get_targets),
'UNCHANGED_SOURCES': TSList(self._get_unchanged_sources),
'UNCHANGED_TARGETS': TSList(self._get_unchanged_targets),
}
return self.lvars
def _get_changes(self) -> None:
cs = []
ct = []
us = []
ut = []
"""Populate all the changed/unchanged lists.
.. versionchanged:: 4.10.0
``_changed_sources``, ``_changed_targets``, ``_unchanged_sources``
and ``_unchanged_targets`` are no longer separate instance
attributes, but rather saved in the :attr:`_memo` dict.
"""
changed_sources = []
changed_targets = []
unchanged_sources = []
unchanged_targets = []
for b in self.batches:
# don't add targets marked always build to unchanged lists
# add to changed list as they always need to build
if not b.targets[0].always_build and b.targets[0].is_up_to_date():
us.extend(list(map(rfile, b.sources)))
ut.extend(b.targets)
unchanged_sources.extend(list(map(rfile, b.sources)))
unchanged_targets.extend(b.targets)
else:
cs.extend(list(map(rfile, b.sources)))
ct.extend(b.targets)
self._changed_sources_list = SCons.Util.NodeList(cs)
self._changed_targets_list = SCons.Util.NodeList(ct)
self._unchanged_sources_list = SCons.Util.NodeList(us)
self._unchanged_targets_list = SCons.Util.NodeList(ut)
changed_sources.extend(list(map(rfile, b.sources)))
changed_targets.extend(b.targets)
self._memo["_get_changed_sources"] = changed_sources
self._memo["_get_changed_targets"] = changed_targets
self._memo["_get_unchanged_sources"] = unchanged_sources
self._memo["_get_unchanged_targets"] = unchanged_targets
@SCons.Memoize.CountMethodCall
def _get_changed_sources(self, *args, **kw):
try:
return self._changed_sources_list
except AttributeError:
self._get_changes()
return self._changed_sources_list
with suppress(KeyError):
return self._memo["_get_changed_sources"]
self._get_changes() # sets the memo entry
return self._memo["_get_changed_sources"]
@SCons.Memoize.CountMethodCall
def _get_changed_targets(self, *args, **kw):
try:
return self._changed_targets_list
except AttributeError:
self._get_changes()
return self._changed_targets_list
with suppress(KeyError):
return self._memo["_get_changed_targets"]
self._get_changes() # sets the memo entry
return self._memo["_get_changed_targets"]
def _get_source(self, *args, **kw):
return rfile(self.batches[0].sources[0]).get_subst_proxy()
@ -249,19 +253,19 @@ class Executor(metaclass=NoSlotsPyPy):
def _get_targets(self, *args, **kw):
return SCons.Util.NodeList([n.get_subst_proxy() for n in self.get_all_targets()])
@SCons.Memoize.CountMethodCall
def _get_unchanged_sources(self, *args, **kw):
try:
return self._unchanged_sources_list
except AttributeError:
self._get_changes()
return self._unchanged_sources_list
with suppress(KeyError):
return self._memo["_get_unchanged_sources"]
self._get_changes() # sets the memo entry
return self._memo["_get_unchanged_sources"]
@SCons.Memoize.CountMethodCall
def _get_unchanged_targets(self, *args, **kw):
try:
return self._unchanged_targets_list
except AttributeError:
self._get_changes()
return self._unchanged_targets_list
with suppress(KeyError):
return self._memo["_get_unchanged_targets"]
self._get_changes() # sets the memo entry
return self._memo["_get_unchanged_targets"]
def get_action_targets(self):
if not self.action_list:
@ -585,6 +589,9 @@ class Null(metaclass=NoSlotsPyPy):
This might be able to disappear when we refactor things to
disassociate Builders from Nodes entirely, so we're not
going to worry about unit tests for this--at least for now.
Note the slots have to match :class:`Executor` exactly,
or the :meth:`_morph` will fail.
"""
__slots__ = ('pre_actions',
@ -595,10 +602,6 @@ class Null(metaclass=NoSlotsPyPy):
'builder_kw',
'_memo',
'lvars',
'_changed_sources_list',
'_changed_targets_list',
'_unchanged_sources_list',
'_unchanged_targets_list',
'action_list',
'_do_execute',
'_execute_str')

View File

@ -145,17 +145,29 @@ def initialize_do_splitdrive() -> None:
do_splitdrive = bool(os.path.splitdrive('X:/foo')[0])
if do_splitdrive:
# Note there's a little dilemma here - we should be able to use the
# Python os.path.splitdrive, which is well debugged over the years.
# For normal usage it should work, but we also want to as much as
# possible run the full testsuite on whichever platform, even if it's
# "wrong" for some feature. POSIX splitdrive doesn't do what we want
# when running the drive and UNC path tests, and the NT one isn't
# designed for use on non-win32. So for now stuck on our private one.
# _my_splitdrive = os.path.splitdrive
def _my_splitdrive(p):
if p[1:2] == ':':
return p[:2], p[2:]
if p[0:2] == '//':
# Note that we leave a leading slash in the path
# because UNC paths are always absolute.
# We leave a leading slash in the path because UNC paths
# are always absolute.
#
# TODO: returning "//" for the drive part is actually
# completely wrong. UNC paths must have minimum three
# slashes (if using '//server/share/path' style), or five
# ('//?/UNC/server/share/path)' and we should return the
# part up to but not including the next slash.
return '//', p[1:]
return '', p
# TODO: the os routine should work and be better debugged than ours,
# but unit test test_unc_path fails on POSIX platforms. Resolve someday.
# _my_splitdrive = os.path.splitdrive
# Keep some commonly used values in global variables to skip to
# module look-up costs.
@ -1011,7 +1023,7 @@ class Entry(Base):
def diskcheck_match(self) -> None:
pass
def disambiguate(self, must_exist=None):
def disambiguate(self, must_exist=False):
"""
"""
if self.isfile():
@ -1069,7 +1081,7 @@ class Entry(Base):
system, we check to see into what sort of subclass we should
morph this Entry."""
try:
self = self.disambiguate(must_exist=1)
self = self.disambiguate(must_exist=True)
except SCons.Errors.UserError:
# There was nothing on disk with which to disambiguate
# this entry. Leave it as an Entry, but return a null

View File

@ -215,7 +215,7 @@ def get_contents_dir(node):
contents.append('%s %s\n' % (n.get_csig(), n.name))
return ''.join(contents)
def get_contents_file(node):
def get_contents_file(node) -> bytes:
if not node.rexists():
return b''
fname = node.rfile().get_abspath()
@ -560,6 +560,12 @@ class Node(metaclass=NoSlotsPyPy):
'_func_target_from_source']
class Attrs:
"""A generic place to store extra information about the Node.
Defines ``__slots__`` for performance, but different consumers
define their own attributes, so to avoid having to collect them
all here, we add a ``__dict__`` slot to get dynamic attributes.
"""
__slots__ = ('shared', '__dict__')
@ -622,6 +628,9 @@ class Node(metaclass=NoSlotsPyPy):
# what line in what file created the node, for example).
Annotate(self)
def __fspath__(self) -> str:
return str(self)
def disambiguate(self, must_exist: bool = False):
return self

View File

@ -43,14 +43,22 @@ their own platform definition.
import SCons.compat
import atexit
import contextlib
import importlib
import locale
import os
import sys
import tempfile
import SCons.Action
import SCons.Errors
import SCons.Platform
import SCons.Subst
import SCons.Tool
import SCons.Util
TEMPFILE_DEFAULT_ENCODING = "utf-8"
def platform_default():
@ -66,15 +74,15 @@ def platform_default():
if osname == 'posix':
if sys.platform == 'cygwin':
return 'cygwin'
elif sys.platform.find('irix') != -1:
elif 'irix' in sys.platform:
return 'irix'
elif sys.platform.find('sunos') != -1:
elif 'sunos' in sys.platform:
return 'sunos'
elif sys.platform.find('hp-ux') != -1:
elif 'hp-ux' in sys.platform:
return 'hpux'
elif sys.platform.find('aix') != -1:
elif 'aix' in sys.platform:
return 'aix'
elif sys.platform.find('darwin') != -1:
elif 'darwin' in sys.platform:
return 'darwin'
else:
return 'posix'
@ -242,6 +250,25 @@ class TempFileMunge:
if cmdlist is not None:
return cmdlist
# try encoding the tempfile data before creating the file -
# avoid orphaned files
tempfile_esc_func = env.get('TEMPFILEARGESCFUNC', SCons.Subst.quote_spaces)
args = [tempfile_esc_func(arg) for arg in cmd[1:]]
join_char = env.get('TEMPFILEARGJOIN', ' ')
contents = join_char.join(args) + "\n"
encoding = env.get('TEMPFILEENCODING', TEMPFILE_DEFAULT_ENCODING)
try:
tempfile_contents = bytes(contents, encoding=encoding)
except (UnicodeError, LookupError, TypeError):
exc_type, exc_value, _ = sys.exc_info()
if 'TEMPFILEENCODING' in env:
encoding_msg = "env['TEMPFILEENCODING']"
else:
encoding_msg = "default"
err_msg = f"tempfile encoding error: [{exc_type.__name__}] {exc_value!s}"
err_msg += f"\n {type(self).__name__} encoding: {encoding_msg} = {encoding!r}"
raise SCons.Errors.UserError(err_msg)
# Default to the .lnk suffix for the benefit of the Phar Lap
# linkloc linker, which likes to append an .lnk suffix if
# none is given.
@ -256,32 +283,21 @@ class TempFileMunge:
else:
tempfile_dir = None
fd, tmp = tempfile.mkstemp(suffix, dir=tempfile_dir, text=True)
fd, tmp = tempfile.mkstemp(suffix, dir=tempfile_dir)
try:
os.write(fd, tempfile_contents)
finally:
os.close(fd)
native_tmp = SCons.Util.get_native_path(tmp)
# arrange for cleanup on exit:
def tmpfile_cleanup(file) -> None:
os.remove(file)
with contextlib.suppress(FileNotFoundError):
os.remove(file)
atexit.register(tmpfile_cleanup, tmp)
if env.get('SHELL', None) == 'sh':
# The sh shell will try to escape the backslashes in the
# path, so unescape them.
native_tmp = native_tmp.replace('\\', r'\\\\')
if 'TEMPFILEPREFIX' in env:
prefix = env.subst('$TEMPFILEPREFIX')
else:
prefix = "@"
tempfile_esc_func = env.get('TEMPFILEARGESCFUNC', SCons.Subst.quote_spaces)
args = [tempfile_esc_func(arg) for arg in cmd[1:]]
join_char = env.get('TEMPFILEARGJOIN', ' ')
os.write(fd, bytearray(join_char.join(args) + "\n", encoding="utf-8"))
os.close(fd)
# XXX Using the SCons.Action.print_actions value directly
# like this is bogus, but expedient. This class should
# really be rewritten as an Action that defines the
@ -311,10 +327,18 @@ class TempFileMunge:
)
self._print_cmd_str(target, source, env, cmdstr)
if env.get('SHELL', None) == 'sh':
# The sh shell will try to escape the backslashes in the
# path, so unescape them.
native_tmp = native_tmp.replace('\\', r'\\\\')
if 'TEMPFILEPREFIX' in env:
prefix = env.subst('$TEMPFILEPREFIX')
else:
prefix = "@"
cmdlist = [cmd[0], prefix + native_tmp]
# Store the temporary file command list into the target Node.attributes
# to avoid creating two temporary files one for print and one for execute.
# to avoid creating separate temporary files for print and execute.
if node is not None:
try:
# Storing in tempfile_cmdlist by self.cmd provided when intializing

View File

@ -21,7 +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.
"""'Platform" support for a Python virtualenv."""
"""Platform support for a Python virtualenv.
This is support code, not a loadable Platform module.
"""
from __future__ import annotations
import os
import sys
@ -41,70 +46,85 @@ def _ignore_virtualenv_default():
enable_virtualenv = _enable_virtualenv_default()
ignore_virtualenv = _ignore_virtualenv_default()
virtualenv_variables = ['VIRTUAL_ENV', 'PIPENV_ACTIVE']
# Variables to export:
# - Python docs:
# When a virtual environment has been activated, the VIRTUAL_ENV environment
# variable is set to the path of the environment. Since explicitly
# activating a virtual environment is not required to use it, VIRTUAL_ENV
# cannot be relied upon to determine whether a virtual environment is being
# used.
# - pipenv: shell sets PIPENV_ACTIVE, cannot find it documented.
# Any others we should include?
VIRTUALENV_VARIABLES = ['VIRTUAL_ENV', 'PIPENV_ACTIVE']
def _running_in_virtualenv():
"""Returns True if scons is executed within a virtualenv"""
# see https://stackoverflow.com/a/42580137
return (hasattr(sys, 'real_prefix') or
(hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix))
def _running_in_virtualenv() -> bool:
"""Check whether scons is running in a virtualenv."""
# TODO: the virtualenv command used to inject a sys.real_prefix before
# Python started officially tracking virtualenvs with the venv module.
# All Pythons since 3.3 use sys.base_prefix for tracking (PEP 405);
# virtualenv has retired their old behavior and now only makes
# venv-style virtualenvs. We're now using the detection suggested in
# PEP 668, and should be able to drop the real_prefix check soon.
return sys.base_prefix != sys.prefix or hasattr(sys, 'real_prefix')
def _is_path_in(path, base) -> bool:
"""Returns true if **path** is located under the **base** directory."""
if not path or not base: # empty path may happen, base too
def _is_path_in(path: str, base: str) -> bool:
"""Check if *path* is located under the *base* directory."""
if not path or not base: # empty path or base are possible
return False
rp = os.path.relpath(path, base)
return (not rp.startswith(os.path.pardir)) and (not rp == os.path.curdir)
def _inject_venv_variables(env) -> None:
"""Copy any set virtualenv variables from ``os.environ`` to *env*."""
if 'ENV' not in env:
env['ENV'] = {}
ENV = env['ENV']
for name in virtualenv_variables:
for name in VIRTUALENV_VARIABLES:
try:
ENV[name] = os.environ[name]
except KeyError:
pass
def _inject_venv_path(env, path_list=None) -> None:
"""Modify environment such that SCons will take into account its virtualenv
when running external tools."""
"""Insert virtualenv-related paths from ``os.environe`` to *env*."""
if path_list is None:
path_list = os.getenv('PATH')
env.PrependENVPath('PATH', select_paths_in_venv(path_list))
def select_paths_in_venv(path_list):
"""Returns a list of paths from **path_list** which are under virtualenv's
home directory."""
def select_paths_in_venv(path_list: str | list[str]) -> list[str]:
"""Filter *path_list*, returning values under the virtualenv."""
if SCons.Util.is_String(path_list):
path_list = path_list.split(os.path.pathsep)
# Find in path_list the paths under the virtualenv's home
return [path for path in path_list if IsInVirtualenv(path)]
def ImportVirtualenv(env) -> None:
"""Copies virtualenv-related environment variables from OS environment
to ``env['ENV']`` and prepends virtualenv's PATH to ``env['ENV']['PATH']``.
"""
"""Add virtualenv information to *env*."""
_inject_venv_variables(env)
_inject_venv_path(env)
def Virtualenv():
"""Returns path to the virtualenv home if scons is executing within a
virtualenv or None, if not."""
def Virtualenv() -> str:
"""Return whether operating in a virtualenv.
Returns the path to the virtualenv home if scons is executing
within a virtualenv, else and empty string.
"""
if _running_in_virtualenv():
return sys.prefix
return None
return ""
def IsInVirtualenv(path):
"""Returns True, if **path** is under virtualenv's home directory. If not,
or if we don't use virtualenv, returns False."""
def IsInVirtualenv(path: str) -> bool:
"""Check whether *path* is under the virtualenv's directory.
Returns ``False`` if not using a virtualenv.
"""
return _is_path_in(path, Virtualenv())

View File

@ -136,10 +136,10 @@ def piped_spawn(sh, escape, cmd, args, env, stdout, stderr):
stderrRedirected = False
for arg in args:
# are there more possibilities to redirect stdout ?
if arg.find(">", 0, 1) != -1 or arg.find("1>", 0, 2) != -1:
if arg.startswith(">")or arg.startswith("1>"):
stdoutRedirected = True
# are there more possibilities to redirect stderr ?
if arg.find("2>", 0, 2) != -1:
if arg.startswith("2>"):
stderrRedirected = True
# redirect output of non-redirected streams to our tempfiles

View File

@ -315,7 +315,8 @@ class LaTeX(ScannerBase):
for n in try_names:
for search_path in search_paths:
paths = tuple([d.Dir(inc_subdir) for d in search_path])
paths = tuple([d.Dir(inc_subdir) for d in search_path] +
list(search_path))
i = SCons.Node.FS.find_file(n, paths)
if i:
return i, include
@ -407,16 +408,16 @@ class LaTeX(ScannerBase):
include = queue.pop()
inc_type, inc_subdir, inc_filename = include
try:
if seen[inc_filename]:
continue
except KeyError:
seen[inc_filename] = True
#
# Handle multiple filenames in include[1]
#
n, i = self.find_include(include, source_dir, path_dict)
try:
if seen[str(n)]:
continue
except KeyError:
seen[str(n)] = True
if n is None:
# Do not bother with 'usepackage' warnings, as they most
# likely refer to system-level files
@ -432,7 +433,9 @@ class LaTeX(ScannerBase):
# recurse down
queue.extend(self.scan(n, inc_subdir))
return [pair[1] for pair in sorted(nodes)]
# Don't sort on a tuple where the second element is an object, just
# use the first element of the tuple which is the "sort_key" value
return [pair[1] for pair in sorted(nodes, key=lambda n: n[0])]
# Local Variables:
# tab-width:4

View File

@ -144,7 +144,7 @@ class Progressor:
self.func = obj
elif SCons.Util.is_List(obj):
self.func = self.spinner
elif obj.find(self.target_string) != -1:
elif self.target_string in obj:
self.func = self.replace_string
else:
self.func = self.string
@ -465,19 +465,28 @@ class TreePrinter:
self.prune = prune
self.status = status
self.sLineDraw = sLineDraw
def get_all_children(self, node):
return node.all_children()
def get_derived_children(self, node):
children = node.all_children(None)
return [x for x in children if x.has_builder()]
def display(self, t) -> None:
if self.derived:
func = self.get_derived_children
else:
func = self.get_all_children
s = self.status and 2 or 0
SCons.Util.print_tree(t, func, prune=self.prune, showtags=s, lastChild=True, singleLineDraw=self.sLineDraw)
s = 2 if self.status else 0
SCons.Util.print_tree(
t,
func,
prune=self.prune,
showtags=s,
lastChild=True,
singleLineDraw=self.sLineDraw,
)
def python_version_string():
return sys.version.split()[0]
@ -519,7 +528,7 @@ def AddOption(*args, **kw) -> SConsOption:
"""Add a local option to the option parser - Public API.
If the SCons-specific *settable* kwarg is true (default ``False``),
the option will allow calling :func:``SetOption`.
the option will allow calling :func:`SetOption`.
.. versionchanged:: 4.8.0
The *settable* parameter added to allow including the new option
@ -635,7 +644,7 @@ def find_deepest_user_frame(tb):
# of SCons:
for frame in tb:
filename = frame[0]
if filename.find(os.sep+'SCons'+os.sep) == -1:
if f'{os.sep}SCons{os.sep}' not in filename:
return frame
return tb[0]

View File

@ -30,16 +30,16 @@ something that we expect other software to want to use, it should go in
some other module. If it's specific to the "scons" script invocation,
it goes here.
"""
import time
start_time = time.time()
from __future__ import annotations
import collections
import itertools
import os
import sys
import time
from io import StringIO
import sys
start_time = time.time()
# Special chicken-and-egg handling of the "--debug=memoizer" flag:
#
@ -135,7 +135,7 @@ DebugOptions = Main.DebugOptions
#profiling = Main.profiling
#repositories = Main.repositories
from . import SConscript as _SConscript
from . import SConscript as _SConscript # pylint: disable=import-outside-toplevel
call_stack = _SConscript.call_stack
@ -208,13 +208,15 @@ DEFAULT_TARGETS = []
# own targets to BUILD_TARGETS.
_build_plus_default = TargetList()
def _Add_Arguments(alist) -> None:
def _Add_Arguments(alist: list[str]) -> None:
"""Add value(s) to ``ARGLIST`` and ``ARGUMENTS``."""
for arg in alist:
a, b = arg.split('=', 1)
ARGUMENTS[a] = b
ARGLIST.append((a, b))
def _Add_Targets(tlist) -> None:
def _Add_Targets(tlist: list[str]) -> None:
"""Add value(s) to ``COMMAND_LINE_TARGETS`` and ``BUILD_TARGETS``."""
if tlist:
COMMAND_LINE_TARGETS.extend(tlist)
BUILD_TARGETS.extend(tlist)
@ -224,6 +226,52 @@ def _Add_Targets(tlist) -> None:
_build_plus_default._add_Default = _build_plus_default._do_nothing
_build_plus_default._clear = _build_plus_default._do_nothing
def _Remove_Argument(aarg: str) -> None:
"""Remove *aarg* from ``ARGLIST`` and ``ARGUMENTS``.
Used to remove a variables-style argument that is no longer valid.
This can happpen because the command line is processed once early,
before we see any :func:`SCons.Script.Main.AddOption` calls, so we
could not recognize it belongs to an option and is not a standalone
variable=value argument.
.. versionadded:: 4.10.0
"""
if aarg:
a, b = aarg.split('=', 1)
if (a, b) in ARGLIST:
ARGLIST.remove((a, b))
ARGUMENTS.pop(a, None)
# ARGLIST might have had multiple values for 'a'. If there
# are any left, put that in ARGUMENTS, keeping the last one
# (retaining cmdline order)
for item in ARGLIST:
if item[0] == a:
ARGUMENTS[a] = item[1]
def _Remove_Target(targ: str) -> None:
"""Remove *targ* from ``BUILD_TARGETS`` and ``COMMAND_LINE_TARGETS``.
Used to remove a target that is no longer valid. This can happpen
because the command line is processed once early, before we see any
:func:`SCons.Script.Main.AddOption` calls, so we could not recognize
it belongs to an option and is not a standalone target argument.
Since we are "correcting an error", we also have to fix up the internal
:data:`_build_plus_default` list.
.. versionadded:: 4.10.0
"""
if targ:
if targ in COMMAND_LINE_TARGETS:
COMMAND_LINE_TARGETS.remove(targ)
if targ in BUILD_TARGETS:
BUILD_TARGETS.remove(targ)
if targ in _build_plus_default:
_build_plus_default.remove(targ)
def _Set_Default_Targets_Has_Been_Called(d, fs):
return DEFAULT_TARGETS

View File

@ -25,11 +25,12 @@
from __future__ import annotations
import collections
import re
from collections import UserList, UserString
from inspect import signature, Parameter
import SCons.Errors
import SCons.Util
from SCons.Util import is_String, is_Sequence
# Indexed by the SUBST_* constants below.
@ -57,10 +58,12 @@ def raise_exception(exception, target, s):
class Literal:
"""A wrapper for a string. If you use this object wrapped
around a string, then it will be interpreted as literal.
When passed to the command interpreter, all special
characters will be escaped."""
"""A string wrapper for a string to prevent expansion.
If you use this object wrapped around a string, then it will
be interpreted as literal. When passed to the command interpreter,
all special characters will be escaped.
"""
def __init__(self, lstr) -> None:
self.lstr = lstr
@ -76,7 +79,7 @@ class Literal:
def is_literal(self) -> bool:
return True
def __eq__(self, other):
def __eq__(self, other) -> bool:
if not isinstance(other, Literal):
return False
return self.lstr == other.lstr
@ -84,9 +87,17 @@ class Literal:
def __neq__(self, other) -> bool:
return not self.__eq__(other)
def __hash__(self):
def __hash__(self) -> int:
return hash(self.lstr)
def __contains__(self, key) -> bool:
# handle cases where key is not a str
if isinstance(key, UserString):
key = key.data
if isinstance(key, Literal):
key = key.lstr
return key in self.lstr
class SpecialAttrWrapper:
"""This is a wrapper for what we call a 'Node special attribute.'
This is any of the attributes of a Node that we can reference from
@ -126,21 +137,23 @@ def quote_spaces(arg):
else:
return str(arg)
class CmdStringHolder(collections.UserString):
"""This is a special class used to hold strings generated by
scons_subst() and scons_subst_list(). It defines a special method
escape(). When passed a function with an escape algorithm for a
particular platform, it will return the contained string with the
proper escape sequences inserted.
class CmdStringHolder(UserString):
"""Holder for substituted strings intended for the command line.
Holds strings generated by :func:`scons_subst`, :func:`scons_subst_list`.
Defines an :meth:`escape` method which allows supplying
platform-specific escape and quote functions. The contained string
will be escaped, quoted, or neither depending on the value of
*literal* and the string contents.
"""
def __init__(self, cmd, literal=None) -> None:
def __init__(self, cmd, literal: bool = False) -> None:
super().__init__(cmd)
self.literal = literal
def is_literal(self) -> bool:
return self.literal
def escape(self, escape_func, quote_func=quote_spaces):
def escape(self, escape_func, quote_func=quote_spaces) -> str:
"""Escape the string with the supplied function. The
function is expected to take an arbitrary string, then
return it with all special characters escaped and ready
@ -149,7 +162,6 @@ class CmdStringHolder(collections.UserString):
After calling this function, the next call to str() will
return the escaped string.
"""
if self.is_literal():
return escape_func(self.data)
elif ' ' in self.data or '\t' in self.data:
@ -157,7 +169,7 @@ class CmdStringHolder(collections.UserString):
else:
return self.data
def escape_list(mylist, escape_func):
def escape_list(mylist, escape_func) -> list[str]:
"""Escape a list of arguments by running the specified escape_func
on every object in the list that has an escape() method."""
def escape(obj, escape_func=escape_func):
@ -201,15 +213,15 @@ class NLWrapper:
_create_nodelist = _gen_nodelist
class Targets_or_Sources(collections.UserList):
class Targets_or_Sources(UserList):
"""A class that implements $TARGETS or $SOURCES expansions by in turn
wrapping a NLWrapper. This class handles the different methods used
to access the list, calling the NLWrapper to create proxies on demand.
Note that we subclass collections.UserList purely so that the
is_Sequence() function will identify an object of this class as
a list during variable expansion. We're not really using any
collections.UserList methods in practice.
Note that we subclass :class:`~collections.UserList` purely so that the
:func:`~SCons.Util.is_Sequence` function will identify an object of this
class as a list during variable expansion. We're not really using any
:class:`~collections.UserList` methods in practice.
"""
def __init__(self, nl) -> None:
self.nl = nl
@ -476,7 +488,7 @@ class StringSubber:
return self.expand(args, lvars)
class ListSubber(collections.UserList):
class ListSubber(UserList):
"""A class to construct the results of a scons_subst_list() call.
Like StringSubber, this class binds a specific construction
@ -549,8 +561,8 @@ class ListSubber(collections.UserList):
self.close_strip('$)')
else:
key = s[1:]
if key[0] == '{' or key.find('.') >= 0:
if key[0] == '{':
if key.startswith('{') or '.' in key:
if key.startswith('{'):
key = key[1:-1]
# Store for error messages if we fail to expand the
@ -650,7 +662,7 @@ class ListSubber(collections.UserList):
"""Arrange for the next word to start a new line. This
is like starting a new word, except that we have to append
another line to the result."""
collections.UserList.append(self, [])
UserList.append(self, [])
self.next_word()
def this_word(self) -> None:
@ -707,7 +719,7 @@ class ListSubber(collections.UserList):
y = self.conv(y)
if is_String(y):
#y = CmdStringHolder(y, literal1 or literal2)
y = CmdStringHolder(y, None)
y = CmdStringHolder(y, literal=False)
self[-1][-1] = y
def add_new_word(self, x) -> None:
@ -867,7 +879,7 @@ def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={
if mode == SUBST_SIG:
result = _list_remove[mode](remove.split(result))
if result is None:
raise SCons.Errors.UserError("Unbalanced $(/$) in: " + res)
raise SCons.Errors.UserError(f"Unbalanced $(/$) in: {res}")
result = ' '.join(result)
else:
result = remove.sub('', result)
@ -950,7 +962,7 @@ def scons_subst_once(strSubst, env, key):
We do this with some straightforward, brute-force code here...
"""
if isinstance(strSubst, str) and strSubst.find('$') < 0:
if isinstance(strSubst, str) and '$' not in strSubst:
return strSubst
matchlist = ['$' + key, '${' + key + '}']

View File

@ -144,6 +144,7 @@ if CONFIG_CACHE_FORCE_DEFAULT_ARGUMENTS:
# MSVC batch file arguments:
#
# VS2026: UWP, SDK, TOOLSET, SPECTRE
# VS2022: UWP, SDK, TOOLSET, SPECTRE
# VS2019: UWP, SDK, TOOLSET, SPECTRE
# VS2017: UWP, SDK, TOOLSET, SPECTRE

View File

@ -36,6 +36,8 @@ installation is used only when no other installation is detected.
+---------+---------+----------------------------------------------------------+
| Product | VCVer | Priority |
+=========+=========+==========================================================+
| VS2026 | 14.5 | Enterprise, Professional, Community, BuildTools |
+=========+=========+==========================================================+
| VS2022 | 14.3 | Enterprise, Professional, Community, BuildTools |
+---------+---------+----------------------------------------------------------+
| VS2019 | 14.2 | Enterprise, Professional, Community, BuildTools |
@ -224,6 +226,7 @@ Example usage:
::
for version in [
'14.5',
'14.4',
'14.3',
'14.2',
@ -360,6 +363,8 @@ Supported MSVC batch file arguments by product:
+---------+---------+--------+---------+---------+
| Product | UWP | SDK | Toolset | Spectre |
+=========+=========+========+=========+=========+
| VS2026 | X | X | X | X |
+=========+=========+========+=========+=========+
| VS2022 | X | X | X | X |
+---------+---------+--------+---------+---------+
| VS2019 | X | X | X | X |
@ -514,6 +519,8 @@ BuildSeries Versions
+-------------+-------+-------+
| BuildSeries | VCVER | CLVER |
+=============+=======+=======+
| 14.5 | 14.5X | 19.5 |
+-------------+-------+-------+
| 14.4 | 14.4X | 19.4 |
+-------------+-------+-------+
| 14.3 | 14.3X | 19.3 |
@ -547,6 +554,8 @@ BuildTools Versions
+------------+-------------+----------+
| BuildTools | BuildSeries | MSVCRT |
+============+=============+==========+
| v145 | 14.5 | 140/ucrt |
+------------+-------------+----------+
| v143 | 14.4, 14.3 | 140/ucrt |
+------------+-------------+----------+
| v142 | 14.2 | 140/ucrt |
@ -575,33 +584,35 @@ BuildTools Versions
Product Versions
----------------
+----------+-------+-------+-----------+------------------------+
| Product | VSVER | SCons | SDK | BuildTools |
+==========+=======+=======+===========+========================+
| 2022 | 17.0 | 14.3 | 10.0, 8.1 | v143, v142, v141, v140 |
+----------+-------+-------+-----------+------------------------+
| 2019 | 16.0 | 14.2 | 10.0, 8.1 | v142, v141, v140 |
+----------+-------+-------+-----------+------------------------+
| 2017 | 15.0 | 14.1 | 10.0, 8.1 | v141, v140 |
+----------+-------+-------+-----------+------------------------+
| 2015 | 14.0 | 14.0 | 10.0, 8.1 | v140 |
+----------+-------+-------+-----------+------------------------+
| 2013 | 12.0 | 12.0 | | v120 |
+----------+-------+-------+-----------+------------------------+
| 2012 | 11.0 | 11.0 | | v110 |
+----------+-------+-------+-----------+------------------------+
| 2010 | 10.0 | 10.0 | | v100 |
+----------+-------+-------+-----------+------------------------+
| 2008 | 9.0 | 9.0 | | v90 |
+----------+-------+-------+-----------+------------------------+
| 2005 | 8.0 | 8.0 | | v80 |
+----------+-------+-------+-----------+------------------------+
| 2003.NET | 7.1 | 7.1 | | v71 |
+----------+-------+-------+-----------+------------------------+
| 2002.NET | 7.0 | 7.0 | | v70 |
+----------+-------+-------+-----------+------------------------+
| 6.0 | 6.0 | 6.0 | | v60 |
+----------+-------+-------+-----------+------------------------+
+----------+-------+-------+-----------+------------------------------+
| Product | VSVER | SCons | SDK | BuildTools |
+==========+=======+=======+===========+==============================+
| 2026 | 18.0 | 14.5 | 10.0, 8.1 | v145, v143, v142, v141, v140 |
+----------+-------+-------+-----------+------------------------------+
| 2022 | 17.0 | 14.3 | 10.0, 8.1 | v143, v142, v141, v140 |
+----------+-------+-------+-----------+------------------------------+
| 2019 | 16.0 | 14.2 | 10.0, 8.1 | v142, v141, v140 |
+----------+-------+-------+-----------+------------------------------+
| 2017 | 15.0 | 14.1 | 10.0, 8.1 | v141, v140 |
+----------+-------+-------+-----------+------------------------------+
| 2015 | 14.0 | 14.0 | 10.0, 8.1 | v140 |
+----------+-------+-------+-----------+------------------------------+
| 2013 | 12.0 | 12.0 | | v120 |
+----------+-------+-------+-----------+------------------------------+
| 2012 | 11.0 | 11.0 | | v110 |
+----------+-------+-------+-----------+------------------------------+
| 2010 | 10.0 | 10.0 | | v100 |
+----------+-------+-------+-----------+------------------------------+
| 2008 | 9.0 | 9.0 | | v90 |
+----------+-------+-------+-----------+------------------------------+
| 2005 | 8.0 | 8.0 | | v80 |
+----------+-------+-------+-----------+------------------------------+
| 2003.NET | 7.1 | 7.1 | | v71 |
+----------+-------+-------+-----------+------------------------------+
| 2002.NET | 7.0 | 7.0 | | v70 |
+----------+-------+-------+-----------+------------------------------+
| 6.0 | 6.0 | 6.0 | | v60 |
+----------+-------+-------+-----------+------------------------------+
SCons Implementation Notes
@ -713,7 +724,7 @@ into the current document format as appropriate.
Problems:
- For VS >= 2017, VS and VS are not 1:1, there can be many VC for one VS
- For VS >= 2017, VC and VS are not 1:1, there can be many VC for one VS
- For vswhere-ready versions, detection does not proceed beyond the
product level ("2019") into individual "features" (individual msvc)
- As documented for MSVC_VERSION, compilers can only be requested if versions

View File

@ -30,6 +30,7 @@ import json
import os
import re
import sys
import time
from contextlib import suppress
from subprocess import DEVNULL, PIPE
from pathlib import Path
@ -38,6 +39,44 @@ import SCons.Errors
import SCons.Util
import SCons.Warnings
# TODO: Hard-coded list of the variables that (may) need to be
# imported from os.environ[] for the chain of development batch
# files to execute correctly. One call to vcvars*.bat may
# end up running a dozen or more scripts, changes not only with
# each release but with what is installed at the time. We think
# in modern installations most are set along the way and don't
# need to be picked from the env, but include these for safety's sake.
# Any VSCMD variables definitely are picked from the env and
# control execution in interesting ways.
# Note these really should be unified - either controlled by vs.py,
# or synced with the the common_tools_var # settings in vs.py.
VS_VC_VARS = [
'COMSPEC', # path to "shell"
'OS', # name of OS family: Windows_NT or undefined (95/98/ME)
'VS180COMNTOOLS', # path to common tools for given version
'VS170COMNTOOLS',
'VS160COMNTOOLS',
'VS150COMNTOOLS',
'VS140COMNTOOLS',
'VS120COMNTOOLS',
'VS110COMNTOOLS',
'VS100COMNTOOLS',
'VS90COMNTOOLS',
'VS80COMNTOOLS',
'VS71COMNTOOLS',
'VSCOMNTOOLS',
'MSDevDir',
'VSCMD_DEBUG', # enable logging and other debug aids
'VSCMD_SKIP_SENDTELEMETRY',
'windir', # windows directory (SystemRoot not available in 95/98/ME)
'VCPKG_DISABLE_METRICS',
'VCPKG_ROOT',
'POWERSHELL_TELEMETRY_OPTOUT',
'PSDisableModuleAnalysisCacheCleanup',
'PSModuleAnalysisCachePath',
]
class MSVCCacheInvalidWarning(SCons.Warnings.WarningOnByDefault):
pass
@ -305,6 +344,112 @@ def _force_vscmd_skip_sendtelemetry(env):
return True
class _PathManager:
_PSEXECUTABLES = (
"pwsh.exe",
"powershell.exe",
)
_PSMODULEPATH_MAP = {os.path.normcase(os.path.abspath(p)): p for p in [
# os.path.expandvars(r"%USERPROFILE%\Documents\PowerShell\Modules"), # current user
os.path.expandvars(r"%ProgramFiles%\PowerShell\Modules"), # all users
os.path.expandvars(r"%ProgramFiles%\PowerShell\7\Modules"), # installation location
# os.path.expandvars(r"%USERPROFILE%\Documents\WindowsPowerShell\Modules"), # current user
os.path.expandvars(r"%ProgramFiles%\WindowsPowerShell\Modules"), # all users
os.path.expandvars(r"%windir%\System32\WindowsPowerShell\v1.0\Modules"), # installation location
]}
_cache_norm_path = {}
@classmethod
def _get_norm_path(cls, p):
norm_path = cls._cache_norm_path.get(p)
if norm_path is None:
norm_path = os.path.normcase(os.path.abspath(p))
cls._cache_norm_path[p] = norm_path
cls._cache_norm_path[norm_path] = norm_path
return norm_path
_cache_is_psmodulepath = {}
@classmethod
def _is_psmodulepath(cls, p):
is_psmodulepath = cls._cache_is_psmodulepath.get(p)
if is_psmodulepath is None:
norm_path = cls._get_norm_path(p)
is_psmodulepath = bool(norm_path in cls._PSMODULEPATH_MAP)
cls._cache_is_psmodulepath[p] = is_psmodulepath
cls._cache_is_psmodulepath[norm_path] = is_psmodulepath
return is_psmodulepath
_cache_psmodulepath_paths = {}
@classmethod
def get_psmodulepath_paths(cls, pathspec):
psmodulepath_paths = cls._cache_psmodulepath_paths.get(pathspec)
if psmodulepath_paths is None:
psmodulepath_paths = []
for p in pathspec.split(os.pathsep):
p = p.strip()
if not p:
continue
if not cls._is_psmodulepath(p):
continue
psmodulepath_paths.append(p)
psmodulepath_paths = tuple(psmodulepath_paths)
cls._cache_psmodulepath_paths[pathspec] = psmodulepath_paths
return psmodulepath_paths
_cache_psexe_paths = {}
@classmethod
def get_psexe_paths(cls, pathspec):
psexe_paths = cls._cache_psexe_paths.get(pathspec)
if psexe_paths is None:
psexe_set = set(cls._PSEXECUTABLES)
psexe_paths = []
for p in pathspec.split(os.pathsep):
p = p.strip()
if not p:
continue
for psexe in psexe_set:
psexe_path = os.path.join(p, psexe)
if not os.path.exists(psexe_path):
continue
psexe_paths.append(p)
psexe_set.remove(psexe)
break
if psexe_set:
continue
break
psexe_paths = tuple(psexe_paths)
cls._cache_psexe_paths[pathspec] = psexe_paths
return psexe_paths
_cache_minimal_pathspec = {}
@classmethod
def get_minimal_pathspec(cls, pathlist):
pathlist_t = tuple(pathlist)
minimal_pathspec = cls._cache_minimal_pathspec.get(pathlist_t)
if minimal_pathspec is None:
minimal_paths = []
seen = set()
for p in pathlist:
p = p.strip()
if not p:
continue
norm_path = cls._get_norm_path(p)
if norm_path in seen:
continue
seen.add(norm_path)
minimal_paths.append(p)
minimal_pathspec = os.pathsep.join(minimal_paths)
cls._cache_minimal_pathspec[pathlist_t] = minimal_pathspec
return minimal_pathspec
def normalize_env(env, keys, force: bool=False):
"""Given a dictionary representing a shell environment, add the variables
from os.environ needed for the processing of .bat files; the keys are
@ -325,6 +470,11 @@ def normalize_env(env, keys, force: bool=False):
for k in keys:
if k in os.environ and (force or k not in normenv):
normenv[k] = os.environ[k]
debug("keys: normenv[%s]=%s", k, normenv[k])
else:
debug("keys: skipped[%s]", k)
syspath_pathlist = normenv.get("PATH", "").split(os.pathsep)
# add some things to PATH to prevent problems:
# Shouldn't be necessary to add system32, since the default environment
@ -332,24 +482,37 @@ def normalize_env(env, keys, force: bool=False):
sys32_dir = os.path.join(
os.environ.get("SystemRoot", os.environ.get("windir", r"C:\Windows")), "System32"
)
if sys32_dir not in normenv["PATH"]:
normenv["PATH"] = normenv["PATH"] + os.pathsep + sys32_dir
syspath_pathlist.append(sys32_dir)
# Without Wbem in PATH, vcvarsall.bat has a "'wmic' is not recognized"
# error starting with Visual Studio 2017, although the script still
# seems to work anyway.
sys32_wbem_dir = os.path.join(sys32_dir, 'Wbem')
if sys32_wbem_dir not in normenv['PATH']:
normenv['PATH'] = normenv['PATH'] + os.pathsep + sys32_wbem_dir
syspath_pathlist.append(sys32_wbem_dir)
# Without Powershell in PATH, an internal call to a telemetry
# function (starting with a VS2019 update) can fail
# Note can also set VSCMD_SKIP_SENDTELEMETRY to avoid this.
sys32_ps_dir = os.path.join(sys32_dir, r'WindowsPowerShell\v1.0')
if sys32_ps_dir not in normenv['PATH']:
normenv['PATH'] = normenv['PATH'] + os.pathsep + sys32_ps_dir
# Find the powershell executable paths. Add the known powershell.exe
# path to the end of the shell system path (just in case).
# The VS vcpkg component prefers pwsh.exe if it's on the path.
sys32_ps_dir = os.path.join(sys32_dir, 'WindowsPowerShell', 'v1.0')
psexe_searchlist = os.pathsep.join([os.environ.get("PATH", ""), sys32_ps_dir])
psexe_pathlist = _PathManager.get_psexe_paths(psexe_searchlist)
# Add powershell executable paths in the order discovered.
syspath_pathlist.extend(psexe_pathlist)
normenv['PATH'] = _PathManager.get_minimal_pathspec(syspath_pathlist)
debug("PATH: %s", normenv['PATH'])
# Add psmodulepath paths in the order discovered.
psmodulepath_pathlist = _PathManager.get_psmodulepath_paths(os.environ.get("PSModulePath", ""))
if psmodulepath_pathlist:
normenv["PSModulePath"] = _PathManager.get_minimal_pathspec(psmodulepath_pathlist)
debug("PSModulePath: %s", normenv.get('PSModulePath',''))
return normenv
@ -360,41 +523,13 @@ def get_output(vcbat, args=None, env=None, skip_sendtelemetry=False):
# Create a blank environment, for use in launching the tools
env = SCons.Environment.Environment(tools=[])
# TODO: Hard-coded list of the variables that (may) need to be
# imported from os.environ[] for the chain of development batch
# files to execute correctly. One call to vcvars*.bat may
# end up running a dozen or more scripts, changes not only with
# each release but with what is installed at the time. We think
# in modern installations most are set along the way and don't
# need to be picked from the env, but include these for safety's sake.
# Any VSCMD variables definitely are picked from the env and
# control execution in interesting ways.
# Note these really should be unified - either controlled by vs.py,
# or synced with the the common_tools_var # settings in vs.py.
vs_vc_vars = [
'COMSPEC', # path to "shell"
'OS', # name of OS family: Windows_NT or undefined (95/98/ME)
'VS170COMNTOOLS', # path to common tools for given version
'VS160COMNTOOLS',
'VS150COMNTOOLS',
'VS140COMNTOOLS',
'VS120COMNTOOLS',
'VS110COMNTOOLS',
'VS100COMNTOOLS',
'VS90COMNTOOLS',
'VS80COMNTOOLS',
'VS71COMNTOOLS',
'VSCOMNTOOLS',
'MSDevDir',
'VSCMD_DEBUG', # enable logging and other debug aids
'VSCMD_SKIP_SENDTELEMETRY',
'windir', # windows directory (SystemRoot not available in 95/98/ME)
]
env['ENV'] = normalize_env(env['ENV'], vs_vc_vars, force=False)
env['ENV'] = normalize_env(env['ENV'], VS_VC_VARS, force=False)
if skip_sendtelemetry:
_force_vscmd_skip_sendtelemetry(env)
# debug("ENV=%r", env['ENV'])
if args:
debug("Calling '%s %s'", vcbat, args)
cmd_str = '"%s" %s & set' % (vcbat, args)
@ -402,10 +537,15 @@ def get_output(vcbat, args=None, env=None, skip_sendtelemetry=False):
debug("Calling '%s'", vcbat)
cmd_str = '"%s" & set' % vcbat
beg_time = time.time()
cp = SCons.Action.scons_subproc_run(
env, cmd_str, stdin=DEVNULL, stdout=PIPE, stderr=PIPE,
)
end_time = time.time()
debug("Elapsed %.2fs", end_time - beg_time)
# Extra debug logic, uncomment if necessary
# debug('stdout:%s', cp.stdout)
# debug('stderr:%s', cp.stderr)
@ -460,6 +600,7 @@ def parse_output(output, keep=KEEPLIST):
# it.
path = path.strip('"')
dkeep[key].append(str(path))
debug("dkeep[%s].append(%r)", key, path)
for line in output.splitlines():
for k, value in rdk.items():

View File

@ -736,6 +736,7 @@ def _skip_sendtelemetry(env):
# If you update this, update SupportedVSList in Tool/MSCommon/vs.py, and the
# MSVC_VERSION documentation in Tool/msvc.xml.
_VCVER = [
"14.5",
"14.3",
"14.2",
"14.1", "14.1Exp",
@ -759,6 +760,7 @@ _VSWHERE_VSMAJOR_TO_VCVERSION = {}
_VSWHERE_SUPPORTED_VCVER = set()
for vs_major, vc_version, vc_ver_list in (
('18', '14.5', None),
('17', '14.3', None),
('16', '14.2', None),
('15', '14.1', ['14.1Exp']),
@ -855,7 +857,7 @@ _VCVER_TO_PRODUCT_DIR = {
# detect ide binaries
VS2022_VS2002_DEV = (
VS2026_VS2002_DEV = (
MSVC.Kind.IDE_PROGRAM_DEVENV_COM, # devenv.com
)
@ -885,27 +887,28 @@ _VCVER_KIND_DETECT = {
# 'VCVer': (relpath from pdir to vsroot, path from vsroot to ide binaries, ide binaries)
'14.3': _DETECT(root='..', path=r'Common7\IDE', programs=VS2022_VS2002_DEV), # 2022
'14.2': _DETECT(root='..', path=r'Common7\IDE', programs=VS2022_VS2002_DEV), # 2019
'14.1': _DETECT(root='..', path=r'Common7\IDE', programs=VS2022_VS2002_DEV + VS2017_EXP), # 2017
'14.1Exp': _DETECT(root='..', path=r'Common7\IDE', programs=VS2022_VS2002_DEV + VS2017_EXP), # 2017
'14.5': _DETECT(root='..', path=r'Common7\IDE', programs=VS2026_VS2002_DEV), # 2026
'14.3': _DETECT(root='..', path=r'Common7\IDE', programs=VS2026_VS2002_DEV), # 2022
'14.2': _DETECT(root='..', path=r'Common7\IDE', programs=VS2026_VS2002_DEV), # 2019
'14.1': _DETECT(root='..', path=r'Common7\IDE', programs=VS2026_VS2002_DEV + VS2017_EXP), # 2017
'14.1Exp': _DETECT(root='..', path=r'Common7\IDE', programs=VS2026_VS2002_DEV + VS2017_EXP), # 2017
'14.0': _DETECT(root='..', path=r'Common7\IDE', programs=VS2022_VS2002_DEV + VS2015_VS2012_EXP), # 2015
'14.0Exp': _DETECT(root='..', path=r'Common7\IDE', programs=VS2022_VS2002_DEV + VS2015_VS2012_EXP), # 2015
'12.0': _DETECT(root='..', path=r'Common7\IDE', programs=VS2022_VS2002_DEV + VS2015_VS2012_EXP), # 2013
'12.0Exp': _DETECT(root='..', path=r'Common7\IDE', programs=VS2022_VS2002_DEV + VS2015_VS2012_EXP), # 2013
'11.0': _DETECT(root='..', path=r'Common7\IDE', programs=VS2022_VS2002_DEV + VS2015_VS2012_EXP), # 2012
'11.0Exp': _DETECT(root='..', path=r'Common7\IDE', programs=VS2022_VS2002_DEV + VS2015_VS2012_EXP), # 2012
'14.0': _DETECT(root='..', path=r'Common7\IDE', programs=VS2026_VS2002_DEV + VS2015_VS2012_EXP), # 2015
'14.0Exp': _DETECT(root='..', path=r'Common7\IDE', programs=VS2026_VS2002_DEV + VS2015_VS2012_EXP), # 2015
'12.0': _DETECT(root='..', path=r'Common7\IDE', programs=VS2026_VS2002_DEV + VS2015_VS2012_EXP), # 2013
'12.0Exp': _DETECT(root='..', path=r'Common7\IDE', programs=VS2026_VS2002_DEV + VS2015_VS2012_EXP), # 2013
'11.0': _DETECT(root='..', path=r'Common7\IDE', programs=VS2026_VS2002_DEV + VS2015_VS2012_EXP), # 2012
'11.0Exp': _DETECT(root='..', path=r'Common7\IDE', programs=VS2026_VS2002_DEV + VS2015_VS2012_EXP), # 2012
'10.0': _DETECT(root='..', path=r'Common7\IDE', programs=VS2022_VS2002_DEV + VS2010_VS2005_EXP), # 2010
'10.0Exp': _DETECT(root='..', path=r'Common7\IDE', programs=VS2022_VS2002_DEV + VS2010_VS2005_EXP), # 2010
'9.0': _DETECT(root='..', path=r'Common7\IDE', programs=VS2022_VS2002_DEV + VS2010_VS2005_EXP), # 2008
'9.0Exp': _DETECT(root='..', path=r'Common7\IDE', programs=VS2022_VS2002_DEV + VS2010_VS2005_EXP), # 2008
'8.0': _DETECT(root='..', path=r'Common7\IDE', programs=VS2022_VS2002_DEV + VS2010_VS2005_EXP), # 2005
'8.0Exp': _DETECT(root='..', path=r'Common7\IDE', programs=VS2022_VS2002_DEV + VS2010_VS2005_EXP), # 2005
'10.0': _DETECT(root='..', path=r'Common7\IDE', programs=VS2026_VS2002_DEV + VS2010_VS2005_EXP), # 2010
'10.0Exp': _DETECT(root='..', path=r'Common7\IDE', programs=VS2026_VS2002_DEV + VS2010_VS2005_EXP), # 2010
'9.0': _DETECT(root='..', path=r'Common7\IDE', programs=VS2026_VS2002_DEV + VS2010_VS2005_EXP), # 2008
'9.0Exp': _DETECT(root='..', path=r'Common7\IDE', programs=VS2026_VS2002_DEV + VS2010_VS2005_EXP), # 2008
'8.0': _DETECT(root='..', path=r'Common7\IDE', programs=VS2026_VS2002_DEV + VS2010_VS2005_EXP), # 2005
'8.0Exp': _DETECT(root='..', path=r'Common7\IDE', programs=VS2026_VS2002_DEV + VS2010_VS2005_EXP), # 2005
'7.1': _DETECT(root='..', path=r'Common7\IDE', programs=VS2022_VS2002_DEV), # 2003
'7.0': _DETECT(root='..', path=r'Common7\IDE', programs=VS2022_VS2002_DEV), # 2001
'7.1': _DETECT(root='..', path=r'Common7\IDE', programs=VS2026_VS2002_DEV), # 2003
'7.0': _DETECT(root='..', path=r'Common7\IDE', programs=VS2026_VS2002_DEV), # 2001
'6.0': _DETECT(root='..', path=r'Common\MSDev98\Bin', programs=VS1998_DEV), # 1998
}

View File

@ -213,6 +213,18 @@ class VisualStudio:
# Tool/MSCommon/vc.py, and the MSVC_VERSION documentation in Tool/msvc.xml.
SupportedVSList = [
# Visual Studio 2026
VisualStudio('14.5',
vc_version='14.5',
sdk_version='10.0A',
hkeys=[],
common_tools_var='VS180COMNTOOLS',
executable_path=r'Common7\IDE\devenv.com',
# should be a fallback, prefer use vswhere installationPath
batch_file_path=r'Common7\Tools\VsDevCmd.bat',
supported_arch=['x86', 'amd64', "arm", 'arm64'],
),
# Visual Studio 2022
VisualStudio('14.3',
vc_version='14.3',

View File

@ -408,7 +408,7 @@ def createObjBuilders(env):
suffix='$OBJSUFFIX',
src_builder=['CFile', 'CXXFile'],
source_scanner=SourceFileScanner,
single_source=1)
single_source=True)
env['BUILDERS']['StaticObject'] = static_obj
env['BUILDERS']['Object'] = static_obj
@ -421,7 +421,7 @@ def createObjBuilders(env):
suffix='$SHOBJSUFFIX',
src_builder=['CFile', 'CXXFile'],
source_scanner=SourceFileScanner,
single_source=1)
single_source=True)
env['BUILDERS']['SharedObject'] = shared_obj
return (static_obj, shared_obj)

View File

@ -156,11 +156,14 @@ def write_compilation_db(target, source, env) -> None:
source_file = entry['file']
output_file = entry['output']
if not source_file.is_derived():
source_file = source_file.srcnode()
if use_abspath:
source_file = source_file.srcnode().abspath
source_file = source_file.abspath
output_file = output_file.abspath
else:
source_file = source_file.srcnode().path
source_file = source_file.path
output_file = output_file.path
if use_path_filter and not fnmatch.fnmatch(output_file, use_path_filter):

Some files were not shown because too many files have changed in this diff Show More