diff options
author | Joseph Brill <48932340+jcbrill@users.noreply.github.com> | 2022-06-20 20:53:31 (GMT) |
---|---|---|
committer | Joseph Brill <48932340+jcbrill@users.noreply.github.com> | 2022-06-20 20:53:31 (GMT) |
commit | 010c2ad46c32827b7ed8082ec11b83404a039faa (patch) | |
tree | 9616ac0aab43888a5ffe69ba002cd1d2e458b37d /SCons/Tool | |
parent | dbd301878f8f7bb004658b25a62d57b48ee4c8a3 (diff) | |
download | SCons-010c2ad46c32827b7ed8082ec11b83404a039faa.zip SCons-010c2ad46c32827b7ed8082ec11b83404a039faa.tar.gz SCons-010c2ad46c32827b7ed8082ec11b83404a039faa.tar.bz2 |
Refactor recent portions of vc.py into MSVC module
Diffstat (limited to 'SCons/Tool')
-rw-r--r-- | SCons/Tool/MSCommon/MSVC/Config.py | 284 | ||||
-rw-r--r-- | SCons/Tool/MSCommon/MSVC/Dispatcher.py | 62 | ||||
-rw-r--r-- | SCons/Tool/MSCommon/MSVC/Exceptions.py | 39 | ||||
-rw-r--r-- | SCons/Tool/MSCommon/MSVC/NotFound.py | 132 | ||||
-rw-r--r-- | SCons/Tool/MSCommon/MSVC/Registry.py | 110 | ||||
-rw-r--r-- | SCons/Tool/MSCommon/MSVC/ScriptArguments.py | 733 | ||||
-rw-r--r-- | SCons/Tool/MSCommon/MSVC/SetupEnvDefault.py | 235 | ||||
-rw-r--r-- | SCons/Tool/MSCommon/MSVC/Util.py | 55 | ||||
-rw-r--r-- | SCons/Tool/MSCommon/MSVC/WinSDK.py | 250 | ||||
-rw-r--r-- | SCons/Tool/MSCommon/MSVC/__init__.py | 50 | ||||
-rw-r--r-- | SCons/Tool/MSCommon/__init__.py | 7 | ||||
-rw-r--r-- | SCons/Tool/MSCommon/vc.py | 1638 |
12 files changed, 1968 insertions, 1627 deletions
diff --git a/SCons/Tool/MSCommon/MSVC/Config.py b/SCons/Tool/MSCommon/MSVC/Config.py new file mode 100644 index 0000000..476dcb3 --- /dev/null +++ b/SCons/Tool/MSCommon/MSVC/Config.py @@ -0,0 +1,284 @@ +# 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 Dispatcher + +Dispatcher.register_modulename(__name__) + +UNDEFINED = object() + +BOOLEAN_SYMBOLS = {} +BOOLEAN_EXTERNAL = {} + +for bool, 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] = list(symbol_list) + for symbol in symbol_case_list: + BOOLEAN_SYMBOLS[bool].extend([symbol, symbol.lower(), symbol.upper()]) + + for symbol in BOOLEAN_SYMBOLS[bool]: + BOOLEAN_EXTERNAL[symbol] = bool + +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', +]) + +MSVC_BUILDTOOLS_DEFINITION_LIST = [] + +MSVC_BUILDTOOLS_INTERNAL = {} +MSVC_BUILDTOOLS_EXTERNAL = {} + +VC_VERSION_MAP = {} + +for vc_buildtools, vc_version, cl_version, vc_runtime in [ + ('v143', '14.3', '19.3', '140'), + ('v142', '14.2', '19.2', '140'), + ('v141', '14.1', '19.1', '140'), + ('v140', '14.0', '19.0', '140'), + ('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'), +]: + + 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, + ) + + 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 = {} + +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) + + 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_buildtools_def.vc_version] = vs_def + MSVC_VERSION_EXTERNAL[vs_product] = vs_def + MSVC_VERSION_EXTERNAL[vc_buildtools_def.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 + + MSVS_VERSION_MAJOR_MAP[vs_version_major] = vs_def + + CL_VERSION_MAP[vc_buildtools_def.cl_version] = vs_def + + if not vc_sdk: + continue + + MSVC_SDK_VERSIONS.update(vc_sdk) + +# 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)] + +MSVS_VERSION_LEGACY = {} +MSVC_VERSION_LEGACY = {} + +for vdict in (MSVS_VERSION_EXTERNAL, MSVC_VERSION_INTERNAL): + for key, vs_def in vdict.items(): + if key not in MSVS_VERSION_LEGACY: + MSVS_VERSION_LEGACY[key] = vs_def + MSVC_VERSION_LEGACY[key] = vs_def + +# 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(vs_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 + diff --git a/SCons/Tool/MSCommon/MSVC/Dispatcher.py b/SCons/Tool/MSCommon/MSVC/Dispatcher.py new file mode 100644 index 0000000..ebcd704 --- /dev/null +++ b/SCons/Tool/MSCommon/MSVC/Dispatcher.py @@ -0,0 +1,62 @@ +# 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++. +""" + +import sys + +from ..common import ( + debug, +) + +_refs = [] + +def register_class(ref): + _refs.append(ref) + +def register_modulename(modname): + module = sys.modules[modname] + _refs.append(module) + +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/Exceptions.py b/SCons/Tool/MSCommon/MSVC/Exceptions.py new file mode 100644 index 0000000..7a61ec5 --- /dev/null +++ b/SCons/Tool/MSCommon/MSVC/Exceptions.py @@ -0,0 +1,39 @@ +# 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++. +""" + +class VisualCException(Exception): + pass + +class MSVCVersionNotFound(VisualCException): + pass + +class MSVCInternalError(VisualCException): + pass + +class MSVCArgumentError(VisualCException): + pass + diff --git a/SCons/Tool/MSCommon/MSVC/NotFound.py b/SCons/Tool/MSCommon/MSVC/NotFound.py new file mode 100644 index 0000000..7abe5ad --- /dev/null +++ b/SCons/Tool/MSCommon/MSVC/NotFound.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. + +""" +Microsoft Visual C/C++ not found policy. + +Notes: + * 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. +""" + +import SCons.Warnings + +from ..common import ( + debug, +) + +from . import Dispatcher +from . import Config + +from .Exceptions import ( + MSVCVersionNotFound, +) + +Dispatcher.register_modulename(__name__) + +_MSVC_NOTFOUND_POLICY_DEF = Config.MSVC_NOTFOUND_POLICY_INTERNAL['warning'] + +def _msvc_notfound_policy_lookup(symbol): + + try: + notfound_policy_def = Config.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 Config.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 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) + diff --git a/SCons/Tool/MSCommon/MSVC/Registry.py b/SCons/Tool/MSCommon/MSVC/Registry.py new file mode 100644 index 0000000..848b125 --- /dev/null +++ b/SCons/Tool/MSCommon/MSVC/Registry.py @@ -0,0 +1,110 @@ +# 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, + HKEY_LOCAL_MACHINE, + HKEY_CURRENT_USER, +) + +from .. common import ( + debug, + read_reg, +) + +from . import Dispatcher +from . import Util + +Dispatcher.register_modulename(__name__) + +def read_value(hkey, subkey_valname): + try: + rval = read_reg(subkey_valname, hkroot=hkey) + except OSError: + debug('OSError: hkey=%s, subkey=%s', repr(hkey), repr(subkey_valname)) + return None + except IndexError: + debug('IndexError: hkey=%s, subkey=%s', repr(hkey), repr(subkey_valname)) + return None + debug('hkey=%s, subkey=%s, rval=%s', repr(hkey), repr(subkey_valname), repr(rval)) + return rval + +def registry_query_path(key, val, suffix): + extval = val + '\\' + suffix if suffix else val + qpath = read_value(key, extval) + 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): + paths = [] + records = [] + for key, val in REG_SOFTWARE_MICROSOFT: + extval = val + '\\' + suffix if suffix else val + qpath = read_value(key, extval) + 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): + records = [] + for key, val in REG_SOFTWARE_MICROSOFT: + extval = val + '\\' + suffix if suffix else val + rval = read_value(key, extval) + if rval: + records.append((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) + diff --git a/SCons/Tool/MSCommon/MSVC/ScriptArguments.py b/SCons/Tool/MSCommon/MSVC/ScriptArguments.py new file mode 100644 index 0000000..324f8be --- /dev/null +++ b/SCons/Tool/MSCommon/MSVC/ScriptArguments.py @@ -0,0 +1,733 @@ +# 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 ( + debug, +) + +from . import Dispatcher +from . import Util +from . import Config +from . import WinSDK + +from .Exceptions import ( + MSVCInternalError, + MSVCArgumentError, +) + +Dispatcher.register_modulename(__name__) + +# 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 + +# capture msvc version +re_toolset_version = re.compile(r'^(?P<version>[1-9][0-9]?[.][0-9])[0-9.]*$', re.IGNORECASE) + +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) + +# valid SxS formats will be matched with re_toolset_full: match 3 '.' format +re_toolset_sxs = re.compile(r'^[1-9][0-9][.][0-9]{2}[.][0-9]{2}[.][0-9]{1,2}$') + +# 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 + +# 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): + ARCH = 0 # arch + 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', # fully qualified msvc version (e.g., '14.1Exp') + 'vs_def', +]) + +def _msvc_version(version): + + verstr = Util.get_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 Config.BOOLEAN_SYMBOLS[True]: + 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 None + + 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 None + + 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(env, msvc, platform_type, 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_type) + ) + + 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.vc_sdk_versions, platform_type) + + if sdk_version not in sdk_list: + err_msg = "MSVC_SDK_VERSION {} not found for platform type {}".format( + repr(sdk_version), repr(platform_type) + ) + raise MSVCArgumentError(err_msg) + + argpair = (SortOrder.SDK, sdk_version) + arglist.append(argpair) + + return sdk_version + +def _msvc_script_default_sdk(env, msvc, platform_type, arglist): + + 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.vc_sdk_versions, platform_type) + 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_type) + ) + + 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) + +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_read_toolset_folders(msvc, vc_dir): + + toolsets_sxs = {} + toolsets_full = [] + + build_dir = os.path.join(vc_dir, "Auxiliary", "Build") + sxs_toolsets = [f.name for f in os.scandir(build_dir) if f.is_dir()] + for sxs_toolset in sxs_toolsets: + filename = 'Microsoft.VCToolsVersion.{}.txt'.format(sxs_toolset) + filepath = os.path.join(build_dir, sxs_toolset, 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_toolset] = toolset_version + debug( + 'sxs toolset: msvc_version=%s, sxs_version=%s, toolset_version=%s', + repr(msvc.version), repr(sxs_toolset), toolset_version + ) + + toolset_dir = os.path.join(vc_dir, "Tools", "MSVC") + toolsets = [f.name for f in os.scandir(toolset_dir) if f.is_dir()] + for toolset_version in toolsets: + binpath = os.path.join(toolset_dir, toolset_version, "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) + ) + + toolsets_full.sort(reverse=True) + 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(): + 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): + + if toolset_version == '14.0': + return toolset_version + + toolsets_sxs, toolsets_full = _msvc_version_toolsets(msvc, vc_dir) + + if msvc.vs_def.vc_buildtools_def.vc_version_numeric == VS2019.vc_buildtools_def.vc_version_numeric: + # necessary to detect toolset not found + if toolset_version == '14.28.16.8': + new_toolset_version = '14.28' + # 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. + debug( + 'rewrite toolset_version=%s => toolset_version=%s', + repr(toolset_version), repr(new_toolset_version) + ) + toolset_version = new_toolset_version + + if toolset_version in toolsets_sxs: + toolset_vcvars = toolsets_sxs[toolset_version] + return toolset_vcvars + + 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 + + m = re_toolset_version.match(toolset_version) + if not m: + debug('invalid: re_toolset_version: toolset_version=%s', repr(toolset_version)) + err_msg = 'MSVC_TOOLSET_VERSION {} format is not supported'.format( + repr(toolset_version) + ) + return err_msg + + toolset_ver = m.group('version') + toolset_vernum = float(toolset_ver) + + 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_ver), 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_ver), repr(msvc.version) + ) + return err_msg + + if toolset_vernum == VS2015.vc_buildtools_def.vc_version_numeric: + # tooset = 14.0 + if re_toolset_full.match(toolset_version): + if not re_toolset_140.match(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 re_toolset_full.match(toolset_version): + debug('valid: re_toolset_full: toolset_version=%s', repr(toolset_version)) + return None + + if re_toolset_sxs.match(toolset_version): + debug('valid: re_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(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 + + 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 MSVCArgumentError(err_msg) + + argpair = (SortOrder.TOOLSET, '-vcvars_ver={}'.format(toolset_vcvars)) + arglist.append(argpair) + + return toolset_vcvars + +def _msvc_script_default_toolset(env, msvc, vc_dir, arglist): + + 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)) + + 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(env, msvc, 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 (True, '1'): + return None + + 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) + ) + raise MSVCArgumentError(err_msg) + + spectre_arg = 'spectre' + + # spectre libs may not be installed + argpair = (SortOrder.SPECTRE, '-vcvars_spectre_libs={}'.format(spectre_arg)) + arglist.append(argpair) + + return spectre_arg + +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 _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_arguments(env, version, vc_dir, arg): + + arglist = [] + + msvc = _msvc_version(version) + + if arg: + argpair = (SortOrder.ARCH, arg) + arglist.append(argpair) + + if 'MSVC_SCRIPT_ARGS' in env: + user_argstr = _msvc_script_argument_user(env, msvc, arglist) + else: + user_argstr = None + + if 'MSVC_UWP_APP' in env: + uwp = _msvc_script_argument_uwp(env, msvc, arglist) + else: + uwp = None + + if user_argstr: + _user_script_argument_uwp(env, uwp, user_argstr) + + platform_type = 'uwp' if uwp else 'desktop' + + if 'MSVC_SDK_VERSION' in env: + sdk_version = _msvc_script_argument_sdk(env, msvc, platform_type, 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_type, arglist) + + 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 MSVC_FORCE_DEFAULT_TOOLSET: + if not toolset_version and not user_toolset: + toolset_version = _msvc_script_default_toolset(env, msvc, vc_dir, arglist) + + if 'MSVC_SPECTRE_LIBS' in env: + spectre = _msvc_script_argument_spectre(env, msvc, arglist) + else: + spectre = None + + if user_argstr: + _user_script_argument_spectre(env, spectre, user_argstr) + + if arglist: + arglist.sort() + argstr = ' '.join([argpair[-1] for argpair in arglist]).strip() + else: + argstr = '' + + debug('arguments: %s', repr(argstr)) + return argstr + +def reset(): + debug('') + _reset_toolset_cache() + +def verify(): + debug('') + _verify_re_sdk_dispatch_map() + diff --git a/SCons/Tool/MSCommon/MSVC/SetupEnvDefault.py b/SCons/Tool/MSCommon/MSVC/SetupEnvDefault.py new file mode 100644 index 0000000..8a79007 --- /dev/null +++ b/SCons/Tool/MSCommon/MSVC/SetupEnvDefault.py @@ -0,0 +1,235 @@ +# 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): + debug('msvc default:tool=%s', tool) + 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): + debug('msvc default') + 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..15abdcd --- /dev/null +++ b/SCons/Tool/MSCommon/MSVC/Util.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. + +""" +Helper functions for Microsoft Visual C/C++. +""" + +import os +import re + +def listdir_dirs(p): + dirs = [] + 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): + if p: + p = os.path.normpath(p) + p = os.path.realpath(p) + p = os.path.normcase(p) + return p + +re_version_prefix = re.compile(r'^(?P<version>[0-9.]+).*') + +def get_version_prefix(version): + m = re_version_prefix.match(version) + if m: + rval = m.group('version') + else: + rval = '' + return rval + diff --git a/SCons/Tool/MSCommon/MSVC/WinSDK.py b/SCons/Tool/MSCommon/MSVC/WinSDK.py new file mode 100644 index 0000000..e95c72e --- /dev/null +++ b/SCons/Tool/MSCommon/MSVC/WinSDK.py @@ -0,0 +1,250 @@ +# 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 Dispatcher +from . import Util +from . import Config +from . import Registry + +from .Exceptions import ( + MSVCInternalError, +) + +Dispatcher.register_modulename(__name__) + +def _new_sdk_map(): + sdk_map = { + 'desktop': [], + 'uwp': [], + } + 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 platform_type, sdk_inc_file in [ + ('desktop', 'winsdkver.h'), + ('uwp', 'windows.h'), + ]: + + if not os.path.exists(os.path.join(sdk_inc_path, sdk_inc_file)): + continue + + key = (version_nbr, platform_type) + if key in sdk_version_platform_seen: + continue + sdk_version_platform_seen.add(key) + + sdk_map[platform_type].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 platform_type, sdk_inc_file in [ + ('desktop', 'winsdkver.h'), + ('uwp', 'windows.h'), + ]: + + if not os.path.exists(os.path.join(sdk_inc_path, sdk_inc_file)): + continue + + key = (version_nbr, platform_type) + if key in sdk_version_platform_seen: + continue + sdk_version_platform_seen.add(key) + + sdk_map[platform_type].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(): + 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_sdk_version_list(version_list, platform_type): + sdk_map = _sdk_map(version_list) + sdk_list = sdk_map.get(platform_type, []) + 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_version_prefix(msvc_version) + 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_type = 'uwp' if is_uwp else 'desktop' + sdk_list = get_sdk_version_list(vs_def.vc_sdk_versions, platform_type) + + 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/__init__.py b/SCons/Tool/MSCommon/MSVC/__init__.py new file mode 100644 index 0000000..afd993f --- /dev/null +++ b/SCons/Tool/MSCommon/MSVC/__init__.py @@ -0,0 +1,50 @@ +# 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++. +""" + +from . import Exceptions +from . import Util + +from . import Dispatcher as _Dispatcher + +from . import Config +from . import Registry +from . import SetupEnvDefault +from . import NotFound +from . import WinSDK +from . import ScriptArguments + +from .NotFound import ( + set_msvc_notfound_policy, + get_msvc_notfound_policy, +) + +def reset(): + _Dispatcher.reset() + +#reset() # testing +_Dispatcher.verify() + diff --git a/SCons/Tool/MSCommon/__init__.py b/SCons/Tool/MSCommon/__init__.py index de78f84..9f35e94 100644 --- a/SCons/Tool/MSCommon/__init__.py +++ b/SCons/Tool/MSCommon/__init__.py @@ -32,14 +32,17 @@ import SCons.Util from SCons.Tool.MSCommon.sdk import mssdk_exists, mssdk_setup_env +from SCons.Tool.MSCommon.MSVC import ( + set_msvc_notfound_policy, + get_msvc_notfound_policy, +) + from SCons.Tool.MSCommon.vc import ( 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, get_msvc_sdk_versions, ) diff --git a/SCons/Tool/MSCommon/vc.py b/SCons/Tool/MSCommon/vc.py index ab05323..5a27f44 100644 --- a/SCons/Tool/MSCommon/vc.py +++ b/SCons/Tool/MSCommon/vc.py @@ -51,7 +51,6 @@ from collections import ( namedtuple, OrderedDict, ) -import enum import SCons.Util import SCons.Warnings @@ -61,9 +60,10 @@ from . import common from .common import CONFIG_CACHE, debug from .sdk import get_installed_sdks - -class VisualCException(Exception): - pass +from . import MSVC +from .MSVC.Exceptions import ( + VisualCException +) class UnsupportedVersion(VisualCException): pass @@ -89,296 +89,10 @@ class MSVCScriptNotFound(VisualCException): class MSVCUseSettingsError(VisualCException): pass -class MSVCVersionNotFound(VisualCException): - pass - -class MSVCArgumentError(VisualCException): - pass - -class MSVCInternalError(VisualCException): - pass - class BatchFileExecutionWarning(SCons.Warnings.WarningOnByDefault): pass -class _Dispatcher: - - classrefs = [] - - @classmethod - def register(cls, classref): - cls.classrefs.append(classref) - - @classmethod - def reset(cls): - debug('reset %s', cls.__name__) - for classref in cls.classrefs: - for method in ['reset', '_reset']: - if not hasattr(classref, method) or not callable(getattr(classref, method, None)): - continue - debug('call %s.%s()', classref.__name__, method) - func = getattr(classref, method) - func() - - @classmethod - def verify(cls): - debug('verify %s', cls.__name__) - for classref in cls.classrefs: - for method in ['verify', '_verify']: - if not hasattr(classref, method) or not callable(getattr(classref, method, None)): - continue - debug('call %s.%s()', classref.__name__, method) - func = getattr(classref, method) - func() - -class _Config: - - BOOLEAN_SYMBOLS = {} - BOOLEAN_EXTERNAL = {} - - for bool, 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] = list(symbol_list) - for symbol in symbol_case_list: - BOOLEAN_SYMBOLS[bool].extend([symbol, symbol.lower(), symbol.upper()]) - - for symbol in BOOLEAN_SYMBOLS[bool]: - BOOLEAN_EXTERNAL[symbol] = bool - - 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', - ]) - - MSVC_BUILDTOOLS_DEFINITION_LIST = [] - - MSVC_BUILDTOOLS_INTERNAL = {} - MSVC_BUILDTOOLS_EXTERNAL = {} - - VC_VERSION_MAP = {} - - for vc_buildtools, vc_version, cl_version, vc_runtime in [ - ('v143', '14.3', '19.3', '140'), - ('v142', '14.2', '19.2', '140'), - ('v141', '14.1', '19.1', '140'), - ('v140', '14.0', '19.0', '140'), - ('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'), - ]: - - 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, - ) - - 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 = {} - - 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) - - 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_buildtools_def.vc_version] = vs_def - MSVC_VERSION_EXTERNAL[vs_product] = vs_def - MSVC_VERSION_EXTERNAL[vc_buildtools_def.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 - - MSVS_VERSION_MAJOR_MAP[vs_version_major] = vs_def - - CL_VERSION_MAP[vc_buildtools_def.cl_version] = vs_def - - if not vc_sdk: - continue - - MSVC_SDK_VERSIONS.update(vc_sdk) - - # 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)] - - MSVS_VERSION_LEGACY = {} - MSVC_VERSION_LEGACY = {} - - for vdict in (MSVS_VERSION_EXTERNAL, MSVC_VERSION_INTERNAL): - for key, vs_def in vdict.items(): - if key not in MSVS_VERSION_LEGACY: - MSVS_VERSION_LEGACY[key] = vs_def - MSVC_VERSION_LEGACY[key] = vs_def - -# 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 = { "amd64" : "amd64", @@ -1253,7 +967,7 @@ 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 - _Dispatcher.reset() + MSVC.reset() def get_default_installed_msvc(env=None): vcs = get_installed_vcs(env) @@ -1344,295 +1058,6 @@ def script_env(script, args=None): 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) - -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. - """ - - 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 - ) - - @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} - - 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) - - # return tool list in order presented - return tools_found_list - -_Dispatcher.register(_MSVCSetupEnvDefault) - def get_default_version(env): msvc_version = env.get('MSVC_VERSION') msvs_version = env.get('MSVS_VERSION') @@ -1673,16 +1098,16 @@ def msvc_setup_env_once(env, tool=None): 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.Notfound.policy_handler(env, msg) def msvc_find_valid_batch_script(env, version): """Find and execute appropriate batch script to set up build env. @@ -1724,7 +1149,7 @@ def msvc_find_valid_batch_script(env, version): debug('use_script 2 %s, args:%s', repr(vc_script), arg) found = None if vc_script: - arg = _ScriptArguments.msvc_script_arguments(env, version, vc_dir, arg) + arg = MSVC.ScriptArguments.msvc_script_arguments(env, version, vc_dir, arg) try: d = script_env(vc_script, args=arg) found = vc_script @@ -1769,7 +1194,7 @@ 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.NotFound.policy_handler(env, msg) return d @@ -1805,7 +1230,7 @@ def msvc_setup_env(env): 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 @@ -1899,7 +1324,7 @@ def msvc_setup_env_user(env=None): 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 @@ -1920,1043 +1345,6 @@ def get_msvc_sdk_versions(msvc_version=None, msvc_uwp_app=False): debug('no msvc versions detected') return rval - rval = _WindowsSDK.get_msvc_sdk_version_list(msvc_version, msvc_uwp_app) + rval = MSVC.WinSDK.get_msvc_sdk_version_list(msvc_version, msvc_uwp_app) return rval -class _Util: - - @staticmethod - def listdir_dirs(p): - dirs = [] - 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 - - @staticmethod - def process_path(p): - if p: - p = os.path.normpath(p) - p = os.path.realpath(p) - p = os.path.normcase(p) - return p - -class _Registry: - - def read_value(hkey, subkey_valname): - try: - rval = common.read_reg(subkey_valname, hkroot=hkey) - except OSError: - debug('OSError: hkey=%s, subkey=%s', repr(hkey), repr(subkey_valname)) - return None - except IndexError: - debug('IndexError: hkey=%s, subkey=%s', repr(hkey), repr(subkey_valname)) - return None - debug('hkey=%s, subkey=%s, rval=%s', repr(hkey), repr(subkey_valname), repr(rval)) - return rval - - @classmethod - def registry_query_path(cls, key, val, suffix): - extval = val + '\\' + suffix if suffix else val - qpath = cls.read_value(key, extval) - if qpath and os.path.exists(qpath): - qpath = _Util.process_path(qpath) - else: - qpath = None - return (qpath, key, val, extval) - - REG_SOFTWARE_MICROSOFT = [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'Software\Wow6432Node\Microsoft'), - (SCons.Util.HKEY_CURRENT_USER, r'Software\Wow6432Node\Microsoft'), # SDK queries - (SCons.Util.HKEY_LOCAL_MACHINE, r'Software\Microsoft'), - (SCons.Util.HKEY_CURRENT_USER, r'Software\Microsoft'), - ] - - @classmethod - def microsoft_query_paths(cls, suffix, usrval=None): - paths = [] - records = [] - for key, val in cls.REG_SOFTWARE_MICROSOFT: - extval = val + '\\' + suffix if suffix else val - qpath = cls.read_value(key, extval) - 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 - - @classmethod - def microsoft_query_keys(cls, suffix, usrval=None): - records = [] - for key, val in cls.REG_SOFTWARE_MICROSOFT: - extval = val + '\\' + suffix if suffix else val - rval = cls.read_value(key, extval) - if rval: - records.append((key, val, extval, usrval)) - return records - - @classmethod - def microsoft_sdks(cls, version): - return '\\'.join([r'Microsoft SDKs\Windows', 'v' + version, r'InstallationFolder']) - - @classmethod - def sdk_query_paths(cls, version): - q = cls.microsoft_sdks(version) - return cls.microsoft_query_paths(q) - - @classmethod - def windows_kits(cls, version): - return r'Windows Kits\Installed Roots\KitsRoot' + version - - @classmethod - def windows_kit_query_paths(cls, version): - q = cls.windows_kits(version) - return cls.microsoft_query_paths(q) - -class _WindowsSDK: - - @classmethod - def _new_sdk_map(cls): - sdk_map = { - 'desktop': [], - 'uwp': [], - } - return sdk_map - - @classmethod - def _sdk_10_layout(cls, version): - - folder_prefix = version + '.' - - sdk_map = cls._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 platform_type, sdk_inc_file in [ - ('desktop', 'winsdkver.h'), - ('uwp', 'windows.h'), - ]: - - if not os.path.exists(os.path.join(sdk_inc_path, sdk_inc_file)): - continue - - key = (version_nbr, platform_type) - if key in sdk_version_platform_seen: - continue - sdk_version_platform_seen.add(key) - - sdk_map[platform_type].append(version_nbr) - - for key, val in sdk_map.items(): - val.sort(reverse=True) - - return sdk_map - - @classmethod - def _sdk_81_layout(cls, version): - - version_nbr = version - - sdk_map = cls._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 platform_type, sdk_inc_file in [ - ('desktop', 'winsdkver.h'), - ('uwp', 'windows.h'), - ]: - - if not os.path.exists(os.path.join(sdk_inc_path, sdk_inc_file)): - continue - - key = (version_nbr, platform_type) - if key in sdk_version_platform_seen: - continue - sdk_version_platform_seen.add(key) - - sdk_map[platform_type].append(version_nbr) - - for key, val in sdk_map.items(): - val.sort(reverse=True) - - return sdk_map - - sdk_map_cache = {} - sdk_cache = {} - - @classmethod - def _reset_sdk_cache(cls): - debug('reset %s: sdk cache', cls.__name__) - cls._sdk_map_cache = {} - cls._sdk_cache = {} - - @classmethod - def _sdk_10(cls, key, reg_version): - if key in cls.sdk_map_cache: - sdk_map = cls.sdk_map_cache[key] - else: - sdk_map = cls._sdk_10_layout(reg_version) - cls.sdk_map_cache[key] = sdk_map - return sdk_map - - @classmethod - def _sdk_81(cls, key, reg_version): - if key in cls.sdk_map_cache: - sdk_map = cls.sdk_map_cache[key] - else: - sdk_map = cls._sdk_81_layout(reg_version) - cls.sdk_map_cache[key] = sdk_map - return sdk_map - - @classmethod - def _combine_sdk_map_list(cls, sdk_map_list): - combined_sdk_map = cls._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 = None - - @classmethod - def _init_sdk_dispatch_map(cls): - cls.sdk_dispatch_map = { - '10.0': (cls._sdk_10, '10.0'), - '8.1': (cls._sdk_81, '8.1'), - } - - @classmethod - def _verify_sdk_dispatch_map(cls): - debug('%s verify sdk_dispatch_map', cls.__name__) - cls._init_sdk_dispatch_map() - for sdk_version in _Config.MSVC_SDK_VERSIONS: - if sdk_version in cls.sdk_dispatch_map: - continue - err_msg = 'sdk version {} not in {}.sdk_dispatch_map'.format(sdk_version, cls.__name__) - raise MSVCInternalError(err_msg) - return None - - @classmethod - def _version_list_sdk_map(cls, version_list): - - if not cls.sdk_dispatch_map: - cls._init_sdk_dispatch_map() - - sdk_map_list = [] - for version in version_list: - func, reg_version = cls.sdk_dispatch_map[version] - sdk_map = func(version, reg_version) - sdk_map_list.append(sdk_map) - - combined_sdk_map = cls._combine_sdk_map_list(sdk_map_list) - return combined_sdk_map - - @classmethod - def _sdk_map(cls, version_list): - key = tuple(version_list) - if key in cls.sdk_cache: - sdk_map = cls.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 = cls._version_list_sdk_map(key) - cls.sdk_cache[key] = sdk_map - return sdk_map - - @classmethod - def get_sdk_version_list(cls, version_list, platform_type): - sdk_map = cls._sdk_map(version_list) - sdk_list = sdk_map.get(platform_type, []) - return sdk_list - - @classmethod - def get_msvc_sdk_version_list(cls, msvc_version=None, msvc_uwp_app=False): - debug('msvc_version=%s, msvc_uwp_app=%s', repr(msvc_version), repr(msvc_uwp_app)) - - sdk_versions = [] - - verstr = get_msvc_version_numeric(msvc_version) - 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_type = 'uwp' if is_uwp else 'desktop' - sdk_list = _WindowsSDK.get_sdk_version_list(vs_def.vc_sdk_versions, platform_type) - - sdk_versions.extend(sdk_list) - debug('sdk_versions=%s', repr(sdk_versions)) - - return sdk_versions - - @classmethod - def reset(cls): - debug('reset %s', cls.__name__) - cls._reset_sdk_cache() - - @classmethod - def verify(cls): - debug('verify %s', cls.__name__) - cls._verify_sdk_dispatch_map() - -_Dispatcher.register(_WindowsSDK) - -class _ScriptArguments: - - # 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, - } - - @classmethod - def _verify_re_sdk_dispatch_map(cls): - debug('%s verify re_sdk_dispatch_map', cls.__name__) - for sdk_version in _Config.MSVC_SDK_VERSIONS: - if sdk_version in cls.re_sdk_dispatch_map: - continue - err_msg = 'sdk version {} not in {}.re_sdk_dispatch_map'.format(sdk_version, cls.__name__) - raise MSVCInternalError(err_msg) - return None - - # capture msvc version - re_toolset_version = re.compile(r'^(?P<version>[1-9][0-9]?[.][0-9])[0-9.]*$', re.IGNORECASE) - - 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) - - # valid SxS formats will be matched with re_toolset_full: match 3 '.' format - re_toolset_sxs = re.compile(r'^[1-9][0-9][.][0-9]{2}[.][0-9]{2}[.][0-9]{1,2}$') - - # 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 - - # 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): - ARCH = 0 # arch - 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', # fully qualified msvc version (e.g., '14.1Exp') - 'vs_def', - ]) - - @classmethod - def _msvc_version(cls, version): - - verstr = get_msvc_version_numeric(version) - vs_def = _Config.MSVC_VERSION_INTERNAL[verstr] - - version_args = cls.MSVC_VERSION_ARGS_DEFINITION( - version = version, - vs_def = vs_def, - ) - - return version_args - - @classmethod - def _msvc_script_argument_uwp(cls, 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 _Config.BOOLEAN_SYMBOLS[True]: - return None - - if msvc.vs_def.vc_buildtools_def.vc_version_numeric < cls.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(cls.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(cls.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 = (cls.SortOrder.UWP, uwp_arg) - arglist.append(argpair) - - return uwp_arg - - @classmethod - def _user_script_argument_uwp(cls, env, uwp, user_argstr): - - matches = [m for m in cls.re_vcvars_uwp.finditer(user_argstr)] - if not matches: - return None - - 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 None - - 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) - - @classmethod - def _msvc_script_argument_sdk_constraints(cls, msvc, sdk_version): - - if msvc.vs_def.vc_buildtools_def.vc_version_numeric < cls.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(cls.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(cls.VS2015.vc_buildtools_def.vc_version) - ) - return err_msg - - for msvc_sdk_version in msvc.vs_def.vc_sdk_versions: - re_sdk_version = cls.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 - - @classmethod - def _msvc_script_argument_sdk(cls, env, msvc, platform_type, 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_type) - ) - - if not sdk_version: - return None - - err_msg = cls._msvc_script_argument_sdk_constraints(msvc, sdk_version) - if err_msg: - raise MSVCArgumentError(err_msg) - - sdk_list = _WindowsSDK.get_sdk_version_list(msvc.vs_def.vc_sdk_versions, platform_type) - - if sdk_version not in sdk_list: - err_msg = "MSVC_SDK_VERSION {} not found for platform type {}".format( - repr(sdk_version), repr(platform_type) - ) - raise MSVCArgumentError(err_msg) - - argpair = (cls.SortOrder.SDK, sdk_version) - arglist.append(argpair) - - return sdk_version - - @classmethod - def _msvc_script_default_sdk(cls, env, msvc, platform_type, arglist): - - if msvc.vs_def.vc_buildtools_def.vc_version_numeric < cls.VS2015.vc_buildtools_def.vc_version_numeric: - return None - - sdk_list = _WindowsSDK.get_sdk_version_list(msvc.vs_def.vc_sdk_versions, platform_type) - 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_type) - ) - - argpair = (cls.SortOrder.SDK, sdk_default) - arglist.append(argpair) - - return sdk_default - - @classmethod - def _user_script_argument_sdk(cls, env, sdk_version, user_argstr): - - matches = [m for m in cls.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) - - @classmethod - def _msvc_read_toolset_file(cls, 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 - - @classmethod - def _msvc_read_toolset_folders(cls, msvc, vc_dir): - - toolsets_sxs = {} - toolsets_full = [] - - build_dir = os.path.join(vc_dir, "Auxiliary", "Build") - sxs_toolsets = [f.name for f in os.scandir(build_dir) if f.is_dir()] - for sxs_toolset in sxs_toolsets: - filename = 'Microsoft.VCToolsVersion.{}.txt'.format(sxs_toolset) - filepath = os.path.join(build_dir, sxs_toolset, filename) - debug('sxs toolset: check file=%s', repr(filepath)) - if os.path.exists(filepath): - toolset_version = cls._msvc_read_toolset_file(msvc, filepath) - if not toolset_version: - continue - toolsets_sxs[sxs_toolset] = toolset_version - debug( - 'sxs toolset: msvc_version=%s, sxs_version=%s, toolset_version=%s', - repr(msvc.version), repr(sxs_toolset), toolset_version - ) - - toolset_dir = os.path.join(vc_dir, "Tools", "MSVC") - toolsets = [f.name for f in os.scandir(toolset_dir) if f.is_dir()] - for toolset_version in toolsets: - binpath = os.path.join(toolset_dir, toolset_version, "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) - ) - - toolsets_full.sort(reverse=True) - debug('msvc_version=%s, toolsets=%s', repr(msvc.version), repr(toolsets_full)) - - return toolsets_sxs, toolsets_full - - @classmethod - def _msvc_read_toolset_default(cls, 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 = cls._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 = cls._msvc_read_toolset_file(msvc, filepath) - if toolset_default: - return toolset_default - - return None - - _toolset_version_cache = {} - _toolset_default_cache = {} - - @classmethod - def _reset_toolset_cache(cls): - debug('reset %s: toolset cache', cls.__name__) - cls._toolset_version_cache = {} - cls._toolset_default_cache = {} - - @classmethod - def _msvc_version_toolsets(cls, msvc, vc_dir): - - if msvc.version in cls._toolset_version_cache: - toolsets_sxs, toolsets_full = cls._toolset_version_cache[msvc.version] - else: - toolsets_sxs, toolsets_full = cls._msvc_read_toolset_folders(msvc, vc_dir) - cls._toolset_version_cache[msvc.version] = toolsets_sxs, toolsets_full - - return toolsets_sxs, toolsets_full - - @classmethod - def _msvc_default_toolset(cls, msvc, vc_dir): - - if msvc.version in cls._toolset_default_cache: - toolset_default = cls._toolset_default_cache[msvc.version] - else: - toolset_default = cls._msvc_read_toolset_default(msvc, vc_dir) - cls._toolset_default_cache[msvc.version] = toolset_default - - return toolset_default - - @classmethod - def _msvc_version_toolset_vcvars(cls, msvc, vc_dir, toolset_version): - - if toolset_version == '14.0': - return toolset_version - - toolsets_sxs, toolsets_full = cls._msvc_version_toolsets(msvc, vc_dir) - - if msvc.vs_def.vc_buildtools_def.vc_version_numeric == cls.VS2019.vc_buildtools_def.vc_version_numeric: - # necessary to detect toolset not found - if toolset_version == '14.28.16.8': - new_toolset_version = '14.28' - # 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. - debug( - 'rewrite toolset_version=%s => toolset_version=%s', - repr(toolset_version), repr(new_toolset_version) - ) - toolset_version = new_toolset_version - - if toolset_version in toolsets_sxs: - toolset_vcvars = toolsets_sxs[toolset_version] - return toolset_vcvars - - for toolset_full in toolsets_full: - if toolset_full.startswith(toolset_version): - toolset_vcvars = toolset_full - return toolset_vcvars - - return None - - @classmethod - def _msvc_script_argument_toolset_constraints(cls, msvc, toolset_version): - - if msvc.vs_def.vc_buildtools_def.vc_version_numeric < cls.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(cls.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(cls.VS2017.vc_buildtools_def.vc_version) - ) - return err_msg - - m = cls.re_toolset_version.match(toolset_version) - if not m: - debug('invalid: re_toolset_version: toolset_version=%s', repr(toolset_version)) - err_msg = 'MSVC_TOOLSET_VERSION {} format is not supported'.format( - repr(toolset_version) - ) - return err_msg - - toolset_ver = m.group('version') - toolset_vernum = float(toolset_ver) - - if toolset_vernum < cls.VS2015.vc_buildtools_def.vc_version_numeric: - debug( - 'invalid: toolset version constraint: %s < %s VS2015', - repr(toolset_vernum), repr(cls.VS2015.vc_buildtools_def.vc_version_numeric) - ) - err_msg = "MSVC_TOOLSET_VERSION ({}) constraint violation: toolset version {} < {} VS2015".format( - repr(toolset_version), repr(toolset_ver), repr(cls.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_ver), repr(msvc.version) - ) - return err_msg - - if toolset_vernum == cls.VS2015.vc_buildtools_def.vc_version_numeric: - # tooset = 14.0 - if cls.re_toolset_full.match(toolset_version): - if not cls.re_toolset_140.match(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 cls.re_toolset_full.match(toolset_version): - debug('valid: re_toolset_full: toolset_version=%s', repr(toolset_version)) - return None - - if cls.re_toolset_sxs.match(toolset_version): - debug('valid: re_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 - - @classmethod - def _msvc_script_argument_toolset(cls, 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 - - err_msg = cls._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 = cls._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 MSVCArgumentError(err_msg) - - argpair = (cls.SortOrder.TOOLSET, '-vcvars_ver={}'.format(toolset_vcvars)) - arglist.append(argpair) - - return toolset_vcvars - - @classmethod - def _msvc_script_default_toolset(cls, env, msvc, vc_dir, arglist): - - if msvc.vs_def.vc_buildtools_def.vc_version_numeric < cls.VS2017.vc_buildtools_def.vc_version_numeric: - return None - - toolset_default = cls._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)) - - argpair = (cls.SortOrder.TOOLSET, '-vcvars_ver={}'.format(toolset_default)) - arglist.append(argpair) - - return toolset_default - - @classmethod - def _user_script_argument_toolset(cls, env, toolset_version, user_argstr): - - matches = [m for m in cls.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) - - @classmethod - def _msvc_script_argument_spectre(cls, env, msvc, 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 (True, '1'): - return None - - if msvc.vs_def.vc_buildtools_def.vc_version_numeric < cls.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(cls.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(cls.VS2017.vc_buildtools_def.vc_version) - ) - raise MSVCArgumentError(err_msg) - - spectre_arg = 'spectre' - - # spectre libs may not be installed - argpair = (cls.SortOrder.SPECTRE, '-vcvars_spectre_libs={}'.format(spectre_arg)) - arglist.append(argpair) - - return spectre_arg - - @classmethod - def _msvc_script_argument_user(cls, 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 < cls.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(cls.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(cls.VS2015.vc_buildtools_def.vc_version) - ) - raise MSVCArgumentError(err_msg) - - # user arguments are not validated - argpair = (cls.SortOrder.USER, script_args) - arglist.append(argpair) - - return script_args - - @classmethod - def _user_script_argument_spectre(cls, env, spectre, user_argstr): - - matches = [m for m in cls.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) - - @classmethod - def msvc_script_arguments(cls, env, version, vc_dir, arg): - - arglist = [] - - msvc = cls._msvc_version(version) - - if arg: - argpair = (cls.SortOrder.ARCH, arg) - arglist.append(argpair) - - if 'MSVC_SCRIPT_ARGS' in env: - user_argstr = cls._msvc_script_argument_user(env, msvc, arglist) - else: - user_argstr = None - - if 'MSVC_UWP_APP' in env: - uwp = cls._msvc_script_argument_uwp(env, msvc, arglist) - else: - uwp = None - - if user_argstr: - cls._user_script_argument_uwp(env, uwp, user_argstr) - - platform_type = 'uwp' if uwp else 'desktop' - - if 'MSVC_SDK_VERSION' in env: - sdk_version = cls._msvc_script_argument_sdk(env, msvc, platform_type, arglist) - else: - sdk_version = None - - if user_argstr: - user_sdk = cls._user_script_argument_sdk(env, sdk_version, user_argstr) - else: - user_sdk = None - - if cls.MSVC_FORCE_DEFAULT_SDK: - if not sdk_version and not user_sdk: - sdk_version = cls._msvc_script_default_sdk(env, msvc, platform_type, arglist) - - if 'MSVC_TOOLSET_VERSION' in env: - toolset_version = cls._msvc_script_argument_toolset(env, msvc, vc_dir, arglist) - else: - toolset_version = None - - if user_argstr: - user_toolset = cls._user_script_argument_toolset(env, toolset_version, user_argstr) - else: - user_toolset = None - - if cls.MSVC_FORCE_DEFAULT_TOOLSET: - if not toolset_version and not user_toolset: - toolset_version = cls._msvc_script_default_toolset(env, msvc, vc_dir, arglist) - - if 'MSVC_SPECTRE_LIBS' in env: - spectre = cls._msvc_script_argument_spectre(env, msvc, arglist) - else: - spectre = None - - if user_argstr: - cls._user_script_argument_spectre(env, spectre, user_argstr) - - if arglist: - arglist.sort() - argstr = ' '.join([argpair[-1] for argpair in arglist]).strip() - else: - argstr = '' - - debug('arguments: %s', repr(argstr)) - return argstr - - @classmethod - def reset(cls): - debug('reset %s', cls.__name__) - cls._reset_toolset_cache() - - @classmethod - def verify(cls): - debug('verify %s', cls.__name__) - cls._verify_re_sdk_dispatch_map() - -_Dispatcher.register(_ScriptArguments) - -# internal consistency check -_Dispatcher.verify() |