diff options
author | William Deegan <bill@baddogconsulting.com> | 2022-07-25 03:49:17 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-07-25 03:49:17 (GMT) |
commit | 41b0831382603609dfd021cb4d5a0b2d9f06021d (patch) | |
tree | a11fe13c87cec4d3e0f1aa3d6aa4741739078340 /SCons/Tool | |
parent | 2f10fe6c285646ee0d4eb021c4ee94aed4f319c7 (diff) | |
parent | 94133d4d3e63185f2649cc231ed182eba6df29d4 (diff) | |
download | SCons-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')
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> |