Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/SCons/scons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWilliam Deegan <bill@baddogconsulting.com>2022-07-25 06:49:17 +0300
committerGitHub <noreply@github.com>2022-07-25 06:49:17 +0300
commit41b0831382603609dfd021cb4d5a0b2d9f06021d (patch)
treea11fe13c87cec4d3e0f1aa3d6aa4741739078340
parent2f10fe6c285646ee0d4eb021c4ee94aed4f319c7 (diff)
parent94133d4d3e63185f2649cc231ed182eba6df29d4 (diff)
Merge pull request #4174 from jcbrill/jbrill-msvc-batchargs
MSVC enhancement to add all remaining msvc batch file command-line options as SCons variables
-rwxr-xr-xCHANGES.txt61
-rwxr-xr-xRELEASE.txt62
-rw-r--r--SCons/Tool/MSCommon/MSVC/Config.py331
-rw-r--r--SCons/Tool/MSCommon/MSVC/ConfigTests.py88
-rw-r--r--SCons/Tool/MSCommon/MSVC/Dispatcher.py84
-rw-r--r--SCons/Tool/MSCommon/MSVC/DispatcherTests.py119
-rw-r--r--SCons/Tool/MSCommon/MSVC/Exceptions.py56
-rw-r--r--SCons/Tool/MSCommon/MSVC/Policy.py301
-rw-r--r--SCons/Tool/MSCommon/MSVC/PolicyTests.py169
-rw-r--r--SCons/Tool/MSCommon/MSVC/Registry.py118
-rw-r--r--SCons/Tool/MSCommon/MSVC/RegistryTests.py83
-rw-r--r--SCons/Tool/MSCommon/MSVC/ScriptArguments.py1031
-rw-r--r--SCons/Tool/MSCommon/MSVC/ScriptArgumentsTests.py591
-rw-r--r--SCons/Tool/MSCommon/MSVC/SetupEnvDefault.py233
-rw-r--r--SCons/Tool/MSCommon/MSVC/Util.py366
-rw-r--r--SCons/Tool/MSCommon/MSVC/UtilTests.py209
-rw-r--r--SCons/Tool/MSCommon/MSVC/Warnings.py35
-rw-r--r--SCons/Tool/MSCommon/MSVC/WinSDK.py264
-rw-r--r--SCons/Tool/MSCommon/MSVC/WinSDKTests.py132
-rw-r--r--SCons/Tool/MSCommon/MSVC/__init__.py55
-rw-r--r--SCons/Tool/MSCommon/README107
-rw-r--r--SCons/Tool/MSCommon/README.rst501
-rw-r--r--SCons/Tool/MSCommon/__init__.py49
-rw-r--r--SCons/Tool/MSCommon/common.py19
-rw-r--r--SCons/Tool/MSCommon/vc.py728
-rw-r--r--SCons/Tool/MSCommon/vcTests.py325
-rw-r--r--SCons/Tool/docbook/__init__.py12
-rw-r--r--SCons/Tool/msvc.xml779
-rw-r--r--SCons/__init__.py14
-rw-r--r--doc/generated/functions.gen52
-rw-r--r--doc/generated/tools.gen17
-rw-r--r--doc/generated/variables.gen864
-rw-r--r--doc/generated/variables.mod12
-rw-r--r--requirements.txt2
-rw-r--r--test/MSVC/MSVC_NOTFOUND_POLICY.py126
-rw-r--r--test/MSVC/MSVC_SCRIPTERROR_POLICY.py141
-rw-r--r--test/MSVC/MSVC_SDK_VERSION.py241
-rw-r--r--test/MSVC/MSVC_SPECTRE_LIBS.py152
-rw-r--r--test/MSVC/MSVC_TOOLSET_VERSION.py233
-rw-r--r--test/MSVC/MSVC_USE_SCRIPT_ARGS-fixture/SConstruct3
-rw-r--r--test/MSVC/MSVC_UWP_APP.py310
-rw-r--r--test/MSVC/msvc_cache_force_defaults.py80
-rw-r--r--test/MSVC/no_msvc.py15
-rw-r--r--test/fixture/no_msvc/no_msvcs_sconstruct_msvc_query_toolset_version.py15
-rw-r--r--test/fixture/no_msvc/no_msvcs_sconstruct_msvc_sdk_versions.py15
-rw-r--r--test/fixture/no_msvc/no_msvcs_sconstruct_msvc_toolset_versions.py15
-rw-r--r--test/fixture/no_msvc/no_msvcs_sconstruct_version.py5
47 files changed, 8381 insertions, 839 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index 5900fcae6..682e9ab59 100755
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -31,8 +31,30 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER
determination when configuring the build environment. This could lead to build failures when
only an MSVC Express instance is installed and the MSVC version is not explicitly specified
(issue #2668 and issue #2697).
- - Added MSVC_USE_SETTINGS variable to pass a dictionary to configure the msvc compiler
+ - Added MSVC_USE_SETTINGS construction variable to pass a dictionary to configure the msvc compiler
system environment as an alternative to bypassing Visual Studio autodetection entirely.
+ - Added MSVC_SDK_VERSION construction variable which allows building with a specific Microsoft
+ SDK version. This variable is used with the msvc batch file determined via autodetection subject
+ to validation constraints. Refer to the documentation for additional requirements and validation
+ details.
+ - Added MSVC_TOOLSET_VERSION construction variable which allows building with a specific toolset
+ version. This variable is used with the msvc batch file determined via autodetection subject to
+ validation constraints. This variable does not affect the autodetection and selection of msvc
+ instances. The toolset version is applied after an msvc instance is selected. This could be the
+ default version of msvc. Refer to the documentation for additional requirements and validation
+ details. Addresses issue #3265, issue #3664, and pull request #4149.
+ - Added MSVC_SPECTRE_LIBS construction variable which allows building with spectre-mitigated
+ Visual C++ libraries. This variable is used with the msvc batch file determined via autodetection
+ subject to validation constraints. Refer to the documentation for additional requirements and
+ validation details.
+ - Added MSVC_SCRIPT_ARGS construction variable which specifies command line arguments that are
+ passed to the msvc batch file determined via autodetection subject to validation constraints.
+ Refer to the documentation for additional requirements and validation details. Addresses
+ enhancement issue #4106.
+ - An exception is raised when MSVC_UWP_APP is enabled for Visual Studio 2013 and earlier.
+ Previous behavior was to silently ignore MSVC_UWP_APP when enabled for Visual Studio 2013
+ and earlier. Refer to the documentation for additional requirements and validation details.
+ MSVC_UWP_APP was extended to accept True, False, and None in addition to '1' and '0'.
- The imported system environment variable names for MSVC 7.0 and 6.0 have been changed to the
names set by their respective installers. Prior to this change, bypassing MSVC detection by
specifying the MSVC 7.0 batch file directly would fail due to using an erroneous environment
@@ -54,12 +76,12 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER
present, there is no easy way to detect during msvc initialization if the default environment
will be used later to build a program and/or library. There is no error/warning issued for the
default tools as there are legitimate SCons uses that do not require a c compiler.
- - Added a global policy setting and an environment policy variable for specifying the action to
- be taken when an msvc request cannot be satisfied. The available options are "error",
+ - Added a global policy setting and an environment construction variable for specifying the
+ action to be taken when an msvc request cannot be satisfied. The available options are "error",
"exception", "warning", "warn", "ignore", and "suppress". The global policy variable may be
- set and retrieved via the functions set_msvc_notfound_policy and get_msvc_notfound_policy,
- respectively. These two methods may be imported from SCons.Tool.MSCommon. The environment
- policy variable introduced is MSVC_NOTFOUND_POLICY. When defined, the environment policy
+ set and retrieved via the functions msvc_set_notfound_policy and msvc_get_notfound_policy,
+ respectively. These two methods may be imported from SCons.Tool.MSCommon. The environment
+ construction variable is MSVC_NOTFOUND_POLICY. When defined, the environment construction
variable overrides the global policy setting for a given environment. When the active policy
is "error" or "exception", an MSVCVersionNotFound exception is raised. When the active policy
is "warning" or "warn", a VisualCMissingWarning warning is issued and the constructed
@@ -67,6 +89,33 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER
is taken and the constructed environment is likely incomplete. As implemented, the default
global policy is "warning". The ability to set the global policy via an SCons command-line
option may be added in a future enhancement.
+ - Added a global policy setting and an environment construction variable for specifying the
+ action to be taken when msvc script errors are detected. The available options are "error",
+ "exception", "warning", "warn", "ignore", and "suppress". The global policy variable may be
+ set and retrieved via the functions msvc_set_scripterror_policy and msvc_get_scripterror_policy,
+ respectively. These two methods may be imported from SCons.Tool.MSCommon. The environment
+ construction variable is MSVC_SCRIPTERROR_POLICY. When defined, the environment construction
+ variable overrides the global policy setting for a given environment. When the active policy
+ is "error" or "exception", an MSVCScriptExecutionError exception is raised when msvc batch file
+ errors are detected. When the active policy is "warning" or "warn", an MSVCScriptExecutionWarning
+ warning is issued when msvc batch file errors are detected. When the active policy is "ignore" or
+ "suppress", msvc batch error messages are suppressed. As implemented, the default global policy
+ is "ignore". The ability to set the global policy via an SCons command-line option may be added
+ in a future enhancement.
+ - Added experimental function msvc_query_version_toolset to SCons.Tool.MSCommon. Given a version
+ specification, this function will return an msvc version and an msvc toolset version. The msvc
+ toolset version may be None. The msvc version and msvc toolset version can be used in the
+ environment construction variables MSVC_VERSION and MSVC_TOOLSET_VERSION, respectively. The
+ version specification may be an msvc version or an msvc toolset version. This is a proxy for
+ using an msvc toolset version to select an msvc instance. This function may be removed when an
+ msvc toolset version is used during msvc instance selection.
+ - Modify the MSCommon logger configuration to be independent of the root logger. This fixes an issue
+ when multiple loggers are created and the MSCommon logger added computed fields to the root logger
+ that are not present in other logging instances.
+ - Modify the MSVC_USE_SCRIPT_ARGS test fixture to disable the msvc cache. This fixes an issue where
+ the MSVC_USE_SCRIPT_ARGS test for success relied on a debug log message that was not produced when
+ the msvc cache file exists and the test keys are already in the cache as the msvc script invocation
+ was bypassed.
From William Deegan:
- Fix check for unsupported Python version. It was broken. Also now the error message
diff --git a/RELEASE.txt b/RELEASE.txt
index b7e5cebbd..d3fc5fda6 100755
--- a/RELEASE.txt
+++ b/RELEASE.txt
@@ -23,19 +23,34 @@ NEW FUNCTIONALITY
variables). This allows the user to customize how (for example) PATH is constructed.
Note that these are called for every build command run by SCons. It could have considerable
performance impact if not used carefully.
-- Added MSVC_USE_SETTINGS variable to pass a dictionary to configure the msvc compiler
+- Added MSVC_USE_SETTINGS construction variable to pass a dictionary to configure the msvc compiler
system environment as an alternative to bypassing Visual Studio autodetection entirely.
+- Added MSVC_SDK_VERSION construction variable which allows building with a specific Microsoft
+ SDK version. This variable is used with the msvc batch file determined via autodetection. Refer
+ to the documentation for additional requirements and validation details.
+- Added MSVC_TOOLSET_VERSION construction variable which allows building with a specific toolset
+ version. This variable is used with the msvc batch file determined via autodetection. This
+ variable does not affect the autodetection and selection of msvc instances. The toolset version
+ is applied after an msvc instance is selected. This could be the default version of msvc. Refer
+ to the documentation for additional requirements and validation details. Addresses issue #3265,
+ issue #3664, and pull request #4149.
+- Added MSVC_SPECTRE_LIBS construction variable which allows building with spectre-mitigated
+ Visual C++ libraries. This variable is used with the msvc batch file determined via autodetection.
+ Refer to the documentation for additional requirements and validation details.
+- Added MSVC_SCRIPT_ARGS construction variable which specifies command line arguments that are
+ passed to the msvc batch file determined via autodetection. Refer to the documentation for
+ additional requirements and validation details. Addresses enhancement issue #4106.
- Ninja: Added new alias "shutdown-ninja-scons-daemon" to allow ninja to shutdown the daemon.
Also added cleanup to test framework to kill ninja scons daemons and clean ip daemon logs.
NOTE: Test for this requires python psutil module. It will be skipped if not present.
- Ninja: Added command line variable NINJA_CMD_ARGS that allows to pass through ninja command line args.
This can also be set in your Environment().
-- Added a global policy setting and an environment policy variable for specifying the action to
- be taken when an msvc request cannot be satisfied. The available options are "error",
+- Added a global policy setting and an environment construction variable for specifying the
+ action to be taken when an msvc request cannot be satisfied. The available options are "error",
"exception", "warning", "warn", "ignore", and "suppress". The global policy variable may be
- set and retrieved via the functions set_msvc_notfound_policy and get_msvc_notfound_policy,
- respectively. These two methods may be imported from SCons.Tool.MSCommon. The environment
- policy variable introduced is MSVC_NOTFOUND_POLICY. When defined, the environment policy
+ set and retrieved via the functions msvc_set_notfound_policy and msvc_get_notfound_policy,
+ respectively. These two methods may be imported from SCons.Tool.MSCommon. The environment
+ construction variable is MSVC_NOTFOUND_POLICY. When defined, the environment construction
variable overrides the global policy setting for a given environment. When the active policy
is "error" or "exception", an MSVCVersionNotFound exception is raised. When the active policy
is "warning" or "warn", a VisualCMissingWarning warning is issued and the constructed
@@ -43,6 +58,26 @@ NEW FUNCTIONALITY
is taken and the constructed environment is likely incomplete. As implemented, the default
global policy is "warning". The ability to set the global policy via an SCons command-line
option may be added in a future enhancement.
+- Added a global policy setting and an environment construction variable for specifying the
+ action to be taken when msvc script errors are detected. The available options are "error",
+ "exception", "warning", "warn", "ignore", and "suppress". The global policy variable may be
+ set and retrieved via the functions msvc_set_scripterror_policy and msvc_get_scripterror_policy,
+ respectively. These two methods may be imported from SCons.Tool.MSCommon. The environment
+ construction variable is MSVC_SCRIPTERROR_POLICY. When defined, the environment construction
+ variable overrides the global policy setting for a given environment. When the active policy
+ is "error" or "exception", an MSVCScriptExecutionError exception is raised when msvc batch file
+ errors are detected. When the active policy is "warning" or "warn", an MSVCScriptExecutionWarning
+ warning is issued when msvc batch file errors are detected. When the active policy is "ignore" or
+ "suppress", msvc batch error messages are suppressed. As implemented, the default global policy
+ is "ignore". The ability to set the global policy via an SCons command-line option may be added
+ in a future enhancement.
+- Added experimental function msvc_query_version_toolset to SCons.Tool.MSCommon. Given a version
+ specification, this function will return an msvc version and an msvc toolset version. The msvc
+ toolset version may be None. The msvc version and msvc toolset version can be used in the
+ environment construction variables MSVC_VERSION and MSVC_TOOLSET_VERSION, respectively. The
+ version specification may be an msvc version or an msvc toolset version. This is a proxy for
+ using an msvc toolset version to select an msvc instance. This function may be removed when an
+ msvc toolset version is used during msvc instance selection.
- Fortran: a new construction variable FORTRANCOMMONFLAGS is added which is
applied to all Fortran dialects, to enable global (all-dialect) settings.
- lex: two new construction variables are introduced (LEX_HEADER_ILE
@@ -102,8 +137,8 @@ CHANGED/ENHANCED EXISTING FUNCTIONALITY
automatically executes ninja.
- Add JavaScanner to include JAVACLASSPATH as a dependency when using the Java tool.
- The build argument (i.e., x86) is no longer passed to the MSVC 6.0 to 7.1 batch
- files. This may improve the effectiveness of the internal msvc cache when using
- MSVC detection and when bypassing MSVC detection as the MSVC 6.0 to 7.1 batch files
+ files. This may improve the effectiveness of the internal msvc cache when using
+ MSVC detection and when bypassing MSVC detection as the MSVC 6.0 to 7.1 batch files
do not expect any arguments.
- Propagate the OS and windir environment variables from the system environment to the msvc
environment. The OS and windir environment variables are used in the MSVC 6.0 batch file
@@ -115,6 +150,10 @@ CHANGED/ENHANCED EXISTING FUNCTIONALITY
require delayed expansion to be enabled which is currently not supported and is
typically not enabled by default on the host system. The batch files may also require
environment variables that are not included by default in the msvc environment.
+- An exception is raised when MSVC_UWP_APP is enabled for Visual Studio 2013 and earlier.
+ Previous behavior was to silently ignore MSVC_UWP_APP when enabled for Visual Studio 2013
+ and earlier. Refer to the documentation for additional requirements and validation details.
+ MSVC_UWP_APP was extended to accept True, False, and None in addition to '1' and '0'.
- Ninja: added option "--skip-ninja-regen" to enable skipping regeneration of the ninja file
if scons can determine the ninja file doesnot need to be regenerated, which will also
skip restarting the scons daemon. Note this option is could result in incorrect rebuilds
@@ -171,6 +210,13 @@ FIXES
error caused when bypassing MSVC detection by specifying the MSVC 7.0 batch file directly.
- lex: Fixed an issue with the lex tool where file arguments specified to either "--header-file="
or "--tables-file=" which included a space in the path to the file would be processed incorrectly
+- Modify the MSCommon logger configuration to be independent of the root logger. This fixes an issue
+ when multiple loggers are created and the MSCommon logger added computed fields to the root logger
+ that are not present in other logging instances.
+- Modify the MSVC_USE_SCRIPT_ARGS test fixture to disable the msvc cache. This fixes an issue where
+ the MSVC_USE_SCRIPT_ARGS test for success relied on a debug log message that was not produced when
+ the msvc cache file exists and the test keys are already in the cache as the msvc script invocation
+ was bypassed.
- Suppress issuing a warning when there are no installed Visual Studio instances for the default
tools configuration (issue #2813). When msvc is the default compiler because there are no
compilers installed, a build may fail due to the cl.exe command not being recognized. At
diff --git a/SCons/Tool/MSCommon/MSVC/Config.py b/SCons/Tool/MSCommon/MSVC/Config.py
new file mode 100644
index 000000000..015cf7245
--- /dev/null
+++ b/SCons/Tool/MSCommon/MSVC/Config.py
@@ -0,0 +1,331 @@
+# MIT License
+#
+# Copyright The SCons Foundation
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# 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.
+
+"""
+Constants and initialized data structures for Microsoft Visual C/C++.
+"""
+
+from collections import (
+ namedtuple,
+)
+
+from . import Util
+
+from .Exceptions import (
+ MSVCInternalError,
+)
+
+from . import Dispatcher
+Dispatcher.register_modulename(__name__)
+
+
+UNDEFINED = object()
+
+BOOLEAN_SYMBOLS = {}
+BOOLEAN_EXTERNAL = {}
+
+for bool_val, symbol_list, symbol_case_list in [
+ (False, (False, 0, '0', None, ''), ('False', 'No', 'F', 'N')),
+ (True, (True, 1, '1'), ('True', 'Yes', 'T', 'Y')),
+]:
+ BOOLEAN_SYMBOLS[bool_val] = list(symbol_list)
+ for symbol in symbol_case_list:
+ BOOLEAN_SYMBOLS[bool_val].extend([symbol, symbol.lower(), symbol.upper()])
+
+ for symbol in BOOLEAN_SYMBOLS[bool_val]:
+ BOOLEAN_EXTERNAL[symbol] = bool_val
+
+MSVC_PLATFORM_DEFINITION = namedtuple('MSVCPlatform', [
+ 'vc_platform',
+ 'is_uwp',
+])
+
+MSVC_PLATFORM_DEFINITION_LIST = []
+
+MSVC_PLATFORM_INTERNAL = {}
+MSVC_PLATFORM_EXTERNAL = {}
+
+for vc_platform, is_uwp in [
+ ('Desktop', False),
+ ('UWP', True),
+]:
+
+ vc_platform_def = MSVC_PLATFORM_DEFINITION(
+ vc_platform = vc_platform,
+ is_uwp = is_uwp,
+ )
+
+ MSVC_PLATFORM_DEFINITION_LIST.append(vc_platform_def)
+
+ MSVC_PLATFORM_INTERNAL[vc_platform] = vc_platform_def
+
+ for symbol in [vc_platform, vc_platform.lower(), vc_platform.upper()]:
+ MSVC_PLATFORM_EXTERNAL[symbol] = vc_platform_def
+
+MSVC_RUNTIME_DEFINITION = namedtuple('MSVCRuntime', [
+ 'vc_runtime',
+ 'vc_runtime_numeric',
+ 'vc_runtime_alias_list',
+ 'vc_runtime_vsdef_list',
+])
+
+MSVC_RUNTIME_DEFINITION_LIST = []
+
+MSVC_RUNTIME_INTERNAL = {}
+MSVC_RUNTIME_EXTERNAL = {}
+
+for vc_runtime, vc_runtime_numeric, vc_runtime_alias_list in [
+ ('140', 140, ['ucrt']),
+ ('120', 120, ['msvcr120']),
+ ('110', 110, ['msvcr110']),
+ ('100', 100, ['msvcr100']),
+ ('90', 90, ['msvcr90']),
+ ('80', 80, ['msvcr80']),
+ ('71', 71, ['msvcr71']),
+ ('70', 70, ['msvcr70']),
+ ('60', 60, ['msvcrt']),
+]:
+ vc_runtime_def = MSVC_RUNTIME_DEFINITION(
+ vc_runtime = vc_runtime,
+ vc_runtime_numeric = vc_runtime_numeric,
+ vc_runtime_alias_list = vc_runtime_alias_list,
+ vc_runtime_vsdef_list = [],
+ )
+
+ MSVC_RUNTIME_DEFINITION_LIST.append(vc_runtime_def)
+
+ MSVC_RUNTIME_INTERNAL[vc_runtime] = vc_runtime_def
+ MSVC_RUNTIME_EXTERNAL[vc_runtime] = vc_runtime_def
+
+ for vc_runtime_alias in vc_runtime_alias_list:
+ MSVC_RUNTIME_EXTERNAL[vc_runtime_alias] = vc_runtime_def
+
+MSVC_BUILDTOOLS_DEFINITION = namedtuple('MSVCBuildtools', [
+ 'vc_buildtools',
+ 'vc_buildtools_numeric',
+ 'vc_version',
+ 'vc_version_numeric',
+ 'cl_version',
+ 'cl_version_numeric',
+ 'vc_runtime_def',
+ 'vc_istoolset',
+])
+
+MSVC_BUILDTOOLS_DEFINITION_LIST = []
+
+MSVC_BUILDTOOLS_INTERNAL = {}
+MSVC_BUILDTOOLS_EXTERNAL = {}
+
+VC_VERSION_MAP = {}
+
+for vc_buildtools, vc_version, cl_version, vc_runtime, vc_istoolset in [
+ ('v143', '14.3', '19.3', '140', True),
+ ('v142', '14.2', '19.2', '140', True),
+ ('v141', '14.1', '19.1', '140', True),
+ ('v140', '14.0', '19.0', '140', True),
+ ('v120', '12.0', '18.0', '120', False),
+ ('v110', '11.0', '17.0', '110', False),
+ ('v100', '10.0', '16.0', '100', False),
+ ('v90', '9.0', '15.0', '90', False),
+ ('v80', '8.0', '14.0', '80', False),
+ ('v71', '7.1', '13.1', '71', False),
+ ('v70', '7.0', '13.0', '70', False),
+ ('v60', '6.0', '12.0', '60', False),
+]:
+
+ vc_runtime_def = MSVC_RUNTIME_INTERNAL[vc_runtime]
+
+ vc_buildtools_def = MSVC_BUILDTOOLS_DEFINITION(
+ vc_buildtools = vc_buildtools,
+ vc_buildtools_numeric = int(vc_buildtools[1:]),
+ vc_version = vc_version,
+ vc_version_numeric = float(vc_version),
+ cl_version = cl_version,
+ cl_version_numeric = float(cl_version),
+ vc_runtime_def = vc_runtime_def,
+ vc_istoolset = vc_istoolset,
+ )
+
+ MSVC_BUILDTOOLS_DEFINITION_LIST.append(vc_buildtools_def)
+
+ MSVC_BUILDTOOLS_INTERNAL[vc_buildtools] = vc_buildtools_def
+ MSVC_BUILDTOOLS_EXTERNAL[vc_buildtools] = vc_buildtools_def
+ MSVC_BUILDTOOLS_EXTERNAL[vc_version] = vc_buildtools_def
+
+ VC_VERSION_MAP[vc_version] = vc_buildtools_def
+
+MSVS_VERSION_INTERNAL = {}
+MSVS_VERSION_EXTERNAL = {}
+
+MSVC_VERSION_INTERNAL = {}
+MSVC_VERSION_EXTERNAL = {}
+MSVC_VERSION_SUFFIX = {}
+
+MSVS_VERSION_MAJOR_MAP = {}
+
+CL_VERSION_MAP = {}
+
+MSVC_SDK_VERSIONS = set()
+
+VISUALSTUDIO_DEFINITION = namedtuple('VisualStudioDefinition', [
+ 'vs_product',
+ 'vs_product_alias_list',
+ 'vs_version',
+ 'vs_version_major',
+ 'vs_envvar',
+ 'vs_express',
+ 'vs_lookup',
+ 'vc_sdk_versions',
+ 'vc_ucrt_versions',
+ 'vc_uwp',
+ 'vc_buildtools_def',
+ 'vc_buildtools_all',
+])
+
+VISUALSTUDIO_DEFINITION_LIST = []
+
+VS_PRODUCT_ALIAS = {
+ '1998': ['6']
+}
+
+# vs_envvar: VisualStudioVersion defined in environment for MSVS 2012 and later
+# MSVS 2010 and earlier cl_version -> vs_def is a 1:1 mapping
+# SDK attached to product or buildtools?
+for vs_product, vs_version, vs_envvar, vs_express, vs_lookup, vc_sdk, vc_ucrt, vc_uwp, vc_buildtools_all in [
+ ('2022', '17.0', True, False, 'vswhere' , ['10.0', '8.1'], ['10'], 'uwp', ['v143', 'v142', 'v141', 'v140']),
+ ('2019', '16.0', True, False, 'vswhere' , ['10.0', '8.1'], ['10'], 'uwp', ['v142', 'v141', 'v140']),
+ ('2017', '15.0', True, True, 'vswhere' , ['10.0', '8.1'], ['10'], 'uwp', ['v141', 'v140']),
+ ('2015', '14.0', True, True, 'registry', ['10.0', '8.1'], ['10'], 'store', ['v140']),
+ ('2013', '12.0', True, True, 'registry', None, None, None, ['v120']),
+ ('2012', '11.0', True, True, 'registry', None, None, None, ['v110']),
+ ('2010', '10.0', False, True, 'registry', None, None, None, ['v100']),
+ ('2008', '9.0', False, True, 'registry', None, None, None, ['v90']),
+ ('2005', '8.0', False, True, 'registry', None, None, None, ['v80']),
+ ('2003', '7.1', False, False, 'registry', None, None, None, ['v71']),
+ ('2002', '7.0', False, False, 'registry', None, None, None, ['v70']),
+ ('1998', '6.0', False, False, 'registry', None, None, None, ['v60']),
+]:
+
+ vs_version_major = vs_version.split('.')[0]
+
+ vc_buildtools_def = MSVC_BUILDTOOLS_INTERNAL[vc_buildtools_all[0]]
+
+ vs_def = VISUALSTUDIO_DEFINITION(
+ vs_product = vs_product,
+ vs_product_alias_list = [],
+ vs_version = vs_version,
+ vs_version_major = vs_version_major,
+ vs_envvar = vs_envvar,
+ vs_express = vs_express,
+ vs_lookup = vs_lookup,
+ vc_sdk_versions = vc_sdk,
+ vc_ucrt_versions = vc_ucrt,
+ vc_uwp = vc_uwp,
+ vc_buildtools_def = vc_buildtools_def,
+ vc_buildtools_all = vc_buildtools_all,
+ )
+
+ VISUALSTUDIO_DEFINITION_LIST.append(vs_def)
+
+ vc_buildtools_def.vc_runtime_def.vc_runtime_vsdef_list.append(vs_def)
+
+ vc_version = vc_buildtools_def.vc_version
+
+ MSVS_VERSION_INTERNAL[vs_product] = vs_def
+ MSVS_VERSION_EXTERNAL[vs_product] = vs_def
+ MSVS_VERSION_EXTERNAL[vs_version] = vs_def
+
+ MSVC_VERSION_INTERNAL[vc_version] = vs_def
+ MSVC_VERSION_EXTERNAL[vs_product] = vs_def
+ MSVC_VERSION_EXTERNAL[vc_version] = vs_def
+ MSVC_VERSION_EXTERNAL[vc_buildtools_def.vc_buildtools] = vs_def
+
+ if vs_product in VS_PRODUCT_ALIAS:
+ for vs_product_alias in VS_PRODUCT_ALIAS[vs_product]:
+ vs_def.vs_product_alias_list.append(vs_product_alias)
+ MSVS_VERSION_EXTERNAL[vs_product_alias] = vs_def
+ MSVC_VERSION_EXTERNAL[vs_product_alias] = vs_def
+
+ MSVC_VERSION_SUFFIX[vc_version] = vs_def
+ if vs_express:
+ MSVC_VERSION_SUFFIX[vc_version + 'Exp'] = vs_def
+
+ MSVS_VERSION_MAJOR_MAP[vs_version_major] = vs_def
+
+ CL_VERSION_MAP[vc_buildtools_def.cl_version] = vs_def
+
+ if vc_sdk:
+ MSVC_SDK_VERSIONS.update(vc_sdk)
+
+# EXPERIMENTAL: msvc version/toolset search lists
+#
+# VS2017 example:
+#
+# defaults['14.1'] = ['14.1', '14.1Exp']
+# defaults['14.1Exp'] = ['14.1Exp']
+#
+# search['14.1'] = ['14.3', '14.2', '14.1', '14.1Exp']
+# search['14.1Exp'] = ['14.1Exp']
+
+MSVC_VERSION_TOOLSET_DEFAULTS_MAP = {}
+MSVC_VERSION_TOOLSET_SEARCH_MAP = {}
+
+# Pass 1: Build defaults lists and setup express versions
+for vs_def in VISUALSTUDIO_DEFINITION_LIST:
+ if not vs_def.vc_buildtools_def.vc_istoolset:
+ continue
+ version_key = vs_def.vc_buildtools_def.vc_version
+ MSVC_VERSION_TOOLSET_DEFAULTS_MAP[version_key] = [version_key]
+ MSVC_VERSION_TOOLSET_SEARCH_MAP[version_key] = []
+ if vs_def.vs_express:
+ express_key = version_key + 'Exp'
+ MSVC_VERSION_TOOLSET_DEFAULTS_MAP[version_key].append(express_key)
+ MSVC_VERSION_TOOLSET_DEFAULTS_MAP[express_key] = [express_key]
+ MSVC_VERSION_TOOLSET_SEARCH_MAP[express_key] = [express_key]
+
+# Pass 2: Extend search lists (decreasing version order)
+for vs_def in VISUALSTUDIO_DEFINITION_LIST:
+ if not vs_def.vc_buildtools_def.vc_istoolset:
+ continue
+ version_key = vs_def.vc_buildtools_def.vc_version
+ for vc_buildtools in vs_def.vc_buildtools_all:
+ toolset_buildtools_def = MSVC_BUILDTOOLS_INTERNAL[vc_buildtools]
+ toolset_vs_def = MSVC_VERSION_INTERNAL[toolset_buildtools_def.vc_version]
+ buildtools_key = toolset_buildtools_def.vc_version
+ MSVC_VERSION_TOOLSET_SEARCH_MAP[buildtools_key].extend(MSVC_VERSION_TOOLSET_DEFAULTS_MAP[version_key])
+
+# convert string version set to string version list ranked in descending order
+MSVC_SDK_VERSIONS = [str(f) for f in sorted([float(s) for s in MSVC_SDK_VERSIONS], reverse=True)]
+
+
+def verify():
+ from .. import vc
+ for msvc_version in vc._VCVER:
+ if msvc_version not in MSVC_VERSION_SUFFIX:
+ err_msg = 'msvc_version {} not in MSVC_VERSION_SUFFIX'.format(repr(msvc_version))
+ raise MSVCInternalError(err_msg)
+ vc_version = Util.get_msvc_version_prefix(msvc_version)
+ if vc_version not in MSVC_VERSION_INTERNAL:
+ err_msg = 'vc_version {} not in MSVC_VERSION_INTERNAL'.format(repr(vc_version))
+ raise MSVCInternalError(err_msg)
+
diff --git a/SCons/Tool/MSCommon/MSVC/ConfigTests.py b/SCons/Tool/MSCommon/MSVC/ConfigTests.py
new file mode 100644
index 000000000..89db6cda5
--- /dev/null
+++ b/SCons/Tool/MSCommon/MSVC/ConfigTests.py
@@ -0,0 +1,88 @@
+# MIT License
+#
+# Copyright The SCons Foundation
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# 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.
+
+"""
+Test constants and initialized data structures for Microsoft Visual C/C++.
+"""
+
+import unittest
+
+from SCons.Tool.MSCommon import vc
+from SCons.Tool.MSCommon.MSVC import Config
+from SCons.Tool.MSCommon.MSVC.Exceptions import MSVCInternalError
+
+
+class Patch:
+ class vc:
+ class _VCVER:
+ _VCVER = vc._VCVER
+
+ @classmethod
+ def enable_copy(cls):
+ hook = list(cls._VCVER)
+ vc._VCVER = hook
+ return hook
+
+ @classmethod
+ def restore(cls):
+ vc._VCVER = cls._VCVER
+
+ class Config:
+ class MSVC_VERSION_INTERNAL:
+ MSVC_VERSION_INTERNAL = Config.MSVC_VERSION_INTERNAL
+
+ @classmethod
+ def enable_copy(cls):
+ hook = dict(cls.MSVC_VERSION_INTERNAL)
+ Config.MSVC_VERSION_INTERNAL = hook
+ return hook
+
+ @classmethod
+ def restore(cls):
+ Config.MSVC_VERSION_INTERNAL = cls.MSVC_VERSION_INTERNAL
+
+
+class ConfigTests(unittest.TestCase):
+
+ def test_vcver(self):
+ # all vc._VCVER in Config.MSVC_VERSION_SUFFIX
+ _VCVER = Patch.vc._VCVER.enable_copy()
+ _VCVER.append('99.9')
+ with self.assertRaises(MSVCInternalError):
+ Config.verify()
+ Patch.vc._VCVER.restore()
+
+ def test_msvc_version_internal(self):
+ # all vc._VCVER numstr in Config.MSVC_VERSION_INTERNAL
+ MSVC_VERSION_INTERNAL = Patch.Config.MSVC_VERSION_INTERNAL.enable_copy()
+ del MSVC_VERSION_INTERNAL['14.3']
+ with self.assertRaises(MSVCInternalError):
+ Config.verify()
+ Patch.Config.MSVC_VERSION_INTERNAL.restore()
+
+ def test_verify(self):
+ Config.verify()
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/SCons/Tool/MSCommon/MSVC/Dispatcher.py b/SCons/Tool/MSCommon/MSVC/Dispatcher.py
new file mode 100644
index 000000000..42b5287b0
--- /dev/null
+++ b/SCons/Tool/MSCommon/MSVC/Dispatcher.py
@@ -0,0 +1,84 @@
+# MIT License
+#
+# Copyright The SCons Foundation
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# 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.
+
+"""
+Internal method dispatcher for Microsoft Visual C/C++.
+
+MSVC modules can register their module (register_modulename) and individual
+classes (register_class) with the method dispatcher during initialization. MSVC
+modules tend to be registered immediately after the Dispatcher import near the
+top of the file. Methods in the MSVC modules can be invoked indirectly without
+having to hard-code the method calls effectively decoupling the upstream module
+with the downstream modules:
+
+The reset method dispatches calls to all registered objects with a reset method
+and/or a _reset method. The reset methods are used to restore data structures
+to their initial state for testing purposes. Typically, this involves clearing
+cached values.
+
+The verify method dispatches calls to all registered objects with a verify
+method and/or a _verify method. The verify methods are used to check that
+initialized data structures distributed across multiple modules are internally
+consistent. An exception is raised when a verification constraint violation
+is detected. Typically, this verifies that initialized dictionaries support
+all of the requisite keys as new versions are added.
+"""
+
+import sys
+
+from ..common import (
+ debug,
+)
+
+_refs = []
+
+
+def register_modulename(modname):
+ module = sys.modules[modname]
+ _refs.append(module)
+
+
+def register_class(ref):
+ _refs.append(ref)
+
+
+def reset():
+ debug('')
+ for ref in _refs:
+ for method in ['reset', '_reset']:
+ if not hasattr(ref, method) or not callable(getattr(ref, method, None)):
+ continue
+ debug('call %s.%s()', ref.__name__, method)
+ func = getattr(ref, method)
+ func()
+
+
+def verify():
+ debug('')
+ for ref in _refs:
+ for method in ['verify', '_verify']:
+ if not hasattr(ref, method) or not callable(getattr(ref, method, None)):
+ continue
+ debug('call %s.%s()', ref.__name__, method)
+ func = getattr(ref, method)
+ func()
diff --git a/SCons/Tool/MSCommon/MSVC/DispatcherTests.py b/SCons/Tool/MSCommon/MSVC/DispatcherTests.py
new file mode 100644
index 000000000..d6af8d464
--- /dev/null
+++ b/SCons/Tool/MSCommon/MSVC/DispatcherTests.py
@@ -0,0 +1,119 @@
+# MIT License
+#
+# Copyright The SCons Foundation
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# 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.
+
+"""
+Test internal method dispatcher for Microsoft Visual C/C++.
+"""
+
+import unittest
+
+from SCons.Tool.MSCommon import MSVC
+
+MSVC.Dispatcher.register_modulename(__name__)
+
+
+class Data:
+ reset_count = 0
+ verify_count = 0
+
+
+# current module - not callable
+_reset = None
+reset = None
+_verify = None
+verify = None
+
+
+class StaticMethods:
+ @staticmethod
+ def _reset():
+ Data.reset_count += 1
+
+ @staticmethod
+ def reset():
+ Data.reset_count += 1
+
+ @staticmethod
+ def _verify():
+ Data.verify_count += 1
+
+ @staticmethod
+ def verify():
+ Data.verify_count += 1
+
+
+class ClassMethods:
+ @classmethod
+ def _reset(cls):
+ Data.reset_count += 1
+
+ @classmethod
+ def reset(cls):
+ Data.reset_count += 1
+
+ @classmethod
+ def _verify(cls):
+ Data.verify_count += 1
+
+ @classmethod
+ def verify(cls):
+ Data.verify_count += 1
+
+
+class NotCallable:
+ _reset = None
+ reset = None
+
+ _verify = None
+ _verify = None
+
+
+MSVC.Dispatcher.register_class(StaticMethods)
+MSVC.Dispatcher.register_class(ClassMethods)
+MSVC.Dispatcher.register_class(NotCallable)
+
+
+class DispatcherTests(unittest.TestCase):
+ def test_dispatcher_reset(self):
+ MSVC.Dispatcher.reset()
+ self.assertTrue(Data.reset_count == 4, "MSVC.Dispatcher.reset() count failed")
+ Data.reset_count = 0
+
+ def test_dispatcher_verify(self):
+ MSVC.Dispatcher.verify()
+ self.assertTrue(Data.verify_count == 4, "MSVC.Dispatcher.verify() count failed")
+ Data.verify_count = 0
+
+ def test_msvc_reset(self):
+ MSVC._reset()
+ self.assertTrue(Data.reset_count == 4, "MSVC._reset() count failed")
+ Data.reset_count = 0
+
+ def test_msvc_verify(self):
+ MSVC._verify()
+ self.assertTrue(Data.verify_count == 4, "MSVC._verify() count failed")
+ Data.verify_count = 0
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/SCons/Tool/MSCommon/MSVC/Exceptions.py b/SCons/Tool/MSCommon/MSVC/Exceptions.py
new file mode 100644
index 000000000..7b24a2b4b
--- /dev/null
+++ b/SCons/Tool/MSCommon/MSVC/Exceptions.py
@@ -0,0 +1,56 @@
+# MIT License
+#
+# Copyright The SCons Foundation
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# 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.
+
+"""
+Exceptions for Microsoft Visual C/C++.
+"""
+
+# reminder: add exceptions to MSCommon if necessary
+
+class VisualCException(Exception):
+ pass
+
+class MSVCInternalError(VisualCException):
+ pass
+
+class MSVCUserError(VisualCException):
+ pass
+
+class MSVCScriptExecutionError(VisualCException):
+ pass
+
+class MSVCVersionNotFound(MSVCUserError):
+ pass
+
+class MSVCSDKVersionNotFound(MSVCUserError):
+ pass
+
+class MSVCToolsetVersionNotFound(MSVCUserError):
+ pass
+
+class MSVCSpectreLibsNotFound(MSVCUserError):
+ pass
+
+class MSVCArgumentError(MSVCUserError):
+ pass
+
diff --git a/SCons/Tool/MSCommon/MSVC/Policy.py b/SCons/Tool/MSCommon/MSVC/Policy.py
new file mode 100644
index 000000000..fe8da3156
--- /dev/null
+++ b/SCons/Tool/MSCommon/MSVC/Policy.py
@@ -0,0 +1,301 @@
+# MIT License
+#
+# Copyright The SCons Foundation
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# 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.
+
+"""
+Microsoft Visual C/C++ policy handlers.
+
+Notes:
+ * The default msvc not found policy is that a warning is issued. This can be
+ changed globally via the function set_msvc_notfound_policy and/or through
+ the environment via the MSVC_NOTFOUND_POLICY construction variable.
+ * The default msvc script error policy is to suppress all msvc batch file
+ error messages. This can be changed globally via the function
+ set_msvc_scripterror_policy and/or through the environment via the
+ MSVC_SCRIPTERROR_POLICY construction variable.
+"""
+
+from collections import (
+ namedtuple,
+)
+
+import SCons.Warnings
+
+from ..common import (
+ debug,
+)
+
+from .Exceptions import (
+ MSVCArgumentError,
+ MSVCVersionNotFound,
+ MSVCScriptExecutionError,
+)
+
+from .Warnings import (
+ MSVCScriptExecutionWarning,
+)
+
+from . import Dispatcher
+Dispatcher.register_modulename(__name__)
+
+
+# MSVC_NOTFOUND_POLICY definition:
+# error: raise exception
+# warning: issue warning and continue
+# ignore: continue
+
+MSVC_NOTFOUND_POLICY_DEFINITION = namedtuple('MSVCNotFoundPolicyDefinition', [
+ 'value',
+ 'symbol',
+])
+
+MSVC_NOTFOUND_DEFINITION_LIST = []
+
+MSVC_NOTFOUND_POLICY_INTERNAL = {}
+MSVC_NOTFOUND_POLICY_EXTERNAL = {}
+
+for policy_value, policy_symbol_list in [
+ (True, ['Error', 'Exception']),
+ (False, ['Warning', 'Warn']),
+ (None, ['Ignore', 'Suppress']),
+]:
+
+ policy_symbol = policy_symbol_list[0].lower()
+ policy_def = MSVC_NOTFOUND_POLICY_DEFINITION(policy_value, policy_symbol)
+
+ MSVC_NOTFOUND_DEFINITION_LIST.append(policy_def)
+
+ MSVC_NOTFOUND_POLICY_INTERNAL[policy_symbol] = policy_def
+
+ for policy_symbol in policy_symbol_list:
+ MSVC_NOTFOUND_POLICY_EXTERNAL[policy_symbol.lower()] = policy_def
+ MSVC_NOTFOUND_POLICY_EXTERNAL[policy_symbol] = policy_def
+ MSVC_NOTFOUND_POLICY_EXTERNAL[policy_symbol.upper()] = policy_def
+
+# default definition
+_MSVC_NOTFOUND_POLICY_DEF = MSVC_NOTFOUND_POLICY_INTERNAL['warning']
+
+
+# MSVC_SCRIPTERROR_POLICY definition:
+# error: raise exception
+# warning: issue warning and continue
+# ignore: continue
+
+MSVC_SCRIPTERROR_POLICY_DEFINITION = namedtuple('MSVCBatchErrorPolicyDefinition', [
+ 'value',
+ 'symbol',
+])
+
+MSVC_SCRIPTERROR_DEFINITION_LIST = []
+
+MSVC_SCRIPTERROR_POLICY_INTERNAL = {}
+MSVC_SCRIPTERROR_POLICY_EXTERNAL = {}
+
+for policy_value, policy_symbol_list in [
+ (True, ['Error', 'Exception']),
+ (False, ['Warning', 'Warn']),
+ (None, ['Ignore', 'Suppress']),
+]:
+
+ policy_symbol = policy_symbol_list[0].lower()
+ policy_def = MSVC_SCRIPTERROR_POLICY_DEFINITION(policy_value, policy_symbol)
+
+ MSVC_SCRIPTERROR_DEFINITION_LIST.append(policy_def)
+
+ MSVC_SCRIPTERROR_POLICY_INTERNAL[policy_symbol] = policy_def
+
+ for policy_symbol in policy_symbol_list:
+ MSVC_SCRIPTERROR_POLICY_EXTERNAL[policy_symbol.lower()] = policy_def
+ MSVC_SCRIPTERROR_POLICY_EXTERNAL[policy_symbol] = policy_def
+ MSVC_SCRIPTERROR_POLICY_EXTERNAL[policy_symbol.upper()] = policy_def
+
+# default definition
+_MSVC_SCRIPTERROR_POLICY_DEF = MSVC_SCRIPTERROR_POLICY_INTERNAL['ignore']
+
+
+def _msvc_notfound_policy_lookup(symbol):
+
+ try:
+ notfound_policy_def = MSVC_NOTFOUND_POLICY_EXTERNAL[symbol]
+ except KeyError:
+ err_msg = "Value specified for MSVC_NOTFOUND_POLICY is not supported: {}.\n" \
+ " Valid values are: {}".format(
+ repr(symbol),
+ ', '.join([repr(s) for s in MSVC_NOTFOUND_POLICY_EXTERNAL.keys()])
+ )
+ raise MSVCArgumentError(err_msg)
+
+ return notfound_policy_def
+
+def msvc_set_notfound_policy(MSVC_NOTFOUND_POLICY=None):
+ """ Set the default policy when MSVC is not found.
+
+ Args:
+ MSVC_NOTFOUND_POLICY:
+ string representing the policy behavior
+ when MSVC is not found or None
+
+ Returns:
+ The previous policy is returned when the MSVC_NOTFOUND_POLICY argument
+ is not None. The active policy is returned when the MSVC_NOTFOUND_POLICY
+ argument is None.
+
+ """
+ global _MSVC_NOTFOUND_POLICY_DEF
+
+ prev_policy = _MSVC_NOTFOUND_POLICY_DEF.symbol
+
+ policy = MSVC_NOTFOUND_POLICY
+ if policy is not None:
+ _MSVC_NOTFOUND_POLICY_DEF = _msvc_notfound_policy_lookup(policy)
+
+ debug(
+ 'prev_policy=%s, set_policy=%s, policy.symbol=%s, policy.value=%s',
+ repr(prev_policy), repr(policy),
+ repr(_MSVC_NOTFOUND_POLICY_DEF.symbol), repr(_MSVC_NOTFOUND_POLICY_DEF.value)
+ )
+
+ return prev_policy
+
+def msvc_get_notfound_policy():
+ """Return the active policy when MSVC is not found."""
+ debug(
+ 'policy.symbol=%s, policy.value=%s',
+ repr(_MSVC_NOTFOUND_POLICY_DEF.symbol), repr(_MSVC_NOTFOUND_POLICY_DEF.value)
+ )
+ return _MSVC_NOTFOUND_POLICY_DEF.symbol
+
+def msvc_notfound_handler(env, msg):
+
+ if env and 'MSVC_NOTFOUND_POLICY' in env:
+ # environment setting
+ notfound_policy_src = 'environment'
+ policy = env['MSVC_NOTFOUND_POLICY']
+ if policy is not None:
+ # user policy request
+ notfound_policy_def = _msvc_notfound_policy_lookup(policy)
+ else:
+ # active global setting
+ notfound_policy_def = _MSVC_NOTFOUND_POLICY_DEF
+ else:
+ # active global setting
+ notfound_policy_src = 'default'
+ policy = None
+ notfound_policy_def = _MSVC_NOTFOUND_POLICY_DEF
+
+ debug(
+ 'source=%s, set_policy=%s, policy.symbol=%s, policy.value=%s',
+ notfound_policy_src, repr(policy), repr(notfound_policy_def.symbol), repr(notfound_policy_def.value)
+ )
+
+ if notfound_policy_def.value is None:
+ # ignore
+ pass
+ elif notfound_policy_def.value:
+ raise MSVCVersionNotFound(msg)
+ else:
+ SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, msg)
+
+
+def _msvc_scripterror_policy_lookup(symbol):
+
+ try:
+ scripterror_policy_def = MSVC_SCRIPTERROR_POLICY_EXTERNAL[symbol]
+ except KeyError:
+ err_msg = "Value specified for MSVC_SCRIPTERROR_POLICY is not supported: {}.\n" \
+ " Valid values are: {}".format(
+ repr(symbol),
+ ', '.join([repr(s) for s in MSVC_SCRIPTERROR_POLICY_EXTERNAL.keys()])
+ )
+ raise MSVCArgumentError(err_msg)
+
+ return scripterror_policy_def
+
+def msvc_set_scripterror_policy(MSVC_SCRIPTERROR_POLICY=None):
+ """ Set the default policy when msvc batch file execution errors are detected.
+
+ Args:
+ MSVC_SCRIPTERROR_POLICY:
+ string representing the policy behavior
+ when msvc batch file execution errors are detected or None
+
+ Returns:
+ The previous policy is returned when the MSVC_SCRIPTERROR_POLICY argument
+ is not None. The active policy is returned when the MSVC_SCRIPTERROR_POLICY
+ argument is None.
+
+ """
+ global _MSVC_SCRIPTERROR_POLICY_DEF
+
+ prev_policy = _MSVC_SCRIPTERROR_POLICY_DEF.symbol
+
+ policy = MSVC_SCRIPTERROR_POLICY
+ if policy is not None:
+ _MSVC_SCRIPTERROR_POLICY_DEF = _msvc_scripterror_policy_lookup(policy)
+
+ debug(
+ 'prev_policy=%s, set_policy=%s, policy.symbol=%s, policy.value=%s',
+ repr(prev_policy), repr(policy),
+ repr(_MSVC_SCRIPTERROR_POLICY_DEF.symbol), repr(_MSVC_SCRIPTERROR_POLICY_DEF.value)
+ )
+
+ return prev_policy
+
+def msvc_get_scripterror_policy():
+ """Return the active policy when msvc batch file execution errors are detected."""
+ debug(
+ 'policy.symbol=%s, policy.value=%s',
+ repr(_MSVC_SCRIPTERROR_POLICY_DEF.symbol), repr(_MSVC_SCRIPTERROR_POLICY_DEF.value)
+ )
+ return _MSVC_SCRIPTERROR_POLICY_DEF.symbol
+
+def msvc_scripterror_handler(env, msg):
+
+ if env and 'MSVC_SCRIPTERROR_POLICY' in env:
+ # environment setting
+ scripterror_policy_src = 'environment'
+ policy = env['MSVC_SCRIPTERROR_POLICY']
+ if policy is not None:
+ # user policy request
+ scripterror_policy_def = _msvc_scripterror_policy_lookup(policy)
+ else:
+ # active global setting
+ scripterror_policy_def = _MSVC_SCRIPTERROR_POLICY_DEF
+ else:
+ # active global setting
+ scripterror_policy_src = 'default'
+ policy = None
+ scripterror_policy_def = _MSVC_SCRIPTERROR_POLICY_DEF
+
+ debug(
+ 'source=%s, set_policy=%s, policy.symbol=%s, policy.value=%s',
+ scripterror_policy_src, repr(policy), repr(scripterror_policy_def.symbol), repr(scripterror_policy_def.value)
+ )
+
+ if scripterror_policy_def.value is None:
+ # ignore
+ pass
+ elif scripterror_policy_def.value:
+ raise MSVCScriptExecutionError(msg)
+ else:
+ SCons.Warnings.warn(MSVCScriptExecutionWarning, msg)
+
diff --git a/SCons/Tool/MSCommon/MSVC/PolicyTests.py b/SCons/Tool/MSCommon/MSVC/PolicyTests.py
new file mode 100644
index 000000000..013fd477d
--- /dev/null
+++ b/SCons/Tool/MSCommon/MSVC/PolicyTests.py
@@ -0,0 +1,169 @@
+# MIT License
+#
+# Copyright The SCons Foundation
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# 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.
+
+"""
+Test Microsoft Visual C/C++ policy handlers.
+"""
+
+import unittest
+
+import SCons.Warnings
+
+from SCons.Tool.MSCommon.MSVC import Policy
+
+from SCons.Tool.MSCommon.MSVC.Exceptions import (
+ MSVCArgumentError,
+ MSVCVersionNotFound,
+ MSVCScriptExecutionError,
+)
+
+from SCons.Tool.MSCommon.MSVC.Warnings import (
+ MSVCScriptExecutionWarning,
+)
+
+class PolicyTests(unittest.TestCase):
+
+ def setUp(self):
+ self.warnstack = []
+
+ def push_warning_as_exception(self, warning_class):
+ SCons.Warnings.enableWarningClass(warning_class)
+ prev_state = SCons.Warnings.warningAsException()
+ self.warnstack.append((warning_class, prev_state))
+
+ def pop_warning_as_exception(self):
+ warning_class, prev_state = self.warnstack.pop()
+ SCons.Warnings.warningAsException(prev_state)
+ SCons.Warnings.suppressWarningClass(warning_class)
+
+ # msvc_set_notfound_policy, msvc_get_notfound_policy, and MSVC_NOTFOUND_POLICY
+
+ def test_notfound_func_valid_symbols(self):
+ def_policy = Policy.msvc_get_notfound_policy()
+ last_policy = def_policy
+ for notfound_def in Policy.MSVC_NOTFOUND_DEFINITION_LIST:
+ for symbol in [notfound_def.symbol, notfound_def.symbol.lower(), notfound_def.symbol.upper()]:
+ prev_policy = Policy.msvc_set_notfound_policy(symbol)
+ self.assertTrue(prev_policy == last_policy, "notfound policy: {} != {}".format(
+ repr(prev_policy), repr(last_policy)
+ ))
+ cur_set_policy = Policy.msvc_set_notfound_policy()
+ cur_get_policy = Policy.msvc_get_notfound_policy()
+ self.assertTrue(cur_set_policy == cur_get_policy, "notfound policy: {} != {}".format(
+ repr(cur_set_policy), repr(cur_get_policy)
+ ))
+ last_policy = cur_get_policy
+ Policy.msvc_set_notfound_policy(def_policy)
+
+ def test_notfound_func_invalid_symbol(self):
+ with self.assertRaises(MSVCArgumentError):
+ Policy.msvc_set_notfound_policy('Undefined')
+
+ def test_notfound_handler_invalid_symbol(self):
+ with self.assertRaises(MSVCArgumentError):
+ Policy.msvc_notfound_handler({'MSVC_NOTFOUND_POLICY': 'Undefined'}, '')
+
+ def test_notfound_handler_ignore(self):
+ def_policy = Policy.msvc_set_notfound_policy('Ignore')
+ Policy.msvc_notfound_handler(None, '')
+ Policy.msvc_notfound_handler({'MSVC_NOTFOUND_POLICY': None}, '')
+ Policy.msvc_set_notfound_policy(def_policy)
+
+ def test_notfound_handler_warning(self):
+ # treat warning as exception for testing
+ self.push_warning_as_exception(SCons.Warnings.VisualCMissingWarning)
+ def_policy = Policy.msvc_set_notfound_policy('Warning')
+ with self.assertRaises(SCons.Warnings.VisualCMissingWarning):
+ Policy.msvc_notfound_handler(None, '')
+ Policy.msvc_set_notfound_policy('Ignore')
+ with self.assertRaises(SCons.Warnings.VisualCMissingWarning):
+ Policy.msvc_notfound_handler({'MSVC_NOTFOUND_POLICY': 'Warning'}, '')
+ Policy.msvc_set_notfound_policy(def_policy)
+ self.pop_warning_as_exception()
+
+ def test_notfound_handler_error(self):
+ def_policy = Policy.msvc_set_notfound_policy('Error')
+ with self.assertRaises(MSVCVersionNotFound):
+ Policy.msvc_notfound_handler(None, '')
+ Policy.msvc_set_notfound_policy('Ignore')
+ with self.assertRaises(MSVCVersionNotFound):
+ Policy.msvc_notfound_handler({'MSVC_NOTFOUND_POLICY': 'Error'}, '')
+ Policy.msvc_set_notfound_policy(def_policy)
+
+ # msvc_set_scripterror_policy, msvc_get_scripterror_policy, and MSVC_SCRIPTERROR_POLICY
+
+ def test_scripterror_func_valid_symbols(self):
+ def_policy = Policy.msvc_get_scripterror_policy()
+ last_policy = def_policy
+ for scripterror_def in Policy.MSVC_SCRIPTERROR_DEFINITION_LIST:
+ for symbol in [scripterror_def.symbol, scripterror_def.symbol.lower(), scripterror_def.symbol.upper()]:
+ prev_policy = Policy.msvc_set_scripterror_policy(symbol)
+ self.assertTrue(prev_policy == last_policy, "scripterror policy: {} != {}".format(
+ repr(prev_policy), repr(last_policy)
+ ))
+ cur_set_policy = Policy.msvc_set_scripterror_policy()
+ cur_get_policy = Policy.msvc_get_scripterror_policy()
+ self.assertTrue(cur_set_policy == cur_get_policy, "scripterror policy: {} != {}".format(
+ repr(cur_set_policy), repr(cur_get_policy)
+ ))
+ last_policy = cur_get_policy
+ Policy.msvc_set_scripterror_policy(def_policy)
+
+ def test_scripterror_func_invalid_symbol(self):
+ with self.assertRaises(MSVCArgumentError):
+ Policy.msvc_set_scripterror_policy('Undefined')
+
+ def test_scripterror_handler_invalid_symbol(self):
+ with self.assertRaises(MSVCArgumentError):
+ Policy.msvc_scripterror_handler({'MSVC_SCRIPTERROR_POLICY': 'Undefined'}, '')
+
+ def test_scripterror_handler_ignore(self):
+ def_policy = Policy.msvc_set_scripterror_policy('Ignore')
+ Policy.msvc_scripterror_handler(None, '')
+ Policy.msvc_scripterror_handler({'MSVC_SCRIPTERROR_POLICY': None}, '')
+ Policy.msvc_set_scripterror_policy(def_policy)
+
+ def test_scripterror_handler_warning(self):
+ # treat warning as exception for testing
+ self.push_warning_as_exception(MSVCScriptExecutionWarning)
+ def_policy = Policy.msvc_set_scripterror_policy('Warning')
+ with self.assertRaises(MSVCScriptExecutionWarning):
+ Policy.msvc_scripterror_handler(None, '')
+ Policy.msvc_set_scripterror_policy('Ignore')
+ with self.assertRaises(MSVCScriptExecutionWarning):
+ Policy.msvc_scripterror_handler({'MSVC_SCRIPTERROR_POLICY': 'Warning'}, '')
+ Policy.msvc_set_scripterror_policy(def_policy)
+ self.pop_warning_as_exception()
+
+ def test_scripterror_handler_error(self):
+ def_policy = Policy.msvc_set_scripterror_policy('Error')
+ with self.assertRaises(MSVCScriptExecutionError):
+ Policy.msvc_scripterror_handler(None, '')
+ Policy.msvc_set_scripterror_policy('Ignore')
+ with self.assertRaises(MSVCScriptExecutionError):
+ Policy.msvc_scripterror_handler({'MSVC_SCRIPTERROR_POLICY': 'Error'}, '')
+ Policy.msvc_set_scripterror_policy(def_policy)
+
+if __name__ == "__main__":
+ unittest.main()
+
diff --git a/SCons/Tool/MSCommon/MSVC/Registry.py b/SCons/Tool/MSCommon/MSVC/Registry.py
new file mode 100644
index 000000000..9519e1553
--- /dev/null
+++ b/SCons/Tool/MSCommon/MSVC/Registry.py
@@ -0,0 +1,118 @@
+# MIT License
+#
+# Copyright The SCons Foundation
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# 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.
+
+"""
+Windows registry functions for Microsoft Visual C/C++.
+"""
+
+import os
+
+from SCons.Util import (
+ HKEY_LOCAL_MACHINE,
+ HKEY_CURRENT_USER,
+ RegGetValue,
+)
+
+from .. common import (
+ debug,
+)
+
+from . import Util
+
+from . import Dispatcher
+Dispatcher.register_modulename(__name__)
+
+
+# A null-terminated string that contains unexpanded references to environment variables.
+REG_EXPAND_SZ = 2
+
+def read_value(hkey, subkey_valname, expand=True):
+ try:
+ rval_t = RegGetValue(hkey, subkey_valname)
+ except OSError:
+ debug('OSError: hkey=%s, subkey=%s', repr(hkey), repr(subkey_valname))
+ return None
+ rval, regtype = rval_t
+ if regtype == REG_EXPAND_SZ and expand:
+ rval = os.path.expandvars(rval)
+ debug('hkey=%s, subkey=%s, rval=%s', repr(hkey), repr(subkey_valname), repr(rval))
+ return rval
+
+def registry_query_path(key, val, suffix, expand=True):
+ extval = val + '\\' + suffix if suffix else val
+ qpath = read_value(key, extval, expand=expand)
+ if qpath and os.path.exists(qpath):
+ qpath = Util.process_path(qpath)
+ else:
+ qpath = None
+ return (qpath, key, val, extval)
+
+REG_SOFTWARE_MICROSOFT = [
+ (HKEY_LOCAL_MACHINE, r'Software\Wow6432Node\Microsoft'),
+ (HKEY_CURRENT_USER, r'Software\Wow6432Node\Microsoft'), # SDK queries
+ (HKEY_LOCAL_MACHINE, r'Software\Microsoft'),
+ (HKEY_CURRENT_USER, r'Software\Microsoft'),
+]
+
+def microsoft_query_paths(suffix, usrval=None, expand=True):
+ paths = []
+ records = []
+ for key, val in REG_SOFTWARE_MICROSOFT:
+ extval = val + '\\' + suffix if suffix else val
+ qpath = read_value(key, extval, expand=expand)
+ if qpath and os.path.exists(qpath):
+ qpath = Util.process_path(qpath)
+ if qpath not in paths:
+ paths.append(qpath)
+ records.append((qpath, key, val, extval, usrval))
+ return records
+
+def microsoft_query_keys(suffix, usrval=None, expand=True):
+ records = []
+ for key, val in REG_SOFTWARE_MICROSOFT:
+ extval = val + '\\' + suffix if suffix else val
+ rval = read_value(key, extval, expand=expand)
+ if rval:
+ records.append((rval, key, val, extval, usrval))
+ return records
+
+def microsoft_sdks(version):
+ return '\\'.join([r'Microsoft SDKs\Windows', 'v' + version, r'InstallationFolder'])
+
+def sdk_query_paths(version):
+ q = microsoft_sdks(version)
+ return microsoft_query_paths(q)
+
+def windows_kits(version):
+ return r'Windows Kits\Installed Roots\KitsRoot' + version
+
+def windows_kit_query_paths(version):
+ q = windows_kits(version)
+ return microsoft_query_paths(q)
+
+def vstudio_sxs_vc7(version):
+ return '\\'.join([r'VisualStudio\SxS\VC7', version])
+
+def devdiv_vs_servicing_component(version, component):
+ return '\\'.join([r'DevDiv\VS\Servicing', version, component, 'Install'])
+
diff --git a/SCons/Tool/MSCommon/MSVC/RegistryTests.py b/SCons/Tool/MSCommon/MSVC/RegistryTests.py
new file mode 100644
index 000000000..aff3b3f99
--- /dev/null
+++ b/SCons/Tool/MSCommon/MSVC/RegistryTests.py
@@ -0,0 +1,83 @@
+# MIT License
+#
+# Copyright The SCons Foundation
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# 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.
+
+"""
+Test windows registry functions for Microsoft Visual C/C++.
+"""
+
+import unittest
+import sys
+
+from SCons.Tool.MSCommon.MSVC import Config
+from SCons.Tool.MSCommon.MSVC import Registry
+
+@unittest.skipUnless(sys.platform.startswith("win"), "requires Windows")
+class RegistryTests(unittest.TestCase):
+
+ _sdk_versions = None
+
+ @classmethod
+ def setUpClass(cls):
+ cls._sdk_versions = []
+ sdk_seen = set()
+ for vs_def in Config.VISUALSTUDIO_DEFINITION_LIST:
+ if not vs_def.vc_sdk_versions:
+ continue
+ for sdk_version in vs_def.vc_sdk_versions:
+ if sdk_version in sdk_seen:
+ continue
+ sdk_seen.add(sdk_version)
+ cls._sdk_versions.append(sdk_version)
+
+ def setUp(self):
+ self.sdk_versions = self.__class__._sdk_versions
+
+ def test_sdk_query_paths(self):
+ for sdk_version in self.sdk_versions:
+ _ = Registry.sdk_query_paths(sdk_version)
+
+ def test_vstudio_sxs_vc7(self):
+ suffix = Registry.vstudio_sxs_vc7('14.0')
+ _ = Registry.microsoft_query_paths(suffix)
+
+ def test_microsoft_query_keys(self):
+ val = r'SYSTEM\CurrentControlSet\Control\Session Manager\Environment'
+ for suffix in ['Temp', 'Tmp']:
+ _ = Registry.registry_query_path(Registry.HKEY_LOCAL_MACHINE, val, suffix, expand=True)
+ _ = Registry.registry_query_path(Registry.HKEY_LOCAL_MACHINE, val, suffix, expand=False)
+
+ def test_registry_query_path(self):
+ # need a better test for when VS2015 is no longer installed
+ for component_reg in ('enterprise', 'professional', 'community'):
+ suffix = Registry.devdiv_vs_servicing_component('14.0', component_reg)
+ rval = Registry.microsoft_query_keys(suffix, component_reg)
+ if not rval:
+ continue
+
+ def test_windows_kit_query_paths(self):
+ for sdk_version in self.sdk_versions:
+ _ = Registry.windows_kit_query_paths(sdk_version)
+
+if __name__ == "__main__":
+ unittest.main()
+
diff --git a/SCons/Tool/MSCommon/MSVC/ScriptArguments.py b/SCons/Tool/MSCommon/MSVC/ScriptArguments.py
new file mode 100644
index 000000000..57dbf9d3f
--- /dev/null
+++ b/SCons/Tool/MSCommon/MSVC/ScriptArguments.py
@@ -0,0 +1,1031 @@
+# MIT License
+#
+# Copyright The SCons Foundation
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# 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.
+
+"""
+Batch file argument functions for Microsoft Visual C/C++.
+"""
+
+import os
+import re
+import enum
+
+from collections import (
+ namedtuple,
+)
+
+from ..common import (
+ CONFIG_CACHE_FORCE_DEFAULT_ARGUMENTS,
+ debug,
+)
+
+from . import Util
+from . import Config
+from . import Registry
+from . import WinSDK
+
+from .Exceptions import (
+ MSVCInternalError,
+ MSVCSDKVersionNotFound,
+ MSVCToolsetVersionNotFound,
+ MSVCSpectreLibsNotFound,
+ MSVCArgumentError,
+)
+
+from . import Dispatcher
+Dispatcher.register_modulename(__name__)
+
+
+# Script argument: boolean True
+_ARGUMENT_BOOLEAN_TRUE_LEGACY = (True, '1') # MSVC_UWP_APP
+_ARGUMENT_BOOLEAN_TRUE = (True,)
+
+# TODO: verify SDK 10 version folder names 10.0.XXXXX.0 {1,3} last?
+re_sdk_version_100 = re.compile(r'^10[.][0-9][.][0-9]{5}[.][0-9]{1}$')
+re_sdk_version_81 = re.compile(r'^8[.]1$')
+
+re_sdk_dispatch_map = {
+ '10.0': re_sdk_version_100,
+ '8.1': re_sdk_version_81,
+}
+
+def _verify_re_sdk_dispatch_map():
+ debug('')
+ for sdk_version in Config.MSVC_SDK_VERSIONS:
+ if sdk_version in re_sdk_dispatch_map:
+ continue
+ err_msg = 'sdk version {} not in re_sdk_dispatch_map'.format(sdk_version)
+ raise MSVCInternalError(err_msg)
+ return None
+
+# SxS version bugfix
+_msvc_sxs_bugfix_map = {}
+_msvc_sxs_bugfix_folder = {}
+_msvc_sxs_bugfix_version = {}
+
+for msvc_version, sxs_version, sxs_bugfix in [
+ # VS2019\Common7\Tools\vsdevcmd\ext\vcvars.bat AzDO Bug#1293526
+ # special handling of the 16.8 SxS toolset, use VC\Auxiliary\Build\14.28 directory and SxS files
+ # if SxS version 14.28 not present/installed, fallback selection of toolset VC\Tools\MSVC\14.28.nnnnn.
+ ('14.2', '14.28.16.8', '14.28')
+]:
+ _msvc_sxs_bugfix_map.setdefault(msvc_version, []).append((sxs_version, sxs_bugfix))
+ _msvc_sxs_bugfix_folder[(msvc_version, sxs_bugfix)] = sxs_version
+ _msvc_sxs_bugfix_version[(msvc_version, sxs_version)] = sxs_bugfix
+
+# MSVC_SCRIPT_ARGS
+re_vcvars_uwp = re.compile(r'(?:(?<!\S)|^)(?P<uwp>(?:uwp|store))(?:(?!\S)|$)',re.IGNORECASE)
+re_vcvars_sdk = re.compile(r'(?:(?<!\S)|^)(?P<sdk>(?:[1-9][0-9]*[.]\S*))(?:(?!\S)|$)',re.IGNORECASE)
+re_vcvars_toolset = re.compile(r'(?:(?<!\S)|^)(?P<toolset_arg>(?:[-]{1,2}|[/])vcvars_ver[=](?P<toolset>\S*))(?:(?!\S)|$)', re.IGNORECASE)
+re_vcvars_spectre = re.compile(r'(?:(?<!\S)|^)(?P<spectre_arg>(?:[-]{1,2}|[/])vcvars_spectre_libs[=](?P<spectre>\S*))(?:(?!\S)|$)',re.IGNORECASE)
+
+# Force default sdk argument
+_MSVC_FORCE_DEFAULT_SDK = False
+
+# Force default toolset argument
+_MSVC_FORCE_DEFAULT_TOOLSET = False
+
+# Force default arguments
+_MSVC_FORCE_DEFAULT_ARGUMENTS = False
+
+def _msvc_force_default_sdk(force=True):
+ global _MSVC_FORCE_DEFAULT_SDK
+ _MSVC_FORCE_DEFAULT_SDK = force
+ debug('_MSVC_FORCE_DEFAULT_SDK=%s', repr(force))
+
+def _msvc_force_default_toolset(force=True):
+ global _MSVC_FORCE_DEFAULT_TOOLSET
+ _MSVC_FORCE_DEFAULT_TOOLSET = force
+ debug('_MSVC_FORCE_DEFAULT_TOOLSET=%s', repr(force))
+
+def msvc_force_default_arguments(force=None):
+ global _MSVC_FORCE_DEFAULT_ARGUMENTS
+ prev_policy = _MSVC_FORCE_DEFAULT_ARGUMENTS
+ if force is not None:
+ _MSVC_FORCE_DEFAULT_ARGUMENTS = force
+ _msvc_force_default_sdk(force)
+ _msvc_force_default_toolset(force)
+ return prev_policy
+
+if CONFIG_CACHE_FORCE_DEFAULT_ARGUMENTS:
+ msvc_force_default_arguments(force=True)
+
+# UWP SDK 8.1 and SDK 10:
+#
+# https://stackoverflow.com/questions/46659238/build-windows-app-compatible-for-8-1-and-10
+# VS2019 - UWP (Except for Win10Mobile)
+# VS2017 - UWP
+# VS2015 - UWP, Win8.1 StoreApp, WP8/8.1 StoreApp
+# VS2013 - Win8/8.1 StoreApp, WP8/8.1 StoreApp
+
+# SPECTRE LIBS (msvc documentation):
+# "There are no versions of Spectre-mitigated libraries for Universal Windows (UWP) apps or
+# components. App-local deployment of such libraries isn't possible."
+
+# MSVC batch file arguments:
+#
+# VS2022: UWP, SDK, TOOLSET, SPECTRE
+# VS2019: UWP, SDK, TOOLSET, SPECTRE
+# VS2017: UWP, SDK, TOOLSET, SPECTRE
+# VS2015: UWP, SDK
+#
+# MSVC_SCRIPT_ARGS: VS2015+
+#
+# MSVC_UWP_APP: VS2015+
+# MSVC_SDK_VERSION: VS2015+
+# MSVC_TOOLSET_VERSION: VS2017+
+# MSVC_SPECTRE_LIBS: VS2017+
+
+@enum.unique
+class SortOrder(enum.IntEnum):
+ UWP = 1 # MSVC_UWP_APP
+ SDK = 2 # MSVC_SDK_VERSION
+ TOOLSET = 3 # MSVC_TOOLSET_VERSION
+ SPECTRE = 4 # MSVC_SPECTRE_LIBS
+ USER = 5 # MSVC_SCRIPT_ARGS
+
+VS2019 = Config.MSVS_VERSION_INTERNAL['2019']
+VS2017 = Config.MSVS_VERSION_INTERNAL['2017']
+VS2015 = Config.MSVS_VERSION_INTERNAL['2015']
+
+MSVC_VERSION_ARGS_DEFINITION = namedtuple('MSVCVersionArgsDefinition', [
+ 'version', # full version (e.g., '14.1Exp', '14.32.31326')
+ 'vs_def',
+])
+
+def _msvc_version(version):
+
+ verstr = Util.get_msvc_version_prefix(version)
+ vs_def = Config.MSVC_VERSION_INTERNAL[verstr]
+
+ version_args = MSVC_VERSION_ARGS_DEFINITION(
+ version = version,
+ vs_def = vs_def,
+ )
+
+ return version_args
+
+def _toolset_version(version):
+
+ verstr = Util.get_msvc_version_prefix(version)
+ vs_def = Config.MSVC_VERSION_INTERNAL[verstr]
+
+ version_args = MSVC_VERSION_ARGS_DEFINITION(
+ version = version,
+ vs_def = vs_def,
+ )
+
+ return version_args
+
+def _msvc_script_argument_uwp(env, msvc, arglist):
+
+ uwp_app = env['MSVC_UWP_APP']
+ debug('MSVC_VERSION=%s, MSVC_UWP_APP=%s', repr(msvc.version), repr(uwp_app))
+
+ if not uwp_app:
+ return None
+
+ if uwp_app not in _ARGUMENT_BOOLEAN_TRUE_LEGACY:
+ return None
+
+ if msvc.vs_def.vc_buildtools_def.vc_version_numeric < VS2015.vc_buildtools_def.vc_version_numeric:
+ debug(
+ 'invalid: msvc version constraint: %s < %s VS2015',
+ repr(msvc.vs_def.vc_buildtools_def.vc_version_numeric),
+ repr(VS2015.vc_buildtools_def.vc_version_numeric)
+ )
+ err_msg = "MSVC_UWP_APP ({}) constraint violation: MSVC_VERSION {} < {} VS2015".format(
+ repr(uwp_app), repr(msvc.version), repr(VS2015.vc_buildtools_def.vc_version)
+ )
+ raise MSVCArgumentError(err_msg)
+
+ # VS2017+ rewrites uwp => store for 14.0 toolset
+ uwp_arg = msvc.vs_def.vc_uwp
+
+ # store/uwp may not be fully installed
+ argpair = (SortOrder.UWP, uwp_arg)
+ arglist.append(argpair)
+
+ return uwp_arg
+
+def _user_script_argument_uwp(env, uwp, user_argstr):
+
+ matches = [m for m in re_vcvars_uwp.finditer(user_argstr)]
+ if not matches:
+ return False
+
+ if len(matches) > 1:
+ debug('multiple uwp declarations: MSVC_SCRIPT_ARGS=%s', repr(user_argstr))
+ err_msg = "multiple uwp declarations: MSVC_SCRIPT_ARGS={}".format(repr(user_argstr))
+ raise MSVCArgumentError(err_msg)
+
+ if not uwp:
+ return True
+
+ env_argstr = env.get('MSVC_UWP_APP','')
+ debug('multiple uwp declarations: MSVC_UWP_APP=%s, MSVC_SCRIPT_ARGS=%s', repr(env_argstr), repr(user_argstr))
+
+ err_msg = "multiple uwp declarations: MSVC_UWP_APP={} and MSVC_SCRIPT_ARGS={}".format(
+ repr(env_argstr), repr(user_argstr)
+ )
+
+ raise MSVCArgumentError(err_msg)
+
+def _msvc_script_argument_sdk_constraints(msvc, sdk_version):
+
+ if msvc.vs_def.vc_buildtools_def.vc_version_numeric < VS2015.vc_buildtools_def.vc_version_numeric:
+ debug(
+ 'invalid: msvc_version constraint: %s < %s VS2015',
+ repr(msvc.vs_def.vc_buildtools_def.vc_version_numeric),
+ repr(VS2015.vc_buildtools_def.vc_version_numeric)
+ )
+ err_msg = "MSVC_SDK_VERSION ({}) constraint violation: MSVC_VERSION {} < {} VS2015".format(
+ repr(sdk_version), repr(msvc.version), repr(VS2015.vc_buildtools_def.vc_version)
+ )
+ return err_msg
+
+ for msvc_sdk_version in msvc.vs_def.vc_sdk_versions:
+ re_sdk_version = re_sdk_dispatch_map[msvc_sdk_version]
+ if re_sdk_version.match(sdk_version):
+ debug('valid: sdk_version=%s', repr(sdk_version))
+ return None
+
+ debug('invalid: method exit: sdk_version=%s', repr(sdk_version))
+ err_msg = "MSVC_SDK_VERSION ({}) is not supported".format(repr(sdk_version))
+ return err_msg
+
+def _msvc_script_argument_sdk_platform_constraints(msvc, toolset, sdk_version, platform_def):
+
+ if sdk_version == '8.1' and platform_def.is_uwp:
+
+ vs_def = toolset.vs_def if toolset else msvc.vs_def
+
+ if vs_def.vc_buildtools_def.vc_version_numeric > VS2015.vc_buildtools_def.vc_version_numeric:
+ debug(
+ 'invalid: uwp/store SDK 8.1 msvc_version constraint: %s > %s VS2015',
+ repr(vs_def.vc_buildtools_def.vc_version_numeric),
+ repr(VS2015.vc_buildtools_def.vc_version_numeric)
+ )
+ if toolset and toolset.vs_def != msvc.vs_def:
+ err_msg = "MSVC_SDK_VERSION ({}) and platform type ({}) constraint violation: toolset version {} > {} VS2015".format(
+ repr(sdk_version), repr(platform_def.vc_platform),
+ repr(toolset.version), repr(VS2015.vc_buildtools_def.vc_version)
+ )
+ else:
+ err_msg = "MSVC_SDK_VERSION ({}) and platform type ({}) constraint violation: MSVC_VERSION {} > {} VS2015".format(
+ repr(sdk_version), repr(platform_def.vc_platform),
+ repr(msvc.version), repr(VS2015.vc_buildtools_def.vc_version)
+ )
+ return err_msg
+
+ return None
+
+def _msvc_script_argument_sdk(env, msvc, toolset, platform_def, arglist):
+
+ sdk_version = env['MSVC_SDK_VERSION']
+ debug(
+ 'MSVC_VERSION=%s, MSVC_SDK_VERSION=%s, platform_type=%s',
+ repr(msvc.version), repr(sdk_version), repr(platform_def.vc_platform)
+ )
+
+ if not sdk_version:
+ return None
+
+ err_msg = _msvc_script_argument_sdk_constraints(msvc, sdk_version)
+ if err_msg:
+ raise MSVCArgumentError(err_msg)
+
+ sdk_list = WinSDK.get_sdk_version_list(msvc.vs_def, platform_def)
+
+ if sdk_version not in sdk_list:
+ err_msg = "MSVC_SDK_VERSION {} not found for platform type {}".format(
+ repr(sdk_version), repr(platform_def.vc_platform)
+ )
+ raise MSVCSDKVersionNotFound(err_msg)
+
+ err_msg = _msvc_script_argument_sdk_platform_constraints(msvc, toolset, sdk_version, platform_def)
+ if err_msg:
+ raise MSVCArgumentError(err_msg)
+
+ argpair = (SortOrder.SDK, sdk_version)
+ arglist.append(argpair)
+
+ return sdk_version
+
+def _msvc_script_default_sdk(env, msvc, platform_def, arglist, force_sdk=False):
+
+ if msvc.vs_def.vc_buildtools_def.vc_version_numeric < VS2015.vc_buildtools_def.vc_version_numeric:
+ return None
+
+ sdk_list = WinSDK.get_sdk_version_list(msvc.vs_def, platform_def)
+ if not len(sdk_list):
+ return None
+
+ sdk_default = sdk_list[0]
+
+ debug(
+ 'MSVC_VERSION=%s, sdk_default=%s, platform_type=%s',
+ repr(msvc.version), repr(sdk_default), repr(platform_def.vc_platform)
+ )
+
+ if force_sdk:
+ argpair = (SortOrder.SDK, sdk_default)
+ arglist.append(argpair)
+
+ return sdk_default
+
+def _user_script_argument_sdk(env, sdk_version, user_argstr):
+
+ matches = [m for m in re_vcvars_sdk.finditer(user_argstr)]
+ if not matches:
+ return None
+
+ if len(matches) > 1:
+ debug('multiple sdk version declarations: MSVC_SCRIPT_ARGS=%s', repr(user_argstr))
+ err_msg = "multiple sdk version declarations: MSVC_SCRIPT_ARGS={}".format(repr(user_argstr))
+ raise MSVCArgumentError(err_msg)
+
+ if not sdk_version:
+ user_sdk = matches[0].group('sdk')
+ return user_sdk
+
+ env_argstr = env.get('MSVC_SDK_VERSION','')
+ debug('multiple sdk version declarations: MSVC_SDK_VERSION=%s, MSVC_SCRIPT_ARGS=%s', repr(env_argstr), repr(user_argstr))
+
+ err_msg = "multiple sdk version declarations: MSVC_SDK_VERSION={} and MSVC_SCRIPT_ARGS={}".format(
+ repr(env_argstr), repr(user_argstr)
+ )
+
+ raise MSVCArgumentError(err_msg)
+
+_toolset_have140_cache = None
+
+def _msvc_have140_toolset():
+ global _toolset_have140_cache
+
+ if _toolset_have140_cache is None:
+ suffix = Registry.vstudio_sxs_vc7('14.0')
+ vcinstalldirs = [record[0] for record in Registry.microsoft_query_paths(suffix)]
+ debug('vc140 toolset: paths=%s', repr(vcinstalldirs))
+ _toolset_have140_cache = True if vcinstalldirs else False
+
+ return _toolset_have140_cache
+
+def _reset_have140_cache():
+ global _toolset_have140_cache
+ debug('reset: cache')
+ _toolset_have140_cache = None
+
+def _msvc_read_toolset_file(msvc, filename):
+ toolset_version = None
+ try:
+ with open(filename) as f:
+ toolset_version = f.readlines()[0].strip()
+ debug(
+ 'msvc_version=%s, filename=%s, toolset_version=%s',
+ repr(msvc.version), repr(filename), repr(toolset_version)
+ )
+ except OSError:
+ debug('OSError: msvc_version=%s, filename=%s', repr(msvc.version), repr(filename))
+ except IndexError:
+ debug('IndexError: msvc_version=%s, filename=%s', repr(msvc.version), repr(filename))
+ return toolset_version
+
+def _msvc_sxs_toolset_folder(msvc, sxs_folder):
+
+ if Util.is_toolset_sxs(sxs_folder):
+ return sxs_folder, sxs_folder
+
+ key = (msvc.vs_def.vc_buildtools_def.vc_version, sxs_folder)
+ if key in _msvc_sxs_bugfix_folder:
+ sxs_version = _msvc_sxs_bugfix_folder[key]
+ return sxs_folder, sxs_version
+
+ debug('sxs folder: ignore version=%s', repr(sxs_folder))
+ return None, None
+
+def _msvc_read_toolset_folders(msvc, vc_dir):
+
+ toolsets_sxs = {}
+ toolsets_full = []
+
+ build_dir = os.path.join(vc_dir, "Auxiliary", "Build")
+ if os.path.exists(build_dir):
+ for sxs_folder, sxs_path in Util.listdir_dirs(build_dir):
+ sxs_folder, sxs_version = _msvc_sxs_toolset_folder(msvc, sxs_folder)
+ if not sxs_version:
+ continue
+ filename = 'Microsoft.VCToolsVersion.{}.txt'.format(sxs_folder)
+ filepath = os.path.join(sxs_path, filename)
+ debug('sxs toolset: check file=%s', repr(filepath))
+ if os.path.exists(filepath):
+ toolset_version = _msvc_read_toolset_file(msvc, filepath)
+ if not toolset_version:
+ continue
+ toolsets_sxs[sxs_version] = toolset_version
+ debug(
+ 'sxs toolset: msvc_version=%s, sxs_version=%s, toolset_version=%s',
+ repr(msvc.version), repr(sxs_version), repr(toolset_version)
+ )
+
+ toolset_dir = os.path.join(vc_dir, "Tools", "MSVC")
+ if os.path.exists(toolset_dir):
+ for toolset_version, toolset_path in Util.listdir_dirs(toolset_dir):
+ binpath = os.path.join(toolset_path, "bin")
+ debug('toolset: check binpath=%s', repr(binpath))
+ if os.path.exists(binpath):
+ toolsets_full.append(toolset_version)
+ debug(
+ 'toolset: msvc_version=%s, toolset_version=%s',
+ repr(msvc.version), repr(toolset_version)
+ )
+
+ vcvars140 = os.path.join(vc_dir, "..", "Common7", "Tools", "vsdevcmd", "ext", "vcvars", "vcvars140.bat")
+ if os.path.exists(vcvars140) and _msvc_have140_toolset():
+ toolset_version = '14.0'
+ toolsets_full.append(toolset_version)
+ debug(
+ 'toolset: msvc_version=%s, toolset_version=%s',
+ repr(msvc.version), repr(toolset_version)
+ )
+
+ toolsets_full.sort(reverse=True)
+
+ # SxS bugfix fixup (if necessary)
+ if msvc.version in _msvc_sxs_bugfix_map:
+ for sxs_version, sxs_bugfix in _msvc_sxs_bugfix_map[msvc.version]:
+ if sxs_version in toolsets_sxs:
+ # have SxS version (folder/file mapping exists)
+ continue
+ for toolset_version in toolsets_full:
+ if not toolset_version.startswith(sxs_bugfix):
+ continue
+ debug(
+ 'sxs toolset: msvc_version=%s, sxs_version=%s, toolset_version=%s',
+ repr(msvc.version), repr(sxs_version), repr(toolset_version)
+ )
+ # SxS compatible bugfix version (equivalent to toolset search)
+ toolsets_sxs[sxs_version] = toolset_version
+ break
+
+ debug('msvc_version=%s, toolsets=%s', repr(msvc.version), repr(toolsets_full))
+
+ return toolsets_sxs, toolsets_full
+
+def _msvc_read_toolset_default(msvc, vc_dir):
+
+ build_dir = os.path.join(vc_dir, "Auxiliary", "Build")
+
+ # VS2019+
+ filename = "Microsoft.VCToolsVersion.{}.default.txt".format(msvc.vs_def.vc_buildtools_def.vc_buildtools)
+ filepath = os.path.join(build_dir, filename)
+
+ debug('default toolset: check file=%s', repr(filepath))
+ if os.path.exists(filepath):
+ toolset_buildtools = _msvc_read_toolset_file(msvc, filepath)
+ if toolset_buildtools:
+ return toolset_buildtools
+
+ # VS2017+
+ filename = "Microsoft.VCToolsVersion.default.txt"
+ filepath = os.path.join(build_dir, filename)
+
+ debug('default toolset: check file=%s', repr(filepath))
+ if os.path.exists(filepath):
+ toolset_default = _msvc_read_toolset_file(msvc, filepath)
+ if toolset_default:
+ return toolset_default
+
+ return None
+
+_toolset_version_cache = {}
+_toolset_default_cache = {}
+
+def _reset_toolset_cache():
+ global _toolset_version_cache
+ global _toolset_default_cache
+ debug('reset: toolset cache')
+ _toolset_version_cache = {}
+ _toolset_default_cache = {}
+
+def _msvc_version_toolsets(msvc, vc_dir):
+
+ if msvc.version in _toolset_version_cache:
+ toolsets_sxs, toolsets_full = _toolset_version_cache[msvc.version]
+ else:
+ toolsets_sxs, toolsets_full = _msvc_read_toolset_folders(msvc, vc_dir)
+ _toolset_version_cache[msvc.version] = toolsets_sxs, toolsets_full
+
+ return toolsets_sxs, toolsets_full
+
+def _msvc_default_toolset(msvc, vc_dir):
+
+ if msvc.version in _toolset_default_cache:
+ toolset_default = _toolset_default_cache[msvc.version]
+ else:
+ toolset_default = _msvc_read_toolset_default(msvc, vc_dir)
+ _toolset_default_cache[msvc.version] = toolset_default
+
+ return toolset_default
+
+def _msvc_version_toolset_vcvars(msvc, vc_dir, toolset_version):
+
+ toolsets_sxs, toolsets_full = _msvc_version_toolsets(msvc, vc_dir)
+
+ if toolset_version in toolsets_full:
+ # full toolset version provided
+ toolset_vcvars = toolset_version
+ return toolset_vcvars
+
+ if Util.is_toolset_sxs(toolset_version):
+ # SxS version provided
+ sxs_version = toolsets_sxs.get(toolset_version, None)
+ if sxs_version and sxs_version in toolsets_full:
+ # SxS full toolset version
+ toolset_vcvars = sxs_version
+ return toolset_vcvars
+ return None
+
+ for toolset_full in toolsets_full:
+ if toolset_full.startswith(toolset_version):
+ toolset_vcvars = toolset_full
+ return toolset_vcvars
+
+ return None
+
+def _msvc_script_argument_toolset_constraints(msvc, toolset_version):
+
+ if msvc.vs_def.vc_buildtools_def.vc_version_numeric < VS2017.vc_buildtools_def.vc_version_numeric:
+ debug(
+ 'invalid: msvc version constraint: %s < %s VS2017',
+ repr(msvc.vs_def.vc_buildtools_def.vc_version_numeric),
+ repr(VS2017.vc_buildtools_def.vc_version_numeric)
+ )
+ err_msg = "MSVC_TOOLSET_VERSION ({}) constraint violation: MSVC_VERSION {} < {} VS2017".format(
+ repr(toolset_version), repr(msvc.version), repr(VS2017.vc_buildtools_def.vc_version)
+ )
+ return err_msg
+
+ toolset_verstr = Util.get_msvc_version_prefix(toolset_version)
+
+ if not toolset_verstr:
+ debug('invalid: msvc version: toolset_version=%s', repr(toolset_version))
+ err_msg = 'MSVC_TOOLSET_VERSION {} format is not supported'.format(
+ repr(toolset_version)
+ )
+ return err_msg
+
+ toolset_vernum = float(toolset_verstr)
+
+ if toolset_vernum < VS2015.vc_buildtools_def.vc_version_numeric:
+ debug(
+ 'invalid: toolset version constraint: %s < %s VS2015',
+ repr(toolset_vernum), repr(VS2015.vc_buildtools_def.vc_version_numeric)
+ )
+ err_msg = "MSVC_TOOLSET_VERSION ({}) constraint violation: toolset version {} < {} VS2015".format(
+ repr(toolset_version), repr(toolset_verstr), repr(VS2015.vc_buildtools_def.vc_version)
+ )
+ return err_msg
+
+ if toolset_vernum > msvc.vs_def.vc_buildtools_def.vc_version_numeric:
+ debug(
+ 'invalid: toolset version constraint: toolset %s > %s msvc',
+ repr(toolset_vernum), repr(msvc.vs_def.vc_buildtools_def.vc_version_numeric)
+ )
+ err_msg = "MSVC_TOOLSET_VERSION ({}) constraint violation: toolset version {} > {} MSVC_VERSION".format(
+ repr(toolset_version), repr(toolset_verstr), repr(msvc.version)
+ )
+ return err_msg
+
+ if toolset_vernum == VS2015.vc_buildtools_def.vc_version_numeric:
+ # tooset = 14.0
+ if Util.is_toolset_full(toolset_version):
+ if not Util.is_toolset_140(toolset_version):
+ debug(
+ 'invalid: toolset version 14.0 constraint: %s != 14.0',
+ repr(toolset_version)
+ )
+ err_msg = "MSVC_TOOLSET_VERSION ({}) constraint violation: toolset version {} != '14.0'".format(
+ repr(toolset_version), repr(toolset_version)
+ )
+ return err_msg
+ return None
+
+ if Util.is_toolset_full(toolset_version):
+ debug('valid: toolset full: toolset_version=%s', repr(toolset_version))
+ return None
+
+ if Util.is_toolset_sxs(toolset_version):
+ debug('valid: toolset sxs: toolset_version=%s', repr(toolset_version))
+ return None
+
+ debug('invalid: method exit: toolset_version=%s', repr(toolset_version))
+ err_msg = "MSVC_TOOLSET_VERSION ({}) format is not supported".format(repr(toolset_version))
+ return err_msg
+
+def _msvc_script_argument_toolset_vcvars(msvc, toolset_version, vc_dir):
+
+ err_msg = _msvc_script_argument_toolset_constraints(msvc, toolset_version)
+ if err_msg:
+ raise MSVCArgumentError(err_msg)
+
+ if toolset_version.startswith('14.0') and len(toolset_version) > len('14.0'):
+ new_toolset_version = '14.0'
+ debug(
+ 'rewrite toolset_version=%s => toolset_version=%s',
+ repr(toolset_version), repr(new_toolset_version)
+ )
+ toolset_version = new_toolset_version
+
+ toolset_vcvars = _msvc_version_toolset_vcvars(msvc, vc_dir, toolset_version)
+ debug(
+ 'toolset: toolset_version=%s, toolset_vcvars=%s',
+ repr(toolset_version), repr(toolset_vcvars)
+ )
+
+ if not toolset_vcvars:
+ err_msg = "MSVC_TOOLSET_VERSION {} not found for MSVC_VERSION {}".format(
+ repr(toolset_version), repr(msvc.version)
+ )
+ raise MSVCToolsetVersionNotFound(err_msg)
+
+ return toolset_vcvars
+
+def _msvc_script_argument_toolset(env, msvc, vc_dir, arglist):
+
+ toolset_version = env['MSVC_TOOLSET_VERSION']
+ debug('MSVC_VERSION=%s, MSVC_TOOLSET_VERSION=%s', repr(msvc.version), repr(toolset_version))
+
+ if not toolset_version:
+ return None
+
+ toolset_vcvars = _msvc_script_argument_toolset_vcvars(msvc, toolset_version, vc_dir)
+
+ # toolset may not be installed for host/target
+ argpair = (SortOrder.TOOLSET, '-vcvars_ver={}'.format(toolset_vcvars))
+ arglist.append(argpair)
+
+ return toolset_vcvars
+
+def _msvc_script_default_toolset(env, msvc, vc_dir, arglist, force_toolset=False):
+
+ if msvc.vs_def.vc_buildtools_def.vc_version_numeric < VS2017.vc_buildtools_def.vc_version_numeric:
+ return None
+
+ toolset_default = _msvc_default_toolset(msvc, vc_dir)
+ if not toolset_default:
+ return None
+
+ debug('MSVC_VERSION=%s, toolset_default=%s', repr(msvc.version), repr(toolset_default))
+
+ if force_toolset:
+ argpair = (SortOrder.TOOLSET, '-vcvars_ver={}'.format(toolset_default))
+ arglist.append(argpair)
+
+ return toolset_default
+
+def _user_script_argument_toolset(env, toolset_version, user_argstr):
+
+ matches = [m for m in re_vcvars_toolset.finditer(user_argstr)]
+ if not matches:
+ return None
+
+ if len(matches) > 1:
+ debug('multiple toolset version declarations: MSVC_SCRIPT_ARGS=%s', repr(user_argstr))
+ err_msg = "multiple toolset version declarations: MSVC_SCRIPT_ARGS={}".format(repr(user_argstr))
+ raise MSVCArgumentError(err_msg)
+
+ if not toolset_version:
+ user_toolset = matches[0].group('toolset')
+ return user_toolset
+
+ env_argstr = env.get('MSVC_TOOLSET_VERSION','')
+ debug('multiple toolset version declarations: MSVC_TOOLSET_VERSION=%s, MSVC_SCRIPT_ARGS=%s', repr(env_argstr), repr(user_argstr))
+
+ err_msg = "multiple toolset version declarations: MSVC_TOOLSET_VERSION={} and MSVC_SCRIPT_ARGS={}".format(
+ repr(env_argstr), repr(user_argstr)
+ )
+
+ raise MSVCArgumentError(err_msg)
+
+def _msvc_script_argument_spectre_constraints(msvc, toolset, spectre_libs, platform_def):
+
+ if msvc.vs_def.vc_buildtools_def.vc_version_numeric < VS2017.vc_buildtools_def.vc_version_numeric:
+ debug(
+ 'invalid: msvc version constraint: %s < %s VS2017',
+ repr(msvc.vs_def.vc_buildtools_def.vc_version_numeric),
+ repr(VS2017.vc_buildtools_def.vc_version_numeric)
+ )
+ err_msg = "MSVC_SPECTRE_LIBS ({}) constraint violation: MSVC_VERSION {} < {} VS2017".format(
+ repr(spectre_libs), repr(msvc.version), repr(VS2017.vc_buildtools_def.vc_version)
+ )
+ return err_msg
+
+ if toolset:
+ if toolset.vs_def.vc_buildtools_def.vc_version_numeric < VS2017.vc_buildtools_def.vc_version_numeric:
+ debug(
+ 'invalid: toolset version constraint: %s < %s VS2017',
+ repr(toolset.vs_def.vc_buildtools_def.vc_version_numeric),
+ repr(VS2017.vc_buildtools_def.vc_version_numeric)
+ )
+ err_msg = "MSVC_SPECTRE_LIBS ({}) constraint violation: toolset version {} < {} VS2017".format(
+ repr(spectre_libs), repr(toolset.version), repr(VS2017.vc_buildtools_def.vc_version)
+ )
+ return err_msg
+
+
+ if platform_def.is_uwp:
+ debug(
+ 'invalid: spectre_libs=%s and platform_type=%s',
+ repr(spectre_libs), repr(platform_def.vc_platform)
+ )
+ err_msg = "MSVC_SPECTRE_LIBS ({}) are not supported for platform type ({})".format(
+ repr(spectre_libs), repr(platform_def.vc_platform)
+ )
+ return err_msg
+
+ return None
+
+def _msvc_toolset_version_spectre_path(vc_dir, toolset_version):
+ spectre_dir = os.path.join(vc_dir, "Tools", "MSVC", toolset_version, "lib", "spectre")
+ return spectre_dir
+
+def _msvc_script_argument_spectre(env, msvc, vc_dir, toolset, platform_def, arglist):
+
+ spectre_libs = env['MSVC_SPECTRE_LIBS']
+ debug('MSVC_VERSION=%s, MSVC_SPECTRE_LIBS=%s', repr(msvc.version), repr(spectre_libs))
+
+ if not spectre_libs:
+ return None
+
+ if spectre_libs not in _ARGUMENT_BOOLEAN_TRUE:
+ return None
+
+ err_msg = _msvc_script_argument_spectre_constraints(msvc, toolset, spectre_libs, platform_def)
+ if err_msg:
+ raise MSVCArgumentError(err_msg)
+
+ if toolset:
+ spectre_dir = _msvc_toolset_version_spectre_path(vc_dir, toolset.version)
+ if not os.path.exists(spectre_dir):
+ debug(
+ 'spectre libs: msvc_version=%s, toolset_version=%s, spectre_dir=%s',
+ repr(msvc.version), repr(toolset.version), repr(spectre_dir)
+ )
+ err_msg = "Spectre libraries not found for MSVC_VERSION {} toolset version {}".format(
+ repr(msvc.version), repr(toolset.version)
+ )
+ raise MSVCSpectreLibsNotFound(err_msg)
+
+ spectre_arg = 'spectre'
+
+ # spectre libs may not be installed for host/target
+ argpair = (SortOrder.SPECTRE, '-vcvars_spectre_libs={}'.format(spectre_arg))
+ arglist.append(argpair)
+
+ return spectre_arg
+
+def _user_script_argument_spectre(env, spectre, user_argstr):
+
+ matches = [m for m in re_vcvars_spectre.finditer(user_argstr)]
+ if not matches:
+ return None
+
+ if len(matches) > 1:
+ debug('multiple spectre declarations: MSVC_SCRIPT_ARGS=%s', repr(user_argstr))
+ err_msg = "multiple spectre declarations: MSVC_SCRIPT_ARGS={}".format(repr(user_argstr))
+ raise MSVCArgumentError(err_msg)
+
+ if not spectre:
+ return None
+
+ env_argstr = env.get('MSVC_SPECTRE_LIBS','')
+ debug('multiple spectre declarations: MSVC_SPECTRE_LIBS=%s, MSVC_SCRIPT_ARGS=%s', repr(env_argstr), repr(user_argstr))
+
+ err_msg = "multiple spectre declarations: MSVC_SPECTRE_LIBS={} and MSVC_SCRIPT_ARGS={}".format(
+ repr(env_argstr), repr(user_argstr)
+ )
+
+ raise MSVCArgumentError(err_msg)
+
+def _msvc_script_argument_user(env, msvc, arglist):
+
+ # subst None -> empty string
+ script_args = env.subst('$MSVC_SCRIPT_ARGS')
+ debug('MSVC_VERSION=%s, MSVC_SCRIPT_ARGS=%s', repr(msvc.version), repr(script_args))
+
+ if not script_args:
+ return None
+
+ if msvc.vs_def.vc_buildtools_def.vc_version_numeric < VS2015.vc_buildtools_def.vc_version_numeric:
+ debug(
+ 'invalid: msvc version constraint: %s < %s VS2015',
+ repr(msvc.vs_def.vc_buildtools_def.vc_version_numeric),
+ repr(VS2015.vc_buildtools_def.vc_version_numeric)
+ )
+ err_msg = "MSVC_SCRIPT_ARGS ({}) constraint violation: MSVC_VERSION {} < {} VS2015".format(
+ repr(script_args), repr(msvc.version), repr(VS2015.vc_buildtools_def.vc_version)
+ )
+ raise MSVCArgumentError(err_msg)
+
+ # user arguments are not validated
+ argpair = (SortOrder.USER, script_args)
+ arglist.append(argpair)
+
+ return script_args
+
+def _msvc_process_construction_variables(env):
+
+ for cache_variable in [
+ _MSVC_FORCE_DEFAULT_TOOLSET,
+ _MSVC_FORCE_DEFAULT_SDK,
+ ]:
+ if cache_variable:
+ return True
+
+ for env_variable in [
+ 'MSVC_UWP_APP',
+ 'MSVC_TOOLSET_VERSION',
+ 'MSVC_SDK_VERSION',
+ 'MSVC_SPECTRE_LIBS',
+ ]:
+ if env.get(env_variable, None) is not None:
+ return True
+
+ return False
+
+def msvc_script_arguments(env, version, vc_dir, arg):
+
+ arguments = [arg] if arg else []
+
+ arglist = []
+ arglist_reverse = False
+
+ msvc = _msvc_version(version)
+
+ if 'MSVC_SCRIPT_ARGS' in env:
+ user_argstr = _msvc_script_argument_user(env, msvc, arglist)
+ else:
+ user_argstr = None
+
+ if _msvc_process_construction_variables(env):
+
+ # MSVC_UWP_APP
+
+ if 'MSVC_UWP_APP' in env:
+ uwp = _msvc_script_argument_uwp(env, msvc, arglist)
+ else:
+ uwp = None
+
+ if user_argstr:
+ user_uwp = _user_script_argument_uwp(env, uwp, user_argstr)
+ else:
+ user_uwp = None
+
+ is_uwp = True if uwp else False
+ platform_def = WinSDK.get_msvc_platform(is_uwp)
+
+ # MSVC_TOOLSET_VERSION
+
+ if 'MSVC_TOOLSET_VERSION' in env:
+ toolset_version = _msvc_script_argument_toolset(env, msvc, vc_dir, arglist)
+ else:
+ toolset_version = None
+
+ if user_argstr:
+ user_toolset = _user_script_argument_toolset(env, toolset_version, user_argstr)
+ else:
+ user_toolset = None
+
+ if not toolset_version and not user_toolset:
+ default_toolset = _msvc_script_default_toolset(env, msvc, vc_dir, arglist, _MSVC_FORCE_DEFAULT_TOOLSET)
+ if _MSVC_FORCE_DEFAULT_TOOLSET:
+ toolset_version = default_toolset
+ else:
+ default_toolset = None
+
+ if user_toolset:
+ toolset = None
+ elif toolset_version:
+ toolset = _toolset_version(toolset_version)
+ elif default_toolset:
+ toolset = _toolset_version(default_toolset)
+ else:
+ toolset = None
+
+ # MSVC_SDK_VERSION
+
+ if 'MSVC_SDK_VERSION' in env:
+ sdk_version = _msvc_script_argument_sdk(env, msvc, toolset, platform_def, arglist)
+ else:
+ sdk_version = None
+
+ if user_argstr:
+ user_sdk = _user_script_argument_sdk(env, sdk_version, user_argstr)
+ else:
+ user_sdk = None
+
+ if _MSVC_FORCE_DEFAULT_SDK:
+ if not sdk_version and not user_sdk:
+ sdk_version = _msvc_script_default_sdk(env, msvc, platform_def, arglist, _MSVC_FORCE_DEFAULT_SDK)
+
+ # MSVC_SPECTRE_LIBS
+
+ if 'MSVC_SPECTRE_LIBS' in env:
+ spectre = _msvc_script_argument_spectre(env, msvc, vc_dir, toolset, platform_def, arglist)
+ else:
+ spectre = None
+
+ if user_argstr:
+ _user_script_argument_spectre(env, spectre, user_argstr)
+
+ if msvc.vs_def.vc_buildtools_def.vc_version == '14.0':
+ if user_uwp and sdk_version and len(arglist) == 2:
+ # VS2015 toolset argument order issue: SDK store => store SDK
+ arglist_reverse = True
+
+ if len(arglist) > 1:
+ arglist.sort()
+ if arglist_reverse:
+ arglist.reverse()
+
+ arguments.extend([argpair[-1] for argpair in arglist])
+ argstr = ' '.join(arguments).strip()
+
+ debug('arguments: %s', repr(argstr))
+ return argstr
+
+def _msvc_toolset_internal(msvc_version, toolset_version, vc_dir):
+
+ msvc = _msvc_version(msvc_version)
+
+ toolset_vcvars = _msvc_script_argument_toolset_vcvars(msvc, toolset_version, vc_dir)
+
+ return toolset_vcvars
+
+def _msvc_toolset_versions_internal(msvc_version, vc_dir, full=True, sxs=False):
+
+ msvc = _msvc_version(msvc_version)
+
+ if len(msvc.vs_def.vc_buildtools_all) <= 1:
+ return None
+
+ toolset_versions = []
+
+ toolsets_sxs, toolsets_full = _msvc_version_toolsets(msvc, vc_dir)
+
+ if sxs:
+ sxs_versions = list(toolsets_sxs.keys())
+ sxs_versions.sort(reverse=True)
+ toolset_versions.extend(sxs_versions)
+
+ if full:
+ toolset_versions.extend(toolsets_full)
+
+ return toolset_versions
+
+def _msvc_toolset_versions_spectre_internal(msvc_version, vc_dir):
+
+ msvc = _msvc_version(msvc_version)
+
+ if len(msvc.vs_def.vc_buildtools_all) <= 1:
+ return None
+
+ _, toolsets_full = _msvc_version_toolsets(msvc, vc_dir)
+
+ spectre_toolset_versions = [
+ toolset_version
+ for toolset_version in toolsets_full
+ if os.path.exists(_msvc_toolset_version_spectre_path(vc_dir, toolset_version))
+ ]
+
+ return spectre_toolset_versions
+
+def reset():
+ debug('')
+ _reset_have140_cache()
+ _reset_toolset_cache()
+
+def verify():
+ debug('')
+ _verify_re_sdk_dispatch_map()
+
diff --git a/SCons/Tool/MSCommon/MSVC/ScriptArgumentsTests.py b/SCons/Tool/MSCommon/MSVC/ScriptArgumentsTests.py
new file mode 100644
index 000000000..441325653
--- /dev/null
+++ b/SCons/Tool/MSCommon/MSVC/ScriptArgumentsTests.py
@@ -0,0 +1,591 @@
+# MIT License
+#
+# Copyright The SCons Foundation
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# 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.
+
+"""
+Test batch file argument functions for Microsoft Visual C/C++.
+"""
+
+import unittest
+
+import SCons.Environment
+
+from SCons.Tool.MSCommon import vc
+from SCons.Tool.MSCommon import vcTests
+
+from SCons.Tool.MSCommon.MSVC import Config
+from SCons.Tool.MSCommon.MSVC import Util
+from SCons.Tool.MSCommon.MSVC import WinSDK
+from SCons.Tool.MSCommon.MSVC import ScriptArguments
+
+from SCons.Tool.MSCommon.MSVC.Exceptions import (
+ MSVCInternalError,
+ MSVCArgumentError,
+ MSVCToolsetVersionNotFound,
+ MSVCSDKVersionNotFound,
+ MSVCSpectreLibsNotFound,
+)
+
+def Environment(**kwargs):
+ tools_key = 'tools'
+ if tools_key not in kwargs:
+ tools = []
+ else:
+ tools = kwargs[tools_key]
+ del kwargs[tools_key]
+ return SCons.Environment.Base(tools=tools, **kwargs)
+
+def _sdk_versions_comps_dict_seen(installed_version_pairs):
+
+ sdk_versions_comps_dict = {}
+ sdk_versions_seen = set()
+
+ _sdk_version_list_seen = {}
+ for version_def, _ in installed_version_pairs:
+ sdk_versions_comps_dict[version_def.msvc_version] = {}
+ for msvc_uwp_app in (True, False):
+ sdk_version_list = WinSDK.get_msvc_sdk_version_list(version_def.msvc_version, msvc_uwp_app=msvc_uwp_app)
+ key = tuple(sdk_version_list)
+ if key in _sdk_version_list_seen:
+ sdk_comps_list = _sdk_version_list_seen[key]
+ else:
+ sdk_versions_seen.update(sdk_version_list)
+ sdk_comps_list = [Util.msvc_sdk_version_components(sdk_version) for sdk_version in sdk_version_list]
+ _sdk_version_list_seen[key] = sdk_comps_list
+ sdk_versions_comps_dict[version_def.msvc_version][msvc_uwp_app] = sdk_comps_list
+
+ return sdk_versions_comps_dict, sdk_versions_seen
+
+def _sdk_versions_notfound(installed_version_pairs, sdk_versions_comps_dict, sdk_versions_seen):
+
+ sdk_versions_notfound_dict = {}
+ sdk_notfound_seen = {}
+
+ def _make_notfound_version(sdk_seen, sdk_def):
+ if len(sdk_def.sdk_comps) == 4:
+ nloop = 0
+ while nloop < 10:
+ ival = int(sdk_def.sdk_comps[-2])
+ if ival == 0:
+ ival = 1000000
+ ival -= 1
+ version = '{}.{}.{:05d}.{}'.format(
+ sdk_def.sdk_comps[0], sdk_def.sdk_comps[1], ival, sdk_def.sdk_comps[-1]
+ )
+ if version not in sdk_seen:
+ return version
+ nloop += 1
+ return None
+
+ for version_def, _ in installed_version_pairs:
+ sdk_versions_notfound_dict[version_def.msvc_version] = {}
+ for msvc_uwp_app in (True, False):
+ sdk_notfound_list = []
+ sdk_versions_notfound_dict[version_def.msvc_version][msvc_uwp_app] = sdk_notfound_list
+ sdk_comps_list = sdk_versions_comps_dict[version_def.msvc_version][msvc_uwp_app]
+ for sdk_def in sdk_comps_list:
+ if sdk_def.sdk_version in sdk_notfound_seen:
+ sdk_notfound_version = sdk_notfound_seen[sdk_def.sdk_version]
+ else:
+ sdk_notfound_version = _make_notfound_version(sdk_versions_seen, sdk_def)
+ sdk_notfound_seen[sdk_def.sdk_version] = sdk_notfound_version
+ if not sdk_notfound_version:
+ continue
+ sdk_notfound_list.append(sdk_notfound_version)
+
+ return sdk_versions_notfound_dict
+
+class Data:
+
+ # all versions
+ ALL_VERSIONS_PAIRS = []
+
+ # installed versions
+ INSTALLED_VERSIONS_PAIRS = []
+
+ # VS2015 installed
+ HAVE140_TOOLSET = ScriptArguments._msvc_have140_toolset()
+
+ for vcver in Config.MSVC_VERSION_SUFFIX.keys():
+ version_def = Util.msvc_version_components(vcver)
+ vc_dir = vc.find_vc_pdir(None, vcver)
+ t = (version_def, vc_dir)
+ ALL_VERSIONS_PAIRS.append(t)
+ if vc_dir:
+ INSTALLED_VERSIONS_PAIRS.append(t)
+
+ HAVE_MSVC = True if len(INSTALLED_VERSIONS_PAIRS) else False
+
+ SPECTRE_TOOLSET_VERSIONS = {
+ version_def.msvc_version: vc.msvc_toolset_versions_spectre(version_def.msvc_version)
+ for version_def, _ in INSTALLED_VERSIONS_PAIRS
+ }
+
+ SDK_VERSIONS_COMPS_DICT, SDK_VERSIONS_SEEN = _sdk_versions_comps_dict_seen(INSTALLED_VERSIONS_PAIRS)
+
+ SDK_VERSIONS_NOTFOUND_DICT = _sdk_versions_notfound(
+ INSTALLED_VERSIONS_PAIRS, SDK_VERSIONS_COMPS_DICT, SDK_VERSIONS_SEEN
+ )
+
+ @classmethod
+ def msvc_sdk_version_list_components(cls, msvc_version, msvc_uwp_app=False):
+ comps_dict = cls.SDK_VERSIONS_COMPS_DICT.get(msvc_version, {})
+ comps_list = comps_dict.get(msvc_uwp_app, [])
+ return comps_list
+
+ @classmethod
+ def msvc_sdk_version(cls, msvc_version, msvc_uwp_app=False):
+ comps_dict = cls.SDK_VERSIONS_COMPS_DICT.get(msvc_version, {})
+ comps_list = comps_dict.get(msvc_uwp_app, [])
+ if not comps_list:
+ sdk_version = '10.0.20348.0'
+ else:
+ sdk_version = comps_list[0].sdk_version
+ return sdk_version
+
+ @classmethod
+ def msvc_sdk_notfound_version(cls, msvc_version, msvc_uwp_app=False):
+ notfound_dict = cls.SDK_VERSIONS_NOTFOUND_DICT.get(msvc_version, {})
+ notfound_list = notfound_dict.get(msvc_uwp_app, [])
+ if not notfound_list:
+ notfound_version = '10.0.00000.1'
+ else:
+ notfound_version = notfound_list[0]
+ return notfound_version
+
+ @classmethod
+ def msvc_toolset_notfound_dict(cls):
+ return vcTests.Data.msvc_toolset_notfound_dict()
+
+ @classmethod
+ def msvc_toolset_notfound_version(cls, msvc_version):
+ d = cls.msvc_toolset_notfound_dict()
+ notfound_versions = d.get(msvc_version,[])
+ if not notfound_versions:
+ notfound_version = msvc_version + '0.00001'
+ else:
+ notfound_version = notfound_versions[0]
+ return notfound_version
+
+class Patch:
+
+ class Config:
+
+ class MSVC_SDK_VERSIONS:
+
+ MSVC_SDK_VERSIONS = Config.MSVC_SDK_VERSIONS
+
+ @classmethod
+ def enable_copy(cls):
+ hook = set(cls.MSVC_SDK_VERSIONS)
+ Config.MSVC_SDK_VERSIONS = hook
+ return hook
+
+ @classmethod
+ def restore(cls):
+ Config.MSVC_SDK_VERSIONS = cls.MSVC_SDK_VERSIONS
+
+class ScriptArgumentsTests(unittest.TestCase):
+
+ def test_verify(self):
+ MSVC_SDK_VERSIONS = Patch.Config.MSVC_SDK_VERSIONS.enable_copy()
+ MSVC_SDK_VERSIONS.add('99.0')
+ with self.assertRaises(MSVCInternalError):
+ ScriptArguments.verify()
+ Patch.Config.MSVC_SDK_VERSIONS.restore()
+
+ def test_msvc_script_arguments_defaults(self):
+ func = ScriptArguments.msvc_script_arguments
+ env = Environment()
+ # disable forcing sdk and toolset versions as arguments
+ force = ScriptArguments.msvc_force_default_arguments(force=False)
+ for version_def, vc_dir in Data.INSTALLED_VERSIONS_PAIRS:
+ for arg in ('', 'arch'):
+ scriptargs = func(env, version_def.msvc_version, vc_dir, arg)
+ self.assertTrue(scriptargs == arg, "{}({},{}) != {} [force=False]".format(
+ func.__name__, repr(version_def.msvc_version), repr(arg), repr(scriptargs)
+ ))
+ # enable forcing sdk and toolset versions as arguments
+ ScriptArguments.msvc_force_default_arguments(force=True)
+ for version_def, vc_dir in Data.INSTALLED_VERSIONS_PAIRS:
+ for arg in ('', 'arch'):
+ scriptargs = func(env, version_def.msvc_version, vc_dir, arg)
+ if version_def.msvc_vernum >= 14.0:
+ if arg and scriptargs.startswith(arg):
+ testargs = scriptargs[len(arg):].lstrip()
+ else:
+ testargs = scriptargs
+ self.assertTrue(testargs, "{}({},{}) is empty [force=True]".format(
+ func.__name__, repr(version_def.msvc_version), repr(arg)
+ ))
+ else:
+ self.assertTrue(scriptargs == arg, "{}({},{}) != {} [force=True]".format(
+ func.__name__, repr(version_def.msvc_version), repr(arg), repr(scriptargs)
+ ))
+ # restore forcing sdk and toolset versions as arguments
+ ScriptArguments.msvc_force_default_arguments(force=force)
+
+ def test_msvc_toolset_versions_internal(self):
+ func = ScriptArguments._msvc_toolset_versions_internal
+ for version_def, vc_dir in Data.INSTALLED_VERSIONS_PAIRS:
+ for full in (True, False):
+ for sxs in (True, False):
+ toolset_versions = func(version_def.msvc_version, vc_dir, full=full, sxs=sxs)
+ if version_def.msvc_vernum < 14.1:
+ self.assertTrue(toolset_versions is None, "{}({},{},full={},sxs={}) is not None ({})".format(
+ func.__name__, repr(version_def.msvc_version), repr(vc_dir), repr(full), repr(sxs),
+ repr(toolset_versions)
+ ))
+ elif full:
+ self.assertTrue(len(toolset_versions), "{}({},{},full={},sxs={}) is empty".format(
+ func.__name__, repr(version_def.msvc_version), repr(vc_dir), repr(full), repr(sxs)
+ ))
+ elif sxs:
+ # sxs list can be empty
+ pass
+ else:
+ self.assertFalse(len(toolset_versions), "{}({},{},full={},sxs={}) is not empty".format(
+ func.__name__, repr(version_def.msvc_version), repr(vc_dir), repr(full), repr(sxs)
+ ))
+
+ def test_msvc_toolset_internal(self):
+ if not Data.HAVE_MSVC:
+ return
+ func = ScriptArguments._msvc_toolset_internal
+ for version_def, vc_dir in Data.INSTALLED_VERSIONS_PAIRS:
+ toolset_versions = ScriptArguments._msvc_toolset_versions_internal(version_def.msvc_version, vc_dir, full=True, sxs=True)
+ if not toolset_versions:
+ continue
+ for toolset_version in toolset_versions:
+ _ = func(version_def.msvc_version, toolset_version, vc_dir)
+
+ def run_msvc_script_args_none(self):
+ func = ScriptArguments.msvc_script_arguments
+ for version_def, vc_dir in Data.INSTALLED_VERSIONS_PAIRS:
+ for kwargs in [
+ {'MSVC_SCRIPT_ARGS': None},
+ {'MSVC_SCRIPT_ARGS': None, 'MSVC_UWP_APP': None},
+ {'MSVC_SCRIPT_ARGS': None, 'MSVC_TOOLSET_VERSION': None},
+ {'MSVC_SCRIPT_ARGS': None, 'MSVC_SDK_VERSION': None},
+ {'MSVC_SCRIPT_ARGS': None, 'MSVC_SPECTRE_LIBS': None},
+ ]:
+ env = Environment(**kwargs)
+ _ = func(env, version_def.msvc_version, vc_dir, '')
+
+ def run_msvc_script_args(self):
+ func = ScriptArguments.msvc_script_arguments
+ for version_def, vc_dir in Data.INSTALLED_VERSIONS_PAIRS:
+ if version_def.msvc_vernum >= 14.1:
+ # VS2017 and later
+
+ toolset_versions = [
+ Util.msvc_extended_version_components(toolset_version)
+ for toolset_version in
+ ScriptArguments._msvc_toolset_versions_internal(
+ version_def.msvc_version, vc_dir, full=True, sxs=False
+ )
+ ]
+
+ toolset_def = toolset_versions[0] if toolset_versions else Util.msvc_extended_version_components(version_def.msvc_verstr)
+
+ earlier_toolset_versions = [toolset_def for toolset_def in toolset_versions if toolset_def.msvc_vernum != version_def.msvc_vernum]
+ earlier_toolset_def = earlier_toolset_versions[0] if earlier_toolset_versions else None
+
+ # should not raise exception (argument not validated)
+ env = Environment(MSVC_SCRIPT_ARGS='undefinedsymbol')
+ _ = func(env, version_def.msvc_version, vc_dir, '')
+
+ for kwargs in [
+ {'MSVC_UWP_APP': False, 'MSVC_SCRIPT_ARGS': None},
+ {'MSVC_UWP_APP': '0', 'MSVC_SCRIPT_ARGS': None},
+ {'MSVC_UWP_APP': False, 'MSVC_SCRIPT_ARGS': 'store'},
+ {'MSVC_UWP_APP': '0', 'MSVC_SCRIPT_ARGS': 'store'},
+ {'MSVC_SPECTRE_LIBS': False, 'MSVC_SCRIPT_ARGS': '-vcvars_spectre_libs=spectre'},
+ {'MSVC_SPECTRE_LIBS': 'True', 'MSVC_SCRIPT_ARGS': '-vcvars_spectre_libs=spectre'}, # not boolean ignored
+ ]:
+ env = Environment(**kwargs)
+ _ = func(env, version_def.msvc_version, vc_dir, '')
+
+ for msvc_uwp_app in (True, False):
+
+ sdk_list = Data.msvc_sdk_version_list_components(version_def.msvc_version, msvc_uwp_app=msvc_uwp_app)
+ for sdk_def in sdk_list:
+
+ if sdk_def.sdk_verstr == '8.1' and msvc_uwp_app:
+
+ more_tests = []
+
+ if earlier_toolset_def:
+ # SDK 8.1 and UWP: toolset must be 14.0
+ expect = True if earlier_toolset_def.msvc_vernum > 14.0 else False
+ more_tests.append(
+ (expect, {
+ 'MSVC_SDK_VERSION': sdk_def.sdk_version,
+ 'MSVC_UWP_APP': msvc_uwp_app,
+ 'MSVC_TOOLSET_VERSION': earlier_toolset_def.msvc_toolset_version
+ })
+ )
+
+ expect = True if version_def.msvc_vernum > 14.0 else False
+
+ for exc, kwargs in [
+ # script args not validated
+ (False, {
+ 'MSVC_SCRIPT_ARGS': sdk_def.sdk_version,
+ 'MSVC_UWP_APP': msvc_uwp_app
+ }),
+ # SDK 8.1 and UWP: msvc_version > 14.0
+ (True, {
+ 'MSVC_SDK_VERSION': sdk_def.sdk_version,
+ 'MSVC_UWP_APP': msvc_uwp_app
+ }),
+ # SDK 8.1 and UWP: toolset must be 14.0
+ (expect, {
+ 'MSVC_SDK_VERSION': sdk_def.sdk_version,
+ 'MSVC_UWP_APP': msvc_uwp_app,
+ 'MSVC_TOOLSET_VERSION': version_def.msvc_verstr
+ }),
+ ] + more_tests:
+ env = Environment(**kwargs)
+ if exc:
+ with self.assertRaises(MSVCArgumentError):
+ _ = func(env, version_def.msvc_version, vc_dir, '')
+ else:
+ _ = func(env, version_def.msvc_version, vc_dir, '')
+
+ else:
+
+ for kwargs in [
+ {'MSVC_SCRIPT_ARGS': sdk_def.sdk_version, 'MSVC_UWP_APP': msvc_uwp_app},
+ {'MSVC_SDK_VERSION': sdk_def.sdk_version, 'MSVC_UWP_APP': msvc_uwp_app},
+ ]:
+ env = Environment(**kwargs)
+ _ = func(env, version_def.msvc_version, vc_dir, '')
+
+ for kwargs in [
+ {'MSVC_SCRIPT_ARGS': '-vcvars_ver={}'.format(version_def.msvc_verstr)},
+ {'MSVC_TOOLSET_VERSION': version_def.msvc_verstr},
+ ]:
+ env = Environment(**kwargs)
+ _ = func(env, version_def.msvc_version, vc_dir, '')
+
+ msvc_toolset_notfound_version = Data.msvc_toolset_notfound_version(version_def.msvc_version)
+
+ for kwargs in [
+ {'MSVC_TOOLSET_VERSION': msvc_toolset_notfound_version},
+ {'MSVC_TOOLSET_VERSION': "{}.{}.00.0".format(
+ toolset_def.msvc_toolset_comps[0], toolset_def.msvc_toolset_comps[1]
+ )},
+ ]:
+ env = Environment(**kwargs)
+ with self.assertRaises(MSVCToolsetVersionNotFound):
+ _ = func(env, version_def.msvc_version, vc_dir, '')
+
+ msvc_sdk_notfound_version = Data.msvc_sdk_notfound_version(version_def.msvc_version)
+
+ for kwargs in [
+ {'MSVC_SDK_VERSION': msvc_sdk_notfound_version},
+ ]:
+ env = Environment(**kwargs)
+ with self.assertRaises(MSVCSDKVersionNotFound):
+ _ = func(env, version_def.msvc_version, vc_dir, '')
+
+ have_spectre = toolset_def.msvc_toolset_version in Data.SPECTRE_TOOLSET_VERSIONS.get(version_def.msvc_version,[])
+ env = Environment(MSVC_SPECTRE_LIBS=True, MSVC_TOOLSET_VERSION=toolset_def.msvc_toolset_version)
+ if not have_spectre:
+ with self.assertRaises(MSVCSpectreLibsNotFound):
+ _ = func(env, version_def.msvc_version, vc_dir, '')
+ else:
+ _ = func(env, version_def.msvc_version, vc_dir, '')
+
+ msvc_sdk_version = Data.msvc_sdk_version(version_def.msvc_version)
+
+ more_tests = []
+
+ if Data.HAVE140_TOOLSET:
+
+ more_tests.append(
+ # toolset != 14.0
+ ({'MSVC_TOOLSET_VERSION': '14.00.00001',
+ },
+ (MSVCArgumentError, ),
+ ),
+ )
+
+ for kwargs, exc_t in [
+ # multiple definitions
+ ({'MSVC_UWP_APP': True,
+ 'MSVC_SCRIPT_ARGS': 'uwp'
+ }, (MSVCArgumentError, ),
+ ),
+ # multiple definitions (args)
+ ({'MSVC_UWP_APP': True,
+ 'MSVC_SCRIPT_ARGS': 'uwp undefined store'
+ }, (MSVCArgumentError, ),
+ ),
+ # multiple definitions
+ ({'MSVC_TOOLSET_VERSION': version_def.msvc_verstr,
+ 'MSVC_SCRIPT_ARGS': "-vcvars_ver={}".format(version_def.msvc_verstr)
+ },
+ (MSVCArgumentError, ),
+ ),
+ # multiple definitions (args)
+ ({'MSVC_TOOLSET_VERSION': version_def.msvc_verstr,
+ 'MSVC_SCRIPT_ARGS': "-vcvars_ver={0} undefined -vcvars_ver={0}".format(version_def.msvc_verstr)
+ },
+ (MSVCArgumentError, ),
+ ),
+ # multiple definitions
+ ({'MSVC_SDK_VERSION': msvc_sdk_version,
+ 'MSVC_SCRIPT_ARGS': msvc_sdk_version
+ },
+ (MSVCArgumentError, ),
+ ),
+ # multiple definitions (args)
+ ({'MSVC_SDK_VERSION': msvc_sdk_version,
+ 'MSVC_SCRIPT_ARGS': '{0} undefined {0}'.format(msvc_sdk_version)
+ },
+ (MSVCArgumentError, ),
+ ),
+ # multiple definitions
+ ({'MSVC_SPECTRE_LIBS': True,
+ 'MSVC_SCRIPT_ARGS': '-vcvars_spectre_libs=spectre'
+ },
+ (MSVCArgumentError, MSVCSpectreLibsNotFound),
+ ),
+ # multiple definitions (args)
+ ({'MSVC_SPECTRE_LIBS': True,
+ 'MSVC_SCRIPT_ARGS': '-vcvars_spectre_libs=spectre undefined -vcvars_spectre_libs=spectre'
+ },
+ (MSVCArgumentError, MSVCSpectreLibsNotFound),
+ ),
+ # toolset < 14.0
+ ({'MSVC_TOOLSET_VERSION': '12.0',
+ },
+ (MSVCArgumentError, ),
+ ),
+ # toolset > msvc_version
+ ({'MSVC_TOOLSET_VERSION': '{}.{}'.format(version_def.msvc_major, version_def.msvc_minor+1),
+ },
+ (MSVCArgumentError, ),
+ ),
+ # version not supported
+ ({'MSVC_TOOLSET_VERSION': "{}".format(version_def.msvc_major),
+ },
+ (MSVCArgumentError, ),
+ ),
+ # version not supported
+ ({'MSVC_TOOLSET_VERSION': "{}.{}.00000.0".format(
+ toolset_def.msvc_toolset_comps[0], toolset_def.msvc_toolset_comps[1]
+ )},
+ (MSVCArgumentError, ),
+ ),
+ # version not supported
+ ({'MSVC_SDK_VERSION': '9.1',
+ },
+ (MSVCArgumentError, ),
+ ),
+ # spectre not available for UWP
+ ({'MSVC_SPECTRE_LIBS': True,
+ 'MSVC_UWP_APP': True,
+ },
+ (MSVCArgumentError, MSVCSpectreLibsNotFound),
+ ),
+ # spectre not available in VS2015
+ ({'MSVC_SPECTRE_LIBS': True,
+ 'MSVC_TOOLSET_VERSION': '14.00.00000',
+ },
+ (MSVCArgumentError, MSVCSpectreLibsNotFound, MSVCToolsetVersionNotFound),
+ ),
+ ] + more_tests:
+ env = Environment(**kwargs)
+ with self.assertRaises(exc_t):
+ _ = func(env, version_def.msvc_version, vc_dir, '')
+
+ elif version_def.msvc_verstr == '14.0':
+ # VS2015: MSVC_SDK_VERSION and MSVC_UWP_APP
+
+ env = Environment(MSVC_SCRIPT_ARGS='undefinedsymbol')
+ _ = func(env, version_def.msvc_version, vc_dir, '')
+
+ for msvc_uwp_app in (True, False):
+
+ sdk_list = WinSDK.get_msvc_sdk_version_list(version_def.msvc_version, msvc_uwp_app=msvc_uwp_app)
+ for sdk_version in sdk_list:
+
+ for kwargs in [
+ {'MSVC_SCRIPT_ARGS': sdk_version, 'MSVC_UWP_APP': msvc_uwp_app},
+ {'MSVC_SDK_VERSION': sdk_version, 'MSVC_UWP_APP': msvc_uwp_app},
+ ]:
+ env = Environment(**kwargs)
+ _ = func(env, version_def.msvc_version, vc_dir, '')
+
+ for kwargs in [
+ {'MSVC_SPECTRE_LIBS': True, 'MSVC_SCRIPT_ARGS': None},
+ {'MSVC_TOOLSET_VERSION': version_def.msvc_verstr, 'MSVC_SCRIPT_ARGS': None},
+ ]:
+ env = Environment(**kwargs)
+ with self.assertRaises(MSVCArgumentError):
+ _ = func(env, version_def.msvc_version, vc_dir, '')
+
+ else:
+ # VS2013 and earlier: no arguments
+
+ env = Environment(MSVC_SCRIPT_ARGS='undefinedsymbol')
+ with self.assertRaises(MSVCArgumentError):
+ _ = func(env, version_def.msvc_version, vc_dir, '')
+
+ for kwargs in [
+ {'MSVC_UWP_APP': True, 'MSVC_SCRIPT_ARGS': None},
+ {'MSVC_UWP_APP': '1', 'MSVC_SCRIPT_ARGS': None},
+ {'MSVC_SPECTRE_LIBS': True, 'MSVC_SCRIPT_ARGS': None},
+ {'MSVC_TOOLSET_VERSION': version_def.msvc_verstr, 'MSVC_SCRIPT_ARGS': None},
+ {'MSVC_SDK_VERSION': '10.0.00000.0', 'MSVC_SCRIPT_ARGS': None},
+ ]:
+ env = Environment(**kwargs)
+ with self.assertRaises(MSVCArgumentError):
+ _ = func(env, version_def.msvc_version, vc_dir, '')
+
+ def test_msvc_script_args_none(self):
+ force = ScriptArguments.msvc_force_default_arguments(force=False)
+ self.run_msvc_script_args_none()
+ if Data.HAVE_MSVC:
+ ScriptArguments.msvc_force_default_arguments(force=True)
+ self.run_msvc_script_args_none()
+ ScriptArguments.msvc_force_default_arguments(force=force)
+
+ def test_msvc_script_args(self):
+ force = ScriptArguments.msvc_force_default_arguments(force=False)
+ self.run_msvc_script_args()
+ ScriptArguments.msvc_force_default_arguments(force=True)
+ self.run_msvc_script_args()
+ ScriptArguments.msvc_force_default_arguments(force=force)
+
+ def test_reset(self):
+ ScriptArguments.reset()
+ self.assertTrue(ScriptArguments._toolset_have140_cache is None, "ScriptArguments._toolset_have140_cache was not reset")
+ self.assertFalse(ScriptArguments._toolset_version_cache, "ScriptArguments._toolset_version_cache was not reset")
+ self.assertFalse(ScriptArguments._toolset_default_cache, "ScriptArguments._toolset_default_cache was not reset")
+
+if __name__ == "__main__":
+ unittest.main()
+
diff --git a/SCons/Tool/MSCommon/MSVC/SetupEnvDefault.py b/SCons/Tool/MSCommon/MSVC/SetupEnvDefault.py
new file mode 100644
index 000000000..e1c05bc1b
--- /dev/null
+++ b/SCons/Tool/MSCommon/MSVC/SetupEnvDefault.py
@@ -0,0 +1,233 @@
+# MIT License
+#
+# Copyright The SCons Foundation
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# 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.
+
+"""
+Determine if and/or when an error/warning should be issued when there
+are no versions of msvc installed. If there is at least one version of
+msvc installed, these routines do (almost) nothing.
+
+Notes:
+ * When msvc is the default compiler because there are no compilers
+ installed, a build may fail due to the cl.exe command not being
+ recognized. Currently, there is no easy way to detect during
+ msvc initialization if the default environment will be used later
+ to build a program and/or library. There is no error/warning
+ as there are legitimate SCons uses that do not require a c compiler.
+ * An error is indicated by returning a non-empty tool list from the
+ function register_iserror.
+"""
+
+import re
+
+from .. common import (
+ debug,
+)
+
+from . import Dispatcher
+Dispatcher.register_modulename(__name__)
+
+
+class _Data:
+
+ separator = r';'
+
+ need_init = True
+
+ @classmethod
+ def reset(cls):
+ debug('msvc default:init')
+ cls.n_setup = 0 # number of calls to msvc_setup_env_once
+ cls.default_ismsvc = False # is msvc the default compiler
+ cls.default_tools_re_list = [] # list of default tools regular expressions
+ cls.msvc_tools_init = set() # tools registered via msvc_exists
+ cls.msvc_tools = None # tools registered via msvc_setup_env_once
+ cls.msvc_installed = False # is msvc installed (vcs_installed > 0)
+ cls.msvc_nodefault = False # is there a default version of msvc
+ cls.need_init = True # reset initialization indicator
+
+def _initialize(env, msvc_exists_func):
+ if _Data.need_init:
+ _Data.reset()
+ _Data.need_init = False
+ _Data.msvc_installed = msvc_exists_func(env)
+ debug('msvc default:msvc_installed=%s', _Data.msvc_installed)
+
+def register_tool(env, tool, msvc_exists_func):
+ if _Data.need_init:
+ _initialize(env, msvc_exists_func)
+ if _Data.msvc_installed:
+ return None
+ if not tool:
+ return None
+ if _Data.n_setup == 0:
+ if tool not in _Data.msvc_tools_init:
+ _Data.msvc_tools_init.add(tool)
+ debug('msvc default:tool=%s, msvc_tools_init=%s', tool, _Data.msvc_tools_init)
+ return None
+ if tool not in _Data.msvc_tools:
+ _Data.msvc_tools.add(tool)
+ debug('msvc default:tool=%s, msvc_tools=%s', tool, _Data.msvc_tools)
+
+def register_setup(env, msvc_exists_func):
+ if _Data.need_init:
+ _initialize(env, msvc_exists_func)
+ _Data.n_setup += 1
+ if not _Data.msvc_installed:
+ _Data.msvc_tools = set(_Data.msvc_tools_init)
+ if _Data.n_setup == 1:
+ tool_list = env.get('TOOLS', None)
+ if tool_list and tool_list[0] == 'default':
+ if len(tool_list) > 1 and tool_list[1] in _Data.msvc_tools:
+ # msvc tools are the default compiler
+ _Data.default_ismsvc = True
+ _Data.msvc_nodefault = False
+ debug(
+ 'msvc default:n_setup=%d, msvc_installed=%s, default_ismsvc=%s',
+ _Data.n_setup, _Data.msvc_installed, _Data.default_ismsvc
+ )
+
+def set_nodefault():
+ # default msvc version, msvc not installed
+ _Data.msvc_nodefault = True
+ debug('msvc default:msvc_nodefault=%s', _Data.msvc_nodefault)
+
+def register_iserror(env, tool, msvc_exists_func):
+
+ register_tool(env, tool, msvc_exists_func)
+
+ if _Data.msvc_installed:
+ # msvc installed
+ return None
+
+ if not _Data.msvc_nodefault:
+ # msvc version specified
+ return None
+
+ tool_list = env.get('TOOLS', None)
+ if not tool_list:
+ # tool list is empty
+ return None
+
+ debug(
+ 'msvc default:n_setup=%s, default_ismsvc=%s, msvc_tools=%s, tool_list=%s',
+ _Data.n_setup, _Data.default_ismsvc, _Data.msvc_tools, tool_list
+ )
+
+ if not _Data.default_ismsvc:
+
+ # Summary:
+ # * msvc is not installed
+ # * msvc version not specified (default)
+ # * msvc is not the default compiler
+
+ # construct tools set
+ tools_set = set(tool_list)
+
+ else:
+
+ if _Data.n_setup == 1:
+ # first setup and msvc is default compiler:
+ # build default tools regex for current tool state
+ tools = _Data.separator.join(tool_list)
+ tools_nchar = len(tools)
+ debug('msvc default:add regex:nchar=%d, tools=%s', tools_nchar, tools)
+ re_default_tools = re.compile(re.escape(tools))
+ _Data.default_tools_re_list.insert(0, (tools_nchar, re_default_tools))
+ # early exit: no error for default environment when msvc is not installed
+ return None
+
+ # Summary:
+ # * msvc is not installed
+ # * msvc version not specified (default)
+ # * environment tools list is not empty
+ # * default tools regex list constructed
+ # * msvc tools set constructed
+ #
+ # Algorithm using tools string and sets:
+ # * convert environment tools list to a string
+ # * iteratively remove default tools sequences via regex
+ # substition list built from longest sequence (first)
+ # to shortest sequence (last)
+ # * build environment tools set with remaining tools
+ # * compute intersection of environment tools and msvc tools sets
+ # * if the intersection is:
+ # empty - no error: default tools and/or no additional msvc tools
+ # not empty - error: user specified one or more msvc tool(s)
+ #
+ # This will not produce an error or warning when there are no
+ # msvc installed instances nor any other recognized compilers
+ # and the default environment is needed for a build. The msvc
+ # compiler is forcibly added to the environment tools list when
+ # there are no compilers installed on win32. In this case, cl.exe
+ # will not be found on the path resulting in a failed build.
+
+ # construct tools string
+ tools = _Data.separator.join(tool_list)
+ tools_nchar = len(tools)
+
+ debug('msvc default:check tools:nchar=%d, tools=%s', tools_nchar, tools)
+
+ # iteratively remove default tool sequences (longest to shortest)
+ re_nchar_min, re_tools_min = _Data.default_tools_re_list[-1]
+ if tools_nchar >= re_nchar_min and re_tools_min.search(tools):
+ # minimum characters satisfied and minimum pattern exists
+ for re_nchar, re_default_tool in _Data.default_tools_re_list:
+ if tools_nchar < re_nchar:
+ # not enough characters for pattern
+ continue
+ tools = re_default_tool.sub('', tools).strip(_Data.separator)
+ tools_nchar = len(tools)
+ debug('msvc default:check tools:nchar=%d, tools=%s', tools_nchar, tools)
+ if tools_nchar < re_nchar_min or not re_tools_min.search(tools):
+ # less than minimum characters or minimum pattern does not exist
+ break
+
+ # construct non-default list(s) tools set
+ tools_set = {msvc_tool for msvc_tool in tools.split(_Data.separator) if msvc_tool}
+
+ debug('msvc default:tools=%s', tools_set)
+ if not tools_set:
+ return None
+
+ # compute intersection of remaining tools set and msvc tools set
+ tools_found = _Data.msvc_tools.intersection(tools_set)
+ debug('msvc default:tools_exist=%s', tools_found)
+ if not tools_found:
+ return None
+
+ # construct in same order as tools list
+ tools_found_list = []
+ seen_tool = set()
+ for tool in tool_list:
+ if tool not in seen_tool:
+ seen_tool.add(tool)
+ if tool in tools_found:
+ tools_found_list.append(tool)
+
+ # return tool list in order presented
+ return tools_found_list
+
+def reset():
+ debug('')
+ _Data.reset()
+
diff --git a/SCons/Tool/MSCommon/MSVC/Util.py b/SCons/Tool/MSCommon/MSVC/Util.py
new file mode 100644
index 000000000..4b487da0e
--- /dev/null
+++ b/SCons/Tool/MSCommon/MSVC/Util.py
@@ -0,0 +1,366 @@
+# MIT License
+#
+# Copyright The SCons Foundation
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# 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.
+
+"""
+Helper functions for Microsoft Visual C/C++.
+"""
+
+import os
+import re
+
+from collections import (
+ namedtuple,
+)
+
+from . import Config
+
+# path utilities
+
+def listdir_dirs(p):
+ """
+ Return a list of tuples for each subdirectory of the given directory path.
+ Each tuple is comprised of the subdirectory name and the qualified subdirectory path.
+
+ Args:
+ p: str
+ directory path
+
+ Returns:
+ list[tuple[str,str]]: a list of tuples
+
+ """
+ dirs = []
+ if p and os.path.exists(p) and os.path.isdir(p):
+ for dir_name in os.listdir(p):
+ dir_path = os.path.join(p, dir_name)
+ if os.path.isdir(dir_path):
+ dirs.append((dir_name, dir_path))
+ return dirs
+
+def process_path(p):
+ """
+ Normalize a system path
+
+ Args:
+ p: str
+ system path
+
+ Returns:
+ str: normalized system path
+
+ """
+ if p:
+ p = os.path.normpath(p)
+ p = os.path.realpath(p)
+ p = os.path.normcase(p)
+ return p
+
+# msvc version and msvc toolset version regexes
+
+re_version_prefix = re.compile('^(?P<version>[0-9]+(?:[.][0-9]+)*)(?![.]).*$')
+
+re_msvc_version_prefix = re.compile(r'^(?P<version>[1-9][0-9]?[.][0-9]).*$')
+
+re_msvc_version = re.compile(r'^(?P<msvc_version>[1-9][0-9]?[.][0-9])(?P<suffix>[A-Z]+)*$', re.IGNORECASE)
+
+re_extended_version = re.compile(r'''^
+ (?P<version>(?:
+ ([1-9][0-9]?[.][0-9]{1,2})| # XX.Y - XX.YY
+ ([1-9][0-9][.][0-9]{2}[.][0-9]{1,5})| # XX.YY.Z - XX.YY.ZZZZZ
+ ([1-9][0-9][.][0-9]{2}[.][0-9]{2}[.][0-9]{1,2}) # XX.YY.AA.B - XX.YY.AA.BB
+ ))
+ (?P<suffix>[A-Z]+)*
+$''', re.IGNORECASE | re.VERBOSE)
+
+re_toolset_full = re.compile(r'''^(?:
+ (?:[1-9][0-9][.][0-9]{1,2})| # XX.Y - XX.YY
+ (?:[1-9][0-9][.][0-9]{2}[.][0-9]{1,5}) # XX.YY.Z - XX.YY.ZZZZZ
+)$''', re.VERBOSE)
+
+re_toolset_140 = re.compile(r'''^(?:
+ (?:14[.]0{1,2})| # 14.0 - 14.00
+ (?:14[.]0{2}[.]0{1,5}) # 14.00.0 - 14.00.00000
+)$''', re.VERBOSE)
+
+re_toolset_sxs = re.compile(
+ r'^[1-9][0-9][.][0-9]{2}[.][0-9]{2}[.][0-9]{1,2}$' # MM.mm.VV.vv format
+)
+
+# msvc sdk version regexes
+
+re_msvc_sdk_version = re.compile(r'''^
+ (?P<version>(?:
+ ([1-9][0-9]?[.][0-9])| # XX.Y
+ ([1-9][0-9][.][0-9]{1}[.][0-9]{5}[.][0-9]{1,2}) # XX.Y.ZZZZZ.A - XX.Y.ZZZZZ.AA
+ ))
+$''', re.IGNORECASE | re.VERBOSE)
+
+# version prefix utilities
+
+def get_version_prefix(version):
+ """
+ Get the version number prefix from a string.
+
+ Args:
+ version: str
+ version specification
+
+ Returns:
+ str: the version number prefix
+
+ """
+ rval = ''
+ if version:
+ m = re_version_prefix.match(version)
+ if m:
+ rval = m.group('version')
+ return rval
+
+def get_msvc_version_prefix(version):
+ """
+ Get the msvc version number prefix from a string.
+
+ Args:
+ version: str
+ version specification
+
+ Returns:
+ str: the msvc version number prefix
+
+ """
+ rval = ''
+ if version:
+ m = re_msvc_version_prefix.match(version)
+ if m:
+ rval = m.group('version')
+ return rval
+
+# toolset version query utilities
+
+def is_toolset_full(toolset_version):
+ rval = False
+ if toolset_version:
+ if re_toolset_full.match(toolset_version):
+ rval = True
+ return rval
+
+def is_toolset_140(toolset_version):
+ rval = False
+ if toolset_version:
+ if re_toolset_140.match(toolset_version):
+ rval = True
+ return rval
+
+def is_toolset_sxs(toolset_version):
+ rval = False
+ if toolset_version:
+ if re_toolset_sxs.match(toolset_version):
+ rval = True
+ return rval
+
+# msvc version and msvc toolset version decomposition utilties
+
+_MSVC_VERSION_COMPONENTS_DEFINITION = namedtuple('MSVCVersionComponentsDefinition', [
+ 'msvc_version', # msvc version (e.g., '14.1Exp')
+ 'msvc_verstr', # msvc version numeric string (e.g., '14.1')
+ 'msvc_suffix', # msvc version component type (e.g., 'Exp')
+ 'msvc_vernum', # msvc version floating point number (e.g, 14.1)
+ 'msvc_major', # msvc major version integer number (e.g., 14)
+ 'msvc_minor', # msvc minor version integer number (e.g., 1)
+ 'msvc_comps', # msvc version components tuple (e.g., ('14', '1'))
+])
+
+def msvc_version_components(vcver):
+ """
+ Decompose an msvc version into components.
+
+ Tuple fields:
+ msvc_version: msvc version (e.g., '14.1Exp')
+ msvc_verstr: msvc version numeric string (e.g., '14.1')
+ msvc_suffix: msvc version component type (e.g., 'Exp')
+ msvc_vernum: msvc version floating point number (e.g., 14.1)
+ msvc_major: msvc major version integer number (e.g., 14)
+ msvc_minor: msvc minor version integer number (e.g., 1)
+ msvc_comps: msvc version components tuple (e.g., ('14', '1'))
+
+ Args:
+ vcver: str
+ msvc version specification
+
+ Returns:
+ None or MSVCVersionComponents namedtuple:
+ """
+
+ if not vcver:
+ return None
+
+ m = re_msvc_version.match(vcver)
+ if not m:
+ return None
+
+ vs_def = Config.MSVC_VERSION_SUFFIX.get(vcver)
+ if not vs_def:
+ return None
+
+ msvc_version = vcver
+ msvc_verstr = m.group('msvc_version')
+ msvc_suffix = m.group('suffix') if m.group('suffix') else ''
+ msvc_vernum = float(msvc_verstr)
+
+ msvc_comps = tuple(msvc_verstr.split('.'))
+ msvc_major, msvc_minor = [int(x) for x in msvc_comps]
+
+ msvc_version_components_def = _MSVC_VERSION_COMPONENTS_DEFINITION(
+ msvc_version = msvc_version,
+ msvc_verstr = msvc_verstr,
+ msvc_suffix = msvc_suffix,
+ msvc_vernum = msvc_vernum,
+ msvc_major = msvc_major,
+ msvc_minor = msvc_minor,
+ msvc_comps = msvc_comps,
+ )
+
+ return msvc_version_components_def
+
+_MSVC_EXTENDED_VERSION_COMPONENTS_DEFINITION = namedtuple('MSVCExtendedVersionComponentsDefinition', [
+ 'msvc_version', # msvc version (e.g., '14.1Exp')
+ 'msvc_verstr', # msvc version numeric string (e.g., '14.1')
+ 'msvc_suffix', # msvc version component type (e.g., 'Exp')
+ 'msvc_vernum', # msvc version floating point number (e.g, 14.1)
+ 'msvc_major', # msvc major version integer number (e.g., 14)
+ 'msvc_minor', # msvc minor version integer number (e.g., 1)
+ 'msvc_comps', # msvc version components tuple (e.g., ('14', '1'))
+ 'msvc_toolset_version', # msvc toolset version
+ 'msvc_toolset_comps', # msvc toolset version components
+ 'version', # msvc version or msvc toolset version
+])
+
+def msvc_extended_version_components(version):
+ """
+ Decompose an msvc version or msvc toolset version into components.
+
+ Args:
+ version: str
+ version specification
+
+ Returns:
+ None or MSVCExtendedVersionComponents namedtuple:
+ """
+
+ if not version:
+ return None
+
+ m = re_extended_version.match(version)
+ if not m:
+ return None
+
+ msvc_toolset_version = m.group('version')
+ msvc_toolset_comps = tuple(msvc_toolset_version.split('.'))
+
+ msvc_verstr = get_msvc_version_prefix(msvc_toolset_version)
+ if not msvc_verstr:
+ return None
+
+ msvc_suffix = m.group('suffix') if m.group('suffix') else ''
+ msvc_version = msvc_verstr + msvc_suffix
+
+ vs_def = Config.MSVC_VERSION_SUFFIX.get(msvc_version)
+ if not vs_def:
+ return None
+
+ msvc_vernum = float(msvc_verstr)
+
+ msvc_comps = tuple(msvc_verstr.split('.'))
+ msvc_major, msvc_minor = [int(x) for x in msvc_comps]
+
+ msvc_extended_version_components_def = _MSVC_EXTENDED_VERSION_COMPONENTS_DEFINITION(
+ msvc_version = msvc_version,
+ msvc_verstr = msvc_verstr,
+ msvc_suffix = msvc_suffix,
+ msvc_vernum = msvc_vernum,
+ msvc_major = msvc_major,
+ msvc_minor = msvc_minor,
+ msvc_comps = msvc_comps,
+ msvc_toolset_version = msvc_toolset_version,
+ msvc_toolset_comps = msvc_toolset_comps,
+ version = version,
+ )
+
+ return msvc_extended_version_components_def
+
+# msvc sdk version decomposition utilties
+
+_MSVC_SDK_VERSION_COMPONENTS_DEFINITION = namedtuple('MSVCSDKVersionComponentsDefinition', [
+ 'sdk_version', # sdk version (e.g., '10.0.20348.0')
+ 'sdk_verstr', # sdk version numeric string (e.g., '10.0')
+ 'sdk_vernum', # sdk version floating point number (e.g, 10.0)
+ 'sdk_major', # sdk major version integer number (e.g., 10)
+ 'sdk_minor', # sdk minor version integer number (e.g., 0)
+ 'sdk_comps', # sdk version components tuple (e.g., ('10', '0', '20348', '0'))
+])
+
+def msvc_sdk_version_components(version):
+ """
+ Decompose an msvc sdk version into components.
+
+ Tuple fields:
+ sdk_version: sdk version (e.g., '10.0.20348.0')
+ sdk_verstr: sdk version numeric string (e.g., '10.0')
+ sdk_vernum: sdk version floating point number (e.g., 10.0)
+ sdk_major: sdk major version integer number (e.g., 10)
+ sdk_minor: sdk minor version integer number (e.g., 0)
+ sdk_comps: sdk version components tuple (e.g., ('10', '0', '20348', '0'))
+
+ Args:
+ version: str
+ sdk version specification
+
+ Returns:
+ None or MSVCSDKVersionComponents namedtuple:
+ """
+
+ if not version:
+ return None
+
+ m = re_msvc_sdk_version.match(version)
+ if not m:
+ return None
+
+ sdk_version = version
+ sdk_comps = tuple(sdk_version.split('.'))
+ sdk_verstr = '.'.join(sdk_comps[:2])
+ sdk_vernum = float(sdk_verstr)
+
+ sdk_major, sdk_minor = [int(x) for x in sdk_comps[:2]]
+
+ msvc_sdk_version_components_def = _MSVC_SDK_VERSION_COMPONENTS_DEFINITION(
+ sdk_version = sdk_version,
+ sdk_verstr = sdk_verstr,
+ sdk_vernum = sdk_vernum,
+ sdk_major = sdk_major,
+ sdk_minor = sdk_minor,
+ sdk_comps = sdk_comps,
+ )
+
+ return msvc_sdk_version_components_def
+
diff --git a/SCons/Tool/MSCommon/MSVC/UtilTests.py b/SCons/Tool/MSCommon/MSVC/UtilTests.py
new file mode 100644
index 000000000..5e14d506d
--- /dev/null
+++ b/SCons/Tool/MSCommon/MSVC/UtilTests.py
@@ -0,0 +1,209 @@
+# MIT License
+#
+# Copyright The SCons Foundation
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# 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.
+
+"""
+Test helper functions for Microsoft Visual C/C++.
+"""
+
+import unittest
+import os
+import re
+
+from SCons.Tool.MSCommon.MSVC import Config
+from SCons.Tool.MSCommon.MSVC import Util
+from SCons.Tool.MSCommon.MSVC import WinSDK
+
+class Data:
+
+ UTIL_PARENT_DIR = os.path.join(os.path.dirname(Util.__file__), os.pardir)
+
+class UtilTests(unittest.TestCase):
+
+ def test_listdir_dirs(self):
+ func = Util.listdir_dirs
+ for dirname, expect in [
+ (None, False), ('', False), ('doesnotexist.xyz.abc', False),
+ (Data.UTIL_PARENT_DIR, True),
+ ]:
+ dirs = func(dirname)
+ self.assertTrue((len(dirs) > 0) == expect, "{}({}): {}".format(
+ func.__name__, repr(dirname), 'list is empty' if expect else 'list is not empty'
+ ))
+
+ def test_process_path(self):
+ func = Util.process_path
+ for p, expect in [
+ (None, True), ('', True),
+ ('doesnotexist.xyz.abc', False), (Data.UTIL_PARENT_DIR, False),
+ ]:
+ rval = func(p)
+ self.assertTrue((p == rval) == expect, "{}({}): {}".format(
+ func.__name__, repr(p), repr(rval)
+ ))
+
+ def test_get_version_prefix(self):
+ func = Util.get_version_prefix
+ for version, expect in [
+ (None, ''), ('', ''),
+ ('.', ''), ('0..0', ''), ('.0', ''), ('0.', ''), ('0.0.', ''),
+ ('0', '0'), ('0Abc', '0'), ('0 0', '0'), ('0,0', '0'),
+ ('0.0', '0.0'), ('0.0.0', '0.0.0'), ('0.0.0.0', '0.0.0.0'), ('0.0.0.0.0', '0.0.0.0.0'),
+ ('00.00.00000', '00.00.00000'), ('00.00.00.0', '00.00.00.0'), ('00.00.00.00', '00.00.00.00'), ('00.0.00000.0', '00.0.00000.0'),
+ ('0.0A', '0.0'), ('0.0.0B', '0.0.0'), ('0.0.0.0 C', '0.0.0.0'), ('0.0.0.0.0 D', '0.0.0.0.0'),
+ ]:
+ prefix = func(version)
+ self.assertTrue(prefix == expect, "{}({}): {} != {}".format(
+ func.__name__, repr(version), repr(prefix), repr(expect)
+ ))
+
+ def test_get_msvc_version_prefix(self):
+ func = Util.get_msvc_version_prefix
+ for version, expect in [
+ (None, ''), ('', ''),
+ ('.', ''), ('0..0', ''), ('.0', ''), ('0.', ''), ('0.0.', ''),
+ ('0', ''), ('0Abc', ''), ('0 0', ''), ('0,0', ''),
+ ('0.0', ''), ('0.0.0', ''), ('0.0.0.0', ''), ('0.0.0.0.0', ''),
+ ('1.0A', '1.0'), ('1.0.0B', '1.0'), ('1.0.0.0 C', '1.0'), ('1.0.0.0.0 D', '1.0'),
+ ('1.00A', '1.0'), ('1.00.0B', '1.0'), ('1.00.0.0 C', '1.0'), ('1.00.0.0.0 D', '1.0'),
+ ]:
+ prefix = func(version)
+ self.assertTrue(prefix == expect, "{}({}): {} != {}".format(
+ func.__name__, repr(version), repr(prefix), repr(expect)
+ ))
+
+ def test_is_toolset_full(self):
+ func = Util.is_toolset_full
+ for toolset, expect in [
+ (None, False), ('', False),
+ ('14.1.', False), ('14.10.', False), ('14.10.00000.', False), ('14.10.000000', False), ('14.1Exp', False),
+ ('14.1', True), ('14.10', True), ('14.10.0', True), ('14.10.00', True), ('14.10.000', True), ('14.10.0000', True), ('14.10.0000', True),
+ ]:
+ rval = func(toolset)
+ self.assertTrue(rval == expect, "{}({}) != {}".format(
+ func.__name__, repr(toolset), repr(rval)
+ ))
+
+ def test_is_toolset_140(self):
+ func = Util.is_toolset_140
+ for toolset, expect in [
+ (None, False), ('', False),
+ ('14.0.', False), ('14.00.', False), ('14.00.00000.', False), ('14.00.000000', False), ('14.0Exp', False),
+ ('14.0', True), ('14.00', True), ('14.00.0', True), ('14.00.00', True), ('14.00.000', True), ('14.00.0000', True), ('14.00.0000', True),
+ ]:
+ rval = func(toolset)
+ self.assertTrue(rval == expect, "{}({}) != {}".format(
+ func.__name__, repr(toolset), repr(rval)
+ ))
+
+ def test_is_toolset_sxs(self):
+ func = Util.is_toolset_sxs
+ for toolset, expect in [
+ (None, False), ('', False),
+ ('14.2.', False), ('14.29.', False), ('14.29.1.', False), ('14.29.16.', False), ('14.29.16.1.', False),
+ ('14.29.16.1', True), ('14.29.16.10', True),
+ ]:
+ rval = func(toolset)
+ self.assertTrue(rval == expect, "{}({}) != {}".format(
+ func.__name__, repr(toolset), repr(rval)
+ ))
+
+ def test_msvc_version_components(self):
+ func = Util.msvc_version_components
+ for vcver, expect in [
+ (None, False), ('', False), ('ABC', False), ('14', False), ('14.1.', False), ('14.16', False),
+ ('14.1', True), ('14.1Exp', True),
+ ('14.1Bug', False),
+ ]:
+ comps_def = func(vcver)
+ msg = 'msvc version components definition is None' if expect else 'msvc version components definition is not None'
+ self.assertTrue((comps_def is not None) == expect, "{}({}): {}".format(
+ func.__name__, repr(vcver), repr(msg)
+ ))
+ for vcver in Config.MSVC_VERSION_SUFFIX.keys():
+ comps_def = func(vcver)
+ self.assertNotEqual(comps_def, None, "{}({}) is None".format(
+ func.__name__, repr(vcver)
+ ))
+
+ def test_msvc_extended_version_components(self):
+ func = Util.msvc_extended_version_components
+ # normal code paths
+ for vcver, expect in [
+ (None, False), ('', False), ('ABC', False), ('14', False), ('14.1.', False),
+ ('14.1', True), ('14.16', True),
+ ('14.1Exp', True), ('14.16Exp', True),
+ ('14.16.2', True), ('14.16.27', True), ('14.16.270', True),
+ ('14.16.2702', True), ('14.16.2702', True), ('14.16.27023', True),
+ ('14.16.270239', False),
+ ('14.16.2Exp', True), ('14.16.27Exp', True), ('14.16.270Exp', True),
+ ('14.16.2702Exp', True), ('14.16.2702Exp', True), ('14.16.27023Exp', True),
+ ('14.16.270239Exp', False),
+ ('14.28.16.9', True), ('14.28.16.10', True),
+ ('14.28.16.9Exp', False), ('14.28.16.10Exp', False),
+ ]:
+ comps_def = func(vcver)
+ msg = 'msvc extended version components definition is None' if expect else 'msvc extended version components definition is not None'
+ self.assertTrue((comps_def is not None) == expect, "{}({}): {}".format(
+ func.__name__, repr(vcver), repr(msg)
+ ))
+ for vcver in Config.MSVC_VERSION_SUFFIX.keys():
+ comps_def = func(vcver)
+ self.assertNotEqual(comps_def, None, "{}({}) is None".format(
+ func.__name__, repr(vcver)
+ ))
+ # force 'just in case' guard code path
+ save_re = Util.re_extended_version
+ Util.re_extended_version = re.compile(r'^(?P<version>[0-9]+)$')
+ for vcver, expect in [
+ ('14', False),
+ ]:
+ comps_def = func(vcver)
+ msg = 'msvc extended version components definition is None' if expect else 'msvc extended version components definition is not None'
+ self.assertTrue((comps_def is not None) == expect, "{}({}): {}".format(
+ func.__name__, repr(vcver), repr(msg)
+ ))
+ Util.re_extended_version = save_re
+
+ def test_msvc_sdk_version_components(self):
+ func = Util.msvc_sdk_version_components
+ for vcver, expect in [
+ (None, False), ('', False), ('ABC', False), ('14', False), ('14.1.', False), ('14.16', False),
+ ('8.1', True), ('10.0', True), ('10.0.20348.0', True),
+ ]:
+ comps_def = func(vcver)
+ msg = 'msvc sdk version components definition is None' if expect else 'msvc sdk version components definition is not None'
+ self.assertTrue((comps_def is not None) == expect, "{}({}): {}".format(
+ func.__name__, repr(vcver), repr(msg)
+ ))
+ for vcver in Config.MSVC_VERSION_SUFFIX.keys():
+ comps_def = func(vcver)
+ sdk_list = WinSDK.get_msvc_sdk_version_list(vcver, msvc_uwp_app=False)
+ for sdk_version in sdk_list:
+ comps_def = func(sdk_version)
+ self.assertNotEqual(comps_def, None, "{}({}) is None".format(
+ func.__name__, repr(vcver)
+ ))
+
+if __name__ == "__main__":
+ unittest.main()
+
diff --git a/SCons/Tool/MSCommon/MSVC/Warnings.py b/SCons/Tool/MSCommon/MSVC/Warnings.py
new file mode 100644
index 000000000..cab5145a9
--- /dev/null
+++ b/SCons/Tool/MSCommon/MSVC/Warnings.py
@@ -0,0 +1,35 @@
+# MIT License
+#
+# Copyright The SCons Foundation
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# 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.
+
+"""
+Warnings for Microsoft Visual C/C++.
+"""
+
+import SCons.Warnings
+
+class VisualCWarning(SCons.Warnings.WarningOnByDefault):
+ pass
+
+class MSVCScriptExecutionWarning(VisualCWarning):
+ pass
+
diff --git a/SCons/Tool/MSCommon/MSVC/WinSDK.py b/SCons/Tool/MSCommon/MSVC/WinSDK.py
new file mode 100644
index 000000000..6d18d0730
--- /dev/null
+++ b/SCons/Tool/MSCommon/MSVC/WinSDK.py
@@ -0,0 +1,264 @@
+# MIT License
+#
+# Copyright The SCons Foundation
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# 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.
+
+"""
+Windows SDK functions for Microsoft Visual C/C++.
+"""
+
+import os
+
+from ..common import (
+ debug,
+)
+
+from . import Util
+from . import Config
+from . import Registry
+
+from .Exceptions import (
+ MSVCInternalError,
+)
+
+from . import Dispatcher
+Dispatcher.register_modulename(__name__)
+
+
+_DESKTOP = Config.MSVC_PLATFORM_INTERNAL['Desktop']
+_UWP = Config.MSVC_PLATFORM_INTERNAL['UWP']
+
+def _new_sdk_map():
+ sdk_map = {
+ _DESKTOP.vc_platform: [],
+ _UWP.vc_platform: [],
+ }
+ return sdk_map
+
+def _sdk_10_layout(version):
+
+ folder_prefix = version + '.'
+
+ sdk_map = _new_sdk_map()
+
+ sdk_roots = Registry.sdk_query_paths(version)
+
+ sdk_version_platform_seen = set()
+ sdk_roots_seen = set()
+
+ for sdk_t in sdk_roots:
+
+ sdk_root = sdk_t[0]
+ if sdk_root in sdk_roots_seen:
+ continue
+ sdk_roots_seen.add(sdk_root)
+
+ if not os.path.exists(sdk_root):
+ continue
+
+ sdk_include_path = os.path.join(sdk_root, 'include')
+ if not os.path.exists(sdk_include_path):
+ continue
+
+ for version_nbr, version_nbr_path in Util.listdir_dirs(sdk_include_path):
+
+ if not version_nbr.startswith(folder_prefix):
+ continue
+
+ sdk_inc_path = Util.process_path(os.path.join(version_nbr_path, 'um'))
+ if not os.path.exists(sdk_inc_path):
+ continue
+
+ for vc_platform, sdk_inc_file in [
+ (_DESKTOP.vc_platform, 'winsdkver.h'),
+ (_UWP.vc_platform, 'windows.h'),
+ ]:
+
+ if not os.path.exists(os.path.join(sdk_inc_path, sdk_inc_file)):
+ continue
+
+ key = (version_nbr, vc_platform)
+ if key in sdk_version_platform_seen:
+ continue
+ sdk_version_platform_seen.add(key)
+
+ sdk_map[vc_platform].append(version_nbr)
+
+ for key, val in sdk_map.items():
+ val.sort(reverse=True)
+
+ return sdk_map
+
+def _sdk_81_layout(version):
+
+ version_nbr = version
+
+ sdk_map = _new_sdk_map()
+
+ sdk_roots = Registry.sdk_query_paths(version)
+
+ sdk_version_platform_seen = set()
+ sdk_roots_seen = set()
+
+ for sdk_t in sdk_roots:
+
+ sdk_root = sdk_t[0]
+ if sdk_root in sdk_roots_seen:
+ continue
+ sdk_roots_seen.add(sdk_root)
+
+ # msvc does not check for existence of root or other files
+
+ sdk_inc_path = Util.process_path(os.path.join(sdk_root, r'include\um'))
+ if not os.path.exists(sdk_inc_path):
+ continue
+
+ for vc_platform, sdk_inc_file in [
+ (_DESKTOP.vc_platform, 'winsdkver.h'),
+ (_UWP.vc_platform, 'windows.h'),
+ ]:
+
+ if not os.path.exists(os.path.join(sdk_inc_path, sdk_inc_file)):
+ continue
+
+ key = (version_nbr, vc_platform)
+ if key in sdk_version_platform_seen:
+ continue
+ sdk_version_platform_seen.add(key)
+
+ sdk_map[vc_platform].append(version_nbr)
+
+ for key, val in sdk_map.items():
+ val.sort(reverse=True)
+
+ return sdk_map
+
+_sdk_map_cache = {}
+_sdk_cache = {}
+
+def _reset_sdk_cache():
+ global _sdk_map_cache
+ global _sdk_cache
+ debug('')
+ _sdk_map_cache = {}
+ _sdk_cache = {}
+
+def _sdk_10(key, reg_version):
+ if key in _sdk_map_cache:
+ sdk_map = _sdk_map_cache[key]
+ else:
+ sdk_map = _sdk_10_layout(reg_version)
+ _sdk_map_cache[key] = sdk_map
+ return sdk_map
+
+def _sdk_81(key, reg_version):
+ if key in _sdk_map_cache:
+ sdk_map = _sdk_map_cache[key]
+ else:
+ sdk_map = _sdk_81_layout(reg_version)
+ _sdk_map_cache[key] = sdk_map
+ return sdk_map
+
+def _combine_sdk_map_list(sdk_map_list):
+ combined_sdk_map = _new_sdk_map()
+ for sdk_map in sdk_map_list:
+ for key, val in sdk_map.items():
+ combined_sdk_map[key].extend(val)
+ return combined_sdk_map
+
+_sdk_dispatch_map = {
+ '10.0': (_sdk_10, '10.0'),
+ '8.1': (_sdk_81, '8.1'),
+}
+
+def _verify_sdk_dispatch_map():
+ debug('')
+ for sdk_version in Config.MSVC_SDK_VERSIONS:
+ if sdk_version in _sdk_dispatch_map:
+ continue
+ err_msg = 'sdk version {} not in sdk_dispatch_map'.format(sdk_version)
+ raise MSVCInternalError(err_msg)
+ return None
+
+def _version_list_sdk_map(version_list):
+ sdk_map_list = []
+ for version in version_list:
+ func, reg_version = _sdk_dispatch_map[version]
+ sdk_map = func(version, reg_version)
+ sdk_map_list.append(sdk_map)
+
+ combined_sdk_map = _combine_sdk_map_list(sdk_map_list)
+ return combined_sdk_map
+
+def _sdk_map(version_list):
+ key = tuple(version_list)
+ if key in _sdk_cache:
+ sdk_map = _sdk_cache[key]
+ else:
+ version_numlist = [float(v) for v in version_list]
+ version_numlist.sort(reverse=True)
+ key = tuple([str(v) for v in version_numlist])
+ sdk_map = _version_list_sdk_map(key)
+ _sdk_cache[key] = sdk_map
+ return sdk_map
+
+def get_msvc_platform(is_uwp=False):
+ platform_def = _UWP if is_uwp else _DESKTOP
+ return platform_def
+
+def get_sdk_version_list(vs_def, platform_def):
+ version_list = vs_def.vc_sdk_versions if vs_def.vc_sdk_versions is not None else []
+ sdk_map = _sdk_map(version_list)
+ sdk_list = sdk_map.get(platform_def.vc_platform, [])
+ return sdk_list
+
+def get_msvc_sdk_version_list(msvc_version, msvc_uwp_app=False):
+ debug('msvc_version=%s, msvc_uwp_app=%s', repr(msvc_version), repr(msvc_uwp_app))
+
+ sdk_versions = []
+
+ verstr = Util.get_msvc_version_prefix(msvc_version)
+ if not verstr:
+ debug('msvc_version is not defined')
+ return sdk_versions
+
+ vs_def = Config.MSVC_VERSION_EXTERNAL.get(verstr, None)
+ if not vs_def:
+ debug('vs_def is not defined')
+ return sdk_versions
+
+ is_uwp = True if msvc_uwp_app in Config.BOOLEAN_SYMBOLS[True] else False
+ platform_def = get_msvc_platform(is_uwp)
+ sdk_list = get_sdk_version_list(vs_def, platform_def)
+
+ sdk_versions.extend(sdk_list)
+ debug('sdk_versions=%s', repr(sdk_versions))
+
+ return sdk_versions
+
+def reset():
+ debug('')
+ _reset_sdk_cache()
+
+def verify():
+ debug('')
+ _verify_sdk_dispatch_map()
+
diff --git a/SCons/Tool/MSCommon/MSVC/WinSDKTests.py b/SCons/Tool/MSCommon/MSVC/WinSDKTests.py
new file mode 100644
index 000000000..2a40e9a68
--- /dev/null
+++ b/SCons/Tool/MSCommon/MSVC/WinSDKTests.py
@@ -0,0 +1,132 @@
+# MIT License
+#
+# Copyright The SCons Foundation
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# 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.
+
+"""
+Test Windows SDK functions for Microsoft Visual C/C++.
+"""
+
+import unittest
+
+from SCons.Tool.MSCommon.MSVC import Config
+from SCons.Tool.MSCommon.MSVC import WinSDK
+from SCons.Tool.MSCommon.MSVC import Registry
+from SCons.Tool.MSCommon.MSVC.Exceptions import MSVCInternalError
+
+class Patch:
+
+ class Config:
+
+ class MSVC_SDK_VERSIONS:
+
+ MSVC_SDK_VERSIONS = Config.MSVC_SDK_VERSIONS
+
+ @classmethod
+ def enable_copy(cls):
+ hook = set(cls.MSVC_SDK_VERSIONS)
+ Config.MSVC_SDK_VERSIONS = hook
+ return hook
+
+ @classmethod
+ def restore(cls):
+ Config.MSVC_SDK_VERSIONS = cls.MSVC_SDK_VERSIONS
+
+ class Registry:
+
+ class sdk_query_paths:
+
+ sdk_query_paths = Registry.sdk_query_paths
+
+ @classmethod
+ def sdk_query_paths_duplicate(cls, version):
+ sdk_roots = cls.sdk_query_paths(version)
+ sdk_roots = sdk_roots + sdk_roots if sdk_roots else sdk_roots
+ return sdk_roots
+
+ @classmethod
+ def enable_duplicate(cls):
+ hook = cls.sdk_query_paths_duplicate
+ Registry.sdk_query_paths = hook
+ return hook
+
+ @classmethod
+ def restore(cls):
+ Registry.sdk_query_paths = cls.sdk_query_paths
+
+class WinSDKTests(unittest.TestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ Patch.Registry.sdk_query_paths.enable_duplicate()
+
+ @classmethod
+ def tearDownClass(cls):
+ Patch.Registry.sdk_query_paths.restore()
+
+ def test_verify(self):
+ MSVC_SDK_VERSIONS = Patch.Config.MSVC_SDK_VERSIONS.enable_copy()
+ MSVC_SDK_VERSIONS.add('99.0')
+ with self.assertRaises(MSVCInternalError):
+ WinSDK.verify()
+ Patch.Config.MSVC_SDK_VERSIONS.restore()
+
+ def _run_reset(self):
+ WinSDK.reset()
+ self.assertFalse(WinSDK._sdk_map_cache, "WinSDK._sdk_map_cache was not reset")
+ self.assertFalse(WinSDK._sdk_cache, "WinSDK._sdk_cache was not reset")
+
+ def _run_get_msvc_sdk_version_list(self):
+ for vcver in Config.MSVC_VERSION_SUFFIX.keys():
+ for msvc_uwp_app in (True, False):
+ _ = WinSDK.get_msvc_sdk_version_list(vcver, msvc_uwp_app=msvc_uwp_app)
+
+ def _run_version_list_sdk_map(self):
+ for vcver in Config.MSVC_VERSION_SUFFIX.keys():
+ vs_def = Config.MSVC_VERSION_SUFFIX.get(vcver)
+ if not vs_def.vc_sdk_versions:
+ continue
+ _ = WinSDK._version_list_sdk_map(vs_def.vc_sdk_versions)
+
+ def test_version_list_sdk_map(self):
+ self._run_version_list_sdk_map()
+ self._run_version_list_sdk_map()
+ self.assertTrue(WinSDK._sdk_map_cache, "WinSDK._sdk_map_cache is empty")
+
+ def test_get_msvc_sdk_version_list(self):
+ self._run_get_msvc_sdk_version_list()
+ self._run_get_msvc_sdk_version_list()
+ self.assertTrue(WinSDK._sdk_cache, "WinSDK._sdk_cache is empty")
+
+ def test_get_msvc_sdk_version_list_empty(self):
+ func = WinSDK.get_msvc_sdk_version_list
+ for vcver in [None, '', '99', '99.9']:
+ sdk_versions = func(vcver)
+ self.assertFalse(sdk_versions, "{}: sdk versions list was not empty for msvc version {}".format(
+ func.__name__, repr(vcver)
+ ))
+
+ def test_reset(self):
+ self._run_reset()
+
+if __name__ == "__main__":
+ unittest.main()
+
diff --git a/SCons/Tool/MSCommon/MSVC/__init__.py b/SCons/Tool/MSCommon/MSVC/__init__.py
new file mode 100644
index 000000000..849c82d14
--- /dev/null
+++ b/SCons/Tool/MSCommon/MSVC/__init__.py
@@ -0,0 +1,55 @@
+# MIT License
+#
+# Copyright The SCons Foundation
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# 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.
+
+"""
+Functions for Microsoft Visual C/C++.
+
+The _reset method is used to restore MSVC module data structures to their
+initial state for testing purposes.
+
+The _verify method is used as a sanity check that MSVC module data structures
+are internally consistent.
+
+Currently:
+* _reset is invoked from reset_installed_vcs in the vc module.
+* _verify is invoked from the last line in the vc module.
+"""
+
+from . import Exceptions # noqa: F401
+
+from . import Config # noqa: F401
+from . import Util # noqa: F401
+from . import Registry # noqa: F401
+from . import SetupEnvDefault # noqa: F401
+from . import Policy # noqa: F401
+from . import WinSDK # noqa: F401
+from . import ScriptArguments # noqa: F401
+
+from . import Dispatcher as _Dispatcher
+
+def _reset():
+ _Dispatcher.reset()
+
+def _verify():
+ _Dispatcher.verify()
+
diff --git a/SCons/Tool/MSCommon/README b/SCons/Tool/MSCommon/README
deleted file mode 100644
index 226865197..000000000
--- a/SCons/Tool/MSCommon/README
+++ /dev/null
@@ -1,107 +0,0 @@
-This is the flow of the compiler detection logic:
-
-External to MSCommon:
-
- The Tool init modules, in their exists() routines, call -> msvc_exists(env)
-
-At the moment, those modules are:
- SCons/Tool/midl.py
- SCons/Tool/mslib.py
- SCons/Tool/mslink.py
- SCons/Tool/msvc.py
- SCons/Tool/msvs.py
-
-env may contain a version request in MSVC_VERSION, but this is not used
-in the detection that follows from msvc_exists(), only in the later
-batch that starts with a call to msvc_setup_env().
-
-Internal to MSCommon/vc.py:
-
-+ MSCommon/vc.py:msvc_exists:
-| vcs = cached_get_installed_vcs(env)
-| returns True if vcs > 0
-|
-+-> MSCommon/vc.py:cached_get_installed_vcs:
- | checks global if we've run previously, if so return it
- | populate the global from -> get_installed_vcs(env)
- |
- +-> MSCommon/vc.py:get_installed_vcs:
- | loop through "known" versions of msvc, granularity is maj.min
- | check for product dir -> find_vc_pdir(env, ver)
- |
- +-> MSCommon/vc.py:find_vc_pdir:
- | From the msvc-version to pdir mapping dict, get reg key base and value
- | If value is none -> find_vc_pdir_vswhere(ver, env)
- |
- +-> MSCommon/vc.py:find_vc_pdir_vswhere:
- | From the vc-version to VS-version mapping table get string
- | Figure out where vswhere is -> msvc_find_vswhere()
- | Use subprocess to call vswhere, return first line of match
- /
- | else get product directory from registry (<= 14.0)
- /
- | if we found one -> _check_cl_exists_in_vc_dir(env, pdir, ver)
- |
- +-> MSCommon/vc.py:_check_cl_exists_in_vc_dir:
- | Figure out host/target pair
- | if version > 14.0 get specific version by looking in
- | pdir + Auxiliary/Build/Microsoft/VCToolsVersion/default.txt
- | look for pdir + Tools/MSVC/{specver}/bin/host/target/cl.exe
- | if 14.0 or less, "do older stuff"
-
-All of this just got us a yes-no answer on whether /some/ msvc version
-exists, but does populate __INSTALLED_VCS_RUN with all of the top-level
-versions as noted for get_installed_vcs
-
-Externally:
-
- Once a module's exists() has been called (or, in the case of
- clang/clangxx, after the compiler has been detected by other means -
- those still expect the rest of the msvc chain but not cl.exe)
- the module's generate() function calls -> msvc_setup_env_once(env)
-
-Internally:
-
-+ MSCommon/vc.py:msvc_setup_env_once:
-| checks for environment flag MSVC_SETUP_RUN
-| if not, -> msvc_setup_env(env) and set flag
-|
-+-+ MSCommon/vc.py:msvc_setup_env:
- | set ver from -> get_default_version(env)
- |
- +-+ MSCommon/vc.py:get_default_version:
- | if no version specified in env.MSVC_VERSION:
- | return first entry from -> cached_get_installed_vcs(env)
- | else return requested version
- /
- | get script from MSVC_USE_SCRIPT if set to a filename
- | -> script_env(script)
- |
- +-+ MSCommon/vc.py:script_env:
- | return (possibly cached) script variables matching script arg
- /
- | else -> msvc_find_valid_batch_script(env, version)
- |
- +-+ MSCommon/vc.py:msvc_find_valid_batch_script:
- | Build a list of plausible target values, and loop through
- | look for host + target -> find_batch_file(env, ver, host, target)
- |
- +-+ MSCommon/vc.py:find_batch_file:
- | call -> find_vc_pdir (see above)
- | use the return to construct a version-biased batfile path, check
- /
- | if not found, try sdk scripts (unknown if this is still useful)
-
-
-Problems:
-- For VS >= 2017, VS 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
- are from the set in _VCVER, so 14.1 but not 14.16 or 14.16.27023
-- Information found in the first pass (msvs_exists) isn't really
- available anywhere except the cached version list, since we just
- return true/false.
-- Since msvc_exists chain of calls does not look at version, we
- can proceed to compiler setup if *any* msvc was found, even if the
- one requested wasn't found.
diff --git a/SCons/Tool/MSCommon/README.rst b/SCons/Tool/MSCommon/README.rst
new file mode 100644
index 000000000..36e58aad4
--- /dev/null
+++ b/SCons/Tool/MSCommon/README.rst
@@ -0,0 +1,501 @@
+.. sectnum::
+
+README - SCons.Tool.MSCommon
+############################
+
+.. contents:: **Table of Contents**
+ :depth: 2
+ :local:
+
+
+Design Notes
+============
+
+* Public, user-callable functions and exception types are available via
+ the ``SCons.Tool.MSCommon`` namespace.
+
+* Some existing code has been moved from ``MSCommon/vc.py`` to the appropriate
+ ``MSCommon/MSVC/<modulename>``.
+
+* No functions from the MSVC module or its child modules are intended to be invoked directly.
+ All functions of interest are made available via the ``SCons.Tool.MSCommon`` namespace.
+ It is anticipated that more code may be moved in the future as new features are added.
+ By exposing the public API through ``SCons.Tool.MSCommon`` there should not be a problem
+ with code movement.
+
+* Additional helper functions primarily used for the test suite were added to
+ ``MSCommon/vc.py`` and are available via the ``SCons.Tool.MSCommon`` namespace.
+
+
+Known Issues
+============
+
+The following issues are known to exist:
+
+* Using ``MSVC_USE_SCRIPT`` and ``MSVC_USE_SCRIPT_ARGS`` to call older Microsoft SDK
+ ``SetEnv.cmd`` batch files may result in build failures. Some of these batch files
+ require delayed expansion to be enabled which is not usually the Windows default.
+ One solution would be to launch the MSVC batch file command in a new command interpreter
+ instance with delayed expansion enabled via command-line options.
+
+* The code to suppress the "No versions of the MSVC compiler were found" warning for
+ the default environment was moved from ``MSCommon/vc.py`` to ``MSCommon/MSVC/SetupEnvDefault.py``.
+ There very few, if any, existing unit tests. Now that the code is isolated in its own
+ module with a limited API, unit tests may be easier to implement.
+
+
+Experimental Features
+=====================
+
+msvc_query_version_toolset(version=None, prefer_newest=True)
+------------------------------------------------------------
+
+The experimental function ``msvc_query_version_toolset`` was added to ``MSCommon/vc.py``
+and is available via the ``SCons.Tool.MSCommon`` namespace. This function takes a version
+specification or a toolset version specification and a product preference as arguments and
+returns the msvc version and the msvc toolset version for the corresponding version specification.
+
+This is a proxy for using the toolset version for selection until that functionality can be added.
+
+Example usage:
+::
+ for version in [
+ '14.3',
+ '14.2',
+ '14.1',
+ '14.0',
+ '14.32',
+ '14.31',
+ '14.29',
+ '14.16',
+ '14.00',
+ '14.28.29333', # only 14.2
+ '14.20.29333', # fictitious for testing
+ ]:
+
+ for prefer_newest in (True, False):
+ try:
+ msvc_version, msvc_toolset_version = msvc_query_version_toolset(version, prefer_newest=prefer_newest)
+ failed = False
+ except MSVCToolsetVersionNotFound:
+ failed = True
+ if failed:
+ msg = 'FAILED'
+ newline = '\n'
+ else:
+ env = Environment(MSVC_VERSION=msvc_version, MSVC_TOOLSET_VERSION=msvc_toolset_version)
+ msg = 'passed'
+ newline = ''
+ print('{}Query: {} version={}, prefer_newest={}'.format(newline, msg, version, prefer_newest))
+
+Example output fragment
+::
+ Build: _build003 {'MSVC_VERSION': '14.3', 'MSVC_TOOLSET_VERSION': '14.29.30133'}
+ Where: C:\Software\MSVS-2022-143-Com\VC\Tools\MSVC\14.29.30133\bin\HostX64\x64\cl.exe
+ Where: C:\Software\MSVS-2022-143-Com\Common7\Tools\guidgen.exe
+ Query: passed version=14.2, prefer_newest=True
+
+ Build: _build004 {'MSVC_VERSION': '14.2', 'MSVC_TOOLSET_VERSION': '14.29.30133'}
+ Where: C:\Software\MSVS-2019-142-Com\VC\Tools\MSVC\14.29.30133\bin\HostX64\x64\cl.exe
+ Where: C:\Software\MSVS-2019-142-Com\Common7\Tools\guidgen.exe
+ Query: passed version=14.2, prefer_newest=False
+
+
+Undocumented Features
+=====================
+
+set SCONS_CACHE_MSVC_FORCE_DEFAULTS=1
+-------------------------------------
+
+The Windows system environment variable ``SCONS_CACHE_MSVC_FORCE_DEFAULTS`` was added. This variable is only
+evaluated when the msvc cache is enabled and accepts the values ``1``, ``true``, and ``True``.
+
+When enabled, the default msvc toolset version and the default sdk version, if not otherwise specified, are
+added to the batch file argument list. This is intended to make the cache more resilient to Visual Studio
+updates that may change the default toolset version and/or the default SDK version.
+
+Example usage:
+::
+
+ @echo Enabling scons cache ...
+ @set "SCONS_CACHE_MSVC_CONFIG=mycachefile.json"
+ @set "SCONS_CACHE_MSVC_FORCE_DEFAULTS=True"
+
+
+End-User Diagnostic Tools
+=========================
+
+Due to the proliferation of user-defined msvc batch file arguments, the likelihood of end-user build
+failures has increased.
+
+Some of the options that may be employed in diagnosing end-user msvc build failures are listed below.
+
+msvc_set_scripterror_policy('Warning') and MSVC_SCRIPTERROR_POLICY='Warning'
+----------------------------------------------------------------------------
+
+Enabling warnings to be produced for detected msvc batch file errors may provide additional context
+for build failures. Refer to the documentation for details.
+
+Change the default policy:
+::
+ from SCons.Tool.MSCommon import msvc_set_scripterror_policy
+
+ msvc_set_scripterror_policy('Warning')
+
+Specify the policy per-environment:
+::
+
+ env = Environment(MSVC_VERSION='14.3', MSVC_SPECTRE_LIBS=True, MSVC_SCRIPTERROR_POLICY='Warning')
+
+
+set SCONS_MSCOMMON_DEBUG=mydebugfile.txt
+----------------------------------------
+
+The traditional method of diagnosing end-user issues is to enable the internal msvc debug logging.
+
+
+set SCONS_CACHE_MSVC_CONFIG=mycachefile.json
+--------------------------------------------
+
+On occasion, enabling the cache file can prove to be a useful diagnostic tool. If nothing else,
+issues with the msvc environment may be readily apparent.
+
+
+vswhere.exe
+-----------
+
+On occasion, the raw vswhere output may prove useful especially if there are suspected issues with
+detection of installed msvc instances.
+
+Windows command-line sample invocations:
+::
+ @rem 64-Bit Windows
+ "%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -all -sort -prerelease -products * -legacy -format json >MYVSWHEREOUTPUT.json
+
+ @rem 32-Bit Windows:
+ "%ProgramFiles%\Microsoft Visual Studio\Installer\vswhere.exe" -all -sort -prerelease -products * -legacy -format json >MYVSWHEREOUTPUT.json
+
+
+Visual Studio Implementation Notes
+==================================
+
+Batch File Arguments
+--------------------
+
+Supported MSVC batch file arguments by product:
+
+======= === === ======= =======
+Product UWP SDK Toolset Spectre
+======= === === ======= =======
+VS2022 X X X X
+------- --- --- ------- -------
+VS2019 X X X X
+------- --- --- ------- -------
+VS2017 X X X X
+------- --- --- ------- -------
+VS2015 X X
+======= === === ======= =======
+
+Supported MSVC batch file arguments in SCons:
+
+======== ====================================== ===================================================
+Argument Construction Variable Script Argument Equivalent
+======== ====================================== ===================================================
+UWP ``MSVC_UWP_APP=True`` ``MSVC_SCRIPT_ARGS='store'``
+-------- -------------------------------------- ---------------------------------------------------
+SDK ``MSVC_SDK_VERSION='10.0.20348.0'`` ``MSVC_SCRIPT_ARGS='10.0.20348.0'``
+-------- -------------------------------------- ---------------------------------------------------
+Toolset ``MSVC_TOOLSET_VERSION='14.31.31103'`` ``MSVC_SCRIPT_ARGS='-vcvars_ver=14.31.31103'``
+-------- -------------------------------------- ---------------------------------------------------
+Spectre ``MSVC_SPECTRE_LIBS=True`` ``MSVC_SCRIPT_ARGS='-vcvars_spectre_libs=spectre'``
+======== ====================================== ===================================================
+
+**MSVC_SCRIPT_ARGS contents are not validated. Utilizing script arguments that have construction
+variable equivalents is discouraged and may lead to difficult to diagnose build errors.**
+
+Additional constraints:
+
+* ``MSVC_SDK_VERSION='8.1'`` and ``MSVC_UWP_APP=True`` is supported only for the v140
+ build tools (i.e., ``MSVC_VERSION='14.0'`` or ``MSVC_TOOLSET_VERSION='14.0'``).
+
+* ``MSVC_SPECTRE_LIBS=True`` and ``MSVC_UWP_APP=True`` is not supported (i.e., there
+ are no spectre mitigations libraries for UWP builds).
+
+Default Toolset Version
+-----------------------
+
+Side-by-side toolset versions were introduced in Visual Studio 2017.
+The examples shown below are for Visual Studio 2022.
+
+The msvc default toolset version is dependent on the installation options
+selected. This means that the default toolset version may be different for
+each machine given the same Visual Studio product.
+
+The msvc default toolset is not necessarily the latest toolset installed.
+This has implications when a toolset version is specified using only one minor
+digit (e.g., ``MSVC_TOOLSET_VERSION='14.3'`` or ``MSVC_SCRIPT_ARGS='-vcvars_ver=14.3'``).
+
+Explicitly defining ``MSVC_TOOLSET_VERSION=None`` will return the same toolset
+that the msvc batch files would return. When using ``MSVC_SCRIPT_ARGS``, the
+toolset specification should be omitted entirely.
+
+Local installation and summary test results:
+::
+ VS2022\VC\Auxiliary\Build\Microsoft.VCToolsVersion.v143.default.txt
+ 14.31.31103
+
+ VS2022\VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt
+ 14.32.31326
+
+Toolset version summary:
+::
+ 14.31.31103 Environment()
+ 14.31.31103 Environment(MSVC_TOOLSET_VERSION=None)
+
+ 14.32.31326* Environment(MSVC_TOOLSET_VERSION='14.3')
+ 14.32.31326* Environment(MSVC_SCRIPT_ARGS=['-vcvars_ver=14.3'])
+
+ 14.31.31103 Environment(MSVC_TOOLSET_VERSION='14.31')
+ 14.31.31103 Environment(MSVC_SCRIPT_ARGS=['-vcvars_ver=14.31'])
+
+ 14.32.31326 Environment(MSVC_TOOLSET_VERSION='14.32')
+ 14.32.31326 Environment(MSVC_SCRIPT_ARGS=['-vcvars_ver=14.32'])
+
+VS2022\\Common7\\Tools\\vsdevcmd\\ext\\vcvars.bat usage fragment:
+::
+ @echo -vcvars_ver=version : Version of VC++ Toolset to select
+ @echo ** [Default] : If -vcvars_ver=version is NOT specified, the toolset specified by
+ @echo [VSInstallDir]\VC\Auxiliary\Build\Microsoft.VCToolsVersion.v143.default.txt will be used.
+ @echo ** 14.0 : VS 2015 (v140) VC++ Toolset (installation of the v140 toolset is a prerequisite)
+ @echo ** 14.xx : VS 2017 or VS 2019 VC++ Toolset, if that version is installed on the system under
+ @echo [VSInstallDir]\VC\MSVC\Tools\[version]. Where '14.xx' specifies a partial
+ @echo [version]. The latest [version] directory that matches the specified value will
+ @echo be used.
+ @echo ** 14.xx.yyyyy : VS 2017 or VS 2019 VC++ Toolset, if that version is installed on the system under
+ @echo [VSInstallDir]\VC\MSVC\Tools\[version]. Where '14.xx.yyyyy' specifies an
+ @echo exact [version] directory to be used.
+ @echo ** 14.xx.VV.vv : VS 2019 C++ side-by-side toolset package identity alias, if the SxS toolset has been installed on the system.
+ @echo Where '14.xx.VV.vv' corresponds to a SxS toolset
+ @echo VV = VS Update Major Version (e.g. "16" for VS 2019 v16.9)
+ @echo vv = VS Update Minor version (e.g. "9" for VS 2019 v16.9)
+ @echo Please see [VSInstallDir]\VC\Auxiliary\Build\[version]\Microsoft.VCToolsVersion.[version].txt for mapping of
+ @echo SxS toolset to [VSInstallDir]\VC\MSVC\Tools\ directory.
+
+VS2022 batch file fragment to determine the default toolset version:
+::
+ @REM Add MSVC
+ set "__VCVARS_DEFAULT_CONFIG_FILE=%VCINSTALLDIR%Auxiliary\Build\Microsoft.VCToolsVersion.default.txt"
+
+ @REM We will "fallback" to Microsoft.VCToolsVersion.default.txt (latest) if Microsoft.VCToolsVersion.v143.default.txt does not exist.
+ if EXIST "%VCINSTALLDIR%Auxiliary\Build\Microsoft.VCToolsVersion.v143.default.txt" (
+ if "%VSCMD_DEBUG%" GEQ "2" @echo [DEBUG:ext\%~nx0] Microsoft.VCToolsVersion.v143.default.txt was found.
+ set "__VCVARS_DEFAULT_CONFIG_FILE=%VCINSTALLDIR%Auxiliary\Build\Microsoft.VCToolsVersion.v143.default.txt"
+
+ ) else (
+ if "%VSCMD_DEBUG%" GEQ "1" @echo [DEBUG:ext\%~nx0] Microsoft.VCToolsVersion.v143.default.txt was not found. Defaulting to 'Microsoft.VCToolsVersion.default.txt'.
+ )
+
+Empirical evidence suggests that the default toolset version is different from the latest
+toolset version when the toolset version immediately preceding the latest version is
+installed. For example, the ``14.31`` toolset version is installed when the ``14.32``
+toolset version is the latest.
+
+
+Visual Studio Version Notes
+============================
+
+SDK Versions
+------------
+
+==== ============
+SDK Format
+==== ============
+10.0 10.0.XXXXX.Y
+---- ------------
+8.1 8.1
+==== ============
+
+BuildTools Versions
+-------------------
+
+========== ===== ===== ========
+BuildTools VCVER CLVER MSVCRT
+========== ===== ===== ========
+v143 14.3 19.3 140/ucrt
+---------- ----- ----- --------
+v142 14.2 19.2 140/ucrt
+---------- ----- ----- --------
+v141 14.1 19.1 140/ucrt
+---------- ----- ----- --------
+v140 14.0 19.0 140/ucrt
+---------- ----- ----- --------
+v120 12.0 18.0 120
+---------- ----- ----- --------
+v110 11.0 17.0 110
+---------- ----- ----- --------
+v100 10.0 16.0 100
+---------- ----- ----- --------
+v90 9.0 15.0 90
+---------- ----- ----- --------
+v80 8.0 14.0 80
+---------- ----- ----- --------
+v71 7.1 13.1 71
+---------- ----- ----- --------
+v70 7.0 13.0 70
+---------- ----- ----- --------
+v60 6.0 12.0 60
+========== ===== ===== ========
+
+Product Versions
+----------------
+
+======== ===== ========= ============
+Product VSVER SDK BuildTools
+======== ===== ========= ============
+2022 17.0 10.0, 8.1 v143 .. v140
+-------- ----- --------- ------------
+2019 16.0 10.0, 8.1 v142 .. v140
+-------- ----- --------- ------------
+2017 15.0 10.0, 8.1 v141 .. v140
+-------- ----- --------- ------------
+2015 14.0 10.0, 8.1 v140
+-------- ----- --------- ------------
+2013 12.0 v120
+-------- ----- --------- ------------
+2012 11.0 v110
+-------- ----- --------- ------------
+2010 10.0 v100
+-------- ----- --------- ------------
+2008 9.0 v90
+-------- ----- --------- ------------
+2005 8.0 v80
+-------- ----- --------- ------------
+2003.NET 7.1 v71
+-------- ----- --------- ------------
+2002.NET 7.0 v70
+-------- ----- --------- ------------
+6.0 6.0 v60
+======== ===== ========= ============
+
+
+SCons Implementation Notes
+==========================
+
+Compiler Detection Logic
+------------------------
+
+**WARNING: the compiler detection logic documentation below is likely out-of-date.**
+
+In the future, the compiler detection logic documentation will be updated and integrated
+into the current document format as appropriate.
+
+::
+
+ This is the flow of the compiler detection logic:
+
+ External to MSCommon:
+
+ The Tool init modules, in their exists() routines, call -> msvc_exists(env)
+
+ At the moment, those modules are:
+ SCons/Tool/midl.py
+ SCons/Tool/mslib.py
+ SCons/Tool/mslink.py
+ SCons/Tool/msvc.py
+ SCons/Tool/msvs.py
+
+ env may contain a version request in MSVC_VERSION, but this is not used
+ in the detection that follows from msvc_exists(), only in the later
+ batch that starts with a call to msvc_setup_env().
+
+ Internal to MSCommon/vc.py:
+
+ + MSCommon/vc.py:msvc_exists:
+ | vcs = cached_get_installed_vcs(env)
+ | returns True if vcs > 0
+ |
+ +-> MSCommon/vc.py:cached_get_installed_vcs:
+ | checks global if we've run previously, if so return it
+ | populate the global from -> get_installed_vcs(env)
+ |
+ +-> MSCommon/vc.py:get_installed_vcs:
+ | loop through "known" versions of msvc, granularity is maj.min
+ | check for product dir -> find_vc_pdir(env, ver)
+ |
+ +-> MSCommon/vc.py:find_vc_pdir:
+ | From the msvc-version to pdir mapping dict, get reg key base and value
+ | If value is none -> find_vc_pdir_vswhere(ver, env)
+ |
+ +-> MSCommon/vc.py:find_vc_pdir_vswhere:
+ | From the vc-version to VS-version mapping table get string
+ | Figure out where vswhere is -> msvc_find_vswhere()
+ | Use subprocess to call vswhere, return first line of match
+ /
+ | else get product directory from registry (<= 14.0)
+ /
+ | if we found one -> _check_cl_exists_in_vc_dir(env, pdir, ver)
+ |
+ +-> MSCommon/vc.py:_check_cl_exists_in_vc_dir:
+ | Figure out host/target pair
+ | if version > 14.0 get specific version by looking in
+ | pdir + Auxiliary/Build/Microsoft/VCToolsVersion/default.txt
+ | look for pdir + Tools/MSVC/{specver}/bin/host/target/cl.exe
+ | if 14.0 or less, "do older stuff"
+
+ All of this just got us a yes-no answer on whether /some/ msvc version
+ exists, but does populate __INSTALLED_VCS_RUN with all of the top-level
+ versions as noted for get_installed_vcs
+
+ Externally:
+
+ Once a module's exists() has been called (or, in the case of
+ clang/clangxx, after the compiler has been detected by other means -
+ those still expect the rest of the msvc chain but not cl.exe)
+ the module's generate() function calls -> msvc_setup_env_once(env)
+
+ Internally:
+
+ + MSCommon/vc.py:msvc_setup_env_once:
+ | checks for environment flag MSVC_SETUP_RUN
+ | if not, -> msvc_setup_env(env) and set flag
+ |
+ +-+ MSCommon/vc.py:msvc_setup_env:
+ | set ver from -> get_default_version(env)
+ |
+ +-+ MSCommon/vc.py:get_default_version:
+ | if no version specified in env.MSVC_VERSION:
+ | return first entry from -> cached_get_installed_vcs(env)
+ | else return requested version
+ /
+ | get script from MSVC_USE_SCRIPT if set to a filename
+ | -> script_env(script)
+ |
+ +-+ MSCommon/vc.py:script_env:
+ | return (possibly cached) script variables matching script arg
+ /
+ | else -> msvc_find_valid_batch_script(env, version)
+ |
+ +-+ MSCommon/vc.py:msvc_find_valid_batch_script:
+ | Build a list of plausible target values, and loop through
+ | look for host + target -> find_batch_file(env, ver, host, target)
+ |
+ +-+ MSCommon/vc.py:find_batch_file:
+ | call -> find_vc_pdir (see above)
+ | use the return to construct a version-biased batfile path, check
+ /
+ | if not found, try sdk scripts (unknown if this is still useful)
+
+
+ Problems:
+ - For VS >= 2017, VS 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
+ are from the set in _VCVER, so 14.1 but not 14.16 or 14.16.27023
+ - Information found in the first pass (msvs_exists) isn't really
+ available anywhere except the cached version list, since we just
+ return true/false.
+ - Since msvc_exists chain of calls does not look at version, we
+ can proceed to compiler setup if *any* msvc was found, even if the
+ one requested wasn't found.
+
diff --git a/SCons/Tool/MSCommon/__init__.py b/SCons/Tool/MSCommon/__init__.py
index 9d8a8ffc4..c3078ac63 100644
--- a/SCons/Tool/MSCommon/__init__.py
+++ b/SCons/Tool/MSCommon/__init__.py
@@ -28,21 +28,26 @@ Common functions for Microsoft Visual Studio and Visual C/C++.
import SCons.Errors
import SCons.Platform.win32
-import SCons.Util
+import SCons.Util # noqa: F401
-from SCons.Tool.MSCommon.sdk import mssdk_exists, mssdk_setup_env
+from SCons.Tool.MSCommon.sdk import ( # noqa: F401
+ mssdk_exists,
+ mssdk_setup_env,
+)
-from SCons.Tool.MSCommon.vc import (
+from SCons.Tool.MSCommon.vc import ( # noqa: F401
msvc_exists,
msvc_setup_env_tool,
msvc_setup_env_once,
msvc_version_to_maj_min,
msvc_find_vswhere,
- set_msvc_notfound_policy,
- get_msvc_notfound_policy,
+ msvc_sdk_versions,
+ msvc_toolset_versions,
+ msvc_toolset_versions_spectre,
+ msvc_query_version_toolset,
)
-from SCons.Tool.MSCommon.vs import (
+from SCons.Tool.MSCommon.vs import ( # noqa: F401
get_default_version,
get_vs_by_version,
merge_default_version,
@@ -50,6 +55,38 @@ from SCons.Tool.MSCommon.vs import (
query_versions,
)
+from .MSVC.Policy import ( # noqa: F401
+ msvc_set_notfound_policy,
+ msvc_get_notfound_policy,
+ msvc_set_scripterror_policy,
+ msvc_get_scripterror_policy,
+)
+
+from .MSVC.Exceptions import ( # noqa: F401
+ VisualCException,
+ MSVCInternalError,
+ MSVCUserError,
+ MSVCScriptExecutionError,
+ MSVCVersionNotFound,
+ MSVCSDKVersionNotFound,
+ MSVCToolsetVersionNotFound,
+ MSVCSpectreLibsNotFound,
+ MSVCArgumentError,
+)
+
+from .vc import ( # noqa: F401
+ MSVCUnsupportedHostArch,
+ MSVCUnsupportedTargetArch,
+ MSVCScriptNotFound,
+ MSVCUseSettingsError,
+)
+
+from .MSVC.Util import ( # noqa: F401
+ msvc_version_components,
+ msvc_extended_version_components,
+ msvc_sdk_version_components,
+)
+
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
diff --git a/SCons/Tool/MSCommon/common.py b/SCons/Tool/MSCommon/common.py
index c9f07f5ca..ad4c827d3 100644
--- a/SCons/Tool/MSCommon/common.py
+++ b/SCons/Tool/MSCommon/common.py
@@ -45,6 +45,7 @@ class MSVCCacheInvalidWarning(SCons.Warnings.WarningOnByDefault):
LOGFILE = os.environ.get('SCONS_MSCOMMON_DEBUG')
if LOGFILE:
import logging
+
modulelist = (
# root module and parent/root module
'MSCommon', 'Tool',
@@ -53,6 +54,7 @@ if LOGFILE:
# scons modules
'SCons', 'test', 'scons'
)
+
def get_relative_filename(filename, module_list):
if not filename:
return filename
@@ -63,6 +65,7 @@ if LOGFILE:
except ValueError:
pass
return filename
+
class _Debug_Filter(logging.Filter):
# custom filter for module relative filename
def filter(self, record):
@@ -70,6 +73,7 @@ if LOGFILE:
relfilename = relfilename.replace('\\', '/')
record.relfilename = relfilename
return True
+
# Log format looks like:
# 00109ms:MSCommon/vc.py:find_vc_pdir#447: VC found '14.3' [file]
# debug: 00109ms:MSCommon/vc.py:find_vc_pdir#447: VC found '14.3' [stdout]
@@ -85,11 +89,11 @@ if LOGFILE:
log_handler = logging.StreamHandler(sys.stdout)
else:
log_handler = logging.FileHandler(filename=LOGFILE)
- logging.basicConfig(
- format=log_format,
- handlers=[log_handler],
- level=logging.DEBUG)
+ log_formatter = logging.Formatter(log_format)
+ log_handler.setFormatter(log_formatter)
logger = logging.getLogger(name=__name__)
+ logger.setLevel(level=logging.DEBUG)
+ logger.addHandler(log_handler)
logger.addFilter(_Debug_Filter())
debug = logger.debug
else:
@@ -102,6 +106,11 @@ CONFIG_CACHE = os.environ.get('SCONS_CACHE_MSVC_CONFIG')
if CONFIG_CACHE in ('1', 'true', 'True'):
CONFIG_CACHE = os.path.join(os.path.expanduser('~'), 'scons_msvc_cache.json')
+# SCONS_CACHE_MSVC_FORCE_DEFAULTS is internal-use so undocumented.
+CONFIG_CACHE_FORCE_DEFAULT_ARGUMENTS = False
+if CONFIG_CACHE:
+ if os.environ.get('SCONS_CACHE_MSVC_FORCE_DEFAULTS') in ('1', 'true', 'True'):
+ CONFIG_CACHE_FORCE_DEFAULT_ARGUMENTS = True
def read_script_env_cache():
""" fetch cached msvc env vars if requested, else return empty dict """
@@ -226,7 +235,7 @@ def normalize_env(env, keys, force=False):
# should include it, but keep this here to be safe (needed for reg.exe)
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
diff --git a/SCons/Tool/MSCommon/vc.py b/SCons/Tool/MSCommon/vc.py
index b542d1571..787194062 100644
--- a/SCons/Tool/MSCommon/vc.py
+++ b/SCons/Tool/MSCommon/vc.py
@@ -60,9 +60,14 @@ from . import common
from .common import CONFIG_CACHE, debug
from .sdk import get_installed_sdks
+from . import MSVC
-class VisualCException(Exception):
- pass
+from .MSVC.Exceptions import (
+ VisualCException,
+ MSVCUserError,
+ MSVCArgumentError,
+ MSVCToolsetVersionNotFound,
+)
class UnsupportedVersion(VisualCException):
pass
@@ -82,45 +87,12 @@ class NoVersionFound(VisualCException):
class BatchFileExecutionError(VisualCException):
pass
-class MSVCScriptNotFound(VisualCException):
- pass
-
-class MSVCUseSettingsError(VisualCException):
+class MSVCScriptNotFound(MSVCUserError):
pass
-class MSVCVersionNotFound(VisualCException):
+class MSVCUseSettingsError(MSVCUserError):
pass
-# MSVC_NOTFOUND_POLICY definition:
-# error: raise exception
-# warning: issue warning and continue
-# ignore: continue
-
-_MSVC_NOTFOUND_POLICY_DEFINITION = namedtuple('MSVCNotFoundPolicyDefinition', [
- 'value',
- 'symbol',
-])
-
-_MSVC_NOTFOUND_POLICY_INTERNAL = {}
-_MSVC_NOTFOUND_POLICY_EXTERNAL = {}
-
-for policy_value, policy_symbol_list in [
- (True, ['Error', 'Exception']),
- (False, ['Warning', 'Warn']),
- (None, ['Ignore', 'Suppress']),
-]:
-
- policy_symbol = policy_symbol_list[0].lower()
- policy_def = _MSVC_NOTFOUND_POLICY_DEFINITION(policy_value, policy_symbol)
-
- _MSVC_NOTFOUND_POLICY_INTERNAL[policy_symbol] = policy_def
-
- for policy_symbol in policy_symbol_list:
- _MSVC_NOTFOUND_POLICY_EXTERNAL[policy_symbol.lower()] = policy_def
- _MSVC_NOTFOUND_POLICY_EXTERNAL[policy_symbol] = policy_def
- _MSVC_NOTFOUND_POLICY_EXTERNAL[policy_symbol.upper()] = policy_def
-
-_MSVC_NOTFOUND_POLICY_DEF = _MSVC_NOTFOUND_POLICY_INTERNAL['warning']
# Dict to 'canonalize' the arch
_ARCH_TO_CANONICAL = {
@@ -451,7 +423,7 @@ def get_msvc_version_numeric(msvc_version):
str: the value converted to a numeric only string
"""
- return ''.join([x for x in msvc_version if x in string_digits + '.'])
+ return ''.join([x for x in msvc_version if x in string_digits + '.'])
def get_host_platform(host_platform):
@@ -546,7 +518,7 @@ def get_host_target(env, msvc_version, all_host_targets=False):
msg = "Unrecognized host architecture %s for version %s"
raise MSVCUnsupportedHostArch(msg % (repr(host_platform), msvc_version)) from None
- return (host_platform, target_platform, host_target_list)
+ return host_platform, target_platform, host_target_list
# If you update this, update SupportedVSList in Tool/MSCommon/vs.py, and the
# MSVC_VERSION documentation in Tool/msvc.xml.
@@ -649,11 +621,11 @@ def msvc_version_to_maj_min(msvc_version):
maj = int(t[0])
min = int(t[1])
return maj, min
- except ValueError as e:
+ except ValueError:
raise ValueError("Unrecognized version %s (%s)" % (msvc_version,msvc_version_numeric)) from None
-VSWHERE_PATHS = [os.path.join(p,'vswhere.exe') for p in [
+VSWHERE_PATHS = [os.path.join(p,'vswhere.exe') for p in [
os.path.expandvars(r"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer"),
os.path.expandvars(r"%ProgramFiles%\Microsoft Visual Studio\Installer"),
os.path.expandvars(r"%ChocolateyInstall%\bin"),
@@ -709,7 +681,8 @@ def find_vc_pdir_vswhere(msvc_version, env=None):
debug("running: %s", vswhere_cmd)
- #cp = subprocess.run(vswhere_cmd, capture_output=True, check=True) # 3.7+ only
+ # TODO: Python 3.7
+ # cp = subprocess.run(vswhere_cmd, capture_output=True, check=True) # 3.7+ only
cp = subprocess.run(vswhere_cmd, stdout=PIPE, stderr=PIPE, check=True)
if cp.stdout:
@@ -779,6 +752,9 @@ def find_vc_pdir(env, msvc_version):
except OSError:
debug('no VC registry key %s', repr(key))
else:
+ if msvc_version == '9.0' and key.lower().endswith('\\vcforpython\\9.0\\installdir'):
+ # Visual C++ for Python registry key is installdir (root) not productdir (vc)
+ comps = os.path.join(comps, 'VC')
debug('found VC in registry: %s', comps)
if os.path.exists(comps):
return comps
@@ -806,15 +782,21 @@ def find_batch_file(env, msvc_version, host_arch, target_arch):
vernum = float(msvc_ver_numeric)
arg = ''
+ vcdir = None
+
if vernum > 14:
# 14.1 (VS2017) and later
batfiledir = os.path.join(pdir, "Auxiliary", "Build")
batfile, _ = _GE2017_HOST_TARGET_BATCHFILE_CLPATHCOMPS[(host_arch, target_arch)]
batfilename = os.path.join(batfiledir, batfile)
+ vcdir = pdir
elif 14 >= vernum >= 8:
# 14.0 (VS2015) to 8.0 (VS2005)
arg, _ = _LE2015_HOST_TARGET_BATCHARG_CLPATHCOMPS[(host_arch, target_arch)]
batfilename = os.path.join(pdir, "vcvarsall.bat")
+ if msvc_version == '9.0' and not os.path.exists(batfilename):
+ # Visual C++ for Python batch file is in installdir (root) not productdir (vc)
+ batfilename = os.path.normpath(os.path.join(pdir, os.pardir, "vcvarsall.bat"))
else:
# 7.1 (VS2003) and earlier
pdir = os.path.join(pdir, "Bin")
@@ -833,9 +815,9 @@ def find_batch_file(env, msvc_version, host_arch, target_arch):
sdk_bat_file_path = os.path.join(pdir, sdk_bat_file)
if os.path.exists(sdk_bat_file_path):
debug('sdk_bat_file_path:%s', sdk_bat_file_path)
- return (batfilename, arg, sdk_bat_file_path)
+ return batfilename, arg, vcdir, sdk_bat_file_path
- return (batfilename, arg, None)
+ return batfilename, arg, vcdir, None
__INSTALLED_VCS_RUN = None
_VC_TOOLS_VERSION_FILE_PATH = ['Auxiliary', 'Build', 'Microsoft.VCToolsVersion.default.txt']
@@ -877,7 +859,7 @@ def _check_cl_exists_in_vc_dir(env, vc_dir, msvc_version):
# 2017 and newer allowed multiple versions of the VC toolset to be
# installed at the same time. This changes the layout.
# Just get the default tool version for now
- #TODO: support setting a specific minor VC version
+ # TODO: support setting a specific minor VC version
default_toolset_file = os.path.join(vc_dir, _VC_TOOLS_VERSION_FILE)
try:
with open(default_toolset_file) as f:
@@ -909,11 +891,6 @@ def _check_cl_exists_in_vc_dir(env, vc_dir, msvc_version):
elif 14 >= vernum >= 8:
# 14.0 (VS2015) to 8.0 (VS2005)
- cl_path_prefixes = [None]
- if msvc_version == '9.0':
- # Visual C++ for Python registry key is installdir (root) not productdir (vc)
- cl_path_prefixes.append(('VC',))
-
for host_platform, target_platform in host_target_list:
debug('host platform %s, target platform %s for version %s', host_platform, target_platform, msvc_version)
@@ -924,15 +901,12 @@ def _check_cl_exists_in_vc_dir(env, vc_dir, msvc_version):
continue
_, cl_path_comps = batcharg_clpathcomps
- for cl_path_prefix in cl_path_prefixes:
-
- cl_path_comps_adj = cl_path_prefix + cl_path_comps if cl_path_prefix else cl_path_comps
- cl_path = os.path.join(vc_dir, *cl_path_comps_adj, _CL_EXE_NAME)
- debug('checking for %s at %s', _CL_EXE_NAME, cl_path)
+ cl_path = os.path.join(vc_dir, *cl_path_comps, _CL_EXE_NAME)
+ debug('checking for %s at %s', _CL_EXE_NAME, cl_path)
- if os.path.exists(cl_path):
- debug('found %s', _CL_EXE_NAME)
- return True
+ if os.path.exists(cl_path):
+ debug('found %s', _CL_EXE_NAME)
+ return True
elif 8 > vernum >= 6:
# 7.1 (VS2003) to 6.0 (VS6)
@@ -993,7 +967,20 @@ def reset_installed_vcs():
"""Make it try again to find VC. This is just for the tests."""
global __INSTALLED_VCS_RUN
__INSTALLED_VCS_RUN = None
- _MSVCSetupEnvDefault.reset()
+ MSVC._reset()
+
+def msvc_default_version(env=None):
+ """Get default msvc version."""
+ vcs = get_installed_vcs(env)
+ msvc_version = vcs[0] if vcs else None
+ debug('msvc_version=%s', repr(msvc_version))
+ return msvc_version
+
+def get_installed_vcs_components(env=None):
+ """Test suite convenience function: return list of installed msvc version component tuples"""
+ vcs = get_installed_vcs(env)
+ msvc_version_component_defs = [MSVC.Util.msvc_version_components(vcver) for vcver in vcs]
+ return msvc_version_component_defs
# Running these batch files isn't cheap: most of the time spent in
# msvs.generate() is due to vcvars*.bat. In a build that uses "tools='msvs'"
@@ -1015,7 +1002,7 @@ def reset_installed_vcs():
script_env_cache = None
-def script_env(script, args=None):
+def script_env(env, script, args=None):
global script_env_cache
if script_env_cache is None:
@@ -1041,306 +1028,44 @@ def script_env(script, args=None):
if cache_data is None:
stdout = common.get_output(script, args)
-
- # Stupid batch files do not set return code: we take a look at the
- # beginning of the output for an error message instead
- olines = stdout.splitlines()
- if re_script_output_error.match(olines[0]):
- raise BatchFileExecutionError("\n".join(olines[:2]))
-
cache_data = common.parse_output(stdout)
- script_env_cache[cache_key] = cache_data
- # once we updated cache, give a chance to write out if user wanted
- common.write_script_env_cache(script_env_cache)
-
- return cache_data
-
-def _msvc_notfound_policy_lookup(symbol):
- try:
- notfound_policy_def = _MSVC_NOTFOUND_POLICY_EXTERNAL[symbol]
- except KeyError:
- err_msg = "Value specified for MSVC_NOTFOUND_POLICY is not supported: {}.\n" \
- " Valid values are: {}".format(
- repr(symbol),
- ', '.join([repr(s) for s in _MSVC_NOTFOUND_POLICY_EXTERNAL.keys()])
- )
- raise ValueError(err_msg)
-
- return notfound_policy_def
-
-def set_msvc_notfound_policy(MSVC_NOTFOUND_POLICY=None):
- """ Set the default policy when MSVC is not found.
-
- Args:
- MSVC_NOTFOUND_POLICY:
- string representing the policy behavior
- when MSVC is not found or None
-
- Returns:
- The previous policy is returned when the MSVC_NOTFOUND_POLICY argument
- is not None. The active policy is returned when the MSVC_NOTFOUND_POLICY
- argument is None.
-
- """
- global _MSVC_NOTFOUND_POLICY_DEF
-
- prev_policy = _MSVC_NOTFOUND_POLICY_DEF.symbol
-
- policy = MSVC_NOTFOUND_POLICY
- if policy is not None:
- _MSVC_NOTFOUND_POLICY_DEF = _msvc_notfound_policy_lookup(policy)
-
- debug(
- 'prev_policy=%s, set_policy=%s, policy.symbol=%s, policy.value=%s',
- repr(prev_policy), repr(policy),
- repr(_MSVC_NOTFOUND_POLICY_DEF.symbol), repr(_MSVC_NOTFOUND_POLICY_DEF.value)
- )
-
- return prev_policy
-
-def get_msvc_notfound_policy():
- """Return the active policy when MSVC is not found."""
- debug(
- 'policy.symbol=%s, policy.value=%s',
- repr(_MSVC_NOTFOUND_POLICY_DEF.symbol), repr(_MSVC_NOTFOUND_POLICY_DEF.value)
- )
- return _MSVC_NOTFOUND_POLICY_DEF.symbol
-
-def _msvc_notfound_policy_handler(env, msg):
-
- if env and 'MSVC_NOTFOUND_POLICY' in env:
- # environment setting
- notfound_policy_src = 'environment'
- policy = env['MSVC_NOTFOUND_POLICY']
- if policy is not None:
- # user policy request
- notfound_policy_def = _msvc_notfound_policy_lookup(policy)
- else:
- # active global setting
- notfound_policy_def = _MSVC_NOTFOUND_POLICY_DEF
- else:
- # active global setting
- notfound_policy_src = 'default'
- policy = None
- notfound_policy_def = _MSVC_NOTFOUND_POLICY_DEF
-
- debug(
- 'source=%s, set_policy=%s, policy.symbol=%s, policy.value=%s',
- notfound_policy_src, repr(policy), repr(notfound_policy_def.symbol), repr(notfound_policy_def.value)
- )
-
- if notfound_policy_def.value is None:
- # ignore
- pass
- elif notfound_policy_def.value:
- raise MSVCVersionNotFound(msg)
- else:
- SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, msg)
+ # debug(stdout)
+ olines = stdout.splitlines()
-class _MSVCSetupEnvDefault:
- """
- Determine if and/or when an error/warning should be issued when there
- are no versions of msvc installed. If there is at least one version of
- msvc installed, these routines do (almost) nothing.
-
- Notes:
- * When msvc is the default compiler because there are no compilers
- installed, a build may fail due to the cl.exe command not being
- recognized. Currently, there is no easy way to detect during
- msvc initialization if the default environment will be used later
- to build a program and/or library. There is no error/warning
- as there are legitimate SCons uses that do not require a c compiler.
- * As implemented, the default is that a warning is issued. This can
- be changed globally via the function set_msvc_notfound_policy and/or
- through the environment via the MSVC_NOTFOUND_POLICY variable.
- """
+ # process stdout: batch file errors (not necessarily first line)
+ script_errlog = []
+ for line in olines:
+ if re_script_output_error.match(line):
+ if not script_errlog:
+ script_errlog.append('vc script errors detected:')
+ script_errlog.append(line)
+
+ if script_errlog:
+ script_errmsg = '\n'.join(script_errlog)
+
+ have_cl = False
+ if cache_data and 'PATH' in cache_data:
+ for p in cache_data['PATH']:
+ if os.path.exists(os.path.join(p, _CL_EXE_NAME)):
+ have_cl = True
+ break
- separator = r';'
-
- need_init = True
-
- @classmethod
- def reset(cls):
- debug('msvc default:init')
- cls.n_setup = 0 # number of calls to msvc_setup_env_once
- cls.default_ismsvc = False # is msvc the default compiler
- cls.default_tools_re_list = [] # list of default tools regular expressions
- cls.msvc_tools_init = set() # tools registered via msvc_exists
- cls.msvc_tools = None # tools registered via msvc_setup_env_once
- cls.msvc_installed = False # is msvc installed (vcs_installed > 0)
- cls.msvc_nodefault = False # is there a default version of msvc
- cls.need_init = True # reset initialization indicator
-
- @classmethod
- def _initialize(cls, env):
- if cls.need_init:
- cls.reset()
- cls.need_init = False
- vcs = get_installed_vcs(env)
- cls.msvc_installed = len(vcs) > 0
- debug('msvc default:msvc_installed=%s', cls.msvc_installed)
-
- @classmethod
- def register_tool(cls, env, tool):
- debug('msvc default:tool=%s', tool)
- if cls.need_init:
- cls._initialize(env)
- if cls.msvc_installed:
- return None
- if not tool:
- return None
- if cls.n_setup == 0:
- if tool not in cls.msvc_tools_init:
- cls.msvc_tools_init.add(tool)
- debug('msvc default:tool=%s, msvc_tools_init=%s', tool, cls.msvc_tools_init)
- return None
- if tool not in cls.msvc_tools:
- cls.msvc_tools.add(tool)
- debug('msvc default:tool=%s, msvc_tools=%s', tool, cls.msvc_tools)
-
- @classmethod
- def register_setup(cls, env):
- debug('msvc default')
- if cls.need_init:
- cls._initialize(env)
- cls.n_setup += 1
- if not cls.msvc_installed:
- cls.msvc_tools = set(cls.msvc_tools_init)
- if cls.n_setup == 1:
- tool_list = env.get('TOOLS', None)
- if tool_list and tool_list[0] == 'default':
- if len(tool_list) > 1 and tool_list[1] in cls.msvc_tools:
- # msvc tools are the default compiler
- cls.default_ismsvc = True
- cls.msvc_nodefault = False
debug(
- 'msvc default:n_setup=%d, msvc_installed=%s, default_ismsvc=%s',
- cls.n_setup, cls.msvc_installed, cls.default_ismsvc
+ 'script=%s args=%s have_cl=%s, errors=%s',
+ repr(script), repr(args), repr(have_cl), script_errmsg
)
+ MSVC.Policy.msvc_scripterror_handler(env, script_errmsg)
- @classmethod
- def set_nodefault(cls):
- # default msvc version, msvc not installed
- cls.msvc_nodefault = True
- debug('msvc default:msvc_nodefault=%s', cls.msvc_nodefault)
-
- @classmethod
- def register_iserror(cls, env, tool):
-
- cls.register_tool(env, tool)
-
- if cls.msvc_installed:
- # msvc installed
- return None
-
- if not cls.msvc_nodefault:
- # msvc version specified
- return None
-
- tool_list = env.get('TOOLS', None)
- if not tool_list:
- # tool list is empty
- return None
-
- debug(
- 'msvc default:n_setup=%s, default_ismsvc=%s, msvc_tools=%s, tool_list=%s',
- cls.n_setup, cls.default_ismsvc, cls.msvc_tools, tool_list
- )
-
- if not cls.default_ismsvc:
-
- # Summary:
- # * msvc is not installed
- # * msvc version not specified (default)
- # * msvc is not the default compiler
-
- # construct tools set
- tools_set = set(tool_list)
-
- else:
-
- if cls.n_setup == 1:
- # first setup and msvc is default compiler:
- # build default tools regex for current tool state
- tools = cls.separator.join(tool_list)
- tools_nchar = len(tools)
- debug('msvc default:add regex:nchar=%d, tools=%s', tools_nchar, tools)
- re_default_tools = re.compile(re.escape(tools))
- cls.default_tools_re_list.insert(0, (tools_nchar, re_default_tools))
- # early exit: no error for default environment when msvc is not installed
- return None
-
- # Summary:
- # * msvc is not installed
- # * msvc version not specified (default)
- # * environment tools list is not empty
- # * default tools regex list constructed
- # * msvc tools set constructed
- #
- # Algorithm using tools string and sets:
- # * convert environment tools list to a string
- # * iteratively remove default tools sequences via regex
- # substition list built from longest sequence (first)
- # to shortest sequence (last)
- # * build environment tools set with remaining tools
- # * compute intersection of environment tools and msvc tools sets
- # * if the intersection is:
- # empty - no error: default tools and/or no additional msvc tools
- # not empty - error: user specified one or more msvc tool(s)
- #
- # This will not produce an error or warning when there are no
- # msvc installed instances nor any other recognized compilers
- # and the default environment is needed for a build. The msvc
- # compiler is forcibly added to the environment tools list when
- # there are no compilers installed on win32. In this case, cl.exe
- # will not be found on the path resulting in a failed build.
-
- # construct tools string
- tools = cls.separator.join(tool_list)
- tools_nchar = len(tools)
-
- debug('msvc default:check tools:nchar=%d, tools=%s', tools_nchar, tools)
-
- # iteratively remove default tool sequences (longest to shortest)
- re_nchar_min, re_tools_min = cls.default_tools_re_list[-1]
- if tools_nchar >= re_nchar_min and re_tools_min.search(tools):
- # minimum characters satisfied and minimum pattern exists
- for re_nchar, re_default_tool in cls.default_tools_re_list:
- if tools_nchar < re_nchar:
- # not enough characters for pattern
- continue
- tools = re_default_tool.sub('', tools).strip(cls.separator)
- tools_nchar = len(tools)
- debug('msvc default:check tools:nchar=%d, tools=%s', tools_nchar, tools)
- if tools_nchar < re_nchar_min or not re_tools_min.search(tools):
- # less than minimum characters or minimum pattern does not exist
- break
-
- # construct non-default list(s) tools set
- tools_set = {msvc_tool for msvc_tool in tools.split(cls.separator) if msvc_tool}
+ if not have_cl:
+ # detected errors, cl.exe not on path
+ raise BatchFileExecutionError(script_errmsg)
- debug('msvc default:tools=%s', tools_set)
- if not tools_set:
- return None
-
- # compute intersection of remaining tools set and msvc tools set
- tools_found = cls.msvc_tools.intersection(tools_set)
- debug('msvc default:tools_exist=%s', tools_found)
- if not tools_found:
- return None
-
- # construct in same order as tools list
- tools_found_list = []
- seen_tool = set()
- for tool in tool_list:
- if tool not in seen_tool:
- seen_tool.add(tool)
- if tool in tools_found:
- tools_found_list.append(tool)
+ # once we updated cache, give a chance to write out if user wanted
+ script_env_cache[cache_key] = cache_data
+ common.write_script_env_cache(script_env_cache)
- # return tool list in order presented
- return tools_found_list
+ return cache_data
def get_default_version(env):
msvc_version = env.get('MSVC_VERSION')
@@ -1356,20 +1081,18 @@ def get_default_version(env):
if not msvc_version == msvs_version:
SCons.Warnings.warn(
SCons.Warnings.VisualVersionMismatch,
- "Requested msvc version (%s) and msvs version (%s) do " \
- "not match: please use MSVC_VERSION only to request a " \
- "visual studio version, MSVS_VERSION is deprecated" \
+ "Requested msvc version (%s) and msvs version (%s) do "
+ "not match: please use MSVC_VERSION only to request a "
+ "visual studio version, MSVS_VERSION is deprecated"
% (msvc_version, msvs_version))
return msvs_version
if not msvc_version:
- installed_vcs = get_installed_vcs(env)
- debug('installed_vcs:%s', installed_vcs)
- if not installed_vcs:
+ msvc_version = msvc_default_version(env)
+ if not msvc_version:
#SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, msg)
debug('No installed VCs')
return None
- msvc_version = installed_vcs[0]
debug('using default installed MSVC version %s', repr(msvc_version))
else:
debug('using specified MSVC version %s', repr(msvc_version))
@@ -1378,22 +1101,21 @@ def get_default_version(env):
def msvc_setup_env_once(env, tool=None):
try:
- has_run = env["MSVC_SETUP_RUN"]
+ has_run = env["MSVC_SETUP_RUN"]
except KeyError:
has_run = False
if not has_run:
- debug('tool=%s', repr(tool))
- _MSVCSetupEnvDefault.register_setup(env)
+ MSVC.SetupEnvDefault.register_setup(env, msvc_exists)
msvc_setup_env(env)
env["MSVC_SETUP_RUN"] = True
- req_tools = _MSVCSetupEnvDefault.register_iserror(env, tool)
+ req_tools = MSVC.SetupEnvDefault.register_iserror(env, tool, msvc_exists)
if req_tools:
msg = "No versions of the MSVC compiler were found.\n" \
" Visual Studio C/C++ compilers may not be set correctly.\n" \
" Requested tool(s) are: {}".format(req_tools)
- _msvc_notfound_policy_handler(env, msg)
+ MSVC.Policy.msvc_notfound_handler(env, msg)
def msvc_find_valid_batch_script(env, version):
"""Find and execute appropriate batch script to set up build env.
@@ -1418,10 +1140,11 @@ def msvc_find_valid_batch_script(env, version):
for host_arch, target_arch, in host_target_list:
# Set to current arch.
env['TARGET_ARCH'] = target_arch
+ arg = ''
# Try to locate a batch file for this host/target platform combo
try:
- (vc_script, arg, sdk_script) = find_batch_file(env, version, host_arch, target_arch)
+ (vc_script, arg, vc_dir, sdk_script) = find_batch_file(env, version, host_arch, target_arch)
debug('vc_script:%s vc_script_arg:%s sdk_script:%s', vc_script, arg, sdk_script)
version_installed = True
except VisualCException as e:
@@ -1434,16 +1157,9 @@ def msvc_find_valid_batch_script(env, version):
debug('use_script 2 %s, args:%s', repr(vc_script), arg)
found = None
if vc_script:
- # Get just version numbers
- maj, min = msvc_version_to_maj_min(version)
- # VS2015+
- if maj >= 14:
- if env.get('MSVC_UWP_APP') == '1':
- # Initialize environment variables with store/UWP paths
- arg = (arg + ' store').lstrip()
-
+ arg = MSVC.ScriptArguments.msvc_script_arguments(env, version, vc_dir, arg)
try:
- d = script_env(vc_script, args=arg)
+ d = script_env(env, vc_script, args=arg)
found = vc_script
except BatchFileExecutionError as e:
debug('use_script 3: failed running VC script %s: %s: Error:%s', repr(vc_script), arg, e)
@@ -1452,7 +1168,7 @@ def msvc_find_valid_batch_script(env, version):
if not vc_script and sdk_script:
debug('use_script 4: trying sdk script: %s', sdk_script)
try:
- d = script_env(sdk_script)
+ d = script_env(env, sdk_script)
found = sdk_script
except BatchFileExecutionError as e:
debug('use_script 5: failed running SDK script %s: Error:%s', repr(sdk_script), e)
@@ -1486,17 +1202,13 @@ def msvc_find_valid_batch_script(env, version):
" No versions of the MSVC compiler were found.\n" \
" Visual Studio C/C++ compilers may not be set correctly".format(version)
- _msvc_notfound_policy_handler(env, msg)
+ MSVC.Policy.msvc_notfound_handler(env, msg)
return d
-_undefined = None
+_UNDEFINED = object()
def get_use_script_use_settings(env):
- global _undefined
-
- if _undefined is None:
- _undefined = object()
# use_script use_settings return values action
# value ignored (value, None) use script or bypass detection
@@ -1505,28 +1217,28 @@ def get_use_script_use_settings(env):
# None (documentation) or evaluates False (code): bypass detection
# need to distinguish between undefined and None
- use_script = env.get('MSVC_USE_SCRIPT', _undefined)
+ use_script = env.get('MSVC_USE_SCRIPT', _UNDEFINED)
- if use_script != _undefined:
+ if use_script != _UNDEFINED:
# use_script defined, use_settings ignored (not type checked)
- return (use_script, None)
+ return use_script, None
# undefined or None: use_settings ignored
use_settings = env.get('MSVC_USE_SETTINGS', None)
if use_settings is not None:
# use script undefined, use_settings defined and not None (type checked)
- return (False, use_settings)
+ return False, use_settings
# use script undefined, use_settings undefined or None
- return (True, None)
+ return True, None
def msvc_setup_env(env):
debug('called')
version = get_default_version(env)
if version is None:
if not msvc_setup_env_user(env):
- _MSVCSetupEnvDefault.set_nodefault()
+ MSVC.SetupEnvDefault.set_nodefault()
return None
# XXX: we set-up both MSVS version for backward
@@ -1542,7 +1254,7 @@ def msvc_setup_env(env):
raise MSVCScriptNotFound('Script specified by MSVC_USE_SCRIPT not found: "{}"'.format(use_script))
args = env.subst('$MSVC_USE_SCRIPT_ARGS')
debug('use_script 1 %s %s', repr(use_script), repr(args))
- d = script_env(use_script, args)
+ d = script_env(env, use_script, args)
elif use_script:
d = msvc_find_valid_batch_script(env,version)
debug('use_script 2 %s', d)
@@ -1576,13 +1288,13 @@ def msvc_setup_env(env):
SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, warn_msg)
def msvc_exists(env=None, version=None):
- debug('version=%s', repr(version))
vcs = get_installed_vcs(env)
if version is None:
rval = len(vcs) > 0
else:
rval = version in vcs
- debug('version=%s, return=%s', repr(version), rval)
+ if not rval:
+ debug('version=%s, return=%s', repr(version), rval)
return rval
def msvc_setup_env_user(env=None):
@@ -1590,9 +1302,10 @@ def msvc_setup_env_user(env=None):
if env:
# Intent is to use msvc tools:
- # MSVC_VERSION or MSVS_VERSION: defined and is True
- # MSVC_USE_SCRIPT: defined and (is string or is False)
- # MSVC_USE_SETTINGS: defined and is not None
+ # MSVC_VERSION: defined and evaluates True
+ # MSVS_VERSION: defined and evaluates True
+ # MSVC_USE_SCRIPT: defined and (is string or evaluates False)
+ # MSVC_USE_SETTINGS: defined and is not None
# defined and is True
for key in ['MSVC_VERSION', 'MSVS_VERSION']:
@@ -1619,13 +1332,238 @@ def msvc_setup_env_user(env=None):
return rval
def msvc_setup_env_tool(env=None, version=None, tool=None):
- debug('tool=%s, version=%s', repr(tool), repr(version))
- _MSVCSetupEnvDefault.register_tool(env, tool)
+ MSVC.SetupEnvDefault.register_tool(env, tool, msvc_exists)
rval = False
if not rval and msvc_exists(env, version):
rval = True
if not rval and msvc_setup_env_user(env):
rval = True
- debug('tool=%s, version=%s, return=%s', repr(tool), repr(version), rval)
return rval
+def msvc_sdk_versions(version=None, msvc_uwp_app=False):
+ debug('version=%s, msvc_uwp_app=%s', repr(version), repr(msvc_uwp_app))
+
+ rval = []
+
+ if not version:
+ version = msvc_default_version()
+
+ if not version:
+ debug('no msvc versions detected')
+ return rval
+
+ version_def = MSVC.Util.msvc_extended_version_components(version)
+ if not version_def:
+ msg = 'Unsupported version {}'.format(repr(version))
+ raise MSVCArgumentError(msg)
+
+ rval = MSVC.WinSDK.get_msvc_sdk_version_list(version, msvc_uwp_app)
+ return rval
+
+def msvc_toolset_versions(msvc_version=None, full=True, sxs=False):
+ debug('msvc_version=%s, full=%s, sxs=%s', repr(msvc_version), repr(full), repr(sxs))
+
+ env = None
+ rval = []
+
+ if not msvc_version:
+ msvc_version = msvc_default_version()
+
+ if not msvc_version:
+ debug('no msvc versions detected')
+ return rval
+
+ if msvc_version not in _VCVER:
+ msg = 'Unsupported msvc version {}'.format(repr(msvc_version))
+ raise MSVCArgumentError(msg)
+
+ vc_dir = find_vc_pdir(env, msvc_version)
+ if not vc_dir:
+ debug('VC folder not found for version %s', repr(msvc_version))
+ return rval
+
+ rval = MSVC.ScriptArguments._msvc_toolset_versions_internal(msvc_version, vc_dir, full=full, sxs=sxs)
+ return rval
+
+def msvc_toolset_versions_spectre(msvc_version=None):
+ debug('msvc_version=%s', repr(msvc_version))
+
+ env = None
+ rval = []
+
+ if not msvc_version:
+ msvc_version = msvc_default_version()
+
+ if not msvc_version:
+ debug('no msvc versions detected')
+ return rval
+
+ if msvc_version not in _VCVER:
+ msg = 'Unsupported msvc version {}'.format(repr(msvc_version))
+ raise MSVCArgumentError(msg)
+
+ vc_dir = find_vc_pdir(env, msvc_version)
+ if not vc_dir:
+ debug('VC folder not found for version %s', repr(msvc_version))
+ return rval
+
+ rval = MSVC.ScriptArguments._msvc_toolset_versions_spectre_internal(msvc_version, vc_dir)
+ return rval
+
+def msvc_query_version_toolset(version=None, prefer_newest=True):
+ """
+ Returns an msvc version and a toolset version given a version
+ specification.
+
+ This is an EXPERIMENTAL proxy for using a toolset version to perform
+ msvc instance selection. This function will be removed when
+ toolset version is taken into account during msvc instance selection.
+
+ Search for an installed Visual Studio instance that supports the
+ specified version.
+
+ When the specified version contains a component suffix (e.g., Exp),
+ the msvc version is returned and the toolset version is None. No
+ search if performed.
+
+ When the specified version does not contain a component suffix, the
+ version is treated as a toolset version specification. A search is
+ performed for the first msvc instance that contains the toolset
+ version.
+
+ Only Visual Studio 2017 and later support toolset arguments. For
+ Visual Studio 2015 and earlier, the msvc version is returned and
+ the toolset version is None.
+
+ Args:
+
+ version: str
+ The version specification may be an msvc version or a toolset
+ version.
+
+ prefer_newest: bool
+ True: prefer newer Visual Studio instances.
+ False: prefer the "native" Visual Studio instance first. If
+ the native Visual Studio instance is not detected, prefer
+ newer Visual Studio instances.
+
+ Returns:
+ tuple: A tuple containing the msvc version and the msvc toolset version.
+ The msvc toolset version may be None.
+
+ Raises:
+ MSVCToolsetVersionNotFound: when the specified version is not found.
+ MSVCArgumentError: when argument validation fails.
+ """
+ debug('version=%s, prefer_newest=%s', repr(version), repr(prefer_newest))
+
+ env = None
+ msvc_version = None
+ msvc_toolset_version = None
+
+ if not version:
+ version = msvc_default_version()
+
+ if not version:
+ debug('no msvc versions detected')
+ return msvc_version, msvc_toolset_version
+
+ version_def = MSVC.Util.msvc_extended_version_components(version)
+
+ if not version_def:
+ msg = 'Unsupported msvc version {}'.format(repr(version))
+ raise MSVCArgumentError(msg)
+
+ if version_def.msvc_suffix:
+ if version_def.msvc_verstr != version_def.msvc_toolset_version:
+ # toolset version with component suffix
+ msg = 'Unsupported toolset version {}'.format(repr(version))
+ raise MSVCArgumentError(msg)
+
+ if version_def.msvc_vernum > 14.0:
+ # VS2017 and later
+ force_toolset_msvc_version = False
+ else:
+ # VS2015 and earlier
+ force_toolset_msvc_version = True
+ extended_version = version_def.msvc_verstr + '0.00000'
+ if not extended_version.startswith(version_def.msvc_toolset_version):
+ # toolset not equivalent to msvc version
+ msg = 'Unsupported toolset version {} (expected {})'.format(
+ repr(version), repr(extended_version)
+ )
+ raise MSVCArgumentError(msg)
+
+ msvc_version = version_def.msvc_version
+
+ if msvc_version not in MSVC.Config.MSVC_VERSION_TOOLSET_SEARCH_MAP:
+ # VS2013 and earlier
+ debug(
+ 'ignore: msvc_version=%s, msvc_toolset_version=%s',
+ repr(msvc_version), repr(msvc_toolset_version)
+ )
+ return msvc_version, msvc_toolset_version
+
+ if force_toolset_msvc_version:
+ query_msvc_toolset_version = version_def.msvc_verstr
+ else:
+ query_msvc_toolset_version = version_def.msvc_toolset_version
+
+ if prefer_newest:
+ query_version_list = MSVC.Config.MSVC_VERSION_TOOLSET_SEARCH_MAP[msvc_version]
+ else:
+ query_version_list = MSVC.Config.MSVC_VERSION_TOOLSET_DEFAULTS_MAP[msvc_version] + \
+ MSVC.Config.MSVC_VERSION_TOOLSET_SEARCH_MAP[msvc_version]
+
+ seen_msvc_version = set()
+ for query_msvc_version in query_version_list:
+
+ if query_msvc_version in seen_msvc_version:
+ continue
+ seen_msvc_version.add(query_msvc_version)
+
+ vc_dir = find_vc_pdir(env, query_msvc_version)
+ if not vc_dir:
+ continue
+
+ if query_msvc_version.startswith('14.0'):
+ # VS2015 does not support toolset version argument
+ msvc_toolset_version = None
+ debug(
+ 'found: msvc_version=%s, msvc_toolset_version=%s',
+ repr(query_msvc_version), repr(msvc_toolset_version)
+ )
+ return query_msvc_version, msvc_toolset_version
+
+ try:
+ toolset_vcvars = MSVC.ScriptArguments._msvc_toolset_internal(query_msvc_version, query_msvc_toolset_version, vc_dir)
+ if toolset_vcvars:
+ msvc_toolset_version = toolset_vcvars
+ debug(
+ 'found: msvc_version=%s, msvc_toolset_version=%s',
+ repr(query_msvc_version), repr(msvc_toolset_version)
+ )
+ return query_msvc_version, msvc_toolset_version
+
+ except MSVCToolsetVersionNotFound:
+ pass
+
+ msvc_toolset_version = query_msvc_toolset_version
+
+ debug(
+ 'not found: msvc_version=%s, msvc_toolset_version=%s',
+ repr(msvc_version), repr(msvc_toolset_version)
+ )
+
+ if version_def.msvc_verstr == msvc_toolset_version:
+ msg = 'MSVC version {} was not found'.format(repr(version))
+ MSVC.Policy.msvc_notfound_handler(None, msg)
+ return msvc_version, msvc_toolset_version
+
+ msg = 'MSVC toolset version {} not found'.format(repr(version))
+ raise MSVCToolsetVersionNotFound(msg)
+
+
+# internal consistency check (should be last)
+MSVC._verify()
+
diff --git a/SCons/Tool/MSCommon/vcTests.py b/SCons/Tool/MSCommon/vcTests.py
index 2b6fbe5a0..3e37def2e 100644
--- a/SCons/Tool/MSCommon/vcTests.py
+++ b/SCons/Tool/MSCommon/vcTests.py
@@ -1,5 +1,6 @@
+# MIT License
#
-# __COPYRIGHT__
+# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -19,10 +20,7 @@
# 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 typing import Dict, Any
-__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import os
import os.path
@@ -31,6 +29,7 @@ import unittest
import SCons.Node.FS
import SCons.Warnings
import SCons.Tool.MSCommon.vc
+from SCons.Tool import MSCommon
import TestCmd
@@ -43,14 +42,15 @@ os.chdir(test.workpath(''))
MSVCUnsupportedHostArch = SCons.Tool.MSCommon.vc.MSVCUnsupportedHostArch
MSVCUnsupportedTargetArch = SCons.Tool.MSCommon.vc.MSVCUnsupportedTargetArch
-MS_TOOLS_VERSION='1.1.1'
+MS_TOOLS_VERSION = '1.1.1'
+
class VswhereTestCase(unittest.TestCase):
@staticmethod
def _createVSWhere(path):
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, 'w') as f:
- f.write("Created:%s"%f)
+ f.write("Created:%s" % f)
def testDefaults(self):
"""
@@ -59,28 +59,25 @@ class VswhereTestCase(unittest.TestCase):
# import pdb; pdb.set_trace()
vswhere_dirs = [os.path.splitdrive(p)[1] for p in SCons.Tool.MSCommon.vc.VSWHERE_PATHS]
base_dir = test.workpath('fake_vswhere')
- test_vswhere_dirs = [os.path.join(base_dir,d[1:]) for d in vswhere_dirs]
+ test_vswhere_dirs = [os.path.join(base_dir,d[1:]) for d in vswhere_dirs]
SCons.Tool.MSCommon.vc.VSWHERE_PATHS = test_vswhere_dirs
for vsw in test_vswhere_dirs:
VswhereTestCase._createVSWhere(vsw)
find_path = SCons.Tool.MSCommon.vc.msvc_find_vswhere()
- self.assertTrue(vsw == find_path, "Didn't find vswhere in %s found in %s"%(vsw, find_path))
+ self.assertTrue(vsw == find_path, "Didn't find vswhere in %s found in %s" % (vsw, find_path))
os.remove(vsw)
# def specifiedVswherePathTest(self):
# "Verify that msvc.generate() respects VSWHERE Specified"
-
-
-
class MSVcTestCase(unittest.TestCase):
@staticmethod
def _createDummyCl(path, add_bin=True):
"""
- Creates a dummy cl.exe in the correct directory.
+ Creates a dummy cl.exe in the correct directory.
It will create all missing parent directories as well
Args:
@@ -96,14 +93,14 @@ class MSVcTestCase(unittest.TestCase):
create_path = path
if create_path and not os.path.isdir(create_path):
os.makedirs(create_path)
-
+
create_this = os.path.join(create_path,'cl.exe')
# print("Creating: %s"%create_this)
with open(create_this,'w') as ct:
ct.write('created')
-
+
def runTest(self):
"""
Check that all proper HOST_PLATFORM and TARGET_PLATFORM are handled.
@@ -117,7 +114,7 @@ class MSVcTestCase(unittest.TestCase):
_, clpathcomps = SCons.Tool.MSCommon.vc._LE2015_HOST_TARGET_BATCHARG_CLPATHCOMPS[('x86','x86')]
path = os.path.join('.', *clpathcomps)
MSVcTestCase._createDummyCl(path, add_bin=False)
-
+
# print("retval:%s"%check(env, '.', '8.0'))
@@ -132,7 +129,7 @@ class MSVcTestCase(unittest.TestCase):
with open(tools_version_file, 'w') as tf:
tf.write(MS_TOOLS_VERSION)
except IOError as e:
- print("Failed trying to write :%s :%s"%(tools_version_file, e))
+ print("Failed trying to write :%s :%s" % (tools_version_file, e))
# Now walk all the valid combinations of host/target for 14.1 (VS2017) and later
@@ -158,7 +155,7 @@ class MSVcTestCase(unittest.TestCase):
except MSVCUnsupportedHostArch:
pass
else:
- self.fail('Did not fail when HOST_ARCH specified as: %s'%env['HOST_ARCH'])
+ self.fail('Did not fail when HOST_ARCH specified as: %s' % env['HOST_ARCH'])
# Now test bogus value for TARGET_ARCH
env={'TARGET_ARCH':'GARBAGE', 'HOST_ARCH':'x86'}
@@ -189,7 +186,7 @@ class MSVcTestCase(unittest.TestCase):
try:
result=check(env, '.', '9.0')
# print("for:%s got :%s"%(env, result))
- self.assertFalse(result, "Did not fail with bogus HOST_ARCH host: %s target: %s"%(env['HOST_ARCH'], env['TARGET_ARCH']))
+ self.assertFalse(result, "Did not fail with bogus HOST_ARCH host: %s target: %s" % (env['HOST_ARCH'], env['TARGET_ARCH']))
except MSVCUnsupportedHostArch:
pass
else:
@@ -200,7 +197,7 @@ class MSVcTestCase(unittest.TestCase):
try:
result=check(env, '.', '9.0')
# print("for:%s got :%s"%(env, result))
- self.assertFalse(result, "Did not fail with bogus TARGET_ARCH host: %s target: %s"%(env['HOST_ARCH'], env['TARGET_ARCH']))
+ self.assertFalse(result, "Did not fail with bogus TARGET_ARCH host: %s target: %s" % (env['HOST_ARCH'], env['TARGET_ARCH']))
except MSVCUnsupportedTargetArch:
pass
else:
@@ -241,6 +238,296 @@ class MSVcTestCase(unittest.TestCase):
self.fail('Did not fail when TARGET_ARCH specified as: %s' % env['TARGET_ARCH'])
+class Data:
+
+ HAVE_MSVC = True if MSCommon.vc.msvc_default_version() else False
+
+ INSTALLED_VCS_COMPONENTS = MSCommon.vc.get_installed_vcs_components()
+
+ @classmethod
+ def _msvc_toolset_notfound_list(cls, toolset_seen, toolset_list):
+ new_toolset_list = []
+ if not toolset_list:
+ return new_toolset_list
+ for toolset_version in toolset_list:
+ version = toolset_version
+ comps = version.split('.')
+ if len(comps) != 3:
+ continue
+ # full versions only
+ nloop = 0
+ while nloop < 10:
+ ival = int(comps[-1])
+ if ival == 0:
+ ival = 1000000
+ ival -= 1
+ version = '{}.{}.{:05d}'.format(comps[0], comps[1], ival)
+ if version not in toolset_seen:
+ new_toolset_list.append(version)
+ break
+ nloop += 1
+ return new_toolset_list
+
+ _msvc_toolset_notfound_dict = None
+
+ @classmethod
+ def msvc_toolset_notfound_dict(cls):
+ if cls._msvc_toolset_notfound_dict is None:
+ toolset_seen = set()
+ toolset_dict = {}
+ for symbol in MSCommon.vc._VCVER:
+ toolset_list = MSCommon.vc.msvc_toolset_versions(msvc_version=symbol, full=True, sxs=False)
+ if not toolset_list:
+ continue
+ toolset_seen.update(toolset_list)
+ toolset_dict[symbol] = toolset_list
+ for key, val in toolset_dict.items():
+ toolset_dict[key] = cls._msvc_toolset_notfound_list(toolset_seen, val)
+ cls._msvc_toolset_notfound_dict = toolset_dict
+ return cls._msvc_toolset_notfound_dict
+
+class Patch:
+
+ class MSCommon:
+
+ class vc:
+
+ class msvc_default_version:
+
+ msvc_default_version = MSCommon.vc.msvc_default_version
+
+ @classmethod
+ def msvc_default_version_none(cls):
+ return None
+
+ @classmethod
+ def enable_none(cls):
+ hook = cls.msvc_default_version_none
+ MSCommon.vc.msvc_default_version = hook
+ return hook
+
+ @classmethod
+ def restore(cls):
+ MSCommon.vc.msvc_default_version = cls.msvc_default_version
+
+class MsvcSdkVersionsTests(unittest.TestCase):
+ """Test msvc_sdk_versions"""
+
+ def run_valid_default_msvc(self):
+ symbol = MSCommon.vc.msvc_default_version()
+ version_def = MSCommon.msvc_version_components(symbol)
+ for msvc_uwp_app in (True, False):
+ sdk_list = MSCommon.vc.msvc_sdk_versions(version=None, msvc_uwp_app=msvc_uwp_app)
+ if symbol and version_def.msvc_vernum >= 14.0:
+ self.assertTrue(sdk_list, "SDK list is empty for msvc version {}".format(repr(None)))
+ else:
+ self.assertFalse(sdk_list, "SDK list is not empty for msvc version {}".format(repr(None)))
+
+ def test_valid_default_msvc(self):
+ if Data.HAVE_MSVC:
+ Patch.MSCommon.vc.msvc_default_version.enable_none()
+ self.run_valid_default_msvc()
+ Patch.MSCommon.vc.msvc_default_version.restore()
+ self.run_valid_default_msvc()
+
+ def test_valid_vcver(self):
+ for symbol in MSCommon.vc._VCVER:
+ version_def = MSCommon.msvc_version_components(symbol)
+ for msvc_uwp_app in (True, False):
+ sdk_list = MSCommon.vc.msvc_sdk_versions(version=symbol, msvc_uwp_app=msvc_uwp_app)
+ if Data.HAVE_MSVC and version_def.msvc_vernum >= 14.0:
+ self.assertTrue(sdk_list, "SDK list is empty for msvc version {}".format(repr(symbol)))
+ else:
+ self.assertFalse(sdk_list, "SDK list is not empty for msvc version {}".format(repr(symbol)))
+
+ def test_valid_vcver_toolsets(self):
+ for symbol in MSCommon.vc._VCVER:
+ toolset_list = MSCommon.vc.msvc_toolset_versions(msvc_version=symbol, full=True, sxs=True)
+ if toolset_list is None:
+ continue
+ for toolset in toolset_list:
+ extended_def = MSCommon.msvc_extended_version_components(toolset)
+ for msvc_uwp_app in (True, False):
+ sdk_list = MSCommon.vc.msvc_sdk_versions(
+ version=extended_def.msvc_toolset_version,
+ msvc_uwp_app=msvc_uwp_app
+ )
+ self.assertTrue(sdk_list, "SDK list is empty for msvc toolset version {}".format(repr(toolset)))
+
+ def test_invalid_vcver(self):
+ for symbol in ['6.0Exp', '14.3Exp', '99', '14.1Bug']:
+ for msvc_uwp_app in (True, False):
+ with self.assertRaises(MSCommon.vc.MSVCArgumentError):
+ _ = MSCommon.vc.msvc_sdk_versions(version=symbol, msvc_uwp_app=msvc_uwp_app)
+
+ def test_invalid_vcver_toolsets(self):
+ for symbol in ['14.31.123456', '14.31.1.1']:
+ for msvc_uwp_app in (True, False):
+ with self.assertRaises(MSCommon.vc.MSVCArgumentError):
+ _ = MSCommon.vc.msvc_sdk_versions(version=symbol, msvc_uwp_app=msvc_uwp_app)
+
+class MsvcToolsetVersionsTests(unittest.TestCase):
+ """Test msvc_toolset_versions"""
+
+ def run_valid_default_msvc(self):
+ symbol = MSCommon.vc.msvc_default_version()
+ version_def = MSCommon.msvc_version_components(symbol)
+ toolset_none_list = MSCommon.vc.msvc_toolset_versions(msvc_version=None, full=False, sxs=False)
+ toolset_full_list = MSCommon.vc.msvc_toolset_versions(msvc_version=None, full=True, sxs=False)
+ toolset_sxs_list = MSCommon.vc.msvc_toolset_versions(msvc_version=None, full=False, sxs=True)
+ toolset_all_list = MSCommon.vc.msvc_toolset_versions(msvc_version=None, full=True, sxs=True)
+ if symbol and version_def in Data.INSTALLED_VCS_COMPONENTS and version_def.msvc_vernum >= 14.1:
+ # sxs list could be empty
+ self.assertTrue(toolset_full_list, "Toolset full list is empty for msvc version {}".format(repr(None)))
+ self.assertTrue(toolset_all_list, "Toolset all list is empty for msvc version {}".format(repr(None)))
+ else:
+ self.assertFalse(toolset_full_list, "Toolset full list is not empty for msvc version {}".format(repr(None)))
+ self.assertFalse(toolset_sxs_list, "Toolset sxs list is not empty for msvc version {}".format(repr(None)))
+ self.assertFalse(toolset_all_list, "Toolset all list is not empty for msvc version {}".format(repr(None)))
+ self.assertFalse(toolset_none_list, "Toolset none list is not empty for msvc version {}".format(repr(None)))
+
+ def test_valid_default_msvc(self):
+ if Data.HAVE_MSVC:
+ Patch.MSCommon.vc.msvc_default_version.enable_none()
+ self.run_valid_default_msvc()
+ Patch.MSCommon.vc.msvc_default_version.restore()
+ self.run_valid_default_msvc()
+
+ def test_valid_vcver(self):
+ for symbol in MSCommon.vc._VCVER:
+ version_def = MSCommon.msvc_version_components(symbol)
+ toolset_none_list = MSCommon.vc.msvc_toolset_versions(msvc_version=symbol, full=False, sxs=False)
+ toolset_full_list = MSCommon.vc.msvc_toolset_versions(msvc_version=symbol, full=True, sxs=False)
+ toolset_sxs_list = MSCommon.vc.msvc_toolset_versions(msvc_version=symbol, full=False, sxs=True)
+ toolset_all_list = MSCommon.vc.msvc_toolset_versions(msvc_version=symbol, full=True, sxs=True)
+ if version_def in Data.INSTALLED_VCS_COMPONENTS and version_def.msvc_vernum >= 14.1:
+ # sxs list could be empty
+ self.assertTrue(toolset_full_list, "Toolset full list is empty for msvc version {}".format(repr(symbol)))
+ self.assertTrue(toolset_all_list, "Toolset all list is empty for msvc version {}".format(repr(symbol)))
+ else:
+ self.assertFalse(toolset_full_list, "Toolset full list is not empty for msvc version {}".format(repr(symbol)))
+ self.assertFalse(toolset_sxs_list, "Toolset sxs list is not empty for msvc version {}".format(repr(symbol)))
+ self.assertFalse(toolset_all_list, "Toolset all list is not empty for msvc version {}".format(repr(symbol)))
+ self.assertFalse(toolset_none_list, "Toolset none list is not empty for msvc version {}".format(repr(symbol)))
+
+ def test_invalid_vcver(self):
+ for symbol in ['12.9', '6.0Exp', '14.3Exp', '99', '14.1Bug']:
+ with self.assertRaises(MSCommon.vc.MSVCArgumentError):
+ _ = MSCommon.vc.msvc_toolset_versions(msvc_version=symbol)
+
+class MsvcToolsetVersionsSpectreTests(unittest.TestCase):
+
+ def run_valid_default_msvc(self):
+ symbol = MSCommon.vc.msvc_default_version()
+ version_def = MSCommon.msvc_version_components(symbol)
+ spectre_toolset_list = MSCommon.vc.msvc_toolset_versions_spectre(msvc_version=None)
+ if symbol and version_def in Data.INSTALLED_VCS_COMPONENTS and version_def.msvc_vernum >= 14.1:
+ # spectre toolset list can empty (may not be installed)
+ pass
+ else:
+ self.assertFalse(spectre_toolset_list, "Toolset spectre list is not empty for msvc version {}".format(repr(None)))
+
+ def test_valid_default_msvc(self):
+ if Data.HAVE_MSVC:
+ Patch.MSCommon.vc.msvc_default_version.enable_none()
+ self.run_valid_default_msvc()
+ Patch.MSCommon.vc.msvc_default_version.restore()
+ self.run_valid_default_msvc()
+
+ def test_valid_vcver(self):
+ for symbol in MSCommon.vc._VCVER:
+ version_def = MSCommon.msvc_version_components(symbol)
+ spectre_toolset_list = MSCommon.vc.msvc_toolset_versions_spectre(msvc_version=symbol)
+ if version_def in Data.INSTALLED_VCS_COMPONENTS and version_def.msvc_vernum >= 14.1:
+ # spectre toolset list can empty (may not be installed)
+ pass
+ else:
+ self.assertFalse(spectre_toolset_list, "Toolset spectre list is not empty for msvc version {}".format(repr(symbol)))
+
+ def test_invalid_vcver(self):
+ for symbol in ['12.9', '6.0Exp', '14.3Exp', '99', '14.1Bug']:
+ with self.assertRaises(MSCommon.vc.MSVCArgumentError):
+ _ = MSCommon.vc.msvc_toolset_versions_spectre(msvc_version=symbol)
+
+class MsvcQueryVersionToolsetTests(unittest.TestCase):
+ """Test msvc_query_toolset_version"""
+
+ def run_valid_default_msvc(self, have_msvc):
+ for prefer_newest in (True, False):
+ msvc_version, msvc_toolset_version = MSCommon.vc.msvc_query_version_toolset(
+ version=None, prefer_newest=prefer_newest
+ )
+ expect = (have_msvc and msvc_version) or (not have_msvc and not msvc_version)
+ self.assertTrue(expect, "unexpected msvc_version {} for for msvc version {}".format(
+ repr(msvc_version), repr(None)
+ ))
+ version_def = MSCommon.msvc_version_components(msvc_version)
+ if have_msvc and version_def.msvc_vernum > 14.0:
+ # VS2017 and later for toolset version
+ self.assertTrue(msvc_toolset_version, "msvc_toolset_version is undefined for msvc version {}".format(
+ repr(None)
+ ))
+
+ def test_valid_default_msvc(self):
+ if Data.HAVE_MSVC:
+ Patch.MSCommon.vc.msvc_default_version.enable_none()
+ self.run_valid_default_msvc(have_msvc=False)
+ Patch.MSCommon.vc.msvc_default_version.restore()
+ self.run_valid_default_msvc(have_msvc=Data.HAVE_MSVC)
+
+ def test_valid_vcver(self):
+ for symbol in MSCommon.vc._VCVER:
+ version_def = MSCommon.msvc_version_components(symbol)
+ for prefer_newest in (True, False):
+ msvc_version, msvc_toolset_version = MSCommon.vc.msvc_query_version_toolset(
+ version=symbol, prefer_newest=prefer_newest
+ )
+ self.assertTrue(msvc_version, "msvc_version is undefined for msvc version {}".format(repr(symbol)))
+ if version_def.msvc_vernum > 14.0:
+ # VS2017 and later for toolset version
+ self.assertTrue(msvc_toolset_version, "msvc_toolset_version is undefined for msvc version {}".format(
+ repr(symbol)
+ ))
+
+ def test_valid_vcver_toolsets(self):
+ for symbol in MSCommon.vc._VCVER:
+ toolset_list = MSCommon.vc.msvc_toolset_versions(msvc_version=symbol, full=True, sxs=True)
+ if toolset_list is None:
+ continue
+ for toolset in toolset_list:
+ extended_def = MSCommon.msvc_extended_version_components(toolset)
+ for prefer_newest in (True, False):
+ version = extended_def.msvc_toolset_version
+ msvc_version, msvc_toolset_version = MSCommon.vc.msvc_query_version_toolset(
+ version=version, prefer_newest=prefer_newest
+ )
+ self.assertTrue(msvc_version, "msvc_version is undefined for msvc toolset version {}".format(repr(toolset)))
+ if extended_def.msvc_vernum > 14.0:
+ # VS2017 and later for toolset version
+ self.assertTrue(msvc_toolset_version, "msvc_toolset_version is undefined for msvc toolset version {}".format(
+ repr(toolset)
+ ))
+
+ def test_msvc_query_version_toolset_notfound(self):
+ toolset_notfound_dict = Data.msvc_toolset_notfound_dict()
+ for toolset_notfound_list in toolset_notfound_dict.values():
+ for toolset in toolset_notfound_list[:1]:
+ for prefer_newest in (True, False):
+ with self.assertRaises(MSCommon.vc.MSVCToolsetVersionNotFound):
+ _ = MSCommon.vc.msvc_query_version_toolset(version=toolset, prefer_newest=prefer_newest)
+
+ def test_invalid_vcver(self):
+ for symbol in ['12.9', '6.0Exp', '14.3Exp', '99', '14.1Bug']:
+ for prefer_newest in (True, False):
+ with self.assertRaises(MSCommon.vc.MSVCArgumentError):
+ _ = MSCommon.vc.msvc_query_version_toolset(version=symbol, prefer_newest=prefer_newest)
+
+ def test_invalid_vcver_toolsets(self):
+ for symbol in ['14.16.00000Exp', '14.00.00001', '14.31.123456', '14.31.1.1']:
+ for prefer_newest in (True, False):
+ with self.assertRaises(MSCommon.vc.MSVCArgumentError):
+ _ = MSCommon.vc.msvc_query_version_toolset(version=symbol, prefer_newest=prefer_newest)
+
if __name__ == "__main__":
unittest.main()
diff --git a/SCons/Tool/docbook/__init__.py b/SCons/Tool/docbook/__init__.py
index 4c3f60cee..5cf5e6198 100644
--- a/SCons/Tool/docbook/__init__.py
+++ b/SCons/Tool/docbook/__init__.py
@@ -66,6 +66,18 @@ re_manvolnum = re.compile(r"<manvolnum>([^<]*)</manvolnum>")
re_refname = re.compile(r"<refname>([^<]*)</refname>")
#
+# lxml etree XSLT global max traversal depth
+#
+
+lmxl_xslt_global_max_depth = 3100
+
+if has_lxml and lmxl_xslt_global_max_depth:
+ def __lxml_xslt_set_global_max_depth(max_depth):
+ from lxml import etree
+ etree.XSLT.set_global_max_depth(max_depth)
+ __lxml_xslt_set_global_max_depth(lmxl_xslt_global_max_depth)
+
+#
# Helper functions
#
def __extend_targets_sources(target, source):
diff --git a/SCons/Tool/msvc.xml b/SCons/Tool/msvc.xml
index e8df1288d..c26c20da8 100644
--- a/SCons/Tool/msvc.xml
+++ b/SCons/Tool/msvc.xml
@@ -1,6 +1,28 @@
<?xml version="1.0"?>
<!--
-__COPYRIGHT__
+ MIT License
+
+ Copyright The SCons Foundation
+
+ Permission is hereby granted, free of charge, to any person obtaining
+ a copy of this software and associated documentation files (the
+ "Software"), to deal in the Software without restriction, including
+ without limitation the rights to use, copy, modify, merge, publish,
+ distribute, sublicense, and/or sell copies of the Software, and to
+ permit persons to whom the Software is furnished to do so, subject to
+ the following conditions:
+
+ The above copyright notice and this permission notice shall be included
+ in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+ KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ 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.
+
This file is processed by the bin/SConsDoc.py module.
See its __doc__ string for a discussion of the format.
@@ -349,8 +371,15 @@ Sets the preferred version of Microsoft Visual C/C++ to use.
If &cv-MSVC_VERSION; is not set, SCons will (by default) select the
latest version of Visual C/C++ installed on your system. If the
specified version isn't installed, tool initialization will fail.
-This variable must be passed as an argument to the &f-link-Environment;
-constructor; setting it later has no effect.
+</para>
+
+<para>
+&cv-MSVC_VERSION; must be passed as an argument to the &f-link-Environment;
+constructor when an msvc tool (e.g., &t-link-msvc;, &t-link-msvs;, etc.) is
+loaded via the default tools list or via a tools list passed to the
+&f-link-Environment; constructor.
+Otherwise, &cv-MSVC_VERSION; must be set before the first msvc tool is
+loaded into the environment.
</para>
<para>
@@ -410,7 +439,7 @@ is, if you are sure everything is set correctly already and
you don't want &SCons; to change anything.
</para>
<para>
-&cv-MSVC_USE_SCRIPT; overrides &cv-link-MSVC_VERSION; and &cv-link-TARGET_ARCH;.
+&cv-MSVC_USE_SCRIPT; ignores &cv-link-MSVC_VERSION; and &cv-link-TARGET_ARCH;.
</para>
</summary>
</cvar>
@@ -481,9 +510,28 @@ env = Environment(MSVC_VERSION='8.0', MSVC_USE_SETTINGS=msvc_use_settings)
</para>
<para>
-Note: the dictionary content requirements are based on the internal msvc implementation and
-therefore may change at any time. The burden is on the user to ensure the dictionary contents
-are minimally sufficient to ensure successful builds.
+Important usage details:
+<itemizedlist>
+
+<listitem><para>
+&cv-MSVC_USE_SETTINGS; must be passed as an argument to the &f-link-Environment;
+constructor when an msvc tool (e.g., &t-link-msvc;, &t-link-msvs;, etc.) is
+loaded via the default tools list or via a tools list passed to the
+&f-link-Environment; constructor.
+Otherwise, &cv-MSVC_USE_SETTINGS; must be set before the first msvc tool is
+loaded into the environment.
+</para></listitem>
+
+<listitem><para>
+<emphasis>
+The dictionary content requirements are based on the internal msvc implementation and
+therefore may change at any time.
+</emphasis>
+The burden is on the user to ensure the dictionary contents are minimally sufficient to
+ensure successful builds.
+</para></listitem>
+
+</itemizedlist>
</para>
</summary>
@@ -510,21 +558,69 @@ are minimally sufficient to ensure successful builds.
<cvar name="MSVC_UWP_APP">
<summary>
<para>
-Build libraries for a Universal Windows Platform (UWP) Application.
+Build with the Universal Windows Platform (UWP) application Visual C++ libraries.
</para>
<para>
-If &cv-MSVC_UWP_APP; is set, the Visual C++ environment will be set up to point
+The valid values for &cv-MSVC_UWP_APP; are: <literal>True</literal>,
+<literal>'1'</literal>, <literal>False</literal>, <literal>'0'</literal>,
+or <literal>None</literal>.
+</para>
+
+<para>
+When &cv-MSVC_UWP_APP; is enabled (i.e., <literal>True</literal> or
+<literal>'1'</literal>), the Visual C++ environment will be set up to point
to the Windows Store compatible libraries and Visual C++ runtimes. In doing so,
any libraries that are built will be able to be used in a UWP App and published
to the Windows Store.
-This flag will only have an effect with Visual Studio 2015 or later.
-This variable must be passed as an argument to the Environment()
-constructor; setting it later has no effect.
+<!-- This flag will only have an effect with Visual Studio 2015 or later. -->
+<!-- This variable must be passed as an argument to the Environment()
+constructor; setting it later has no effect. -->
</para>
<para>
-Valid values are '1' or '0'
+An exception is raised when any of the following conditions are satisfied:
+<itemizedlist>
+<listitem><para>
+&cv-MSVC_UWP_APP; is enabled for Visual Studio 2013 and earlier.
+</para></listitem>
+<listitem><para>
+&cv-MSVC_UWP_APP; is enabled and a UWP argument is specified in
+&cv-link-MSVC_SCRIPT_ARGS;. Multiple UWP declarations via &cv-MSVC_UWP_APP;
+and &cv-link-MSVC_SCRIPT_ARGS; are not allowed.
+</para></listitem>
+</itemizedlist>
+</para>
+
+<para>
+Example - A Visual Studio 2022 build for the Universal Windows Platform:
+<example_commands>
+env = Environment(MSVC_VERSION='14.3', MSVC_UWP_APP=True)
+</example_commands>
+</para>
+
+<para>
+Important usage details:
+<itemizedlist>
+
+<listitem><para>
+&cv-MSVC_UWP_APP; must be passed as an argument to the &f-link-Environment;
+constructor when an msvc tool (e.g., &t-link-msvc;, &t-link-msvs;, etc.) is
+loaded via the default tools list or via a tools list passed to the
+&f-link-Environment; constructor.
+Otherwise, &cv-MSVC_UWP_APP; must be set before the first msvc tool is
+loaded into the environment.
+</para></listitem>
+
+<listitem><para>
+<emphasis>
+The existence of the UWP libraries is not verified when &cv-MSVC_UWP_APP; is enabled
+which could result in build failures.
+</emphasis>
+The burden is on the user to ensure the requisite UWP libraries are installed.
+</para></listitem>
+
+</itemizedlist>
</para>
</summary>
@@ -582,16 +678,16 @@ and also before &f-link-env-Tool; is called to ininitialize any of those tools:
<cvar name="MSVC_NOTFOUND_POLICY">
<summary>
<para>
-Specify the scons behavior when the Microsoft Visual C/C++ compiler is not detected.
+Specify the &scons; behavior when the Microsoft Visual C/C++ compiler is not detected.
</para>
<para>
- The <envar>MSVC_NOTFOUND_POLICY</envar> specifies the &scons; behavior when no msvc versions are detected or
+ The &cv-MSVC_NOTFOUND_POLICY; specifies the &scons; behavior when no msvc versions are detected or
when the requested msvc version is not detected.
</para>
-<para>
-The valid values for <envar>MSVC_NOTFOUND_POLICY</envar> and the corresponding &scons; behavior are:
+<para>
+The valid values for &cv-MSVC_NOTFOUND_POLICY; and the corresponding &scons; behavior are:
</para>
<variablelist>
@@ -632,7 +728,7 @@ Note: in addition to the camel case values shown above, lower case and upper cas
</para>
<para>
-The <envar>MSVC_NOTFOUND_POLICY</envar> is applied when any of the following conditions are satisfied:
+The &cv-MSVC_NOTFOUND_POLICY; is applied when any of the following conditions are satisfied:
<itemizedlist>
<listitem><para>
&cv-MSVC_VERSION; is specified, the default tools list is implicitly defined (i.e., the tools list is not specified),
@@ -649,7 +745,7 @@ A non-default tools list is specified that contains one or more of the msvc tool
</para>
<para>
-The <envar>MSVC_NOTFOUND_POLICY</envar> is ignored when any of the following conditions are satisfied:
+The &cv-MSVC_NOTFOUND_POLICY; is ignored when any of the following conditions are satisfied:
<itemizedlist>
<listitem><para>
&cv-MSVC_VERSION; is not specified and the default tools list is implicitly defined (i.e., the tools list is not specified).
@@ -664,13 +760,656 @@ A non-default tool list is specified that does not contain any of the msvc tools
</para>
<para>
-When <envar>MSVC_NOTFOUND_POLICY</envar> is not specified, the default &scons; behavior is to issue a warning and continue
+Important usage details:
+<itemizedlist>
+
+<listitem><para>
+&cv-MSVC_NOTFOUND_POLICY; must be passed as an argument to the &f-link-Environment;
+constructor when an msvc tool (e.g., &t-link-msvc;, &t-link-msvs;, etc.) is
+loaded via the default tools list or via a tools list passed to the
+&f-link-Environment; constructor.
+Otherwise, &cv-MSVC_NOTFOUND_POLICY; must be set before the first msvc tool is
+loaded into the environment.
+</para></listitem>
+
+</itemizedlist>
+</para>
+
+<para>
+When &cv-MSVC_NOTFOUND_POLICY; is not specified, the default &scons; behavior is to issue a warning and continue
subject to the conditions listed above. The default &scons; behavior may change in the future.
</para>
+</summary>
+</cvar>
+
+<cvar name="MSVC_SCRIPTERROR_POLICY">
+<summary>
+<para>
+Specify the &scons; behavior when Microsoft Visual C/C++ batch file errors are detected.
+</para>
+
+<para>
+The &cv-MSVC_SCRIPTERROR_POLICY; specifies the &scons; behavior when msvc batch file errors are
+detected.
+When &cv-MSVC_SCRIPTERROR_POLICY; is not specified, the default &scons; behavior is to suppress
+msvc batch file error messages.
+</para>
+<para>
+The root cause of msvc build failures may be difficult to diagnose. In these situations, setting
+the &scons; behavior to issue a warning when msvc batch file errors are detected <emphasis>may</emphasis>
+produce additional diagnostic information.
+</para>
+
+<para>
+The valid values for &cv-MSVC_SCRIPTERROR_POLICY; and the corresponding &scons; behavior are:
+</para>
+
+<variablelist>
+
+<varlistentry>
+<term><parameter>'Error' or 'Exception'</parameter></term>
+<listitem>
+<para>
+Raise an exception when msvc batch file errors are detected.
+</para>
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term><parameter>'Warning' or 'Warn'</parameter></term>
+<listitem>
+<para>
+Issue a warning when msvc batch file errors are detected.
+</para>
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term><parameter>'Ignore' or 'Suppress'</parameter></term>
+<listitem>
+<para>
+Suppress msvc batch file error messages.
+</para>
+</listitem>
+</varlistentry>
+
+</variablelist>
+
+<para>
+Note: in addition to the camel case values shown above, lower case and upper case values are accepted as well.
+</para>
+
+<para>
+Example 1 - A Visual Studio 2022 build with user-defined script arguments:
+<example_commands>
+env = environment(MSVC_VERSION='14.3', MSVC_SCRIPT_ARGS=['8.1', 'store', '-vcvars_ver=14.1'])
+env.Program('hello', ['hello.c'], CCFLAGS='/MD', LIBS=['kernel32', 'user32', 'runtimeobject'])
+</example_commands>
+</para>
+
+<para>
+Example 1 - Output fragment:
+<example_commands>
+...
+link /nologo /OUT:_build001\hello.exe kernel32.lib user32.lib runtimeobject.lib _build001\hello.obj
+LINK : fatal error LNK1104: cannot open file 'MSVCRT.lib'
+...
+</example_commands>
+</para>
+
+<para>
+Example 2 - A Visual Studio 2022 build with user-defined script arguments and the script error policy set
+to issue a warning when msvc batch file errors are detected:
+<example_commands>
+env = environment(MSVC_VERSION='14.3', MSVC_SCRIPT_ARGS=['8.1', 'store', '-vcvars_ver=14.1'], MSVC_SCRIPTERROR_POLICY='warn')
+env.Program('hello', ['hello.c'], CCFLAGS='/MD', LIBS=['kernel32', 'user32', 'runtimeobject'])
+</example_commands>
+</para>
+
+<para>
+Example 2 - Output fragment:
+<example_commands>
+...
+scons: warning: vc script errors detected:
+[ERROR:vcvars.bat] The UWP Application Platform requires a Windows 10 SDK.
+[ERROR:vcvars.bat] WindowsSdkDir = "C:\Program Files (x86)\Windows Kits\8.1\"
+[ERROR:vcvars.bat] host/target architecture is not supported : { x64 , x64 }
+...
+link /nologo /OUT:_build001\hello.exe kernel32.lib user32.lib runtimeobject.lib _build001\hello.obj
+LINK : fatal error LNK1104: cannot open file 'MSVCRT.lib'
+</example_commands>
+</para>
+
+<para>
+Important usage details:
+<itemizedlist>
+
+<listitem><para>
+&cv-MSVC_SCRIPTERROR_POLICY; must be passed as an argument to the &f-link-Environment;
+constructor when an msvc tool (e.g., &t-link-msvc;, &t-link-msvs;, etc.) is
+loaded via the default tools list or via a tools list passed to the
+&f-link-Environment; constructor.
+Otherwise, &cv-MSVC_SCRIPTERROR_POLICY; must be set before the first msvc tool is
+loaded into the environment.
+</para></listitem>
+
+<listitem><para>
+Due to &scons; implementation details, not all Windows system environment variables are propagated
+to the environment in which the msvc batch file is executed. Depending on Visual Studio version
+and installation options, non-fatal msvc batch file error messages may be generated for ancillary
+tools which may not affect builds with the msvc compiler. For this reason, caution is recommended
+when setting the script error policy to raise an exception (e.g., <literal>'Error'</literal>).
+</para></listitem>
+
+</itemizedlist>
+</para>
+
+</summary>
+</cvar>
+
+<cvar name="MSVC_SCRIPT_ARGS">
+<summary>
+<para>
+Pass user-defined arguments to the Visual C++ batch file determined via autodetection.
+</para>
+
+<para>
+&cv-MSVC_SCRIPT_ARGS; is available for msvc batch file arguments that do not have first-class support
+via construction variables or when there is an issue with the appropriate construction variable validation.
+When available, it is recommended to use the appropriate construction variables (e.g., &cv-link-MSVC_TOOLSET_VERSION;)
+rather than &cv-MSVC_SCRIPT_ARGS; arguments.
+</para>
+
+<para>
+The valid values for &cv-MSVC_SCRIPT_ARGS; are: <literal>None</literal>, a string,
+or a list of strings.
+</para>
+
+<para>
+The &cv-MSVC_SCRIPT_ARGS; value is converted to a scalar string (i.e., "flattened").
+The resulting scalar string, if not empty, is passed as an argument to the msvc batch file determined
+via autodetection subject to the validation conditions listed below.
+</para>
+
+<para>
+&cv-MSVC_SCRIPT_ARGS; is ignored when the value is <literal>None</literal> and when the
+result from argument conversion is an empty string. The validation conditions below do not apply.
+</para>
+
+<para>
+An exception is raised when any of the following conditions are satisfied:
+<itemizedlist>
+
+<listitem><para>
+&cv-MSVC_SCRIPT_ARGS; is specified for Visual Studio 2013 and earlier.
+</para></listitem>
+
+<listitem><para>
+Multiple SDK version arguments (e.g., <literal>'10.0.20348.0'</literal>) are specified
+in &cv-MSVC_SCRIPT_ARGS;.
+</para></listitem>
+
+<listitem><para>
+&cv-link-MSVC_SDK_VERSION; is specified and an SDK version argument
+(e.g., <literal>'10.0.20348.0'</literal>) is specified in &cv-MSVC_SCRIPT_ARGS;.
+Multiple SDK version declarations via &cv-link-MSVC_SDK_VERSION; and &cv-MSVC_SCRIPT_ARGS;
+are not allowed.
+</para></listitem>
+
+<listitem><para>
+Multiple toolset version arguments (e.g., <literal>'-vcvars_ver=14.29'</literal>)
+are specified in &cv-MSVC_SCRIPT_ARGS;.
+</para></listitem>
+
+<listitem><para>
+&cv-link-MSVC_TOOLSET_VERSION; is specified and a toolset version argument
+(e.g., <literal>'-vcvars_ver=14.29'</literal>) is specified in &cv-MSVC_SCRIPT_ARGS;.
+Multiple toolset version declarations via &cv-link-MSVC_TOOLSET_VERSION; and
+&cv-MSVC_SCRIPT_ARGS; are not allowed.
+</para></listitem>
+
+<listitem><para>
+Multiple spectre library arguments (e.g., <literal>'-vcvars_spectre_libs=spectre'</literal>)
+are specified in &cv-MSVC_SCRIPT_ARGS;.
+</para></listitem>
+
+<listitem><para>
+&cv-link-MSVC_SPECTRE_LIBS; is enabled and a spectre library argument
+(e.g., <literal>'-vcvars_spectre_libs=spectre'</literal>) is specified in
+&cv-MSVC_SCRIPT_ARGS;. Multiple spectre library declarations via &cv-link-MSVC_SPECTRE_LIBS;
+and &cv-MSVC_SCRIPT_ARGS; are not allowed.
+</para></listitem>
+
+<listitem><para>
+Multiple UWP arguments (e.g., <literal>uwp</literal> or <literal>store</literal>) are specified
+in &cv-MSVC_SCRIPT_ARGS;.
+</para></listitem>
+
+<listitem><para>
+&cv-link-MSVC_UWP_APP; is enabled and a UWP argument (e.g., <literal>uwp</literal> or
+<literal>store</literal>) is specified in &cv-MSVC_SCRIPT_ARGS;. Multiple UWP declarations
+via &cv-link-MSVC_UWP_APP; and &cv-MSVC_SCRIPT_ARGS; are not allowed.
+</para></listitem>
+
+</itemizedlist>
+</para>
+
+<para>
+Example 1 - A Visual Studio 2022 build with an SDK version and a toolset version
+specified with a string argument:
+<example_commands>
+env = Environment(MSVC_VERSION='14.3', MSVC_SCRIPT_ARGS='10.0.20348.0 -vcvars_ver=14.29.30133')
+</example_commands>
+</para>
+
+<para>
+Example 2 - A Visual Studio 2022 build with an SDK version and a toolset version
+specified with a list argument:
+<example_commands>
+env = Environment(MSVC_VERSION='14.3', MSVC_SCRIPT_ARGS=['10.0.20348.0', '-vcvars_ver=14.29.30133'])
+</example_commands>
+</para>
+
+<para>
+Important usage details:
+<itemizedlist>
+
+<listitem><para>
+&cv-MSVC_SCRIPT_ARGS; must be passed as an argument to the &f-link-Environment;
+constructor when an msvc tool (e.g., &t-link-msvc;, &t-link-msvs;, etc.) is
+loaded via the default tools list or via a tools list passed to the
+&f-link-Environment; constructor.
+Otherwise, &cv-MSVC_SCRIPT_ARGS; must be set before the first msvc tool is
+loaded into the environment.
+</para></listitem>
+
+<listitem><para>
+Other than checking for multiple declarations as described above, &cv-MSVC_SCRIPT_ARGS; arguments
+are not validated.
+</para></listitem>
+
+<listitem><para>
+<emphasis>
+Erroneous, inconsistent, and/or version incompatible &cv-MSVC_SCRIPT_ARGS; arguments are likely
+to result in build failures for reasons that are not readily apparent and may be difficult to diagnose.
+</emphasis>
+The burden is on the user to ensure that the arguments provided to the msvc batch file are valid, consistent
+and compatible with the version of msvc selected.
+</para></listitem>
+
+</itemizedlist>
+</para>
</summary>
</cvar>
+<cvar name="MSVC_SDK_VERSION">
+<summary>
+<para>
+Build with a specific version of the Microsoft Software Development Kit (SDK).
+</para>
+
+<para>
+The valid values for &cv-MSVC_SDK_VERSION; are: <literal>None</literal>
+or a string containing the requested SDK version (e.g., <literal>'10.0.20348.0'</literal>).
+</para>
+
+<para>
+&cv-MSVC_SDK_VERSION; is ignored when the value is <literal>None</literal> and when
+the value is an empty string. The validation conditions below do not apply.
+</para>
+
+<para>
+An exception is raised when any of the following conditions are satisfied:
+<itemizedlist>
+
+<listitem><para>
+&cv-MSVC_SDK_VERSION; is specified for Visual Studio 2013 and earlier.
+</para></listitem>
+
+<listitem><para>
+&cv-MSVC_SDK_VERSION; is specified and an SDK version argument is specified in
+&cv-link-MSVC_SCRIPT_ARGS;. Multiple SDK version declarations via &cv-MSVC_SDK_VERSION;
+and &cv-link-MSVC_SCRIPT_ARGS; are not allowed.
+</para></listitem>
+
+<listitem><para>
+The &cv-MSVC_SDK_VERSION; specified does not match any of the supported formats:
+<itemizedlist>
+<listitem><para>
+<literal>'10.0.XXXXX.Y'</literal> [SDK 10.0]
+</para></listitem>
+<listitem><para>
+<literal>'8.1'</literal> [SDK 8.1]
+</para></listitem>
+</itemizedlist>
+</para></listitem>
+
+<listitem><para>
+The system folder for the corresponding &cv-MSVC_SDK_VERSION; version is not found.
+The requested SDK version does not appear to be installed.
+</para></listitem>
+
+<listitem><para>
+The &cv-MSVC_SDK_VERSION; version does not appear to support the requested platform
+type (i.e., <literal>UWP</literal> or <literal>Desktop</literal>). The requested SDK version
+platform type components do not appear to be installed.
+</para></listitem>
+
+<listitem><para>
+The &cv-MSVC_SDK_VERSION; version is <literal>8.1</literal>, the platform type is
+<literal>UWP</literal>, and the build tools selected are from Visual Studio 2017
+and later (i.e., &cv-link-MSVC_VERSION; must be '14.0' or &cv-link-MSVC_TOOLSET_VERSION;
+must be '14.0').
+</para></listitem>
+
+</itemizedlist>
+</para>
+
+<para>
+Example 1 - A Visual Studio 2022 build with a specific Windows SDK version:
+<example_commands>
+env = Environment(MSVC_VERSION='14.3', MSVC_SDK_VERSION='10.0.20348.0')
+</example_commands>
+</para>
+
+<para>
+Example 2 - A Visual Studio 2022 build with a specific SDK version for the Universal Windows Platform:
+<example_commands>
+env = Environment(MSVC_VERSION='14.3', MSVC_SDK_VERSION='10.0.20348.0', MSVC_UWP_APP=True)
+</example_commands>
+</para>
+
+<para>
+Important usage details:
+<itemizedlist>
+
+<listitem><para>
+&cv-MSVC_SDK_VERSION; must be passed as an argument to the &f-link-Environment;
+constructor when an msvc tool (e.g., &t-link-msvc;, &t-link-msvs;, etc.) is
+loaded via the default tools list or via a tools list passed to the
+&f-link-Environment; constructor.
+Otherwise, &cv-MSVC_SDK_VERSION; must be set before the first msvc tool is
+loaded into the environment.
+</para></listitem>
+
+<listitem>
+<para><emphasis>
+Should a SDK 10.0 version be installed that does not follow the naming scheme above, the
+SDK version will need to be specified via &cv-link-MSVC_SCRIPT_ARGS; until the version number
+validation format can be extended.
+</emphasis></para>
+</listitem>
+
+<listitem><para>
+Should an exception be raised indicating that the SDK version is not found, verify that
+the requested SDK version is installed with the necessary platform type components.
+</para></listitem>
+
+<listitem><para>
+There is a known issue with the Microsoft libraries when the target architecture is
+<literal>ARM64</literal> and a Windows 11 SDK (version <literal>'10.0.22000.0'</literal> and later) is used
+with the <literal>v141</literal> build tools and older <literal>v142</literal> toolsets
+(versions <literal>'14.28.29333'</literal> and earlier). Should build failures arise with these combinations
+of settings due to unresolved symbols in the Microsoft libraries, &cv-MSVC_SDK_VERSION; may be employed to
+specify a Windows 10 SDK (e.g., <literal>'10.0.20348.0'</literal>) for the build.
+</para></listitem>
+
+</itemizedlist>
+</para>
+
+</summary>
+</cvar>
+
+<cvar name="MSVC_TOOLSET_VERSION">
+<summary>
+<para>
+Build with a specific Visual C++ toolset version.
+</para>
+
+<para><emphasis>
+Specifying &cv-MSVC_TOOLSET_VERSION; does not affect the autodetection and selection
+of msvc instances. The &cv-MSVC_TOOLSET_VERSION; is applied <emphasis>after</emphasis>
+an msvc instance is selected. This could be the default version of msvc if &cv-link-MSVC_VERSION;
+is not specified.
+</emphasis></para>
+
+<para>
+The valid values for &cv-MSVC_TOOLSET_VERSION; are: <literal>None</literal>
+or a string containing the requested toolset version (e.g., <literal>'14.29'</literal>).
+</para>
+
+<para>
+&cv-MSVC_TOOLSET_VERSION; is ignored when the value is <literal>None</literal> and when
+the value is an empty string. The validation conditions below do not apply.
+</para>
+
+<para>
+An exception is raised when any of the following conditions are satisfied:
+<itemizedlist>
+
+<listitem><para>
+&cv-MSVC_TOOLSET_VERSION; is specified for Visual Studio 2015 and earlier.
+</para></listitem>
+
+<listitem><para>
+&cv-MSVC_TOOLSET_VERSION; is specified and a toolset version argument is specified in
+&cv-link-MSVC_SCRIPT_ARGS;. Multiple toolset version declarations via &cv-MSVC_TOOLSET_VERSION;
+and &cv-link-MSVC_SCRIPT_ARGS; are not allowed.
+</para></listitem>
+
+<listitem>
+<para>
+The &cv-MSVC_TOOLSET_VERSION; specified does not match any of the supported formats:
+</para>
+
+<itemizedlist>
+
+<listitem><para>
+<literal>'XX.Y'</literal>
+</para></listitem>
+
+<listitem><para>
+<literal>'XX.YY'</literal>
+</para></listitem>
+
+<listitem><para>
+<literal>'XX.YY.ZZZZZ'</literal>
+</para></listitem>
+
+<listitem><para>
+<literal>'XX.YY.Z'</literal> to <literal>'XX.YY.ZZZZ'</literal>
+<emphasis>
+[&scons; extension not directly supported by the msvc batch files and may be removed in the future]
+</emphasis>
+</para></listitem>
+
+<listitem><para>
+<literal>'XX.YY.ZZ.N'</literal> [SxS format]
+</para></listitem>
+
+<listitem><para>
+<literal>'XX.YY.ZZ.NN'</literal> [SxS format]
+</para></listitem>
+
+</itemizedlist>
+
+</listitem>
+
+<listitem><para>
+The major msvc version prefix (i.e., <literal>'XX.Y'</literal>) of the &cv-MSVC_TOOLSET_VERSION; specified
+is for Visual Studio 2013 and earlier (e.g., <literal>'12.0'</literal>).
+</para></listitem>
+
+<listitem><para>
+The major msvc version prefix (i.e., <literal>'XX.Y'</literal>) of the &cv-MSVC_TOOLSET_VERSION; specified
+is greater than the msvc version selected (e.g., <literal>'99.0'</literal>).
+</para></listitem>
+
+<listitem><para>
+A system folder for the corresponding &cv-MSVC_TOOLSET_VERSION; version is not found.
+The requested toolset version does not appear to be installed.
+</para></listitem>
+
+</itemizedlist>
+</para>
+
+<para>
+Toolset selection details:
+<itemizedlist>
+
+<listitem><para>
+When &cv-MSVC_TOOLSET_VERSION; is not an SxS version number or a full toolset version number:
+the first toolset version, ranked in descending order, that matches the &cv-MSVC_TOOLSET_VERSION;
+prefix is selected.
+</para></listitem>
+
+<listitem><para>
+When &cv-MSVC_TOOLSET_VERSION; is specified using the major msvc version prefix
+(i.e., <literal>'XX.Y'</literal>) and the major msvc version is that of the latest release of
+Visual Studio, the selected toolset version may not be the same as the default Visual C++ toolset version.
+</para><para>
+In the latest release of Visual Studio, the default Visual C++ toolset version is not necessarily the
+toolset with the largest version number.
+</para></listitem>
+
+</itemizedlist>
+</para>
+
+<para>
+Example 1 - A default Visual Studio build with a partial toolset version specified:
+<example_commands>
+env = Environment(MSVC_TOOLSET_VERSION='14.2')
+</example_commands>
+</para>
+
+<para>
+Example 2 - A default Visual Studio build with a partial toolset version specified:
+<example_commands>
+env = Environment(MSVC_TOOLSET_VERSION='14.29')
+</example_commands>
+</para>
+
+<para>
+Example 3 - A Visual Studio 2022 build with a full toolset version specified:
+<example_commands>
+env = Environment(MSVC_VERSION='14.3', MSVC_TOOLSET_VERSION='14.29.30133')
+</example_commands>
+</para>
+
+<para>
+Example 4 - A Visual Studio 2022 build with an SxS toolset version specified:
+<example_commands>
+env = Environment(MSVC_VERSION='14.3', MSVC_TOOLSET_VERSION='14.29.16.11')
+</example_commands>
+</para>
+
+<para>
+Important usage details:
+<itemizedlist>
+
+<listitem><para>
+&cv-MSVC_TOOLSET_VERSION; must be passed as an argument to the &f-link-Environment;
+constructor when an msvc tool (e.g., &t-link-msvc;, &t-link-msvs;, etc.) is
+loaded via the default tools list or via a tools list passed to the
+&f-link-Environment; constructor.
+Otherwise, &cv-MSVC_TOOLSET_VERSION; must be set before the first msvc tool is
+loaded into the environment.
+</para></listitem>
+
+<listitem><para>
+<emphasis>
+The existence of the toolset host architecture and target architecture folders are not verified
+when &cv-MSVC_TOOLSET_VERSION; is specified which could result in build failures.
+</emphasis>
+The burden is on the user to ensure the requisite toolset target architecture build tools are installed.
+</para></listitem>
+
+</itemizedlist>
+</para>
+
+</summary>
+</cvar>
+
+<cvar name="MSVC_SPECTRE_LIBS">
+<summary>
+<para>
+Build with the spectre-mitigated Visual C++ libraries.
+</para>
+
+<para>
+The valid values for &cv-MSVC_SPECTRE_LIBS; are: <literal>True</literal>,
+<literal>False</literal>, or <literal>None</literal>.
+</para>
+
+<para>
+When &cv-MSVC_SPECTRE_LIBS; is enabled (i.e., <literal>True</literal>),
+the Visual C++ environment will include the paths to the spectre-mitigated implementations
+of the Microsoft Visual C++ libraries.
+</para>
+
+<para>
+An exception is raised when any of the following conditions are satisfied:
+<itemizedlist>
+
+<listitem><para>
+&cv-MSVC_SPECTRE_LIBS; is enabled for Visual Studio 2015 and earlier.
+</para></listitem>
+
+<listitem><para>
+&cv-MSVC_SPECTRE_LIBS; is enabled and a spectre library argument is specified in
+&cv-link-MSVC_SCRIPT_ARGS;. Multiple spectre library declarations via &cv-MSVC_SPECTRE_LIBS;
+and &cv-link-MSVC_SCRIPT_ARGS; are not allowed.
+</para></listitem>
+
+<listitem><para>
+&cv-MSVC_SPECTRE_LIBS; is enabled and the platform type is <literal>UWP</literal>. There
+are no spectre-mitigated libraries for Universal Windows Platform (UWP) applications or
+components.
+</para></listitem>
+
+</itemizedlist>
+</para>
+
+<para>
+Example - A Visual Studio 2022 build with spectre mitigated Visual C++ libraries:
+<example_commands>
+env = Environment(MSVC_VERSION='14.3', MSVC_SPECTRE_LIBS=True)
+</example_commands>
+</para>
+
+<para>
+Important usage details:
+<itemizedlist>
+
+<listitem><para>
+&cv-MSVC_SPECTRE_LIBS; must be passed as an argument to the &f-link-Environment;
+constructor when an msvc tool (e.g., &t-link-msvc;, &t-link-msvs;, etc.) is
+loaded via the default tools list or via a tools list passed to the
+&f-link-Environment; constructor.
+Otherwise, &cv-MSVC_SPECTRE_LIBS; must be set before the first msvc tool is
+loaded into the environment.
+</para></listitem>
+
+<listitem><para>
+Additional compiler switches (e.g., <literal>/Qspectre</literal>) are necessary for including
+spectre mitigations when building user artifacts. Refer to the Visual Studio documentation for
+details.
+</para></listitem>
+
+<listitem><para>
+<emphasis>
+The existence of the spectre libraries host architecture and target architecture folders are not
+verified when &cv-MSVC_SPECTRE_LIBS; is enabled which could result in build failures.
+</emphasis>
+The burden is on the user to ensure the requisite libraries with spectre mitigations are installed.
+</para></listitem>
+
+</itemizedlist>
+</para>
+
+</summary>
+</cvar>
</sconsdoc>
diff --git a/SCons/__init__.py b/SCons/__init__.py
index c95386a33..784a392e9 100644
--- a/SCons/__init__.py
+++ b/SCons/__init__.py
@@ -1,9 +1,9 @@
-__version__="4.3.0"
-__copyright__="Copyright (c) 2001 - 2021 The SCons Foundation"
-__developer__="bdeegan"
-__date__="Tue, 16 Nov 2021 19:09:21 +0000"
-__buildsys__="octodog"
-__revision__="559790274f66fa55251f5754de34820a29c7327a"
-__build__="559790274f66fa55251f5754de34820a29c7327a"
+__version__="4.3.1"
+__copyright__="Copyright (c) 2001 - 2022 The SCons Foundation"
+__developer__="bdbaddog"
+__date__="Sun, 24 Jul 2022 18:02:27 -0700"
+__buildsys__="M1Dog2021"
+__revision__="2cf871f2d61b489220a93d9020a0af5310054f91"
+__build__="2cf871f2d61b489220a93d9020a0af5310054f91"
# make sure compatibility is always in place
import SCons.compat # noqa \ No newline at end of file
diff --git a/doc/generated/functions.gen b/doc/generated/functions.gen
index a2d9acd50..efc4c9edf 100644
--- a/doc/generated/functions.gen
+++ b/doc/generated/functions.gen
@@ -1611,45 +1611,49 @@ See the manpage section "Construction Environments" for more details.
</listitem>
</varlistentry>
<varlistentry id="f-Execute">
- <term><function>Execute</function>(<parameter>action, [strfunction, varlist]</parameter>)</term>
- <term><replaceable>env</replaceable>.<methodname>Execute</methodname>(<parameter>action, [strfunction, varlist]</parameter>)</term>
+ <term><function>Execute</function>(<parameter>action, [actionargs ...]</parameter>)</term>
+ <term><replaceable>env</replaceable>.<methodname>Execute</methodname>(<parameter>action, [actionargs ...]</parameter>)</term>
<listitem><para>
-Executes an Action object.
-The specified
+Executes an Action.
<parameter>action</parameter>
may be an Action object
-(see manpage section "Action Objects"
-for an explanation of behavior),
or it may be a command-line string,
list of commands,
or executable &Python; function,
-each of which will be converted
+each of which will first be converted
into an Action object
and then executed.
Any additional arguments to &f-Execute;
-(<parameter>strfunction</parameter>, <parameter>varlist</parameter>)
are passed on to the &f-link-Action; factory function
-which actually creates the Action object.
-The exit value of the command
-or return value of the &Python; function
-will be returned.
+which actually creates the Action object
+(see the manpage section <link linkend="action_objects">Action Objects</link>
+for a description). Example:
+</para>
+
+<example_commands>
+Execute(Copy('file.out', 'file.in'))
+</example_commands>
+
+<para>&f-Execute; performs its action immediately,
+as part of the SConscript-reading phase.
+There are no sources or targets declared in an
+&f-Execute; call, so any objects it manipulates
+will not be tracked as part of the &SCons; dependency graph.
+In the example above, neither
+<filename>file.out</filename> nor
+<filename>file.in</filename> will be tracked objects.
</para>
<para>
-Note that
+&f-Execute; returns the exit value of the command
+or return value of the &Python; function.
&scons;
-will print an error message if the executed
+prints an error message if the executed
<parameter>action</parameter>
-fails--that is,
-exits with or returns a non-zero value.
-&scons;
-will
+fails (exits with or returns a non-zero value),
+however it does
<emphasis>not</emphasis>,
-however,
-automatically terminate the build
-if the specified
-<parameter>action</parameter>
-fails.
+automatically terminate the build for such a failure.
If you want the build to stop in response to a failed
&f-Execute;
call,
@@ -1657,8 +1661,6 @@ you must explicitly check for a non-zero return value:
</para>
<example_commands>
-Execute(Copy('file.out', 'file.in'))
-
if Execute("mkdir sub/dir/ectory"):
# The mkdir failed, don't try to build.
Exit(1)
diff --git a/doc/generated/tools.gen b/doc/generated/tools.gen
index ecc301a08..35ded17ce 100644
--- a/doc/generated/tools.gen
+++ b/doc/generated/tools.gen
@@ -389,35 +389,35 @@ Sets construction variables for the dvips utility.
<listitem><para>
Set construction variables for generic POSIX Fortran 03 compilers.
</para>
-<para>Sets: &cv-link-F03;, &cv-link-F03COM;, &cv-link-F03FLAGS;, &cv-link-F03PPCOM;, &cv-link-SHF03;, &cv-link-SHF03COM;, &cv-link-SHF03FLAGS;, &cv-link-SHF03PPCOM;, &cv-link-_F03INCFLAGS;.</para><para>Uses: &cv-link-F03COMSTR;, &cv-link-F03PPCOMSTR;, &cv-link-SHF03COMSTR;, &cv-link-SHF03PPCOMSTR;.</para></listitem>
+<para>Sets: &cv-link-F03;, &cv-link-F03COM;, &cv-link-F03FLAGS;, &cv-link-F03PPCOM;, &cv-link-SHF03;, &cv-link-SHF03COM;, &cv-link-SHF03FLAGS;, &cv-link-SHF03PPCOM;, &cv-link-_F03INCFLAGS;.</para><para>Uses: &cv-link-F03COMSTR;, &cv-link-F03PPCOMSTR;, &cv-link-FORTRANCOMMONFLAGS;, &cv-link-SHF03COMSTR;, &cv-link-SHF03PPCOMSTR;.</para></listitem>
</varlistentry>
<varlistentry id="t-f08">
<term>f08</term>
<listitem><para>
Set construction variables for generic POSIX Fortran 08 compilers.
</para>
-<para>Sets: &cv-link-F08;, &cv-link-F08COM;, &cv-link-F08FLAGS;, &cv-link-F08PPCOM;, &cv-link-SHF08;, &cv-link-SHF08COM;, &cv-link-SHF08FLAGS;, &cv-link-SHF08PPCOM;, &cv-link-_F08INCFLAGS;.</para><para>Uses: &cv-link-F08COMSTR;, &cv-link-F08PPCOMSTR;, &cv-link-SHF08COMSTR;, &cv-link-SHF08PPCOMSTR;.</para></listitem>
+<para>Sets: &cv-link-F08;, &cv-link-F08COM;, &cv-link-F08FLAGS;, &cv-link-F08PPCOM;, &cv-link-SHF08;, &cv-link-SHF08COM;, &cv-link-SHF08FLAGS;, &cv-link-SHF08PPCOM;, &cv-link-_F08INCFLAGS;.</para><para>Uses: &cv-link-F08COMSTR;, &cv-link-F08PPCOMSTR;, &cv-link-FORTRANCOMMONFLAGS;, &cv-link-SHF08COMSTR;, &cv-link-SHF08PPCOMSTR;.</para></listitem>
</varlistentry>
<varlistentry id="t-f77">
<term>f77</term>
<listitem><para>
Set construction variables for generic POSIX Fortran 77 compilers.
</para>
-<para>Sets: &cv-link-F77;, &cv-link-F77COM;, &cv-link-F77FILESUFFIXES;, &cv-link-F77FLAGS;, &cv-link-F77PPCOM;, &cv-link-F77PPFILESUFFIXES;, &cv-link-FORTRAN;, &cv-link-FORTRANCOM;, &cv-link-FORTRANFLAGS;, &cv-link-SHF77;, &cv-link-SHF77COM;, &cv-link-SHF77FLAGS;, &cv-link-SHF77PPCOM;, &cv-link-SHFORTRAN;, &cv-link-SHFORTRANCOM;, &cv-link-SHFORTRANFLAGS;, &cv-link-SHFORTRANPPCOM;, &cv-link-_F77INCFLAGS;.</para><para>Uses: &cv-link-F77COMSTR;, &cv-link-F77PPCOMSTR;, &cv-link-FORTRANCOMSTR;, &cv-link-FORTRANPPCOMSTR;, &cv-link-SHF77COMSTR;, &cv-link-SHF77PPCOMSTR;, &cv-link-SHFORTRANCOMSTR;, &cv-link-SHFORTRANPPCOMSTR;.</para></listitem>
+<para>Sets: &cv-link-F77;, &cv-link-F77COM;, &cv-link-F77FILESUFFIXES;, &cv-link-F77FLAGS;, &cv-link-F77PPCOM;, &cv-link-F77PPFILESUFFIXES;, &cv-link-FORTRAN;, &cv-link-FORTRANCOM;, &cv-link-FORTRANFLAGS;, &cv-link-SHF77;, &cv-link-SHF77COM;, &cv-link-SHF77FLAGS;, &cv-link-SHF77PPCOM;, &cv-link-SHFORTRAN;, &cv-link-SHFORTRANCOM;, &cv-link-SHFORTRANFLAGS;, &cv-link-SHFORTRANPPCOM;, &cv-link-_F77INCFLAGS;.</para><para>Uses: &cv-link-F77COMSTR;, &cv-link-F77PPCOMSTR;, &cv-link-FORTRANCOMMONFLAGS;, &cv-link-FORTRANCOMSTR;, &cv-link-FORTRANFLAGS;, &cv-link-FORTRANPPCOMSTR;, &cv-link-SHF77COMSTR;, &cv-link-SHF77PPCOMSTR;, &cv-link-SHFORTRANCOMSTR;, &cv-link-SHFORTRANFLAGS;, &cv-link-SHFORTRANPPCOMSTR;.</para></listitem>
</varlistentry>
<varlistentry id="t-f90">
<term>f90</term>
<listitem><para>
Set construction variables for generic POSIX Fortran 90 compilers.
</para>
-<para>Sets: &cv-link-F90;, &cv-link-F90COM;, &cv-link-F90FLAGS;, &cv-link-F90PPCOM;, &cv-link-SHF90;, &cv-link-SHF90COM;, &cv-link-SHF90FLAGS;, &cv-link-SHF90PPCOM;, &cv-link-_F90INCFLAGS;.</para><para>Uses: &cv-link-F90COMSTR;, &cv-link-F90PPCOMSTR;, &cv-link-SHF90COMSTR;, &cv-link-SHF90PPCOMSTR;.</para></listitem>
+<para>Sets: &cv-link-F90;, &cv-link-F90COM;, &cv-link-F90FLAGS;, &cv-link-F90PPCOM;, &cv-link-SHF90;, &cv-link-SHF90COM;, &cv-link-SHF90FLAGS;, &cv-link-SHF90PPCOM;, &cv-link-_F90INCFLAGS;.</para><para>Uses: &cv-link-F90COMSTR;, &cv-link-F90PPCOMSTR;, &cv-link-FORTRANCOMMONFLAGS;, &cv-link-SHF90COMSTR;, &cv-link-SHF90PPCOMSTR;.</para></listitem>
</varlistentry>
<varlistentry id="t-f95">
<term>f95</term>
<listitem><para>
Set construction variables for generic POSIX Fortran 95 compilers.
</para>
-<para>Sets: &cv-link-F95;, &cv-link-F95COM;, &cv-link-F95FLAGS;, &cv-link-F95PPCOM;, &cv-link-SHF95;, &cv-link-SHF95COM;, &cv-link-SHF95FLAGS;, &cv-link-SHF95PPCOM;, &cv-link-_F95INCFLAGS;.</para><para>Uses: &cv-link-F95COMSTR;, &cv-link-F95PPCOMSTR;, &cv-link-SHF95COMSTR;, &cv-link-SHF95PPCOMSTR;.</para></listitem>
+<para>Sets: &cv-link-F95;, &cv-link-F95COM;, &cv-link-F95FLAGS;, &cv-link-F95PPCOM;, &cv-link-SHF95;, &cv-link-SHF95COM;, &cv-link-SHF95FLAGS;, &cv-link-SHF95PPCOM;, &cv-link-_F95INCFLAGS;.</para><para>Uses: &cv-link-F95COMSTR;, &cv-link-F95PPCOMSTR;, &cv-link-FORTRANCOMMONFLAGS;, &cv-link-SHF95COMSTR;, &cv-link-SHF95PPCOMSTR;.</para></listitem>
</varlistentry>
<varlistentry id="t-fortran">
<term>fortran</term>
@@ -437,10 +437,8 @@ Set construction variables for the &gXX; C++ compiler.
<term>g77</term>
<listitem><para>
Set construction variables for the &g77; Fortran compiler.
-Calls the &t-f77; Tool module
-to set variables.
</para>
-</listitem>
+<para>Sets: &cv-link-F77;, &cv-link-F77COM;, &cv-link-F77FILESUFFIXES;, &cv-link-F77PPCOM;, &cv-link-F77PPFILESUFFIXES;, &cv-link-FORTRAN;, &cv-link-FORTRANCOM;, &cv-link-FORTRANPPCOM;, &cv-link-SHF77;, &cv-link-SHF77COM;, &cv-link-SHF77FLAGS;, &cv-link-SHF77PPCOM;, &cv-link-SHFORTRAN;, &cv-link-SHFORTRANCOM;, &cv-link-SHFORTRANFLAGS;, &cv-link-SHFORTRANPPCOM;.</para><para>Uses: &cv-link-F77FLAGS;, &cv-link-FORTRANCOMMONFLAGS;, &cv-link-FORTRANFLAGS;.</para></listitem>
</varlistentry>
<varlistentry id="t-gas">
<term>gas</term>
@@ -516,7 +514,8 @@ environment:
<varlistentry id="t-gfortran">
<term>gfortran</term>
<listitem><para>
-Sets construction variables for the GNU F95/F2003 GNU compiler.
+Sets construction variables for the GNU Fortran compiler.
+Calls the &t-link-fortran; Tool module to set variables.
</para>
<para>Sets: &cv-link-F77;, &cv-link-F90;, &cv-link-F95;, &cv-link-FORTRAN;, &cv-link-SHF77;, &cv-link-SHF77FLAGS;, &cv-link-SHF90;, &cv-link-SHF90FLAGS;, &cv-link-SHF95;, &cv-link-SHF95FLAGS;, &cv-link-SHFORTRAN;, &cv-link-SHFORTRANFLAGS;.</para></listitem>
</varlistentry>
diff --git a/doc/generated/variables.gen b/doc/generated/variables.gen
index 996e35acb..533ea2293 100644
--- a/doc/generated/variables.gen
+++ b/doc/generated/variables.gen
@@ -2682,6 +2682,17 @@ in the &cv-link-FORTRANFLAGS;,
</para>
</listitem>
</varlistentry>
+ <varlistentry id="cv-FORTRANCOMMONFLAGS">
+ <term>
+ <envar>FORTRANCOMMONFLAGS</envar>
+ </term>
+ <listitem><para>
+General user-specified options that are passed to the Fortran compiler.
+Similar to &cv-link-FORTRANFLAGS;,
+but this variable is applied to all dialects.
+</para>
+</listitem>
+ </varlistentry>
<varlistentry id="cv-FORTRANCOMSTR">
<term>
<envar>FORTRANCOMSTR</envar>
@@ -2709,7 +2720,8 @@ default, this is <literal>['.f', '.for', '.ftn']</literal>
<envar>FORTRANFLAGS</envar>
</term>
<listitem><para>
-General user-specified options that are passed to the Fortran compiler.
+General user-specified options for the FORTRAN dialect
+that are passed to the Fortran compiler.
Note that this variable does
<emphasis>not</emphasis>
contain
@@ -4686,16 +4698,16 @@ will be compiled separately.
<envar>MSVC_NOTFOUND_POLICY</envar>
</term>
<listitem><para>
-Specify the scons behavior when the Microsoft Visual C/C++ compiler is not detected.
+Specify the &scons; behavior when the Microsoft Visual C/C++ compiler is not detected.
</para>
<para>
- The <envar>MSVC_NOTFOUND_POLICY</envar> specifies the &scons; behavior when no msvc versions are detected or
+ The &cv-MSVC_NOTFOUND_POLICY; specifies the &scons; behavior when no msvc versions are detected or
when the requested msvc version is not detected.
</para>
-<para>
-The valid values for <envar>MSVC_NOTFOUND_POLICY</envar> and the corresponding &scons; behavior are:
+<para>
+The valid values for &cv-MSVC_NOTFOUND_POLICY; and the corresponding &scons; behavior are:
</para>
<variablelist>
@@ -4736,7 +4748,7 @@ Note: in addition to the camel case values shown above, lower case and upper cas
</para>
<para>
-The <envar>MSVC_NOTFOUND_POLICY</envar> is applied when any of the following conditions are satisfied:
+The &cv-MSVC_NOTFOUND_POLICY; is applied when any of the following conditions are satisfied:
<itemizedlist>
<listitem><para>
&cv-MSVC_VERSION; is specified, the default tools list is implicitly defined (i.e., the tools list is not specified),
@@ -4753,7 +4765,7 @@ A non-default tools list is specified that contains one or more of the msvc tool
</para>
<para>
-The <envar>MSVC_NOTFOUND_POLICY</envar> is ignored when any of the following conditions are satisfied:
+The &cv-MSVC_NOTFOUND_POLICY; is ignored when any of the following conditions are satisfied:
<itemizedlist>
<listitem><para>
&cv-MSVC_VERSION; is not specified and the default tools list is implicitly defined (i.e., the tools list is not specified).
@@ -4768,10 +4780,659 @@ A non-default tool list is specified that does not contain any of the msvc tools
</para>
<para>
-When <envar>MSVC_NOTFOUND_POLICY</envar> is not specified, the default &scons; behavior is to issue a warning and continue
+Important usage details:
+<itemizedlist>
+
+<listitem><para>
+&cv-MSVC_NOTFOUND_POLICY; must be passed as an argument to the &f-link-Environment;
+constructor when an msvc tool (e.g., &t-link-msvc;, &t-link-msvs;, etc.) is
+loaded via the default tools list or via a tools list passed to the
+&f-link-Environment; constructor.
+Otherwise, &cv-MSVC_NOTFOUND_POLICY; must be set before the first msvc tool is
+loaded into the environment.
+</para></listitem>
+
+</itemizedlist>
+</para>
+
+<para>
+When &cv-MSVC_NOTFOUND_POLICY; is not specified, the default &scons; behavior is to issue a warning and continue
subject to the conditions listed above. The default &scons; behavior may change in the future.
</para>
+</listitem>
+ </varlistentry>
+ <varlistentry id="cv-MSVC_SCRIPT_ARGS">
+ <term>
+ <envar>MSVC_SCRIPT_ARGS</envar>
+ </term>
+ <listitem><para>
+Pass user-defined arguments to the Visual C++ batch file determined via autodetection.
+</para>
+
+<para>
+&cv-MSVC_SCRIPT_ARGS; is available for msvc batch file arguments that do not have first-class support
+via construction variables or when there is an issue with the appropriate construction variable validation.
+When available, it is recommended to use the appropriate construction variables (e.g., &cv-link-MSVC_TOOLSET_VERSION;)
+rather than &cv-MSVC_SCRIPT_ARGS; arguments.
+</para>
+
+<para>
+The valid values for &cv-MSVC_SCRIPT_ARGS; are: <literal>None</literal>, a string,
+or a list of strings.
+</para>
+
+<para>
+The &cv-MSVC_SCRIPT_ARGS; value is converted to a scalar string (i.e., "flattened").
+The resulting scalar string, if not empty, is passed as an argument to the msvc batch file determined
+via autodetection subject to the validation conditions listed below.
+</para>
+
+<para>
+&cv-MSVC_SCRIPT_ARGS; is ignored when the value is <literal>None</literal> and when the
+result from argument conversion is an empty string. The validation conditions below do not apply.
+</para>
+
+<para>
+An exception is raised when any of the following conditions are satisfied:
+<itemizedlist>
+
+<listitem><para>
+&cv-MSVC_SCRIPT_ARGS; is specified for Visual Studio 2013 and earlier.
+</para></listitem>
+
+<listitem><para>
+Multiple SDK version arguments (e.g., <literal>'10.0.20348.0'</literal>) are specified
+in &cv-MSVC_SCRIPT_ARGS;.
+</para></listitem>
+
+<listitem><para>
+&cv-link-MSVC_SDK_VERSION; is specified and an SDK version argument
+(e.g., <literal>'10.0.20348.0'</literal>) is specified in &cv-MSVC_SCRIPT_ARGS;.
+Multiple SDK version declarations via &cv-link-MSVC_SDK_VERSION; and &cv-MSVC_SCRIPT_ARGS;
+are not allowed.
+</para></listitem>
+
+<listitem><para>
+Multiple toolset version arguments (e.g., <literal>'-vcvars_ver=14.29'</literal>)
+are specified in &cv-MSVC_SCRIPT_ARGS;.
+</para></listitem>
+
+<listitem><para>
+&cv-link-MSVC_TOOLSET_VERSION; is specified and a toolset version argument
+(e.g., <literal>'-vcvars_ver=14.29'</literal>) is specified in &cv-MSVC_SCRIPT_ARGS;.
+Multiple toolset version declarations via &cv-link-MSVC_TOOLSET_VERSION; and
+&cv-MSVC_SCRIPT_ARGS; are not allowed.
+</para></listitem>
+
+<listitem><para>
+Multiple spectre library arguments (e.g., <literal>'-vcvars_spectre_libs=spectre'</literal>)
+are specified in &cv-MSVC_SCRIPT_ARGS;.
+</para></listitem>
+
+<listitem><para>
+&cv-link-MSVC_SPECTRE_LIBS; is enabled and a spectre library argument
+(e.g., <literal>'-vcvars_spectre_libs=spectre'</literal>) is specified in
+&cv-MSVC_SCRIPT_ARGS;. Multiple spectre library declarations via &cv-link-MSVC_SPECTRE_LIBS;
+and &cv-MSVC_SCRIPT_ARGS; are not allowed.
+</para></listitem>
+
+<listitem><para>
+Multiple UWP arguments (e.g., <literal>uwp</literal> or <literal>store</literal>) are specified
+in &cv-MSVC_SCRIPT_ARGS;.
+</para></listitem>
+
+<listitem><para>
+&cv-link-MSVC_UWP_APP; is enabled and a UWP argument (e.g., <literal>uwp</literal> or
+<literal>store</literal>) is specified in &cv-MSVC_SCRIPT_ARGS;. Multiple UWP declarations
+via &cv-link-MSVC_UWP_APP; and &cv-MSVC_SCRIPT_ARGS; are not allowed.
+</para></listitem>
+
+</itemizedlist>
+</para>
+
+<para>
+Example 1 - A Visual Studio 2022 build with an SDK version and a toolset version
+specified with a string argument:
+<example_commands>
+env = Environment(MSVC_VERSION='14.3', MSVC_SCRIPT_ARGS='10.0.20348.0 -vcvars_ver=14.29.30133')
+</example_commands>
+</para>
+
+<para>
+Example 2 - A Visual Studio 2022 build with an SDK version and a toolset version
+specified with a list argument:
+<example_commands>
+env = Environment(MSVC_VERSION='14.3', MSVC_SCRIPT_ARGS=['10.0.20348.0', '-vcvars_ver=14.29.30133'])
+</example_commands>
+</para>
+
+<para>
+Important usage details:
+<itemizedlist>
+
+<listitem><para>
+&cv-MSVC_SCRIPT_ARGS; must be passed as an argument to the &f-link-Environment;
+constructor when an msvc tool (e.g., &t-link-msvc;, &t-link-msvs;, etc.) is
+loaded via the default tools list or via a tools list passed to the
+&f-link-Environment; constructor.
+Otherwise, &cv-MSVC_SCRIPT_ARGS; must be set before the first msvc tool is
+loaded into the environment.
+</para></listitem>
+
+<listitem><para>
+Other than checking for multiple declarations as described above, &cv-MSVC_SCRIPT_ARGS; arguments
+are not validated.
+</para></listitem>
+
+<listitem><para>
+<emphasis>
+Erroneous, inconsistent, and/or version incompatible &cv-MSVC_SCRIPT_ARGS; arguments are likely
+to result in build failures for reasons that are not readily apparent and may be difficult to diagnose.
+</emphasis>
+The burden is on the user to ensure that the arguments provided to the msvc batch file are valid, consistent
+and compatible with the version of msvc selected.
+</para></listitem>
+
+</itemizedlist>
+</para>
+
+</listitem>
+ </varlistentry>
+ <varlistentry id="cv-MSVC_SCRIPTERROR_POLICY">
+ <term>
+ <envar>MSVC_SCRIPTERROR_POLICY</envar>
+ </term>
+ <listitem><para>
+Specify the &scons; behavior when Microsoft Visual C/C++ batch file errors are detected.
+</para>
+
+<para>
+The &cv-MSVC_SCRIPTERROR_POLICY; specifies the &scons; behavior when msvc batch file errors are
+detected.
+When &cv-MSVC_SCRIPTERROR_POLICY; is not specified, the default &scons; behavior is to suppress
+msvc batch file error messages.
+</para>
+<para>
+The root cause of msvc build failures may be difficult to diagnose. In these situations, setting
+the &scons; behavior to issue a warning when msvc batch file errors are detected <emphasis>may</emphasis>
+produce additional diagnostic information.
+</para>
+
+<para>
+The valid values for &cv-MSVC_SCRIPTERROR_POLICY; and the corresponding &scons; behavior are:
+</para>
+
+<variablelist>
+
+<varlistentry>
+<term><parameter>'Error' or 'Exception'</parameter></term>
+<listitem>
+<para>
+Raise an exception when msvc batch file errors are detected.
+</para>
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term><parameter>'Warning' or 'Warn'</parameter></term>
+<listitem>
+<para>
+Issue a warning when msvc batch file errors are detected.
+</para>
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term><parameter>'Ignore' or 'Suppress'</parameter></term>
+<listitem>
+<para>
+Suppress msvc batch file error messages.
+</para>
+</listitem>
+</varlistentry>
+
+</variablelist>
+
+<para>
+Note: in addition to the camel case values shown above, lower case and upper case values are accepted as well.
+</para>
+
+<para>
+Example 1 - A Visual Studio 2022 build with user-defined script arguments:
+<example_commands>
+env = environment(MSVC_VERSION='14.3', MSVC_SCRIPT_ARGS=['8.1', 'store', '-vcvars_ver=14.1'])
+env.Program('hello', ['hello.c'], CCFLAGS='/MD', LIBS=['kernel32', 'user32', 'runtimeobject'])
+</example_commands>
+</para>
+
+<para>
+Example 1 - Output fragment:
+<example_commands>
+...
+link /nologo /OUT:_build001\hello.exe kernel32.lib user32.lib runtimeobject.lib _build001\hello.obj
+LINK : fatal error LNK1104: cannot open file 'MSVCRT.lib'
+...
+</example_commands>
+</para>
+
+<para>
+Example 2 - A Visual Studio 2022 build with user-defined script arguments and the script error policy set
+to issue a warning when msvc batch file errors are detected:
+<example_commands>
+env = environment(MSVC_VERSION='14.3', MSVC_SCRIPT_ARGS=['8.1', 'store', '-vcvars_ver=14.1'], MSVC_SCRIPTERROR_POLICY='warn')
+env.Program('hello', ['hello.c'], CCFLAGS='/MD', LIBS=['kernel32', 'user32', 'runtimeobject'])
+</example_commands>
+</para>
+
+<para>
+Example 2 - Output fragment:
+<example_commands>
+...
+scons: warning: vc script errors detected:
+[ERROR:vcvars.bat] The UWP Application Platform requires a Windows 10 SDK.
+[ERROR:vcvars.bat] WindowsSdkDir = "C:\Program Files (x86)\Windows Kits\8.1\"
+[ERROR:vcvars.bat] host/target architecture is not supported : { x64 , x64 }
+...
+link /nologo /OUT:_build001\hello.exe kernel32.lib user32.lib runtimeobject.lib _build001\hello.obj
+LINK : fatal error LNK1104: cannot open file 'MSVCRT.lib'
+</example_commands>
+</para>
+
+<para>
+Important usage details:
+<itemizedlist>
+
+<listitem><para>
+&cv-MSVC_SCRIPTERROR_POLICY; must be passed as an argument to the &f-link-Environment;
+constructor when an msvc tool (e.g., &t-link-msvc;, &t-link-msvs;, etc.) is
+loaded via the default tools list or via a tools list passed to the
+&f-link-Environment; constructor.
+Otherwise, &cv-MSVC_SCRIPTERROR_POLICY; must be set before the first msvc tool is
+loaded into the environment.
+</para></listitem>
+
+<listitem><para>
+Due to &scons; implementation details, not all Windows system environment variables are propagated
+to the environment in which the msvc batch file is executed. Depending on Visual Studio version
+and installation options, non-fatal msvc batch file error messages may be generated for ancillary
+tools which may not affect builds with the msvc compiler. For this reason, caution is recommended
+when setting the script error policy to raise an exception (e.g., <literal>'Error'</literal>).
+</para></listitem>
+
+</itemizedlist>
+</para>
+
+</listitem>
+ </varlistentry>
+ <varlistentry id="cv-MSVC_SDK_VERSION">
+ <term>
+ <envar>MSVC_SDK_VERSION</envar>
+ </term>
+ <listitem><para>
+Build with a specific version of the Microsoft Software Development Kit (SDK).
+</para>
+
+<para>
+The valid values for &cv-MSVC_SDK_VERSION; are: <literal>None</literal>
+or a string containing the requested SDK version (e.g., <literal>'10.0.20348.0'</literal>).
+</para>
+
+<para>
+&cv-MSVC_SDK_VERSION; is ignored when the value is <literal>None</literal> and when
+the value is an empty string. The validation conditions below do not apply.
+</para>
+
+<para>
+An exception is raised when any of the following conditions are satisfied:
+<itemizedlist>
+
+<listitem><para>
+&cv-MSVC_SDK_VERSION; is specified for Visual Studio 2013 and earlier.
+</para></listitem>
+
+<listitem><para>
+&cv-MSVC_SDK_VERSION; is specified and an SDK version argument is specified in
+&cv-link-MSVC_SCRIPT_ARGS;. Multiple SDK version declarations via &cv-MSVC_SDK_VERSION;
+and &cv-link-MSVC_SCRIPT_ARGS; are not allowed.
+</para></listitem>
+
+<listitem><para>
+The &cv-MSVC_SDK_VERSION; specified does not match any of the supported formats:
+<itemizedlist>
+<listitem><para>
+<literal>'10.0.XXXXX.Y'</literal> [SDK 10.0]
+</para></listitem>
+<listitem><para>
+<literal>'8.1'</literal> [SDK 8.1]
+</para></listitem>
+</itemizedlist>
+</para></listitem>
+
+<listitem><para>
+The system folder for the corresponding &cv-MSVC_SDK_VERSION; version is not found.
+The requested SDK version does not appear to be installed.
+</para></listitem>
+
+<listitem><para>
+The &cv-MSVC_SDK_VERSION; version does not appear to support the requested platform
+type (i.e., <literal>UWP</literal> or <literal>Desktop</literal>). The requested SDK version
+platform type components do not appear to be installed.
+</para></listitem>
+
+<listitem><para>
+The &cv-MSVC_SDK_VERSION; version is <literal>8.1</literal>, the platform type is
+<literal>UWP</literal>, and the build tools selected are from Visual Studio 2017
+and later (i.e., &cv-link-MSVC_VERSION; must be '14.0' or &cv-link-MSVC_TOOLSET_VERSION;
+must be '14.0').
+</para></listitem>
+
+</itemizedlist>
+</para>
+
+<para>
+Example 1 - A Visual Studio 2022 build with a specific Windows SDK version:
+<example_commands>
+env = Environment(MSVC_VERSION='14.3', MSVC_SDK_VERSION='10.0.20348.0')
+</example_commands>
+</para>
+
+<para>
+Example 2 - A Visual Studio 2022 build with a specific SDK version for the Universal Windows Platform:
+<example_commands>
+env = Environment(MSVC_VERSION='14.3', MSVC_SDK_VERSION='10.0.20348.0', MSVC_UWP_APP=True)
+</example_commands>
+</para>
+
+<para>
+Important usage details:
+<itemizedlist>
+
+<listitem><para>
+&cv-MSVC_SDK_VERSION; must be passed as an argument to the &f-link-Environment;
+constructor when an msvc tool (e.g., &t-link-msvc;, &t-link-msvs;, etc.) is
+loaded via the default tools list or via a tools list passed to the
+&f-link-Environment; constructor.
+Otherwise, &cv-MSVC_SDK_VERSION; must be set before the first msvc tool is
+loaded into the environment.
+</para></listitem>
+
+<listitem>
+<para><emphasis>
+Should a SDK 10.0 version be installed that does not follow the naming scheme above, the
+SDK version will need to be specified via &cv-link-MSVC_SCRIPT_ARGS; until the version number
+validation format can be extended.
+</emphasis></para>
+</listitem>
+
+<listitem><para>
+Should an exception be raised indicating that the SDK version is not found, verify that
+the requested SDK version is installed with the necessary platform type components.
+</para></listitem>
+
+<listitem><para>
+There is a known issue with the Microsoft libraries when the target architecture is
+<literal>ARM64</literal> and a Windows 11 SDK (version <literal>'10.0.22000.0'</literal> and later) is used
+with the <literal>v141</literal> build tools and older <literal>v142</literal> toolsets
+(versions <literal>'14.28.29333'</literal> and earlier). Should build failures arise with these combinations
+of settings due to unresolved symbols in the Microsoft libraries, &cv-MSVC_SDK_VERSION; may be employed to
+specify a Windows 10 SDK (e.g., <literal>'10.0.20348.0'</literal>) for the build.
+</para></listitem>
+
+</itemizedlist>
+</para>
+
+</listitem>
+ </varlistentry>
+ <varlistentry id="cv-MSVC_SPECTRE_LIBS">
+ <term>
+ <envar>MSVC_SPECTRE_LIBS</envar>
+ </term>
+ <listitem><para>
+Build with the spectre-mitigated Visual C++ libraries.
+</para>
+
+<para>
+The valid values for &cv-MSVC_SPECTRE_LIBS; are: <literal>True</literal>,
+<literal>False</literal>, or <literal>None</literal>.
+</para>
+
+<para>
+When &cv-MSVC_SPECTRE_LIBS; is enabled (i.e., <literal>True</literal>),
+the Visual C++ environment will include the paths to the spectre-mitigated implementations
+of the Microsoft Visual C++ libraries.
+</para>
+
+<para>
+An exception is raised when any of the following conditions are satisfied:
+<itemizedlist>
+
+<listitem><para>
+&cv-MSVC_SPECTRE_LIBS; is enabled for Visual Studio 2015 and earlier.
+</para></listitem>
+
+<listitem><para>
+&cv-MSVC_SPECTRE_LIBS; is enabled and a spectre library argument is specified in
+&cv-link-MSVC_SCRIPT_ARGS;. Multiple spectre library declarations via &cv-MSVC_SPECTRE_LIBS;
+and &cv-link-MSVC_SCRIPT_ARGS; are not allowed.
+</para></listitem>
+
+<listitem><para>
+&cv-MSVC_SPECTRE_LIBS; is enabled and the platform type is <literal>UWP</literal>. There
+are no spectre-mitigated libraries for Universal Windows Platform (UWP) applications or
+components.
+</para></listitem>
+
+</itemizedlist>
+</para>
+
+<para>
+Example - A Visual Studio 2022 build with spectre mitigated Visual C++ libraries:
+<example_commands>
+env = Environment(MSVC_VERSION='14.3', MSVC_SPECTRE_LIBS=True)
+</example_commands>
+</para>
+
+<para>
+Important usage details:
+<itemizedlist>
+
+<listitem><para>
+&cv-MSVC_SPECTRE_LIBS; must be passed as an argument to the &f-link-Environment;
+constructor when an msvc tool (e.g., &t-link-msvc;, &t-link-msvs;, etc.) is
+loaded via the default tools list or via a tools list passed to the
+&f-link-Environment; constructor.
+Otherwise, &cv-MSVC_SPECTRE_LIBS; must be set before the first msvc tool is
+loaded into the environment.
+</para></listitem>
+
+<listitem><para>
+Additional compiler switches (e.g., <literal>/Qspectre</literal>) are necessary for including
+spectre mitigations when building user artifacts. Refer to the Visual Studio documentation for
+details.
+</para></listitem>
+
+<listitem><para>
+<emphasis>
+The existence of the spectre libraries host architecture and target architecture folders are not
+verified when &cv-MSVC_SPECTRE_LIBS; is enabled which could result in build failures.
+</emphasis>
+The burden is on the user to ensure the requisite libraries with spectre mitigations are installed.
+</para></listitem>
+
+</itemizedlist>
+</para>
+
+</listitem>
+ </varlistentry>
+ <varlistentry id="cv-MSVC_TOOLSET_VERSION">
+ <term>
+ <envar>MSVC_TOOLSET_VERSION</envar>
+ </term>
+ <listitem><para>
+Build with a specific Visual C++ toolset version.
+</para>
+
+<para><emphasis>
+Specifying &cv-MSVC_TOOLSET_VERSION; does not affect the autodetection and selection
+of msvc instances. The &cv-MSVC_TOOLSET_VERSION; is applied <emphasis>after</emphasis>
+an msvc instance is selected. This could be the default version of msvc if &cv-link-MSVC_VERSION;
+is not specified.
+</emphasis></para>
+
+<para>
+The valid values for &cv-MSVC_TOOLSET_VERSION; are: <literal>None</literal>
+or a string containing the requested toolset version (e.g., <literal>'14.29'</literal>).
+</para>
+
+<para>
+&cv-MSVC_TOOLSET_VERSION; is ignored when the value is <literal>None</literal> and when
+the value is an empty string. The validation conditions below do not apply.
+</para>
+
+<para>
+An exception is raised when any of the following conditions are satisfied:
+<itemizedlist>
+
+<listitem><para>
+&cv-MSVC_TOOLSET_VERSION; is specified for Visual Studio 2015 and earlier.
+</para></listitem>
+
+<listitem><para>
+&cv-MSVC_TOOLSET_VERSION; is specified and a toolset version argument is specified in
+&cv-link-MSVC_SCRIPT_ARGS;. Multiple toolset version declarations via &cv-MSVC_TOOLSET_VERSION;
+and &cv-link-MSVC_SCRIPT_ARGS; are not allowed.
+</para></listitem>
+
+<listitem>
+<para>
+The &cv-MSVC_TOOLSET_VERSION; specified does not match any of the supported formats:
+</para>
+
+<itemizedlist>
+
+<listitem><para>
+<literal>'XX.Y'</literal>
+</para></listitem>
+
+<listitem><para>
+<literal>'XX.YY'</literal>
+</para></listitem>
+
+<listitem><para>
+<literal>'XX.YY.ZZZZZ'</literal>
+</para></listitem>
+
+<listitem><para>
+<literal>'XX.YY.Z'</literal> to <literal>'XX.YY.ZZZZ'</literal>
+<emphasis>
+[&scons; extension not directly supported by the msvc batch files and may be removed in the future]
+</emphasis>
+</para></listitem>
+
+<listitem><para>
+<literal>'XX.YY.ZZ.N'</literal> [SxS format]
+</para></listitem>
+
+<listitem><para>
+<literal>'XX.YY.ZZ.NN'</literal> [SxS format]
+</para></listitem>
+
+</itemizedlist>
+
+</listitem>
+
+<listitem><para>
+The major msvc version prefix (i.e., <literal>'XX.Y'</literal>) of the &cv-MSVC_TOOLSET_VERSION; specified
+is for Visual Studio 2013 and earlier (e.g., <literal>'12.0'</literal>).
+</para></listitem>
+
+<listitem><para>
+The major msvc version prefix (i.e., <literal>'XX.Y'</literal>) of the &cv-MSVC_TOOLSET_VERSION; specified
+is greater than the msvc version selected (e.g., <literal>'99.0'</literal>).
+</para></listitem>
+
+<listitem><para>
+A system folder for the corresponding &cv-MSVC_TOOLSET_VERSION; version is not found.
+The requested toolset version does not appear to be installed.
+</para></listitem>
+
+</itemizedlist>
+</para>
+
+<para>
+Toolset selection details:
+<itemizedlist>
+
+<listitem><para>
+When &cv-MSVC_TOOLSET_VERSION; is not an SxS version number or a full toolset version number:
+the first toolset version, ranked in descending order, that matches the &cv-MSVC_TOOLSET_VERSION;
+prefix is selected.
+</para></listitem>
+
+<listitem><para>
+When &cv-MSVC_TOOLSET_VERSION; is specified using the major msvc version prefix
+(i.e., <literal>'XX.Y'</literal>) and the major msvc version is that of the latest release of
+Visual Studio, the selected toolset version may not be the same as the default Visual C++ toolset version.
+</para><para>
+In the latest release of Visual Studio, the default Visual C++ toolset version is not necessarily the
+toolset with the largest version number.
+</para></listitem>
+
+</itemizedlist>
+</para>
+
+<para>
+Example 1 - A default Visual Studio build with a partial toolset version specified:
+<example_commands>
+env = Environment(MSVC_TOOLSET_VERSION='14.2')
+</example_commands>
+</para>
+
+<para>
+Example 2 - A default Visual Studio build with a partial toolset version specified:
+<example_commands>
+env = Environment(MSVC_TOOLSET_VERSION='14.29')
+</example_commands>
+</para>
+
+<para>
+Example 3 - A Visual Studio 2022 build with a full toolset version specified:
+<example_commands>
+env = Environment(MSVC_VERSION='14.3', MSVC_TOOLSET_VERSION='14.29.30133')
+</example_commands>
+</para>
+
+<para>
+Example 4 - A Visual Studio 2022 build with an SxS toolset version specified:
+<example_commands>
+env = Environment(MSVC_VERSION='14.3', MSVC_TOOLSET_VERSION='14.29.16.11')
+</example_commands>
+</para>
+
+<para>
+Important usage details:
+<itemizedlist>
+
+<listitem><para>
+&cv-MSVC_TOOLSET_VERSION; must be passed as an argument to the &f-link-Environment;
+constructor when an msvc tool (e.g., &t-link-msvc;, &t-link-msvs;, etc.) is
+loaded via the default tools list or via a tools list passed to the
+&f-link-Environment; constructor.
+Otherwise, &cv-MSVC_TOOLSET_VERSION; must be set before the first msvc tool is
+loaded into the environment.
+</para></listitem>
+
+<listitem><para>
+<emphasis>
+The existence of the toolset host architecture and target architecture folders are not verified
+when &cv-MSVC_TOOLSET_VERSION; is specified which could result in build failures.
+</emphasis>
+The burden is on the user to ensure the requisite toolset target architecture build tools are installed.
+</para></listitem>
+
+</itemizedlist>
+</para>
</listitem>
</varlistentry>
@@ -4806,7 +5467,7 @@ is, if you are sure everything is set correctly already and
you don't want &SCons; to change anything.
</para>
<para>
-&cv-MSVC_USE_SCRIPT; overrides &cv-link-MSVC_VERSION; and &cv-link-TARGET_ARCH;.
+&cv-MSVC_USE_SCRIPT; ignores &cv-link-MSVC_VERSION; and &cv-link-TARGET_ARCH;.
</para>
</listitem>
</varlistentry>
@@ -4879,9 +5540,28 @@ env = Environment(MSVC_VERSION='8.0', MSVC_USE_SETTINGS=msvc_use_settings)
</para>
<para>
-Note: the dictionary content requirements are based on the internal msvc implementation and
-therefore may change at any time. The burden is on the user to ensure the dictionary contents
-are minimally sufficient to ensure successful builds.
+Important usage details:
+<itemizedlist>
+
+<listitem><para>
+&cv-MSVC_USE_SETTINGS; must be passed as an argument to the &f-link-Environment;
+constructor when an msvc tool (e.g., &t-link-msvc;, &t-link-msvs;, etc.) is
+loaded via the default tools list or via a tools list passed to the
+&f-link-Environment; constructor.
+Otherwise, &cv-MSVC_USE_SETTINGS; must be set before the first msvc tool is
+loaded into the environment.
+</para></listitem>
+
+<listitem><para>
+<emphasis>
+The dictionary content requirements are based on the internal msvc implementation and
+therefore may change at any time.
+</emphasis>
+The burden is on the user to ensure the dictionary contents are minimally sufficient to
+ensure successful builds.
+</para></listitem>
+
+</itemizedlist>
</para>
</listitem>
@@ -4891,21 +5571,69 @@ are minimally sufficient to ensure successful builds.
<envar>MSVC_UWP_APP</envar>
</term>
<listitem><para>
-Build libraries for a Universal Windows Platform (UWP) Application.
+Build with the Universal Windows Platform (UWP) application Visual C++ libraries.
+</para>
+
+<para>
+The valid values for &cv-MSVC_UWP_APP; are: <literal>True</literal>,
+<literal>'1'</literal>, <literal>False</literal>, <literal>'0'</literal>,
+or <literal>None</literal>.
</para>
<para>
-If &cv-MSVC_UWP_APP; is set, the Visual C++ environment will be set up to point
+When &cv-MSVC_UWP_APP; is enabled (i.e., <literal>True</literal> or
+<literal>'1'</literal>), the Visual C++ environment will be set up to point
to the Windows Store compatible libraries and Visual C++ runtimes. In doing so,
any libraries that are built will be able to be used in a UWP App and published
to the Windows Store.
-This flag will only have an effect with Visual Studio 2015 or later.
-This variable must be passed as an argument to the Environment()
-constructor; setting it later has no effect.
+<!-- This flag will only have an effect with Visual Studio 2015 or later. -->
+<!-- This variable must be passed as an argument to the Environment()
+constructor; setting it later has no effect. -->
</para>
<para>
-Valid values are '1' or '0'
+An exception is raised when any of the following conditions are satisfied:
+<itemizedlist>
+<listitem><para>
+&cv-MSVC_UWP_APP; is enabled for Visual Studio 2013 and earlier.
+</para></listitem>
+<listitem><para>
+&cv-MSVC_UWP_APP; is enabled and a UWP argument is specified in
+&cv-link-MSVC_SCRIPT_ARGS;. Multiple UWP declarations via &cv-MSVC_UWP_APP;
+and &cv-link-MSVC_SCRIPT_ARGS; are not allowed.
+</para></listitem>
+</itemizedlist>
+</para>
+
+<para>
+Example - A Visual Studio 2022 build for the Universal Windows Platform:
+<example_commands>
+env = Environment(MSVC_VERSION='14.3', MSVC_UWP_APP=True)
+</example_commands>
+</para>
+
+<para>
+Important usage details:
+<itemizedlist>
+
+<listitem><para>
+&cv-MSVC_UWP_APP; must be passed as an argument to the &f-link-Environment;
+constructor when an msvc tool (e.g., &t-link-msvc;, &t-link-msvs;, etc.) is
+loaded via the default tools list or via a tools list passed to the
+&f-link-Environment; constructor.
+Otherwise, &cv-MSVC_UWP_APP; must be set before the first msvc tool is
+loaded into the environment.
+</para></listitem>
+
+<listitem><para>
+<emphasis>
+The existence of the UWP libraries is not verified when &cv-MSVC_UWP_APP; is enabled
+which could result in build failures.
+</emphasis>
+The burden is on the user to ensure the requisite UWP libraries are installed.
+</para></listitem>
+
+</itemizedlist>
</para>
</listitem>
@@ -4922,8 +5650,15 @@ Sets the preferred version of Microsoft Visual C/C++ to use.
If &cv-MSVC_VERSION; is not set, SCons will (by default) select the
latest version of Visual C/C++ installed on your system. If the
specified version isn't installed, tool initialization will fail.
-This variable must be passed as an argument to the &f-link-Environment;
-constructor; setting it later has no effect.
+</para>
+
+<para>
+&cv-MSVC_VERSION; must be passed as an argument to the &f-link-Environment;
+constructor when an msvc tool (e.g., &t-link-msvc;, &t-link-msvs;, etc.) is
+loaded via the default tools list or via a tools list passed to the
+&f-link-Environment; constructor.
+Otherwise, &cv-MSVC_VERSION; must be set before the first msvc tool is
+loaded into the environment.
</para>
<para>
@@ -6005,44 +6740,65 @@ A Python function used to print the command lines as they are executed
or
<option>-s</option>
options or their equivalents).
-The function should take four arguments:
+The function must accept four arguments:
<varname>s</varname>,
-the command being executed (a string),
<varname>target</varname>,
-the target being built (file node, list, or string name(s)),
+<varname>source</varname> and
+<varname>env</varname>.
+<varname>s</varname>
+is a string showing the command being executed,
+<varname>target</varname>,
+is the target being built (file node, list, or string name(s)),
<varname>source</varname>,
-the source(s) used (file node, list, or string name(s)), and
-<varname>env</varname>,
-the environment being used.
+is the source(s) used (file node, list, or string name(s)),
+and <varname>env</varname>
+is the environment being used.
</para>
<para>
-The function must do the printing itself. The default implementation,
-used if this variable is not set or is None, is:
+The function must do the printing itself.
+The default implementation,
+used if this variable is not set or is <constant>None</constant>,
+is to just print the string, as in:
</para>
<example_commands>
def print_cmd_line(s, target, source, env):
- sys.stdout.write(s + "\n")
+ sys.stdout.write(s + "\n")
</example_commands>
<para>
-Here's an example of a more interesting function:
+Here is an example of a more interesting function:
</para>
<example_commands>
def print_cmd_line(s, target, source, env):
- sys.stdout.write("Building %s -&gt; %s...\n" %
- (' and '.join([str(x) for x in source]),
- ' and '.join([str(x) for x in target])))
-env=Environment(PRINT_CMD_LINE_FUNC=print_cmd_line)
-env.Program('foo', 'foo.c')
+ sys.stdout.write(
+ "Building %s -&gt; %s...\n"
+ % (
+ ' and '.join([str(x) for x in source]),
+ ' and '.join([str(x) for x in target]),
+ )
+ )
+
+env = Environment(PRINT_CMD_LINE_FUNC=print_cmd_line)
+env.Program('foo', ['foo.c', 'bar.c'])
</example_commands>
<para>
-This just prints "Building <varname>targetname</varname> from <varname>sourcename</varname>..." instead
-of the actual commands.
-Such a function could also log the actual commands to a log file,
-for example.
+This prints:
+</para>
+
+<screen>
+...
+scons: Building targets ...
+Building bar.c -&gt; bar.o...
+Building foo.c -&gt; foo.o...
+Building foo.o and bar.o -&gt; foo...
+scons: done building targets.
+</screen>
+
+<para>
+Another example could be a function that logs the actual commands to a file.
</para>
</listitem>
</varlistentry>
@@ -7014,9 +7770,9 @@ targets.
def custom_shell_env(env, target, source, shell_env):
"""customize shell_env if desired"""
if str(target[0]) == 'special_target':
- shell_env['SPECIAL_VAR'] = env.subst('SOME_VAR', target=target, source=source)
+ shell_env['SPECIAL_VAR'] = env.subst('SOME_VAR', target=target, source=source)
return shell_env
-
+
env["SHELL_ENV_GENERATORS"] = [custom_shell_env]
</example_commands>
@@ -7090,7 +7846,7 @@ Options that are passed to the Fortran 03 compiler
to generated shared-library objects.
You only need to set &cv-link-SHF03FLAGS; if you need to define specific
user options for Fortran 03 files.
-You should normally set the &cv-link-SHFORTRANFLAGS; variable,
+You should normally set the &cv-link-FORTRANCOMMONFLAGS; variable,
which specifies the user-specified options
passed to the default Fortran compiler
for all Fortran versions.
@@ -7178,7 +7934,7 @@ Options that are passed to the Fortran 08 compiler
to generated shared-library objects.
You only need to set &cv-link-SHF08FLAGS; if you need to define specific
user options for Fortran 08 files.
-You should normally set the &cv-link-SHFORTRANFLAGS; variable,
+You should normally set the &cv-link-FORTRANCOMMONFLAGS; variable,
which specifies the user-specified options
passed to the default Fortran compiler
for all Fortran versions.
@@ -7266,7 +8022,7 @@ Options that are passed to the Fortran 77 compiler
to generated shared-library objects.
You only need to set &cv-link-SHF77FLAGS; if you need to define specific
user options for Fortran 77 files.
-You should normally set the &cv-link-SHFORTRANFLAGS; variable,
+You should normally set the &cv-link-FORTRANCOMMONFLAGS; variable,
which specifies the user-specified options
passed to the default Fortran compiler
for all Fortran versions.
@@ -7354,7 +8110,7 @@ Options that are passed to the Fortran 90 compiler
to generated shared-library objects.
You only need to set &cv-link-SHF90FLAGS; if you need to define specific
user options for Fortran 90 files.
-You should normally set the &cv-link-SHFORTRANFLAGS; variable,
+You should normally set the &cv-link-FORTRANCOMMONFLAGS; variable,
which specifies the user-specified options
passed to the default Fortran compiler
for all Fortran versions.
@@ -7442,7 +8198,7 @@ Options that are passed to the Fortran 95 compiler
to generated shared-library objects.
You only need to set &cv-link-SHF95FLAGS; if you need to define specific
user options for Fortran 95 files.
-You should normally set the &cv-link-SHFORTRANFLAGS; variable,
+You should normally set the &cv-link-FORTRANCOMMONFLAGS; variable,
which specifies the user-specified options
passed to the default Fortran compiler
for all Fortran versions.
@@ -7816,7 +8572,7 @@ which would be a symlink and point to <filename>libtest.so.0.1.2</filename>
</term>
<listitem><para>
A command interpreter function that will be called to execute command line
-strings. The function must expect the following arguments:
+strings. The function must accept five arguments:
</para>
<example_commands>
@@ -7824,18 +8580,18 @@ def spawn(shell, escape, cmd, args, env):
</example_commands>
<para>
-<varname>sh</varname>
-is a string naming the shell program to use.
+<varname>shell</varname>
+is a string naming the shell program to use,
<varname>escape</varname>
is a function that can be called to escape shell special characters in
-the command line.
+the command line,
<varname>cmd</varname>
-is the path to the command to be executed.
+is the path to the command to be executed,
<varname>args</varname>
-is the arguments to the command.
+holds the arguments to the command and
<varname>env</varname>
-is a dictionary of the environment variables
-in which the command should be executed.
+is a dictionary of environment variables
+defining the execution environment in which the command should be executed.
</para>
</listitem>
</varlistentry>
diff --git a/doc/generated/variables.mod b/doc/generated/variables.mod
index c5cd0cdf1..5d898878c 100644
--- a/doc/generated/variables.mod
+++ b/doc/generated/variables.mod
@@ -186,6 +186,7 @@ THIS IS AN AUTOMATICALLY-GENERATED FILE. DO NOT EDIT.
<!ENTITY cv-File "<envar xmlns='http://www.scons.org/dbxsd/v1.0'>$File</envar>">
<!ENTITY cv-FORTRAN "<envar xmlns='http://www.scons.org/dbxsd/v1.0'>$FORTRAN</envar>">
<!ENTITY cv-FORTRANCOM "<envar xmlns='http://www.scons.org/dbxsd/v1.0'>$FORTRANCOM</envar>">
+<!ENTITY cv-FORTRANCOMMONFLAGS "<envar xmlns='http://www.scons.org/dbxsd/v1.0'>$FORTRANCOMMONFLAGS</envar>">
<!ENTITY cv-FORTRANCOMSTR "<envar xmlns='http://www.scons.org/dbxsd/v1.0'>$FORTRANCOMSTR</envar>">
<!ENTITY cv-FORTRANFILESUFFIXES "<envar xmlns='http://www.scons.org/dbxsd/v1.0'>$FORTRANFILESUFFIXES</envar>">
<!ENTITY cv-FORTRANFLAGS "<envar xmlns='http://www.scons.org/dbxsd/v1.0'>$FORTRANFLAGS</envar>">
@@ -321,6 +322,11 @@ THIS IS AN AUTOMATICALLY-GENERATED FILE. DO NOT EDIT.
<!ENTITY cv-MSSDK_VERSION "<envar xmlns='http://www.scons.org/dbxsd/v1.0'>$MSSDK_VERSION</envar>">
<!ENTITY cv-MSVC_BATCH "<envar xmlns='http://www.scons.org/dbxsd/v1.0'>$MSVC_BATCH</envar>">
<!ENTITY cv-MSVC_NOTFOUND_POLICY "<envar xmlns='http://www.scons.org/dbxsd/v1.0'>$MSVC_NOTFOUND_POLICY</envar>">
+<!ENTITY cv-MSVC_SCRIPT_ARGS "<envar xmlns='http://www.scons.org/dbxsd/v1.0'>$MSVC_SCRIPT_ARGS</envar>">
+<!ENTITY cv-MSVC_SCRIPTERROR_POLICY "<envar xmlns='http://www.scons.org/dbxsd/v1.0'>$MSVC_SCRIPTERROR_POLICY</envar>">
+<!ENTITY cv-MSVC_SDK_VERSION "<envar xmlns='http://www.scons.org/dbxsd/v1.0'>$MSVC_SDK_VERSION</envar>">
+<!ENTITY cv-MSVC_SPECTRE_LIBS "<envar xmlns='http://www.scons.org/dbxsd/v1.0'>$MSVC_SPECTRE_LIBS</envar>">
+<!ENTITY cv-MSVC_TOOLSET_VERSION "<envar xmlns='http://www.scons.org/dbxsd/v1.0'>$MSVC_TOOLSET_VERSION</envar>">
<!ENTITY cv-MSVC_USE_SCRIPT "<envar xmlns='http://www.scons.org/dbxsd/v1.0'>$MSVC_USE_SCRIPT</envar>">
<!ENTITY cv-MSVC_USE_SCRIPT_ARGS "<envar xmlns='http://www.scons.org/dbxsd/v1.0'>$MSVC_USE_SCRIPT_ARGS</envar>">
<!ENTITY cv-MSVC_USE_SETTINGS "<envar xmlns='http://www.scons.org/dbxsd/v1.0'>$MSVC_USE_SETTINGS</envar>">
@@ -850,6 +856,7 @@ THIS IS AN AUTOMATICALLY-GENERATED FILE. DO NOT EDIT.
<!ENTITY cv-link-File "<link linkend='cv-File' xmlns='http://www.scons.org/dbxsd/v1.0'><envar>$File</envar></link>">
<!ENTITY cv-link-FORTRAN "<link linkend='cv-FORTRAN' xmlns='http://www.scons.org/dbxsd/v1.0'><envar>$FORTRAN</envar></link>">
<!ENTITY cv-link-FORTRANCOM "<link linkend='cv-FORTRANCOM' xmlns='http://www.scons.org/dbxsd/v1.0'><envar>$FORTRANCOM</envar></link>">
+<!ENTITY cv-link-FORTRANCOMMONFLAGS "<link linkend='cv-FORTRANCOMMONFLAGS' xmlns='http://www.scons.org/dbxsd/v1.0'><envar>$FORTRANCOMMONFLAGS</envar></link>">
<!ENTITY cv-link-FORTRANCOMSTR "<link linkend='cv-FORTRANCOMSTR' xmlns='http://www.scons.org/dbxsd/v1.0'><envar>$FORTRANCOMSTR</envar></link>">
<!ENTITY cv-link-FORTRANFILESUFFIXES "<link linkend='cv-FORTRANFILESUFFIXES' xmlns='http://www.scons.org/dbxsd/v1.0'><envar>$FORTRANFILESUFFIXES</envar></link>">
<!ENTITY cv-link-FORTRANFLAGS "<link linkend='cv-FORTRANFLAGS' xmlns='http://www.scons.org/dbxsd/v1.0'><envar>$FORTRANFLAGS</envar></link>">
@@ -985,6 +992,11 @@ THIS IS AN AUTOMATICALLY-GENERATED FILE. DO NOT EDIT.
<!ENTITY cv-link-MSSDK_VERSION "<link linkend='cv-MSSDK_VERSION' xmlns='http://www.scons.org/dbxsd/v1.0'><envar>$MSSDK_VERSION</envar></link>">
<!ENTITY cv-link-MSVC_BATCH "<link linkend='cv-MSVC_BATCH' xmlns='http://www.scons.org/dbxsd/v1.0'><envar>$MSVC_BATCH</envar></link>">
<!ENTITY cv-link-MSVC_NOTFOUND_POLICY "<link linkend='cv-MSVC_NOTFOUND_POLICY' xmlns='http://www.scons.org/dbxsd/v1.0'><envar>$MSVC_NOTFOUND_POLICY</envar></link>">
+<!ENTITY cv-link-MSVC_SCRIPT_ARGS "<link linkend='cv-MSVC_SCRIPT_ARGS' xmlns='http://www.scons.org/dbxsd/v1.0'><envar>$MSVC_SCRIPT_ARGS</envar></link>">
+<!ENTITY cv-link-MSVC_SCRIPTERROR_POLICY "<link linkend='cv-MSVC_SCRIPTERROR_POLICY' xmlns='http://www.scons.org/dbxsd/v1.0'><envar>$MSVC_SCRIPTERROR_POLICY</envar></link>">
+<!ENTITY cv-link-MSVC_SDK_VERSION "<link linkend='cv-MSVC_SDK_VERSION' xmlns='http://www.scons.org/dbxsd/v1.0'><envar>$MSVC_SDK_VERSION</envar></link>">
+<!ENTITY cv-link-MSVC_SPECTRE_LIBS "<link linkend='cv-MSVC_SPECTRE_LIBS' xmlns='http://www.scons.org/dbxsd/v1.0'><envar>$MSVC_SPECTRE_LIBS</envar></link>">
+<!ENTITY cv-link-MSVC_TOOLSET_VERSION "<link linkend='cv-MSVC_TOOLSET_VERSION' xmlns='http://www.scons.org/dbxsd/v1.0'><envar>$MSVC_TOOLSET_VERSION</envar></link>">
<!ENTITY cv-link-MSVC_USE_SCRIPT "<link linkend='cv-MSVC_USE_SCRIPT' xmlns='http://www.scons.org/dbxsd/v1.0'><envar>$MSVC_USE_SCRIPT</envar></link>">
<!ENTITY cv-link-MSVC_USE_SCRIPT_ARGS "<link linkend='cv-MSVC_USE_SCRIPT_ARGS' xmlns='http://www.scons.org/dbxsd/v1.0'><envar>$MSVC_USE_SCRIPT_ARGS</envar></link>">
<!ENTITY cv-link-MSVC_USE_SETTINGS "<link linkend='cv-MSVC_USE_SETTINGS' xmlns='http://www.scons.org/dbxsd/v1.0'><envar>$MSVC_USE_SETTINGS</envar></link>">
diff --git a/requirements.txt b/requirements.txt
index 133b5bd91..f79520dea 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -4,7 +4,7 @@
# Can be used with twinecheck
# See: https://github.com/pypa/readme_renderer
readme-renderer
-sphinx
+sphinx<=5.0.0
sphinx_rtd_theme
rst2pdf
# for now keep pinning "known working" lxml,
diff --git a/test/MSVC/MSVC_NOTFOUND_POLICY.py b/test/MSVC/MSVC_NOTFOUND_POLICY.py
new file mode 100644
index 000000000..f5a8de29a
--- /dev/null
+++ b/test/MSVC/MSVC_NOTFOUND_POLICY.py
@@ -0,0 +1,126 @@
+# MIT License
+#
+# Copyright The SCons Foundation
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# 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.
+
+"""
+Test the MSVC_NOTFOUND_POLICY construction variable and functions.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.skip_if_not_msvc()
+
+import textwrap
+
+# Test construction variable with valid symbols
+test.write('SConstruct', textwrap.dedent(
+ """
+ env_list = []
+ DefaultEnvironment(tools=[])
+ for symbol in ['Error', 'Exception', 'Warn', 'Warning', 'Ignore', 'Suppress']:
+ for policy in [symbol, symbol.upper(), symbol.lower()]:
+ env = Environment(MSVC_NOTFOUND_POLICY=policy, tools=['msvc'])
+ env_list.append(env)
+ """
+))
+test.run(arguments='-Q -s', stdout='')
+
+# Test construction variable with invalid symbol
+test.write('SConstruct', textwrap.dedent(
+ """
+ env = Environment(MSVC_VERSION='12.9', MSVC_NOTFOUND_POLICY='Undefined', tools=['msvc'])
+ """
+))
+test.run(arguments='-Q -s', status=2, stderr=None)
+expect = "MSVCArgumentError: Value specified for MSVC_NOTFOUND_POLICY is not supported: 'Undefined'."
+test.must_contain_all(test.stderr(), expect)
+
+# Test environment construction with global policy
+test.write('SConstruct', textwrap.dedent(
+ """
+ from SCons.Tool.MSCommon import msvc_set_notfound_policy
+ msvc_set_notfound_policy('Exception')
+ env = Environment(MSVC_VERSION='12.9', tools=['msvc'])
+ """
+))
+test.run(arguments='-Q -s', status=2, stderr=None)
+expect = "MSVCVersionNotFound: MSVC version '12.9' was not found."
+test.must_contain_all(test.stderr(), expect)
+
+# Test environment construction with global policy and construction variable ignored
+test.write('SConstruct', textwrap.dedent(
+ """
+ from SCons.Tool.MSCommon import msvc_set_notfound_policy
+ msvc_set_notfound_policy('Exception')
+ env = Environment(MSVC_VERSION='12.9', MSVC_NOTFOUND_POLICY=None, tools=['msvc'])
+ """
+))
+test.run(arguments='-Q -s', status=2, stderr=None)
+expect = "MSVCVersionNotFound: MSVC version '12.9' was not found."
+test.must_contain_all(test.stderr(), expect)
+
+# Test environment construction with construction variable
+test.write('SConstruct', textwrap.dedent(
+ """
+ env = Environment(MSVC_VERSION='12.9', MSVC_NOTFOUND_POLICY='Error', tools=['msvc'])
+ """
+))
+test.run(arguments='-Q -s', status=2, stderr=None)
+expect = "MSVCVersionNotFound: MSVC version '12.9' was not found."
+test.must_contain_all(test.stderr(), expect)
+
+# Test environment construction with global policy
+test.write('SConstruct', textwrap.dedent(
+ """
+ from SCons.Tool.MSCommon import msvc_set_notfound_policy
+ msvc_set_notfound_policy('Warning')
+ env = Environment(MSVC_VERSION='12.9', tools=['msvc'])
+ """
+))
+test.run(arguments="-Q -s --warn=visual-c-missing .", status=0, stderr=None)
+expect = "scons: warning: MSVC version '12.9' was not found."
+test.must_contain_all(test.stderr(), expect)
+
+# Test environment construction with construction variable
+test.write('SConstruct', textwrap.dedent(
+ """
+ env = Environment(MSVC_VERSION='12.9', MSVC_NOTFOUND_POLICY='Warning', tools=['msvc'])
+ """
+))
+test.run(arguments="-Q -s --warn=visual-c-missing .", status=0, stderr=None)
+expect = "scons: warning: MSVC version '12.9' was not found."
+test.must_contain_all(test.stderr(), expect)
+
+# Test environment construction with construction variable (override global)
+test.write('SConstruct', textwrap.dedent(
+ """
+ from SCons.Tool.MSCommon import msvc_set_notfound_policy
+ msvc_set_notfound_policy('Exception')
+ env = Environment(MSVC_VERSION='12.9', MSVC_NOTFOUND_POLICY='Ignore', tools=['msvc'])
+ """
+))
+test.run(arguments='-Q -s', stdout='')
+
+test.pass_test()
+
diff --git a/test/MSVC/MSVC_SCRIPTERROR_POLICY.py b/test/MSVC/MSVC_SCRIPTERROR_POLICY.py
new file mode 100644
index 000000000..5aa1682ed
--- /dev/null
+++ b/test/MSVC/MSVC_SCRIPTERROR_POLICY.py
@@ -0,0 +1,141 @@
+# MIT License
+#
+# Copyright The SCons Foundation
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# 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.
+
+"""
+Test the MSVC_SCRIPTERROR_POLICY construction variable and functions.
+"""
+
+import TestSCons
+import textwrap
+
+from SCons.Tool.MSCommon.vc import get_installed_vcs_components
+
+test = TestSCons.TestSCons()
+
+test.skip_if_not_msvc()
+
+installed_versions = get_installed_vcs_components()
+
+default_version = installed_versions[0]
+
+# Test construction variable with valid symbols
+test.write('SConstruct', textwrap.dedent(
+ """
+ env_list = []
+ DefaultEnvironment(tools=[])
+ for symbol in ['Error', 'Exception', 'Warn', 'Warning', 'Ignore', 'Suppress']:
+ for policy in [symbol, symbol.upper(), symbol.lower()]:
+ env = Environment(MSVC_SCRIPTERROR_POLICY=policy, tools=['msvc'])
+ env_list.append(env)
+ """
+))
+test.run(arguments='-Q -s', stdout='')
+
+if default_version.msvc_vernum >= 14.1:
+ # Need VS2017 or later for MSVC_SCRIPT_ARGS
+
+ # Test environment construction with construction variable (invalid)
+ test.write('SConstruct', textwrap.dedent(
+ """
+ DefaultEnvironment(tools=[])
+ env = Environment(MSVC_SCRIPT_ARGS=['-thisdoesnotexist=somevalue'], MSVC_SCRIPTERROR_POLICY='Undefined', tools=['msvc'])
+ """
+ ))
+ test.run(arguments='-Q -s', status=2, stderr=None)
+ expect = "MSVCArgumentError: Value specified for MSVC_SCRIPTERROR_POLICY is not supported: 'Undefined'."
+ test.must_contain_all(test.stderr(), expect)
+
+ # Test environment construction with construction variable (override global)
+ test.write('SConstruct', textwrap.dedent(
+ """
+ from SCons.Tool.MSCommon import msvc_set_scripterror_policy
+ DefaultEnvironment(tools=[])
+ msvc_set_scripterror_policy('Exception')
+ env = Environment(MSVC_SCRIPT_ARGS=['-thisdoesnotexist=somevalue'], MSVC_SCRIPTERROR_POLICY='Ignore', tools=['msvc'])
+ """
+ ))
+ test.run(arguments='-Q -s', stdout='')
+
+ # Test environment construction with global policy
+ test.write('SConstruct', textwrap.dedent(
+ """
+ from SCons.Tool.MSCommon import msvc_set_scripterror_policy
+ DefaultEnvironment(tools=[])
+ msvc_set_scripterror_policy('Exception')
+ env = Environment(MSVC_SCRIPT_ARGS=['-thisdoesnotexist=somevalue'], tools=['msvc'])
+ """
+ ))
+ test.run(arguments='-Q -s', status=2, stderr=None)
+ expect = "MSVCScriptExecutionError: vc script errors detected:"
+ test.must_contain_all(test.stderr(), expect)
+
+ # Test environment construction with global policy and construction variable ignored
+ test.write('SConstruct', textwrap.dedent(
+ """
+ from SCons.Tool.MSCommon import msvc_set_scripterror_policy
+ DefaultEnvironment(tools=[])
+ msvc_set_scripterror_policy('Exception')
+ env = Environment(MSVC_SCRIPT_ARGS=['-thisdoesnotexist=somevalue'], MSVC_SCRIPTERROR_POLICY=None, tools=['msvc'])
+ """
+ ))
+ test.run(arguments='-Q -s', status=2, stderr=None)
+ expect = "MSVCScriptExecutionError: vc script errors detected:"
+ test.must_contain_all(test.stderr(), expect)
+
+ # Test environment construction with construction variable
+ test.write('SConstruct', textwrap.dedent(
+ """
+ DefaultEnvironment(tools=[])
+ env = Environment(MSVC_SCRIPT_ARGS=['-thisdoesnotexist=somevalue'], MSVC_SCRIPTERROR_POLICY='Error', tools=['msvc'])
+ """
+ ))
+ test.run(arguments='-Q -s', status=2, stderr=None)
+ expect = "MSVCScriptExecutionError: vc script errors detected:"
+ test.must_contain_all(test.stderr(), expect)
+
+ # Test environment construction with global policy
+ test.write('SConstruct', textwrap.dedent(
+ """
+ from SCons.Tool.MSCommon import msvc_set_scripterror_policy
+ DefaultEnvironment(tools=[])
+ msvc_set_scripterror_policy('Warning')
+ env = Environment(MSVC_SCRIPT_ARGS=['-thisdoesnotexist=somevalue'], tools=['msvc'])
+ """
+ ))
+ test.run(arguments='-Q -s', status=0, stderr=None)
+ expect = "scons: warning: vc script errors detected:"
+ test.must_contain_all(test.stderr(), expect)
+
+ # Test environment construction with construction variable
+ test.write('SConstruct', textwrap.dedent(
+ """
+ DefaultEnvironment(tools=[])
+ env = Environment(MSVC_SCRIPT_ARGS=['-thisdoesnotexist=somevalue'], MSVC_SCRIPTERROR_POLICY='Warning', tools=['msvc'])
+ """
+ ))
+ test.run(arguments='-Q -s', status=0, stderr=None)
+ expect = "scons: warning: vc script errors detected:"
+ test.must_contain_all(test.stderr(), expect)
+
+test.pass_test()
+
diff --git a/test/MSVC/MSVC_SDK_VERSION.py b/test/MSVC/MSVC_SDK_VERSION.py
new file mode 100644
index 000000000..e2f78be9e
--- /dev/null
+++ b/test/MSVC/MSVC_SDK_VERSION.py
@@ -0,0 +1,241 @@
+# MIT License
+#
+# Copyright The SCons Foundation
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# 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.
+
+"""
+Test the MSVC_SDK_VERSION construction variable.
+"""
+import textwrap
+
+from SCons.Tool.MSCommon.vc import get_installed_vcs_components
+from SCons.Tool.MSCommon import msvc_sdk_versions
+from SCons.Tool.MSCommon import msvc_toolset_versions
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.skip_if_not_msvc()
+
+
+installed_versions = get_installed_vcs_components()
+
+default_version = installed_versions[0]
+
+GE_VS2015_versions = [v for v in installed_versions if v.msvc_vernum >= 14.0]
+LT_VS2015_versions = [v for v in installed_versions if v.msvc_vernum < 14.0]
+
+default_sdk_versions_uwp = msvc_sdk_versions(version=None, msvc_uwp_app=True)
+default_sdk_versions_def = msvc_sdk_versions(version=None, msvc_uwp_app=False)
+
+have_140 = any([v.msvc_verstr == '14.0' for v in GE_VS2015_versions])
+
+def version_major(version):
+ components = version.split('.')
+ if len(components) >= 2:
+ return components[0] + '.' + components[1][0]
+ if len(components) == 1:
+ return components[0] + '.0'
+ return version
+
+def version_major_list(version_list):
+ versions = []
+ seen_major = set()
+ for version in version_list:
+ major = version_major(version)
+ if major in seen_major:
+ continue
+ versions.append(version)
+ seen_major.add(major)
+ return versions
+
+if GE_VS2015_versions:
+
+ for supported in GE_VS2015_versions:
+
+ sdk_versions_uwp = msvc_sdk_versions(version=supported.msvc_version, msvc_uwp_app=True)
+ sdk_versions_def = msvc_sdk_versions(version=supported.msvc_version, msvc_uwp_app=False)
+
+ # find sdk version for each major SDK
+ sdk_versions = version_major_list(sdk_versions_def)
+
+ for sdk_version in sdk_versions:
+
+ # sdk version construction variable
+ test.write('SConstruct', textwrap.dedent(
+ """
+ DefaultEnvironment(tools=[])
+ env = Environment(MSVC_VERSION={0}, MSVC_SDK_VERSION={1}, tools=['msvc'])
+ lib_path = env['ENV']['LIB']
+ if '\\\\{2}\\\\' not in lib_path:
+ raise RuntimeError("{1} not found in lib path " + lib_path)
+ """.format(repr(supported.msvc_version), repr(sdk_version), sdk_version)
+ ))
+ test.run(arguments='-Q -s', stdout='')
+
+ # sdk version script argument
+ test.write('SConstruct', textwrap.dedent(
+ """
+ DefaultEnvironment(tools=[])
+ env = Environment(MSVC_VERSION={0}, MSVC_SCRIPT_ARGS={1}, tools=['msvc'])
+ lib_path = env['ENV']['LIB']
+ if '\\\\{2}\\\\' not in lib_path:
+ raise RuntimeError("{1} not found in lib path " + lib_path)
+ """.format(repr(supported.msvc_version), repr(sdk_version), sdk_version)
+ ))
+ test.run(arguments='-Q -s', stdout='')
+
+ # sdk version construction variable and script argument
+ test.write('SConstruct', textwrap.dedent(
+ """
+ DefaultEnvironment(tools=[])
+ env = Environment(MSVC_VERSION={}, MSVC_SDK_VERSION={}, MSVC_SCRIPT_ARGS={}, tools=['msvc'])
+ """.format(repr(supported.msvc_version), repr(sdk_version), repr(sdk_version))
+ ))
+ test.run(arguments='-Q -s', status=2, stderr=None)
+ expect = "MSVCArgumentError: multiple sdk version declarations: MSVC_SDK_VERSION={} and MSVC_SCRIPT_ARGS={}:".format(
+ repr(sdk_version), repr(sdk_version)
+ )
+ test.must_contain_all(test.stderr(), expect)
+
+ # sdk version is not supported
+ invalid_sdk_version = '9.1'
+ test.write('SConstruct', textwrap.dedent(
+ """
+ DefaultEnvironment(tools=[])
+ env = Environment(MSVC_VERSION={}, MSVC_SDK_VERSION={}, tools=['msvc'])
+ """.format(repr(supported.msvc_version), repr(invalid_sdk_version))
+ ))
+ test.run(arguments='-Q -s', status=2, stderr=None)
+ expect = "MSVCArgumentError: MSVC_SDK_VERSION ({}) is not supported:".format(
+ repr(invalid_sdk_version)
+ )
+ test.must_contain_all(test.stderr(), expect)
+
+ # sdk version not found
+ missing_sdk_version = '10.0.12345.6'
+ test.write('SConstruct', textwrap.dedent(
+ """
+ DefaultEnvironment(tools=[])
+ env = Environment(MSVC_VERSION={}, MSVC_SDK_VERSION={}, tools=['msvc'])
+ """.format(repr(supported.msvc_version), repr(missing_sdk_version))
+ ))
+ test.run(arguments='-Q -s', status=2, stderr=None)
+ expect = "MSVCSDKVersionNotFound: MSVC_SDK_VERSION {} not found for platform type 'Desktop':".format(
+ repr(missing_sdk_version)
+ )
+ test.must_contain_all(test.stderr(), expect)
+
+ # platform contraints: 8.1 and UWP
+ if '8.1' in sdk_versions:
+
+ if supported.msvc_vernum > 14.0:
+
+ toolset_full_versions = msvc_toolset_versions(supported.msvc_version, full=True, sxs=False)
+ toolset_versions = version_major_list(toolset_full_versions)
+
+ # toolset msvc_version != current msvc_version and toolset msvc_version != 14.0
+ toolset_candidates = [v for v in toolset_versions if version_major(v) not in (supported.msvc_verstr, '14.0')]
+ toolset_version = toolset_candidates[0] if toolset_candidates else None
+
+ # sdk version 8.1, UWP, and msvc_verson > VS2015
+ test.write('SConstruct', textwrap.dedent(
+ """
+ DefaultEnvironment(tools=[])
+ env = Environment(MSVC_VERSION={}, MSVC_SDK_VERSION='8.1', MSVC_UWP_APP=True, tools=['msvc'])
+ """.format(repr(supported.msvc_version))
+ ))
+ test.run(arguments='-Q -s', status=2, stderr=None)
+ expect = "MSVCArgumentError: MSVC_SDK_VERSION ('8.1') and platform type ('UWP') constraint violation: MSVC_VERSION {} > '14.0' VS2015:".format(
+ repr(supported.msvc_version)
+ )
+ test.must_contain_all(test.stderr(), expect)
+
+ if toolset_version:
+
+ # sdk version 8.1, UWP, and msvc_toolset_verson > VS2015
+ test.write('SConstruct', textwrap.dedent(
+ """
+ DefaultEnvironment(tools=[])
+ env = Environment(MSVC_VERSION={}, MSVC_TOOLSET_VERSION={}, MSVC_SDK_VERSION='8.1', MSVC_UWP_APP=True, tools=['msvc'])
+ """.format(repr(supported.msvc_version), repr(toolset_version))
+ ))
+ test.run(arguments='-Q -s', status=2, stderr=None)
+ expect = "MSVCArgumentError: MSVC_SDK_VERSION ('8.1') and platform type ('UWP') constraint violation: toolset version {} > '14.0' VS2015:".format(
+ repr(toolset_version)
+ )
+ test.must_contain_all(test.stderr(), expect)
+
+ if have_140:
+
+ # sdk version 8.1, UWP, and msvc_toolset_version > VS2015
+ test.write('SConstruct', textwrap.dedent(
+ """
+ DefaultEnvironment(tools=[])
+ env = Environment(MSVC_VERSION={}, MSVC_SDK_VERSION='8.1', MSVC_TOOLSET_VERSION='14.0', MSVC_UWP_APP=True, tools=['msvc'])
+ """.format(repr(supported.msvc_version))
+ ))
+ test.run(arguments='-Q -s', stdout='')
+
+ elif supported.msvc_vernum == 14.0:
+
+ # sdk version 8.1, UWP, and msvc_verson == VS2015
+ test.write('SConstruct', textwrap.dedent(
+ """
+ DefaultEnvironment(tools=[])
+ env = Environment(MSVC_VERSION={}, MSVC_SDK_VERSION='8.1', MSVC_UWP_APP=True, tools=['msvc'])
+ """.format(repr(supported.msvc_version))
+ ))
+ test.run(arguments='-Q -s', stdout='')
+
+if LT_VS2015_versions:
+
+ for unsupported in LT_VS2015_versions:
+ # must be VS2015 or later
+
+ sdk_version = default_sdk_versions_def[0] if default_sdk_versions_def else '8.1'
+
+ test.write('SConstruct', textwrap.dedent(
+ """
+ DefaultEnvironment(tools=[])
+ env = Environment(MSVC_VERSION={}, MSVC_SDK_VERSION={}, tools=['msvc'])
+ """.format(repr(unsupported.msvc_version), repr(sdk_version))
+ ))
+ test.run(arguments='-Q -s', status=2, stderr=None)
+ expect = "MSVCArgumentError: MSVC_SDK_VERSION ({}) constraint violation: MSVC_VERSION {} < '14.0' VS2015:".format(
+ repr(sdk_version), repr(unsupported.msvc_version)
+ )
+ test.must_contain_all(test.stderr(), expect)
+
+ test.write('SConstruct', textwrap.dedent(
+ """
+ DefaultEnvironment(tools=[])
+ env = Environment(MSVC_VERSION={}, MSVC_SCRIPT_ARGS={}, tools=['msvc'])
+ """.format(repr(unsupported.msvc_version), repr(sdk_version))
+ ))
+ test.run(arguments='-Q -s', status=2, stderr=None)
+ expect = "MSVCArgumentError: MSVC_SCRIPT_ARGS ({}) constraint violation: MSVC_VERSION {} < '14.0' VS2015:".format(
+ repr(sdk_version), repr(unsupported.msvc_version)
+ )
+ test.must_contain_all(test.stderr(), expect)
+
+test.pass_test()
+
diff --git a/test/MSVC/MSVC_SPECTRE_LIBS.py b/test/MSVC/MSVC_SPECTRE_LIBS.py
new file mode 100644
index 000000000..ea98fd5c6
--- /dev/null
+++ b/test/MSVC/MSVC_SPECTRE_LIBS.py
@@ -0,0 +1,152 @@
+# MIT License
+#
+# Copyright The SCons Foundation
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# 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.
+
+"""
+Test the MSVC_SPECTRE_LIBS construction variable.
+"""
+
+import TestSCons
+import textwrap
+
+from SCons.Tool.MSCommon.vc import get_installed_vcs_components
+from SCons.Tool.MSCommon import msvc_toolset_versions_spectre
+
+test = TestSCons.TestSCons()
+
+test.skip_if_not_msvc()
+
+installed_versions = get_installed_vcs_components()
+
+GE_VS2017_versions = [v for v in installed_versions if v.msvc_vernum >= 14.1]
+LT_VS2017_versions = [v for v in installed_versions if v.msvc_vernum < 14.1]
+
+if GE_VS2017_versions:
+ # VS2017 and later for toolset argument
+
+ for supported in GE_VS2017_versions:
+
+ spectre_toolset_versions = msvc_toolset_versions_spectre(supported.msvc_version)
+ spectre_toolset_version = spectre_toolset_versions[0] if spectre_toolset_versions else None
+
+ if spectre_toolset_version:
+
+ # spectre libs using construction variable
+ test.write('SConstruct', textwrap.dedent(
+ """
+ DefaultEnvironment(tools=[])
+ env = Environment(MSVC_VERSION={}, MSVC_SPECTRE_LIBS=True, tools=['msvc'])
+ lib_path = env['ENV']['LIB']
+ if '\\\\lib\\\\spectre\\\\' not in lib_path.lower():
+ raise RuntimeError("'spectre' not found in lib path " + lib_path)
+ """.format(repr(supported.msvc_version))
+ ))
+ test.run(arguments='-Q -s', stdout='')
+
+ # spectre libs using script argument
+ test.write('SConstruct', textwrap.dedent(
+ """
+ DefaultEnvironment(tools=[])
+ env = Environment(MSVC_VERSION={}, MSVC_SCRIPT_ARGS='-vcvars_spectre_libs=spectre', tools=['msvc'])
+ lib_path = env['ENV']['LIB']
+ if '\\\\lib\\\\spectre\\\\' not in lib_path.lower():
+ raise RuntimeError("'spectre' not found in lib path " + lib_path)
+ """.format(repr(supported.msvc_version))
+ ))
+ test.run(arguments='-Q -s', stdout='')
+
+ # error construction variable and script argument
+ test.write('SConstruct', textwrap.dedent(
+ """
+ DefaultEnvironment(tools=[])
+ env = Environment(MSVC_VERSION={}, MSVC_SPECTRE_LIBS=True, MSVC_SCRIPT_ARGS='-vcvars_spectre_libs=spectre', tools=['msvc'])
+ """.format(repr(supported.msvc_version))
+ ))
+ test.run(arguments='-Q -s', status=2, stderr=None)
+ expect = "MSVCArgumentError: multiple spectre declarations: MSVC_SPECTRE_LIBS=True and MSVC_SCRIPT_ARGS='-vcvars_spectre_libs=spectre':"
+ test.must_contain_all(test.stderr(), expect)
+
+ else:
+
+ # spectre libs using construction variable
+ test.write('SConstruct', textwrap.dedent(
+ """
+ DefaultEnvironment(tools=[])
+ env = Environment(MSVC_VERSION={}, MSVC_SPECTRE_LIBS=True, tools=['msvc'])
+ """.format(repr(supported.msvc_version))
+ ))
+ test.run(arguments='-Q -s', status=2, stderr=None)
+ if not test.stderr().strip().startswith('MSVCSpectreLibsNotFound: Spectre libraries not found'):
+ test.fail_test()
+
+ # spectre libs using script argument
+ test.write('SConstruct', textwrap.dedent(
+ """
+ DefaultEnvironment(tools=[])
+ env = Environment(MSVC_VERSION={}, MSVC_SCRIPT_ARGS='-vcvars_spectre_libs=spectre', MSVC_SCRIPTERROR_POLICY='error', tools=['msvc'])
+ """.format(repr(supported.msvc_version))
+ ))
+ test.run(arguments='-Q -s', status=2, stderr=None)
+ if not test.stderr().strip().startswith('MSVCScriptExecutionError: vc script errors detected:'):
+ test.fail_test()
+
+ # spectre libs using construction variable
+ test.write('SConstruct', textwrap.dedent(
+ """
+ DefaultEnvironment(tools=[])
+ env = Environment(MSVC_VERSION={}, MSVC_SPECTRE_LIBS=False, tools=['msvc'])
+ lib_path = env['ENV']['LIB']
+ if '\\\\lib\\\\spectre\\\\' in lib_path.lower():
+ raise RuntimeError("'spectre' found in lib path " + lib_path)
+ """.format(repr(supported.msvc_version))
+ ))
+ test.run(arguments='-Q -s', stdout='')
+
+if LT_VS2017_versions:
+ # VS2015 and earlier for toolset argument error
+
+ for unsupported in LT_VS2017_versions:
+
+ # must be VS2017 or later
+ test.write('SConstruct', textwrap.dedent(
+ """
+ DefaultEnvironment(tools=[])
+ env = Environment(MSVC_VERSION={}, MSVC_SPECTRE_LIBS=True, tools=['msvc'])
+ """.format(repr(unsupported.msvc_version))
+ ))
+ test.run(arguments='-Q -s', status=2, stderr=None)
+ if not test.stderr().strip().startswith('MSVCArgumentError: MSVC_SPECTRE_LIBS (True) constraint violation:'):
+ test.fail_test()
+
+ for disabled in (False, None):
+
+ # ignore
+ test.write('SConstruct', textwrap.dedent(
+ """
+ DefaultEnvironment(tools=[])
+ env = Environment(MSVC_VERSION={}, MSVC_SPECTRE_LIBS={}, tools=['msvc'])
+ """.format(repr(unsupported.msvc_version), disabled)
+ ))
+ test.run(arguments='-Q -s', stdout='')
+
+test.pass_test()
+
diff --git a/test/MSVC/MSVC_TOOLSET_VERSION.py b/test/MSVC/MSVC_TOOLSET_VERSION.py
new file mode 100644
index 000000000..db40c95fd
--- /dev/null
+++ b/test/MSVC/MSVC_TOOLSET_VERSION.py
@@ -0,0 +1,233 @@
+# MIT License
+#
+# Copyright The SCons Foundation
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# 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.
+
+"""
+Test the MSVC_TOOLSET_VERSION construction variable.
+"""
+import textwrap
+
+from SCons.Tool.MSCommon.vc import get_installed_vcs_components
+from SCons.Tool.MSCommon import msvc_toolset_versions
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.skip_if_not_msvc()
+
+installed_versions = get_installed_vcs_components()
+
+default_version = installed_versions[0]
+
+GE_VS2017_versions = [v for v in installed_versions if v.msvc_vernum >= 14.1]
+LT_VS2017_versions = [v for v in installed_versions if v.msvc_vernum < 14.1]
+LT_VS2015_versions = [v for v in LT_VS2017_versions if v.msvc_vernum < 14.0]
+
+if GE_VS2017_versions:
+ # VS2017 and later for toolset argument
+
+ for supported in GE_VS2017_versions:
+
+ toolset_full_versions = msvc_toolset_versions(supported.msvc_version, full=True, sxs=False)
+ toolset_full_version = toolset_full_versions[0] if toolset_full_versions else None
+
+ toolset_sxs_versions = msvc_toolset_versions(supported.msvc_version, full=False, sxs=True)
+ toolset_sxs_version = toolset_sxs_versions[0] if toolset_sxs_versions else None
+
+ if toolset_full_version:
+
+ # toolset version using construction variable
+ test.write('SConstruct', textwrap.dedent(
+ """
+ DefaultEnvironment(tools=[])
+ env = Environment(MSVC_VERSION={0}, MSVC_TOOLSET_VERSION={1}, tools=['msvc'])
+ lib_path = env['ENV']['LIB']
+ if '\\\\{2}\\\\' not in lib_path:
+ raise RuntimeError("{1} not found in lib path " + lib_path)
+ """.format(repr(supported.msvc_version), repr(toolset_full_version), toolset_full_version)
+ ))
+ test.run(arguments='-Q -s', stdout='')
+
+ # toolset version using script argument
+ test.write('SConstruct', textwrap.dedent(
+ """
+ DefaultEnvironment(tools=[])
+ env = Environment(MSVC_VERSION={0}, MSVC_SCRIPT_ARGS='-vcvars_ver={1}', tools=['msvc'])
+ lib_path = env['ENV']['LIB']
+ if '\\\\{1}\\\\' not in lib_path:
+ raise RuntimeError("'{1}' not found in lib path " + lib_path)
+ """.format(repr(supported.msvc_version), toolset_full_version)
+ ))
+ test.run(arguments='-Q -s', stdout='')
+
+ if toolset_sxs_version:
+
+ # sxs toolset version using construction variable
+ test.write('SConstruct', textwrap.dedent(
+ """
+ DefaultEnvironment(tools=[])
+ env = Environment(MSVC_VERSION={}, MSVC_TOOLSET_VERSION={}, tools=['msvc'])
+ """.format(repr(supported.msvc_version), repr(toolset_sxs_version))
+ ))
+ test.run(arguments='-Q -s', stdout='')
+
+ # msvc_version as toolset version
+ test.write('SConstruct', textwrap.dedent(
+ """
+ DefaultEnvironment(tools=[])
+ env = Environment(MSVC_VERSION={}, MSVC_TOOLSET_VERSION={}, tools=['msvc'])
+ """.format(repr(supported.msvc_version), repr(supported.msvc_verstr))
+ ))
+ test.run(arguments='-Q -s', stdout='')
+
+ # msvc_version as toolset version using script argument
+ test.write('SConstruct', textwrap.dedent(
+ """
+ DefaultEnvironment(tools=[])
+ env = Environment(MSVC_VERSION={}, MSVC_SCRIPT_ARGS='-vcvars_ver={}', tools=['msvc'])
+ """.format(repr(supported.msvc_version), supported.msvc_verstr)
+ ))
+ test.run(arguments='-Q -s', stdout='')
+
+ # error toolset version and script argument
+ test.write('SConstruct', textwrap.dedent(
+ """
+ DefaultEnvironment(tools=[])
+ env = Environment(MSVC_VERSION={}, MSVC_TOOLSET_VERSION={}, MSVC_SCRIPT_ARGS='-vcvars_ver={}', tools=['msvc'])
+ """.format(repr(supported.msvc_version), repr(supported.msvc_verstr), supported.msvc_verstr)
+ ))
+ test.run(arguments='-Q -s', status=2, stderr=None)
+ expect = "MSVCArgumentError: multiple toolset version declarations: MSVC_TOOLSET_VERSION={} and MSVC_SCRIPT_ARGS='-vcvars_ver={}':".format(
+ repr(supported.msvc_verstr), supported.msvc_verstr
+ )
+ test.must_contain_all(test.stderr(), expect)
+
+ # msvc_toolset_version does not exist (hopefully)
+ missing_toolset_version = supported.msvc_verstr + '9.99999'
+ test.write('SConstruct', textwrap.dedent(
+ """
+ DefaultEnvironment(tools=[])
+ env = Environment(MSVC_VERSION={}, MSVC_TOOLSET_VERSION={}, tools=['msvc'])
+ """.format(repr(supported.msvc_version), repr(missing_toolset_version))
+ ))
+ test.run(arguments='-Q -s', status=2, stderr=None)
+ expect = "MSVCToolsetVersionNotFound: MSVC_TOOLSET_VERSION {} not found for MSVC_VERSION {}:".format(
+ repr(missing_toolset_version), repr(supported.msvc_version)
+ )
+ test.must_contain_all(test.stderr(), expect)
+
+ # msvc_toolset_version is invalid (format)
+ invalid_toolset_version = supported.msvc_verstr + '9.99999.99999'
+ test.write('SConstruct', textwrap.dedent(
+ """
+ DefaultEnvironment(tools=[])
+ env = Environment(MSVC_VERSION={}, MSVC_TOOLSET_VERSION={}, tools=['msvc'])
+ """.format(repr(supported.msvc_version), repr(invalid_toolset_version))
+ ))
+ test.run(arguments='-Q -s', status=2, stderr=None)
+ expect = "MSVCArgumentError: MSVC_TOOLSET_VERSION ({}) format is not supported:".format(
+ repr(invalid_toolset_version)
+ )
+ test.must_contain_all(test.stderr(), expect)
+
+ # msvc_toolset_version is invalid (version greater than msvc version)
+ invalid_toolset_vernum = round(supported.msvc_vernum + 0.1, 1)
+ invalid_toolset_version = str(invalid_toolset_vernum)
+ test.write('SConstruct', textwrap.dedent(
+ """
+ DefaultEnvironment(tools=[])
+ env = Environment(MSVC_VERSION={}, MSVC_TOOLSET_VERSION={}, tools=['msvc'])
+ """.format(repr(supported.msvc_version), repr(invalid_toolset_version))
+ ))
+ test.run(arguments='-Q -s', status=2, stderr=None)
+ expect = "MSVCArgumentError: MSVC_TOOLSET_VERSION ({}) constraint violation: toolset version {} > {} MSVC_VERSION:".format(
+ repr(invalid_toolset_version), repr(invalid_toolset_version), repr(supported.msvc_version)
+ )
+ test.must_contain_all(test.stderr(), expect)
+
+ # msvc_toolset_version is invalid (version less than 14.0)
+ invalid_toolset_version = '12.0'
+ test.write('SConstruct', textwrap.dedent(
+ """
+ DefaultEnvironment(tools=[])
+ env = Environment(MSVC_VERSION={}, MSVC_TOOLSET_VERSION={}, tools=['msvc'])
+ """.format(repr(supported.msvc_version), repr(invalid_toolset_version))
+ ))
+ test.run(arguments='-Q -s', status=2, stderr=None)
+ expect = "MSVCArgumentError: MSVC_TOOLSET_VERSION ({}) constraint violation: toolset version {} < '14.0' VS2015:".format(
+ repr(invalid_toolset_version), repr(invalid_toolset_version)
+ )
+ test.must_contain_all(test.stderr(), expect)
+
+ # 14.0 toolset is invalid (toolset version != 14.0)
+ invalid_toolset_version = '14.00.00001'
+ test.write('SConstruct', textwrap.dedent(
+ """
+ DefaultEnvironment(tools=[])
+ env = Environment(MSVC_VERSION={}, MSVC_TOOLSET_VERSION={}, tools=['msvc'])
+ """.format(repr(supported.msvc_version), repr(invalid_toolset_version))
+ ))
+ test.run(arguments='-Q -s', status=2, stderr=None)
+ expect = "MSVCArgumentError: MSVC_TOOLSET_VERSION ({}) constraint violation: toolset version {} != '14.0':".format(
+ repr(invalid_toolset_version), repr(invalid_toolset_version)
+ )
+ test.must_contain_all(test.stderr(), expect)
+
+if LT_VS2017_versions:
+ # VS2015 and earlier for toolset argument error
+
+ for unsupported in LT_VS2017_versions:
+
+ # must be VS2017 or later
+ test.write('SConstruct', textwrap.dedent(
+ """
+ DefaultEnvironment(tools=[])
+ env = Environment(MSVC_VERSION={}, MSVC_TOOLSET_VERSION={}, tools=['msvc'])
+ """.format(repr(unsupported.msvc_version), repr(unsupported.msvc_verstr))
+ ))
+ test.run(arguments='-Q -s', status=2, stderr=None)
+ expect = "MSVCArgumentError: MSVC_TOOLSET_VERSION ({}) constraint violation: MSVC_VERSION {} < '14.1' VS2017:".format(
+ repr(unsupported.msvc_verstr), repr(unsupported.msvc_version)
+ )
+ test.must_contain_all(test.stderr(), expect)
+
+if LT_VS2015_versions:
+ # VS2013 and earlier for script argument error
+
+ for unsupported in LT_VS2015_versions:
+
+ # must be VS2015 or later for MSVC_SCRIPT_ARGS
+ test.write('SConstruct', textwrap.dedent(
+ """
+ DefaultEnvironment(tools=[])
+ env = Environment(MSVC_VERSION={}, MSVC_SCRIPT_ARGS='-vcvars_ver={}', tools=['msvc'])
+ """.format(repr(unsupported.msvc_version), unsupported.msvc_verstr)
+ ))
+ test.run(arguments='-Q -s', status=2, stderr=None)
+ expect = "MSVCArgumentError: MSVC_SCRIPT_ARGS ('-vcvars_ver={}') constraint violation: MSVC_VERSION {} < '14.0' VS2015:".format(
+ unsupported.msvc_verstr, repr(unsupported.msvc_version)
+ )
+ test.must_contain_all(test.stderr(), expect)
+
+test.pass_test()
+
diff --git a/test/MSVC/MSVC_USE_SCRIPT_ARGS-fixture/SConstruct b/test/MSVC/MSVC_USE_SCRIPT_ARGS-fixture/SConstruct
index 68ec4b068..54d140e5b 100644
--- a/test/MSVC/MSVC_USE_SCRIPT_ARGS-fixture/SConstruct
+++ b/test/MSVC/MSVC_USE_SCRIPT_ARGS-fixture/SConstruct
@@ -1,5 +1,8 @@
import os
+if 'SCONS_CACHE_MSVC_CONFIG' in os.environ:
+ del os.environ['SCONS_CACHE_MSVC_CONFIG']
+
os.environ['SCONS_MSCOMMON_DEBUG']='MSDEBUG_OUTPUT.log'
DefaultEnvironment(tools=[])
diff --git a/test/MSVC/MSVC_UWP_APP.py b/test/MSVC/MSVC_UWP_APP.py
index 6ccd1177f..645fc0ed1 100644
--- a/test/MSVC/MSVC_UWP_APP.py
+++ b/test/MSVC/MSVC_UWP_APP.py
@@ -1,6 +1,6 @@
-#!/usr/bin/env python
+# MIT License
#
-# __COPYRIGHT__
+# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -20,190 +20,148 @@
# 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.
-#
-
-__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
"""
-Test the ability to configure the $MSVC_UWP_APP construction variable with
-the desired effect.
+Test the MSVC_UWP_APP construction variable.
"""
+import textwrap
+import re
+from SCons.Tool.MSCommon.vc import get_installed_vcs_components
import TestSCons
-import SCons.Tool.MSCommon.vc as msvc
-from SCons.Tool.MSCommon.vc import get_msvc_version_numeric
-
-def AreVCStoreLibPathsInLIBPATH(output):
- libpath = None
- msvc_version = None
- UWP_APP = None
- lines = output.splitlines()
- for line in lines:
- if 'env[ENV][LIBPATH]=' in line:
- libpath = line.split('=')[1]
- elif 'env[MSVC_VERSION]=' in line:
- msvc_version = line.split('=')[1]
- elif 'env[ENV][VSCMD_ARG_app_plat]=' in line:
- UWP_APP = line.split('=')[1]
-
- if not libpath or not msvc_version:
- # Couldn't find the libpath or msvc version in the output
- return (False, False, None)
-
- libpaths = libpath.lower().split(';')
- msvc_num = float(get_msvc_version_numeric(msvc_version))
-
- (vclibstore_path_present, vclibstorerefs_path_present) = (False, False)
- for path in libpaths:
- # Look for the Store VC Lib paths in the LIBPATH:
- # [VS install path]\VC\LIB\store[\arch] and
- # [VS install path]\VC\LIB\store\references
- # For example,
- # C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\LIB\store\amd64
- # C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\LIB\store\references
-
- if msvc_num <= 14:
- if r'vc\lib\store\references' in path:
- vclibstorerefs_path_present = True
- elif r'vc\lib\store' in path:
- vclibstore_path_present = True
- elif msvc_num > 14:
- if UWP_APP == "UWP":
- if(r'\lib\x86\store\references' in path
- or r'\lib\x64\store' in path):
- vclibstorerefs_path_present = True
- vclibstore_path_present = True
-
- return (vclibstore_path_present, vclibstorerefs_path_present, msvc_version)
-
-_python_ = TestSCons._python_
test = TestSCons.TestSCons()
test.skip_if_not_msvc()
-installed_msvc_versions = msvc.get_installed_vcs()
-# MSVC guaranteed to be at least one version on the system or else
-# skip_if_not_msvc() function would have skipped the test
-
-msvc_140 = '14.0' in installed_msvc_versions
-msvc_141 = '14.1' in installed_msvc_versions
-msvc_142 = '14.2' in installed_msvc_versions
-msvc_143 = '14.3' in installed_msvc_versions
-
-if not any((msvc_140, msvc_141, msvc_142, msvc_143)):
- test.skip_test("Available MSVC doesn't support App store\n")
-
-if msvc_140:
- test.write('SConstruct', """\
-if ARGUMENTS.get('MSVC_UWP_APP'):
- help_vars = Variables()
- help_vars.Add(EnumVariable(
- 'MSVC_UWP_APP',
- 'Build a Universal Windows Platform (UWP) Application',
- '0',
- allowed_values=('0', '1')))
-else:
- help_vars = None
-env = Environment(tools=['default', 'msvc'], variables=help_vars, MSVC_VERSION='14.0')
-# Print the ENV LIBPATH to stdout
-print('env[ENV][LIBPATH]=%s' % env.get('ENV').get('LIBPATH'))
-print('env[MSVC_VERSION]=%s' % env.get('MSVC_VERSION'))
-""")
-
- # Test setting MSVC_UWP_APP is '1' (True)
- test.run(arguments = "MSVC_UWP_APP=1")
- (vclibstore_path_present, vclibstorerefs_path_present, msvc_version) = AreVCStoreLibPathsInLIBPATH(test.stdout())
- test.fail_test((vclibstore_path_present is False) or (vclibstorerefs_path_present is False),
- message='VC Store LIBPATHs NOT present when MSVC_UWP_APP=1 (msvc_version=%s)' % msvc_version)
-
- # Test setting MSVC_UWP_APP is '0' (False)
- test.run(arguments = "MSVC_UWP_APP=0")
- (vclibstore_path_present, vclibstorerefs_path_present, msvc_version) = AreVCStoreLibPathsInLIBPATH(test.stdout())
- test.fail_test((vclibstore_path_present is True) or (vclibstorerefs_path_present is True),
- message='VC Store LIBPATHs present when MSVC_UWP_APP=0 (msvc_version=%s)' % msvc_version)
-
- # Test not setting MSVC_UWP_APP
- test.run(arguments = "")
- (vclibstore_path_present, vclibstorerefs_path_present, msvc_version) = AreVCStoreLibPathsInLIBPATH(test.stdout())
- test.fail_test((vclibstore_path_present is True) or (vclibstorerefs_path_present is True),
- message='VC Store LIBPATHs present when MSVC_UWP_APP not set (msvc_version=%s)' % msvc_version)
-
-if msvc_141 or msvc_142 or msvc_143:
- if msvc_143:
- test.write('SConstruct', """\
-if ARGUMENTS.get('MSVC_UWP_APP'):
- help_vars = Variables()
- help_vars.Add(EnumVariable(
- 'MSVC_UWP_APP',
- 'Build a Universal Windows Platform (UWP) Application',
- '0',
- allowed_values=('0', '1')))
-else:
- help_vars = None
-env = Environment(tools=['default', 'msvc'], variables=help_vars, MSVC_VERSION='14.3')
-# Print the ENV LIBPATH to stdout
-print('env[ENV][LIBPATH]=%s' % env.get('ENV').get('LIBPATH'))
-print('env[MSVC_VERSION]=%s' % env.get('MSVC_VERSION'))
-print('env[ENV][VSCMD_ARG_app_plat]=%s' % env.get('ENV').get('VSCMD_ARG_app_plat'))
-""")
- elif msvc_142:
- test.write('SConstruct', """\
-if ARGUMENTS.get('MSVC_UWP_APP'):
- help_vars = Variables()
- help_vars.Add(EnumVariable(
- 'MSVC_UWP_APP',
- 'Build a Universal Windows Platform (UWP) Application',
- '0',
- allowed_values=('0', '1')))
-else:
- help_vars = None
-env = Environment(tools=['default', 'msvc'], variables=help_vars, MSVC_VERSION='14.2')
-# Print the ENV LIBPATH to stdout
-print('env[ENV][LIBPATH]=%s' % env.get('ENV').get('LIBPATH'))
-print('env[MSVC_VERSION]=%s' % env.get('MSVC_VERSION'))
-print('env[ENV][VSCMD_ARG_app_plat]=%s' % env.get('ENV').get('VSCMD_ARG_app_plat'))
-""")
- elif msvc_141:
- test.write('SConstruct', """\
-if ARGUMENTS.get('MSVC_UWP_APP'):
- help_vars = Variables()
- help_vars.Add(EnumVariable(
- 'MSVC_UWP_APP',
- 'Build a Universal Windows Platform (UWP) Application',
- '0',
- allowed_values=('0', '1')))
-else:
- help_vars = None
-env = Environment(tools=['default', 'msvc'], variables=help_vars, MSVC_VERSION='14.1')
-# Print the ENV LIBPATH to stdout
-print('env[ENV][LIBPATH]=%s' % env.get('ENV').get('LIBPATH'))
-print('env[MSVC_VERSION]=%s' % env.get('MSVC_VERSION'))
-print('env[ENV][VSCMD_ARG_app_plat]=%s' % env.get('ENV').get('VSCMD_ARG_app_plat'))
-""")
-
- # Test setting MSVC_UWP_APP is '1' (True)
- test.run(arguments = "MSVC_UWP_APP=1")
- (vclibstore_path_present, vclibstorerefs_path_present, msvc_version) = AreVCStoreLibPathsInLIBPATH(test.stdout())
- test.fail_test((vclibstore_path_present is False) or (vclibstorerefs_path_present is False),
- message='VC Store LIBPATHs NOT present when MSVC_UWP_APP=1 (msvc_version=%s)' % msvc_version)
-
- # Test setting MSVC_UWP_APP is '0' (False)
- test.run(arguments = "MSVC_UWP_APP=0")
- (vclibstore_path_present, vclibstorerefs_path_present, msvc_version) = AreVCStoreLibPathsInLIBPATH(test.stdout())
- test.fail_test((vclibstore_path_present is True) or (vclibstorerefs_path_present is True),
- message='VC Store LIBPATHs NOT present when MSVC_UWP_APP=1 (msvc_version=%s)' % msvc_version)
-
- # Test not setting MSVC_UWP_APP
- test.run(arguments = "")
- (vclibstore_path_present, vclibstorerefs_path_present, msvc_version) = AreVCStoreLibPathsInLIBPATH(test.stdout())
- test.fail_test((vclibstore_path_present is True) or (vclibstorerefs_path_present is True),
- message='VC Store LIBPATHs NOT present when MSVC_UWP_APP=1 (msvc_version=%s)' % msvc_version)
+
+installed_versions = get_installed_vcs_components()
+
+GE_VS2015_versions = [v for v in installed_versions if v.msvc_vernum >= 14.0]
+LT_VS2015_versions = [v for v in installed_versions if v.msvc_vernum < 14.0]
+
+# Look for the Store VC Lib paths in the LIBPATH:
+# [VS install path]\VC\LIB\store[\arch] and
+# [VS install path]\VC\LIB\store\references
+# For example,
+# C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\LIB\store\amd64
+# C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\LIB\store\references
+
+re_lib_eq2015_1 = re.compile(r'\\vc\\lib\\store\\references', re.IGNORECASE)
+re_lib_eq2015_2 = re.compile(r'\\vc\\lib\\store', re.IGNORECASE)
+
+re_lib_ge2017_1 = re.compile(r'\\lib\\x86\\store\\references', re.IGNORECASE)
+re_lib_ge2017_2 = re.compile(r'\\lib\\x64\\store', re.IGNORECASE)
+
+
+def check_libpath(msvc, active, output):
+
+ def _check_libpath(msvc, output):
+ outdict = {key.strip(): val.strip() for key, val in [line.split('|') for line in output.splitlines()]}
+ platform = outdict.get('PLATFORM', '')
+ libpath = outdict.get('LIBPATH', '')
+ n_matches = 0
+ if msvc.msvc_verstr == '14.0':
+ for regex in (re_lib_eq2015_1, re_lib_eq2015_2):
+ if regex.search(libpath):
+ n_matches += 1
+ return n_matches >= 2, 'store', libpath
+ elif platform == 'UWP':
+ for regex in (re_lib_ge2017_1, re_lib_ge2017_2):
+ if regex.search(libpath):
+ n_matches += 1
+ return n_matches > 0, 'uwp', libpath
+ return False, 'uwp', libpath
+
+ found, kind, libpath = _check_libpath(msvc, output)
+
+ failmsg = None
+
+ if active and not found:
+ failmsg = 'msvc version {} {} paths not found in lib path {}'.format(
+ repr(msvc.msvc_version), repr(kind), repr(libpath)
+ )
+ elif not active and found:
+ failmsg = 'msvc version {} {} paths found in lib path {}'.format(
+ repr(msvc.msvc_version), repr(kind), repr(libpath)
+ )
+
+ return failmsg
+
+if GE_VS2015_versions:
+ # VS2015 and later for uwp/store argument
+
+ for supported in GE_VS2015_versions:
+
+ for msvc_uwp_app in (True, '1', False, '0', None):
+
+ active = msvc_uwp_app in (True, '1')
+
+ # uwp using construction variable
+ test.write('SConstruct', textwrap.dedent(
+ """
+ DefaultEnvironment(tools=[])
+ env = Environment(MSVC_VERSION={}, MSVC_UWP_APP={}, tools=['msvc'])
+ print('LIBPATH|' + env['ENV'].get('LIBPATH', ''))
+ print('PLATFORM|' + env['ENV'].get('VSCMD_ARG_app_plat',''))
+ """.format(repr(supported.msvc_version), repr(msvc_uwp_app))
+ ))
+ test.run(arguments='-Q -s', stdout=None)
+ failmsg = check_libpath(supported, active, test.stdout())
+ if failmsg:
+ test.fail_test(message=failmsg)
+
+ if not active:
+ continue
+
+ # error construction variable and script argument
+ test.write('SConstruct', textwrap.dedent(
+ """
+ DefaultEnvironment(tools=[])
+ env = Environment(MSVC_VERSION={}, MSVC_UWP_APP={}, MSVC_SCRIPT_ARGS='store', tools=['msvc'])
+ """.format(repr(supported.msvc_version), repr(msvc_uwp_app))
+ ))
+ test.run(arguments='-Q -s', status=2, stderr=None)
+ if not test.stderr().strip().startswith("MSVCArgumentError: multiple uwp declarations:"):
+ test.fail_test(message='Expected MSVCArgumentError')
+
+ # uwp using script argument
+ test.write('SConstruct', textwrap.dedent(
+ """
+ DefaultEnvironment(tools=[])
+ env = Environment(MSVC_VERSION={}, MSVC_SCRIPT_ARGS='store', tools=['msvc'])
+ print('LIBPATH|' + env['ENV'].get('LIBPATH', ''))
+ print('PLATFORM|' + env['ENV'].get('VSCMD_ARG_app_plat',''))
+ """.format(repr(supported.msvc_version))
+ ))
+ test.run(arguments='-Q -s', stdout=None)
+ failmsg = check_libpath(supported, True, test.stdout())
+ if failmsg:
+ test.fail_test(message=failmsg)
+
+if LT_VS2015_versions:
+ # VS2013 and earlier for uwp/store error
+
+ for unsupported in LT_VS2015_versions:
+
+ for msvc_uwp_app in (True, '1', False, '0', None):
+
+ active = msvc_uwp_app in (True, '1')
+
+ # uwp using construction variable
+ test.write('SConstruct', textwrap.dedent(
+ """
+ DefaultEnvironment(tools=[])
+ env = Environment(MSVC_VERSION={0}, MSVC_UWP_APP={1}, tools=['msvc'])
+ """.format(repr(unsupported.msvc_version), repr(msvc_uwp_app))
+ ))
+ if not active:
+ test.run(arguments='-Q -s', stdout=None)
+ else:
+ test.run(arguments='-Q -s', status=2, stderr=None)
+ expect = 'MSVCArgumentError: MSVC_UWP_APP ({}) constraint violation:'.format(repr(msvc_uwp_app))
+ if not test.stderr().strip().startswith(expect):
+ test.fail_test(message='Expected MSVCArgumentError')
test.pass_test()
-# Local Variables:
-# tab-width:4
-# indent-tabs-mode:nil
-# End:
-# vim: set expandtab tabstop=4 shiftwidth=4:
diff --git a/test/MSVC/msvc_cache_force_defaults.py b/test/MSVC/msvc_cache_force_defaults.py
new file mode 100644
index 000000000..a8396d959
--- /dev/null
+++ b/test/MSVC/msvc_cache_force_defaults.py
@@ -0,0 +1,80 @@
+# MIT License
+#
+# Copyright The SCons Foundation
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# 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.
+
+"""
+Test SCONS_CACHE_MSVC_FORCE_DEFAULTS system environment variable.
+"""
+
+import textwrap
+
+from SCons.Tool.MSCommon.vc import get_installed_vcs_components
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.skip_if_not_msvc()
+
+installed_versions = get_installed_vcs_components()
+
+default_version = installed_versions[0]
+
+if default_version.msvc_vernum >= 14.0:
+ # VS2015 and later
+
+ # force SDK version and toolset version as msvc batch file arguments
+ test.write('SConstruct', textwrap.dedent(
+ """
+ import os
+ import json
+
+ cache_file = 'MSCACHE.json'
+
+ os.environ['SCONS_CACHE_MSVC_CONFIG']=cache_file
+ os.environ['SCONS_CACHE_MSVC_FORCE_DEFAULTS']='1'
+
+ DefaultEnvironment(tools=[])
+ env = Environment(tools=['msvc'])
+
+ envcache_keys = []
+ with open(cache_file, 'r') as file:
+ envcache_list = json.load(file)
+ envcache_keys = [tuple(d['key']) for d in envcache_list]
+
+ if envcache_keys:
+ # key = (script, arguments)
+ print("SCRIPT_ARGS: {}".format(envcache_keys[0][-1]))
+ """
+ ))
+ test.run(arguments = "-Q -s", status=0, stdout=None)
+
+ cache_arg = test.stdout().strip()
+
+ if default_version.msvc_verstr == '14.0':
+ # VS2015: target_arch msvc_sdk_version
+ expect = r'^SCRIPT_ARGS: .* [0-9.]+$'
+ else:
+ # VS2017+ msvc_sdk_version msvc_toolset_version
+ expect = r'^SCRIPT_ARGS: [0-9.]+ -vcvars_ver=[0-9.]+$'
+
+ test.must_contain_all(cache_arg, expect, find=TestSCons.match_re)
+
diff --git a/test/MSVC/no_msvc.py b/test/MSVC/no_msvc.py
index 4ab7dd85d..e255c65ae 100644
--- a/test/MSVC/no_msvc.py
+++ b/test/MSVC/no_msvc.py
@@ -67,5 +67,20 @@ def exists(env):
test.file_fixture('no_msvc/no_msvcs_sconstruct_tools.py', 'SConstruct')
test.run(arguments='-Q -s')
+# test no msvc's and msvc_sdk_version() call
+test.file_fixture('no_msvc/no_msvcs_sconstruct_msvc_sdk_versions.py', 'SConstruct')
+test.run(arguments='-Q -s')
+test.must_contain_all(test.stdout(), 'sdk_version_list=[]')
+
+# test no msvc's and msvc_sdk_version() call
+test.file_fixture('no_msvc/no_msvcs_sconstruct_msvc_toolset_versions.py', 'SConstruct')
+test.run(arguments='-Q -s')
+test.must_contain_all(test.stdout(), 'toolset_version_list=[]')
+
+# test no msvc's and msvc_query_version_toolset() call
+test.file_fixture('no_msvc/no_msvcs_sconstruct_msvc_query_toolset_version.py', 'SConstruct')
+test.run(arguments='-Q -s')
+test.must_contain_all(test.stdout(), 'msvc_version=None, msvc_toolset_version=None')
+
test.pass_test()
diff --git a/test/fixture/no_msvc/no_msvcs_sconstruct_msvc_query_toolset_version.py b/test/fixture/no_msvc/no_msvcs_sconstruct_msvc_query_toolset_version.py
new file mode 100644
index 000000000..8e3c65fbc
--- /dev/null
+++ b/test/fixture/no_msvc/no_msvcs_sconstruct_msvc_query_toolset_version.py
@@ -0,0 +1,15 @@
+import SCons
+import SCons.Tool.MSCommon
+
+def DummyVsWhere(msvc_version, env):
+ # not testing versions with vswhere, so return none
+ return None
+
+for key in SCons.Tool.MSCommon.vc._VCVER_TO_PRODUCT_DIR:
+ SCons.Tool.MSCommon.vc._VCVER_TO_PRODUCT_DIR[key]=[(SCons.Util.HKEY_LOCAL_MACHINE, r'')]
+
+SCons.Tool.MSCommon.vc.find_vc_pdir_vswhere = DummyVsWhere
+
+msvc_version, msvc_toolset_version = SCons.Tool.MSCommon.msvc_query_version_toolset()
+
+print('msvc_version={}, msvc_toolset_version={}'.format(repr(msvc_version), repr(msvc_toolset_version))) \ No newline at end of file
diff --git a/test/fixture/no_msvc/no_msvcs_sconstruct_msvc_sdk_versions.py b/test/fixture/no_msvc/no_msvcs_sconstruct_msvc_sdk_versions.py
new file mode 100644
index 000000000..7953ce62a
--- /dev/null
+++ b/test/fixture/no_msvc/no_msvcs_sconstruct_msvc_sdk_versions.py
@@ -0,0 +1,15 @@
+import SCons
+import SCons.Tool.MSCommon
+
+def DummyVsWhere(msvc_version, env):
+ # not testing versions with vswhere, so return none
+ return None
+
+for key in SCons.Tool.MSCommon.vc._VCVER_TO_PRODUCT_DIR:
+ SCons.Tool.MSCommon.vc._VCVER_TO_PRODUCT_DIR[key]=[(SCons.Util.HKEY_LOCAL_MACHINE, r'')]
+
+SCons.Tool.MSCommon.vc.find_vc_pdir_vswhere = DummyVsWhere
+
+sdk_version_list = SCons.Tool.MSCommon.msvc_sdk_versions()
+
+print('sdk_version_list='+repr(sdk_version_list))
diff --git a/test/fixture/no_msvc/no_msvcs_sconstruct_msvc_toolset_versions.py b/test/fixture/no_msvc/no_msvcs_sconstruct_msvc_toolset_versions.py
new file mode 100644
index 000000000..fd209fd19
--- /dev/null
+++ b/test/fixture/no_msvc/no_msvcs_sconstruct_msvc_toolset_versions.py
@@ -0,0 +1,15 @@
+import SCons
+import SCons.Tool.MSCommon
+
+def DummyVsWhere(msvc_version, env):
+ # not testing versions with vswhere, so return none
+ return None
+
+for key in SCons.Tool.MSCommon.vc._VCVER_TO_PRODUCT_DIR:
+ SCons.Tool.MSCommon.vc._VCVER_TO_PRODUCT_DIR[key]=[(SCons.Util.HKEY_LOCAL_MACHINE, r'')]
+
+SCons.Tool.MSCommon.vc.find_vc_pdir_vswhere = DummyVsWhere
+
+toolset_version_list = SCons.Tool.MSCommon.msvc_toolset_versions()
+
+print('toolset_version_list='+repr(toolset_version_list))
diff --git a/test/fixture/no_msvc/no_msvcs_sconstruct_version.py b/test/fixture/no_msvc/no_msvcs_sconstruct_version.py
index f5cabf769..b586d6c76 100644
--- a/test/fixture/no_msvc/no_msvcs_sconstruct_version.py
+++ b/test/fixture/no_msvc/no_msvcs_sconstruct_version.py
@@ -1,16 +1,19 @@
import SCons
import SCons.Tool.MSCommon
+
def DummyVsWhere(msvc_version, env):
# not testing versions with vswhere, so return none
return None
+
for key in SCons.Tool.MSCommon.vc._VCVER_TO_PRODUCT_DIR:
SCons.Tool.MSCommon.vc._VCVER_TO_PRODUCT_DIR[key]=[(SCons.Util.HKEY_LOCAL_MACHINE, r'')]
SCons.Tool.MSCommon.vc.find_vc_pdir_vswhere = DummyVsWhere
-SCons.Tool.MSCommon.set_msvc_notfound_policy('error')
+SCons.Tool.MSCommon.msvc_set_notfound_policy('error')
env = SCons.Environment.Environment(MSVC_VERSION='14.3')
+