summaryrefslogtreecommitdiffstats
path: root/SCons/Tool
diff options
context:
space:
mode:
authorWilliam Deegan <bill@baddogconsulting.com>2022-07-25 03:49:17 (GMT)
committerGitHub <noreply@github.com>2022-07-25 03:49:17 (GMT)
commit41b0831382603609dfd021cb4d5a0b2d9f06021d (patch)
treea11fe13c87cec4d3e0f1aa3d6aa4741739078340 /SCons/Tool
parent2f10fe6c285646ee0d4eb021c4ee94aed4f319c7 (diff)
parent94133d4d3e63185f2649cc231ed182eba6df29d4 (diff)
downloadSCons-41b0831382603609dfd021cb4d5a0b2d9f06021d.zip
SCons-41b0831382603609dfd021cb4d5a0b2d9f06021d.tar.gz
SCons-41b0831382603609dfd021cb4d5a0b2d9f06021d.tar.bz2
Merge pull request #4174 from jcbrill/jbrill-msvc-batchargs
MSVC enhancement to add all remaining msvc batch file command-line options as SCons variables
Diffstat (limited to 'SCons/Tool')
-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
26 files changed, 6233 insertions, 552 deletions
diff --git a/SCons/Tool/MSCommon/MSVC/Config.py b/SCons/Tool/MSCommon/MSVC/Config.py
new file mode 100644
index 0000000..015cf72
--- /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 0000000..89db6cd
--- /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 0000000..42b5287
--- /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 0000000..d6af8d4
--- /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 0000000..7b24a2b
--- /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 0000000..fe8da31
--- /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 0000000..013fd47
--- /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 0000000..9519e15
--- /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 0000000..aff3b3f
--- /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 0000000..57dbf9d
--- /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 0000000..4413256
--- /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 0000000..e1c05bc
--- /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 0000000..4b487da
--- /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 0000000..5e14d50
--- /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 0000000..cab5145
--- /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 0000000..6d18d07
--- /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 0000000..2a40e9a
--- /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 0000000..849c82d
--- /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 2268651..0000000
--- 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 0000000..36e58aa
--- /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 9d8a8ff..c3078ac 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 c9f07f5..ad4c827 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 b542d15..7871940 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 2b6fbe5..3e37def 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 4c3f60c..5cf5e61 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 e8df128..c26c20d 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>