diff --git a/CHANGES.txt b/CHANGES.txt index e2fe339db1..9d69e707ad 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -12,6 +12,64 @@ NOTE: Python 3.6 support is deprecated and will be dropped in a future reease. RELEASE VERSION/DATE TO BE FILLED IN LATER + From Joseph Brill: + - For msvc version specifications without an 'Exp' suffix, an express installation + is used when no other edition is detected for the msvc version. Similarly, an + express installation of the IDE binary is used when no other IDE edition is + detected. + - VS2015 Express (14.0Exp) does not support the sdk version argument. VS2015 Express + does not support the store argument for target architectures other than x86. + Script argument validation now takes into account these restrictions. + - VS2015 BuildTools (14.0) does not support the sdk version argument and does not + support the store argument. Script argument validation now takes into account + these restrictions. + - The Windows SDK for Windows 7 and .NET Framework 4" (SDK 7.1) populates the + registry keys in a manner in which the msvc detection would report that VS2010 + (10.0) is installed when only the SDK was installed. The installed files are + intended to be used via the sdk batch file setenv.cmd. The installed msvc + batch files will fail. The msvc detection logic now ignores SDK-only VS2010 + installations. Similar protection is implemented for the sdk-only installs that + populate the installation folder and registry keys for VS2008 (9.0), if necessary. + - For VS2005 (8.0) to VS2015 (14.0), vsvarsall.bat is employed to dispatch to a + dependent batch file when configuring the msvc environment. Previously, only the + existence of the compiler executable was verified. In certain installations, the + dependent batch file (e.g., vcvars64.bat) may not exist while the compiler + executable does exist resulting in build failures. The existence of vcvarsall.bat, + the dependent batch file, and the compiler executable are now validated. + - MSVC configuration data specific to versions VS2005 (8.0) to VS2008 (9.0) was added + as the dependent batch files have different names than the batch file names used + for VS2010 (10.0) and later. The VS2008 (9.0) Visual C++ For Python installation + is handled as a special case as the dependent batch files are: (a) not used and (b) + in different locations. + - When VS2008 (9.0) Visual C++ For Python is installed using the ALLUSERS=1 option + (i.e., msiexec /i VCForPython27.msi ALLUSERS=1), the registry keys are written to + HKEY_LOCAL_MACHINE rather than HKEY_CURRENT_USER. An entry was added to query the + Visual C++ For Python keys in HKLM following the HKCU query, if necessary. + - For VS2008, a full development edition (e.g., Professional) is now selected before + a Visual C++ For Python edition. Prior to this change, Visual C++ For Python was + selected before a full development edition when both editions are installed. + - The registry detection of VS2015 (14.0), and earlier, is now cached at runtime and + is only evaluated once for each msvc version. + - The vswhere executable is frozen upon initial detection. Specifying a different + vswhere executable via the construction variable VSWHERE after the initial + detection now results in an exception. Multiple bugs in the implementation of + specifying a vswhere executable via the construction variable VSWHERE have been + fixed. Previously, when a user specified vswhere executable detects new msvc + installations after the initial detection, the internal msvc installation cache + and the default msvc version based on the initial detection are no longer valid. + For example, when no vswhere executable is found for the initial detection + and then later an environment is constructed with a user specified vswhere + executable that detects new msvc installations. + - The vswhere detection of VS2017 (14.1), and later, is now cached at runtime and is + only evaluated once using a single vswhere invocation for all msvc versions. + Previously, the vswhere executable was invoked for each supported msvc version. + - The vswhere executable locations for the WinGet and Scoop package managers were + added to the default vswhere executable search list after the Chocolatey + installation location. + - Fix issue #4543: add support for msvc toolset versions 14.4X installed as the + latest msvc toolset versions for msvc buildtools v143. The v143 msvc buildtools + may contain msvc toolset versions from 14.30 to 14.4X. + From Thaddeus Crews: - GetSConsVersion() to grab the latest SCons version without needing to access SCons internals. @@ -1490,7 +1548,7 @@ RELEASE 3.1.2 - Mon, 17 Dec 2019 02:06:27 +0000 in the case where a child process is spawned while a Python action has a file open. Original author: Ryan Beasley. - Added memoization support for calls to Environment.Value() in order to - improve performance of repeated calls. + improve performance of repeated calls. From Jason Kenny diff --git a/RELEASE.txt b/RELEASE.txt index 8851109e06..aa17f64839 100644 --- a/RELEASE.txt +++ b/RELEASE.txt @@ -44,6 +44,15 @@ CHANGED/ENHANCED EXISTING FUNCTIONALITY of CCFLAGS; the latter variable could cause a compiler warning. - The implementation of Variables was slightly refactored, there should not be user-visible changes. +- MSVC: For msvc version specifications without an 'Exp' suffix, an express + installation is used when no other edition is detected for the msvc version. + This was the behavior for Visual Studio 2008 (9.0) through Visual Studio 2015 + (14.0). This behavior was extended to Visual Studio 2017 (14.1) and Visual + Studio 2008 (8.0). An express installation of the IDE binary is used when no + other IDE edition is detected. +- The vswhere executable locations for the WinGet and Scoop package managers were + added to the default vswhere executable search list after the Chocolatey + installation location. - SCons.Environment.is_valid_construction_var() now returns a boolean to match the convention that functions beginning with "is" have yes/no answers (previously returned either None or an re.match object). @@ -73,6 +82,37 @@ FIXES - Improved the conversion of a "foreign" exception from an action into BuildError by making sure our defaults get applied even in corner cases. Fixes Issue #4530 +- MSVC: Visual Studio 2010 (10.0) could be inadvertently detected due to an + sdk-only install of Windows SDK 7.1. An sdk-only install of Visual Studio + 2010 is ignored as the msvc batch files will fail. The installed files are + intended to be used in conjunction with the SDK batch file. Similar protection + was added for Visual Studio 2008 (9.0). +- MSVC: For Visual Studio 2005 (8.0) to Visual Studio 2015 (14.0), detection of + installed files was expanded to include the primary msvc batch file, dependent + msvc batch file, and compiler executable. In certain installations, the + dependent msvc batch file may not exist while the compiler executable does exist + resulting in a build failure. +- MSVC: Visual Studio 2008 (9.0) Visual C++ For Python was not detected when + installed using the ALLUSERS command-line option: + msiexec /i VCForPython27.msi ALLUSERS=1 + When installed for all users, Visual Studio 2008 (9.0) Visual C++ For Python is + now correctly detected. +- MSVC: For Visual Studio 2008 (9.0), a full development edition (e.g., Professional) + is now selected before a Visual C++ For Python edition. Prior to this change, + Visual C++ For Python was selected before a full development edition when both + editions are installed. +- The vswhere executable is frozen upon initial detection. Specifying a different + vswhere executable via the construction variable VSWHERE after the initial + detection now results in an exception. Multiple bugs in the implementation of + specifying a vswhere executable via the construction variable VSWHERE have been + fixed. Previously, when a user specified vswhere executable detects new msvc + installations after the initial detection, the internal msvc installation cache + and the default msvc version based on the initial detection are no longer valid. + For example, when no vswhere executable is found for the initial detection + and then later an environment is constructed with a user specified vswhere + executable that detects new msvc installations. +- MSVC: Visual Studio 2022 v143 BuildTools now supports msvc toolset versions from + 14.30 to 14.4X. Fixes Issue #4543. - The Clone() method now respects the variables argument (fixes #3590) IMPROVEMENTS @@ -84,6 +124,17 @@ IMPROVEMENTS - Performance tweak: the __setitem__ method of an Environment, used for setting construction variables, now uses the string method isidentifier to validate the name (updated from microbenchmark results). +- MSVC: Visual Studio 2015 Express (14.0Exp) does not support the sdk version + argument. Visual Studio 2015 Express does not support the store argument for + target architectures other than x86. Script argument validation now takes into + account these restrictions. +- MSVC: Visual Studio 2015 BuildTools (14.0) does not support the sdk version + argument and does not support the store argument. Script argument validation now + takes into account these restrictions. +- MSVC: The registry detection of VS2015 (14.0), and earlier, is now cached at runtime + and is only evaluated once for each msvc version. +- MSVC: The vswhere detection of VS2017 (14.1), and later, is now cached at runtime and + is only evaluated once using a single vswhere invocation for all msvc versions. PACKAGING --------- diff --git a/SCons/Tool/MSCommon/MSVC/Config.py b/SCons/Tool/MSCommon/MSVC/Config.py index 7c0f1fe6ff..d0ccb6d0ba 100644 --- a/SCons/Tool/MSCommon/MSVC/Config.py +++ b/SCons/Tool/MSCommon/MSVC/Config.py @@ -118,15 +118,67 @@ 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', +MSVC_BUILDSERIES_DEFINITION = namedtuple('MSVCBuildSeries', [ + 'vc_buildseries', + 'vc_buildseries_numeric', 'vc_version', 'vc_version_numeric', 'cl_version', 'cl_version_numeric', +]) + +MSVC_BUILDSERIES_DEFINITION_LIST = [] + +MSVC_BUILDSERIES_INTERNAL = {} +MSVC_BUILDSERIES_EXTERNAL = {} + +VC_BUILDTOOLS_MAP = {} + +VC_VERSION_MAP = {} +CL_VERSION_MAP = {} + +for (vc_buildseries, vc_version, cl_version) in [ + ('144', '14.4', '19.4'), + ('143', '14.3', '19.3'), + ('142', '14.2', '19.2'), + ('141', '14.1', '19.1'), + ('140', '14.0', '19.0'), + ('120', '12.0', '18.0'), + ('110', '11.0', '17.0'), + ('100', '10.0', '16.0'), + ('90', '9.0', '15.0'), + ('80', '8.0', '14.0'), + ('71', '7.1', '13.1'), + ('70', '7.0', '13.0'), + ('60', '6.0', '12.0'), +]: + + vc_buildseries_def = MSVC_BUILDSERIES_DEFINITION( + vc_buildseries=vc_buildseries, + vc_buildseries_numeric=int(vc_buildseries), + vc_version=vc_version, + vc_version_numeric=float(vc_version), + cl_version=cl_version, + cl_version_numeric=float(cl_version), + ) + + MSVC_BUILDSERIES_DEFINITION_LIST.append(vc_buildseries_def) + + MSVC_BUILDSERIES_INTERNAL[vc_buildseries] = vc_buildseries_def + MSVC_BUILDSERIES_EXTERNAL[vc_buildseries] = vc_buildseries_def + MSVC_BUILDSERIES_EXTERNAL[vc_version] = vc_buildseries_def + + VC_VERSION_MAP[vc_version] = vc_buildseries_def + CL_VERSION_MAP[cl_version] = vc_buildseries_def + +MSVC_BUILDTOOLS_DEFINITION = namedtuple('MSVCBuildtools', [ + 'vc_buildtools', + 'vc_buildtools_numeric', + 'vc_buildseries_list', 'vc_runtime_def', 'vc_istoolset', + 'msvc_version', + 'msvc_version_numeric', ]) MSVC_BUILDTOOLS_DEFINITION_LIST = [] @@ -134,43 +186,58 @@ MSVC_BUILDTOOLS_INTERNAL = {} MSVC_BUILDTOOLS_EXTERNAL = {} -VC_VERSION_MAP = {} - -for vc_buildtools, vc_version, cl_version, vc_runtime, vc_istoolset in [ - ('v143', '14.3', '19.3', '140', True), - ('v142', '14.2', '19.2', '140', True), - ('v141', '14.1', '19.1', '140', True), - ('v140', '14.0', '19.0', '140', True), - ('v120', '12.0', '18.0', '120', False), - ('v110', '11.0', '17.0', '110', False), - ('v100', '10.0', '16.0', '100', False), - ('v90', '9.0', '15.0', '90', False), - ('v80', '8.0', '14.0', '80', False), - ('v71', '7.1', '13.1', '71', False), - ('v70', '7.0', '13.0', '70', False), - ('v60', '6.0', '12.0', '60', False), +MSVC_VERSION_NEWEST = None +MSVC_VERSION_NEWEST_NUMERIC = 0.0 + +for vc_buildtools, vc_buildseries_list, vc_runtime, vc_istoolset in [ + ('v143', ['144', '143'], '140', True), + ('v142', ['142'], '140', True), + ('v141', ['141'], '140', True), + ('v140', ['140'], '140', True), + ('v120', ['120'], '120', False), + ('v110', ['110'], '110', False), + ('v100', ['100'], '100', False), + ('v90', ['90'], '90', False), + ('v80', ['80'], '80', False), + ('v71', ['71'], '71', False), + ('v70', ['70'], '70', False), + ('v60', ['60'], '60', False), ]: vc_runtime_def = MSVC_RUNTIME_INTERNAL[vc_runtime] + vc_buildseries_list = tuple( + MSVC_BUILDSERIES_INTERNAL[vc_buildseries] + for vc_buildseries in vc_buildseries_list + ) + + vc_buildtools_numstr = vc_buildtools[1:] + + msvc_version = vc_buildtools_numstr[:-1] + '.' + vc_buildtools_numstr[-1] + msvc_version_numeric = float(msvc_version) + 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_buildseries_list = vc_buildseries_list, vc_runtime_def = vc_runtime_def, vc_istoolset = vc_istoolset, + msvc_version = msvc_version, + msvc_version_numeric = msvc_version_numeric, ) 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 + MSVC_BUILDTOOLS_EXTERNAL[msvc_version] = vc_buildtools_def + + for vc_buildseries_def in vc_buildseries_list: + VC_BUILDTOOLS_MAP[vc_buildseries_def.vc_buildseries] = vc_buildtools_def - VC_VERSION_MAP[vc_version] = vc_buildtools_def + if vc_buildtools_def.msvc_version_numeric > MSVC_VERSION_NEWEST_NUMERIC: + MSVC_VERSION_NEWEST_NUMERIC = vc_buildtools_def.msvc_version_numeric + MSVC_VERSION_NEWEST = vc_buildtools_def.msvc_version MSVS_VERSION_INTERNAL = {} MSVS_VERSION_EXTERNAL = {} @@ -181,8 +248,6 @@ MSVS_VERSION_MAJOR_MAP = {} -CL_VERSION_MAP = {} - MSVC_SDK_VERSIONS = set() VISUALSTUDIO_DEFINITION = namedtuple('VisualStudioDefinition', [ @@ -247,15 +312,15 @@ vc_buildtools_def.vc_runtime_def.vc_runtime_vsdef_list.append(vs_def) - vc_version = vc_buildtools_def.vc_version + msvc_version = vc_buildtools_def.msvc_version MSVS_VERSION_INTERNAL[vs_product] = vs_def MSVS_VERSION_EXTERNAL[vs_product] = vs_def MSVS_VERSION_EXTERNAL[vs_version] = vs_def - MSVC_VERSION_INTERNAL[vc_version] = vs_def + MSVC_VERSION_INTERNAL[msvc_version] = vs_def MSVC_VERSION_EXTERNAL[vs_product] = vs_def - MSVC_VERSION_EXTERNAL[vc_version] = vs_def + MSVC_VERSION_EXTERNAL[msvc_version] = vs_def MSVC_VERSION_EXTERNAL[vc_buildtools_def.vc_buildtools] = vs_def if vs_product in VS_PRODUCT_ALIAS: @@ -264,14 +329,12 @@ MSVS_VERSION_EXTERNAL[vs_product_alias] = vs_def MSVC_VERSION_EXTERNAL[vs_product_alias] = vs_def - MSVC_VERSION_SUFFIX[vc_version] = vs_def + MSVC_VERSION_SUFFIX[msvc_version] = vs_def if vs_express: - MSVC_VERSION_SUFFIX[vc_version + 'Exp'] = vs_def + MSVC_VERSION_SUFFIX[msvc_version + 'Exp'] = vs_def MSVS_VERSION_MAJOR_MAP[vs_version_major] = vs_def - CL_VERSION_MAP[vc_buildtools_def.cl_version] = vs_def - if vc_sdk: MSVC_SDK_VERSIONS.update(vc_sdk) @@ -292,7 +355,7 @@ for vs_def in VISUALSTUDIO_DEFINITION_LIST: if not vs_def.vc_buildtools_def.vc_istoolset: continue - version_key = vs_def.vc_buildtools_def.vc_version + version_key = vs_def.vc_buildtools_def.msvc_version MSVC_VERSION_TOOLSET_DEFAULTS_MAP[version_key] = [version_key] MSVC_VERSION_TOOLSET_SEARCH_MAP[version_key] = [] if vs_def.vs_express: @@ -305,11 +368,11 @@ for vs_def in VISUALSTUDIO_DEFINITION_LIST: if not vs_def.vc_buildtools_def.vc_istoolset: continue - version_key = vs_def.vc_buildtools_def.vc_version + version_key = vs_def.vc_buildtools_def.msvc_version for vc_buildtools in vs_def.vc_buildtools_all: toolset_buildtools_def = MSVC_BUILDTOOLS_INTERNAL[vc_buildtools] - toolset_vs_def = MSVC_VERSION_INTERNAL[toolset_buildtools_def.vc_version] - buildtools_key = toolset_buildtools_def.vc_version + toolset_vs_def = MSVC_VERSION_INTERNAL[toolset_buildtools_def.msvc_version] + buildtools_key = toolset_buildtools_def.msvc_version MSVC_VERSION_TOOLSET_SEARCH_MAP[buildtools_key].extend(MSVC_VERSION_TOOLSET_DEFAULTS_MAP[version_key]) # convert string version set to string version list ranked in descending order diff --git a/SCons/Tool/MSCommon/MSVC/Kind.py b/SCons/Tool/MSCommon/MSVC/Kind.py new file mode 100644 index 0000000000..47d4a25d9f --- /dev/null +++ b/SCons/Tool/MSCommon/MSVC/Kind.py @@ -0,0 +1,668 @@ +# 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. + +""" +Version kind categorization for Microsoft Visual C/C++. +""" + +import os +import re + +from collections import ( + namedtuple, +) + +from ..common import ( + debug, +) + +from . import Registry +from . import Util + +from . import Dispatcher +Dispatcher.register_modulename(__name__) + + +# use express install for non-express msvc_version if no other installations found +USE_EXPRESS_FOR_NONEXPRESS = True + +# productdir kind + +VCVER_KIND_UNKNOWN = 0 # undefined +VCVER_KIND_DEVELOP = 1 # devenv binary +VCVER_KIND_EXPRESS = 2 # express binary +VCVER_KIND_BTDISPATCH = 3 # no ide binaries (buildtools dispatch folder) +VCVER_KIND_VCFORPYTHON = 4 # no ide binaries (2008/9.0) +VCVER_KIND_EXPRESS_WIN = 5 # express for windows binary (VSWinExpress) +VCVER_KIND_EXPRESS_WEB = 6 # express for web binary (VWDExpress) +VCVER_KIND_SDK = 7 # no ide binaries +VCVER_KIND_CMDLINE = 8 # no ide binaries + +VCVER_KIND_STR = { + VCVER_KIND_UNKNOWN: '', + VCVER_KIND_DEVELOP: 'Develop', + VCVER_KIND_EXPRESS: 'Express', + VCVER_KIND_BTDISPATCH: 'BTDispatch', + VCVER_KIND_VCFORPYTHON: 'VCForPython', + VCVER_KIND_EXPRESS_WIN: 'Express-Win', + VCVER_KIND_EXPRESS_WEB: 'Express-Web', + VCVER_KIND_SDK: 'SDK', + VCVER_KIND_CMDLINE: 'CmdLine', +} + +BITFIELD_KIND_DEVELOP = 0b_1000 +BITFIELD_KIND_EXPRESS = 0b_0100 +BITFIELD_KIND_EXPRESS_WIN = 0b_0010 +BITFIELD_KIND_EXPRESS_WEB = 0b_0001 + +VCVER_KIND_PROGRAM = namedtuple("VCVerKindProgram", [ + 'kind', # relpath from pdir to vsroot + 'program', # ide binaries + 'bitfield', +]) + +# + +IDE_PROGRAM_DEVENV_COM = VCVER_KIND_PROGRAM( + kind=VCVER_KIND_DEVELOP, + program='devenv.com', + bitfield=BITFIELD_KIND_DEVELOP, +) + +IDE_PROGRAM_MSDEV_COM = VCVER_KIND_PROGRAM( + kind=VCVER_KIND_DEVELOP, + program='msdev.com', + bitfield=BITFIELD_KIND_DEVELOP, +) + +IDE_PROGRAM_WDEXPRESS_EXE = VCVER_KIND_PROGRAM( + kind=VCVER_KIND_EXPRESS, + program='WDExpress.exe', + bitfield=BITFIELD_KIND_EXPRESS, +) + +IDE_PROGRAM_VCEXPRESS_EXE = VCVER_KIND_PROGRAM( + kind=VCVER_KIND_EXPRESS, + program='VCExpress.exe', + bitfield=BITFIELD_KIND_EXPRESS, +) + +IDE_PROGRAM_VSWINEXPRESS_EXE = VCVER_KIND_PROGRAM( + kind=VCVER_KIND_EXPRESS_WIN, + program='VSWinExpress.exe', + bitfield=BITFIELD_KIND_EXPRESS_WIN, +) + +IDE_PROGRAM_VWDEXPRESS_EXE = VCVER_KIND_PROGRAM( + kind=VCVER_KIND_EXPRESS_WEB, + program='VWDExpress.exe', + bitfield=BITFIELD_KIND_EXPRESS_WEB, +) + +# detection configuration + +VCVER_KIND_DETECT = namedtuple("VCVerKindDetect", [ + 'root', # relpath from pdir to vsroot + 'path', # vsroot to ide dir + 'programs', # ide binaries +]) + +# detected binaries + +VCVER_DETECT_BINARIES = namedtuple("VCVerDetectBinaries", [ + 'bitfields', # detect values + 'have_dev', # develop ide binary + 'have_exp', # express ide binary + 'have_exp_win', # express windows ide binary + 'have_exp_web', # express web ide binary +]) + + +VCVER_DETECT_KIND = namedtuple("VCVerDetectKind", [ + 'skip', # skip vs root + 'save', # save in case no other kind found + 'kind', # internal kind + 'binaries_t', + 'extended', +]) + +# unknown value + +_VCVER_DETECT_KIND_UNKNOWN = VCVER_DETECT_KIND( + skip=True, + save=False, + kind=VCVER_KIND_UNKNOWN, + binaries_t=VCVER_DETECT_BINARIES( + bitfields=0b0, + have_dev=False, + have_exp=False, + have_exp_win=False, + have_exp_web=False, + ), + extended={}, +) + +# + +_msvc_pdir_func = None + +def register_msvc_version_pdir_func(func): + global _msvc_pdir_func + if func: + _msvc_pdir_func = func + +_cache_vcver_kind_map = {} + +def msvc_version_register_kind(msvc_version, kind_t) -> None: + global _cache_vcver_kind_map + if kind_t is None: + kind_t = _VCVER_DETECT_KIND_UNKNOWN + debug('msvc_version=%s, kind=%s', repr(msvc_version), repr(VCVER_KIND_STR[kind_t.kind])) + _cache_vcver_kind_map[msvc_version] = kind_t + +def _msvc_version_kind_lookup(msvc_version, env=None): + global _cache_vcver_kind_map + global _msvc_pdir_func + if msvc_version not in _cache_vcver_kind_map: + _msvc_pdir_func(msvc_version, env) + kind_t = _cache_vcver_kind_map.get(msvc_version, _VCVER_DETECT_KIND_UNKNOWN) + debug( + 'kind=%s, dev=%s, exp=%s, msvc_version=%s', + repr(VCVER_KIND_STR[kind_t.kind]), + kind_t.binaries_t.have_dev, kind_t.binaries_t.have_exp, + repr(msvc_version) + ) + return kind_t + +def msvc_version_is_btdispatch(msvc_version, env=None): + kind_t = _msvc_version_kind_lookup(msvc_version, env) + is_btdispatch = bool(kind_t.kind == VCVER_KIND_BTDISPATCH) + debug( + 'is_btdispatch=%s, kind:%s, msvc_version=%s', + repr(is_btdispatch), repr(VCVER_KIND_STR[kind_t.kind]), repr(msvc_version) + ) + return is_btdispatch + +def msvc_version_is_express(msvc_version, env=None): + kind_t = _msvc_version_kind_lookup(msvc_version, env) + is_express = bool(kind_t.kind == VCVER_KIND_EXPRESS) + debug( + 'is_express=%s, kind:%s, msvc_version=%s', + repr(is_express), repr(VCVER_KIND_STR[kind_t.kind]), repr(msvc_version) + ) + return is_express + +def msvc_version_is_vcforpython(msvc_version, env=None): + kind_t = _msvc_version_kind_lookup(msvc_version, env) + is_vcforpython = bool(kind_t.kind == VCVER_KIND_VCFORPYTHON) + debug( + 'is_vcforpython=%s, kind:%s, msvc_version=%s', + repr(is_vcforpython), repr(VCVER_KIND_STR[kind_t.kind]), repr(msvc_version) + ) + return is_vcforpython + +def msvc_version_skip_uwp_target(env, msvc_version): + + vernum = float(Util.get_msvc_version_prefix(msvc_version)) + vernum_int = int(vernum * 10) + + if vernum_int != 140: + return False + + kind_t = _msvc_version_kind_lookup(msvc_version, env) + if kind_t.kind != VCVER_KIND_EXPRESS: + return False + + target_arch = env.get('TARGET_ARCH') + + uwp_is_supported = kind_t.extended.get('uwp_is_supported', {}) + is_supported = uwp_is_supported.get(target_arch, True) + + if is_supported: + return False + + return True + +def _pdir_detect_binaries(pdir, detect): + + vs_root = os.path.join(pdir, detect.root) + ide_path = os.path.join(vs_root, detect.path) + + bitfields = 0b_0000 + for ide_program in detect.programs: + prog = os.path.join(ide_path, ide_program.program) + if not os.path.exists(prog): + continue + bitfields |= ide_program.bitfield + + have_dev = bool(bitfields & BITFIELD_KIND_DEVELOP) + have_exp = bool(bitfields & BITFIELD_KIND_EXPRESS) + have_exp_win = bool(bitfields & BITFIELD_KIND_EXPRESS_WIN) + have_exp_web = bool(bitfields & BITFIELD_KIND_EXPRESS_WEB) + + binaries_t = VCVER_DETECT_BINARIES( + bitfields=bitfields, + have_dev=have_dev, + have_exp=have_exp, + have_exp_win=have_exp_win, + have_exp_web=have_exp_web, + ) + + debug( + 'vs_root=%s, dev=%s, exp=%s, exp_win=%s, exp_web=%s, pdir=%s', + repr(vs_root), + binaries_t.have_dev, binaries_t.have_exp, + binaries_t.have_exp_win, binaries_t.have_exp_web, + repr(pdir) + ) + + return vs_root, binaries_t + +_cache_pdir_vswhere_kind = {} + +def msvc_version_pdir_vswhere_kind(msvc_version, pdir, detect_t): + global _cache_pdir_vswhere_kind + + vc_dir = os.path.normcase(os.path.normpath(pdir)) + cache_key = (msvc_version, vc_dir) + + rval = _cache_pdir_vswhere_kind.get(cache_key) + if rval is not None: + debug('cache=%s', repr(rval)) + return rval + + extended = {} + + prefix, suffix = Util.get_msvc_version_prefix_suffix(msvc_version) + + vs_root, binaries_t = _pdir_detect_binaries(pdir, detect_t) + + if binaries_t.have_dev: + kind = VCVER_KIND_DEVELOP + elif binaries_t.have_exp: + kind = VCVER_KIND_EXPRESS + else: + kind = VCVER_KIND_CMDLINE + + skip = False + save = False + + if suffix != 'Exp' and kind == VCVER_KIND_EXPRESS: + skip = True + save = USE_EXPRESS_FOR_NONEXPRESS + elif suffix == 'Exp' and kind != VCVER_KIND_EXPRESS: + skip = True + + kind_t = VCVER_DETECT_KIND( + skip=skip, + save=save, + kind=kind, + binaries_t=binaries_t, + extended=extended, + ) + + debug( + 'skip=%s, save=%s, kind=%s, msvc_version=%s, pdir=%s', + kind_t.skip, kind_t.save, repr(VCVER_KIND_STR[kind_t.kind]), + repr(msvc_version), repr(pdir) + ) + + _cache_pdir_vswhere_kind[cache_key] = kind_t + + return kind_t + +# VS2015 buildtools batch file call detection +# vs2015 buildtools do not support sdk_version or UWP arguments + +_VS2015BT_PATH = r'..\Microsoft Visual C++ Build Tools\vcbuildtools.bat' + +_VS2015BT_REGEX_STR = ''.join([ + r'^\s*if\s+exist\s+', + re.escape(fr'"%~dp0..\{_VS2015BT_PATH}"'), + r'\s+goto\s+setup_buildsku\s*$', +]) + +_VS2015BT_VCVARS_BUILDTOOLS = re.compile(_VS2015BT_REGEX_STR, re.IGNORECASE) +_VS2015BT_VCVARS_STOP = re.compile(r'^\s*[:]Setup_VS\s*$', re.IGNORECASE) + +def _vs_buildtools_2015_vcvars(vcvars_file): + have_buildtools_vcvars = False + with open(vcvars_file) as fh: + for line in fh: + if _VS2015BT_VCVARS_BUILDTOOLS.match(line): + have_buildtools_vcvars = True + break + if _VS2015BT_VCVARS_STOP.match(line): + break + return have_buildtools_vcvars + +def _vs_buildtools_2015(vs_root, vc_dir): + + is_btdispatch = False + + do_once = True + while do_once: + do_once = False + + buildtools_file = os.path.join(vs_root, _VS2015BT_PATH) + have_buildtools = os.path.exists(buildtools_file) + debug('have_buildtools=%s', have_buildtools) + if not have_buildtools: + break + + vcvars_file = os.path.join(vc_dir, 'vcvarsall.bat') + have_vcvars = os.path.exists(vcvars_file) + debug('have_vcvars=%s', have_vcvars) + if not have_vcvars: + break + + have_buildtools_vcvars = _vs_buildtools_2015_vcvars(vcvars_file) + debug('have_buildtools_vcvars=%s', have_buildtools_vcvars) + if not have_buildtools_vcvars: + break + + is_btdispatch = True + + debug('is_btdispatch=%s', is_btdispatch) + return is_btdispatch + +_VS2015EXP_VCVARS_LIBPATH = re.compile( + ''.join([ + r'^\s*\@if\s+exist\s+\"\%VCINSTALLDIR\%LIB\\store\\(amd64|arm)"\s+', + r'set (LIB|LIBPATH)=\%VCINSTALLDIR\%LIB\\store\\(amd64|arm);.*\%(LIB|LIBPATH)\%\s*$' + ]), + re.IGNORECASE +) + +_VS2015EXP_VCVARS_STOP = re.compile(r'^\s*[:]GetVSCommonToolsDir\s*$', re.IGNORECASE) + +def _vs_express_2015_vcvars(vcvars_file): + n_libpath = 0 + with open(vcvars_file) as fh: + for line in fh: + if _VS2015EXP_VCVARS_LIBPATH.match(line): + n_libpath += 1 + elif _VS2015EXP_VCVARS_STOP.match(line): + break + have_uwp_fix = n_libpath >= 2 + return have_uwp_fix + +def _vs_express_2015(pdir): + + have_uwp_amd64 = False + have_uwp_arm = False + + vcvars_file = os.path.join(pdir, r'vcvarsall.bat') + if os.path.exists(vcvars_file): + + vcvars_file = os.path.join(pdir, r'bin\x86_amd64\vcvarsx86_amd64.bat') + if os.path.exists(vcvars_file): + have_uwp_fix = _vs_express_2015_vcvars(vcvars_file) + if have_uwp_fix: + have_uwp_amd64 = True + + vcvars_file = os.path.join(pdir, r'bin\x86_arm\vcvarsx86_arm.bat') + if os.path.exists(vcvars_file): + have_uwp_fix = _vs_express_2015_vcvars(vcvars_file) + if have_uwp_fix: + have_uwp_arm = True + + debug('have_uwp_amd64=%s, have_uwp_arm=%s', have_uwp_amd64, have_uwp_arm) + return have_uwp_amd64, have_uwp_arm + +# winsdk installed 2010 [7.1], 2008 [7.0, 6.1] folders + +_REGISTRY_WINSDK_VERSIONS = {'10.0', '9.0'} + +_cache_pdir_registry_winsdk = {} + +def _msvc_version_pdir_registry_winsdk(msvc_version, pdir): + global _cache_pdir_registry_winsdk + + # detect winsdk-only installations + # + # registry keys: + # [prefix]\VisualStudio\SxS\VS7\10.0 + # [prefix]\VisualStudio\SxS\VC7\10.0 product directory + # [prefix]\VisualStudio\SxS\VS7\9.0 + # [prefix]\VisualStudio\SxS\VC7\9.0 product directory + # + # winsdk notes: + # - winsdk installs do not define the common tools env var + # - the product dir is detected but the vcvars batch files will fail + # - regular installations populate the VS7 registry keys + # + + vc_dir = os.path.normcase(os.path.normpath(pdir)) + cache_key = (msvc_version, vc_dir) + + rval = _cache_pdir_registry_winsdk.get(cache_key) + if rval is not None: + debug('cache=%s', repr(rval)) + return rval + + if msvc_version not in _REGISTRY_WINSDK_VERSIONS: + + is_sdk = False + + debug('is_sdk=%s, msvc_version=%s', is_sdk, repr(msvc_version)) + + else: + + vc_dir = os.path.normcase(os.path.normpath(pdir)) + + vc_suffix = Registry.vstudio_sxs_vc7(msvc_version) + vc_qresults = [record[0] for record in Registry.microsoft_query_paths(vc_suffix)] + vc_root = os.path.normcase(os.path.normpath(vc_qresults[0])) if vc_qresults else None + + if vc_dir != vc_root: + # registry vc path is not the current pdir + + is_sdk = False + + debug( + 'is_sdk=%s, msvc_version=%s, pdir=%s, vc_root=%s', + is_sdk, repr(msvc_version), repr(vc_dir), repr(vc_root) + ) + + else: + # registry vc path is the current pdir + + vs_suffix = Registry.vstudio_sxs_vs7(msvc_version) + vs_qresults = [record[0] for record in Registry.microsoft_query_paths(vs_suffix)] + vs_root = vs_qresults[0] if vs_qresults else None + + is_sdk = bool(not vs_root and vc_root) + + debug( + 'is_sdk=%s, msvc_version=%s, vs_root=%s, vc_root=%s', + is_sdk, repr(msvc_version), repr(vs_root), repr(vc_root) + ) + + _cache_pdir_registry_winsdk[cache_key] = is_sdk + + return is_sdk + +_cache_pdir_registry_kind = {} + +def msvc_version_pdir_registry_kind(msvc_version, pdir, detect_t, is_vcforpython=False): + global _cache_pdir_registry_kind + + vc_dir = os.path.normcase(os.path.normpath(pdir)) + cache_key = (msvc_version, vc_dir) + + rval = _cache_pdir_registry_kind.get(cache_key) + if rval is not None: + debug('cache=%s', repr(rval)) + return rval + + extended = {} + + prefix, suffix = Util.get_msvc_version_prefix_suffix(msvc_version) + + vs_root, binaries_t = _pdir_detect_binaries(pdir, detect_t) + + if binaries_t.have_dev: + kind = VCVER_KIND_DEVELOP + elif binaries_t.have_exp: + kind = VCVER_KIND_EXPRESS + elif msvc_version == '14.0' and _vs_buildtools_2015(vs_root, pdir): + kind = VCVER_KIND_BTDISPATCH + elif msvc_version == '9.0' and is_vcforpython: + kind = VCVER_KIND_VCFORPYTHON + elif binaries_t.have_exp_win: + kind = VCVER_KIND_EXPRESS_WIN + elif binaries_t.have_exp_web: + kind = VCVER_KIND_EXPRESS_WEB + elif _msvc_version_pdir_registry_winsdk(msvc_version, pdir): + kind = VCVER_KIND_SDK + else: + kind = VCVER_KIND_CMDLINE + + skip = False + save = False + + if kind in (VCVER_KIND_EXPRESS_WIN, VCVER_KIND_EXPRESS_WEB, VCVER_KIND_SDK): + skip = True + elif suffix != 'Exp' and kind == VCVER_KIND_EXPRESS: + skip = True + save = USE_EXPRESS_FOR_NONEXPRESS + elif suffix == 'Exp' and kind != VCVER_KIND_EXPRESS: + skip = True + + if prefix == '14.0' and kind == VCVER_KIND_EXPRESS: + have_uwp_amd64, have_uwp_arm = _vs_express_2015(pdir) + uwp_is_supported = { + 'x86': True, + 'amd64': have_uwp_amd64, + 'arm': have_uwp_arm, + } + extended['uwp_is_supported'] = uwp_is_supported + + kind_t = VCVER_DETECT_KIND( + skip=skip, + save=save, + kind=kind, + binaries_t=binaries_t, + extended=extended, + ) + + debug( + 'skip=%s, save=%s, kind=%s, msvc_version=%s, pdir=%s', + kind_t.skip, kind_t.save, repr(VCVER_KIND_STR[kind_t.kind]), + repr(msvc_version), repr(pdir) + ) + + _cache_pdir_registry_kind[cache_key] = kind_t + + return kind_t + +# queries + +def get_msvc_version_kind(msvc_version, env=None): + kind_t = _msvc_version_kind_lookup(msvc_version, env) + kind_str = VCVER_KIND_STR[kind_t.kind] + debug( + 'kind=%s, kind_str=%s, msvc_version=%s', + repr(kind_t.kind), repr(kind_str), repr(msvc_version) + ) + return (kind_t.kind, kind_str) + +def msvc_version_sdk_version_is_supported(msvc_version, env=None): + + vernum = float(Util.get_msvc_version_prefix(msvc_version)) + vernum_int = int(vernum * 10) + + kind_t = _msvc_version_kind_lookup(msvc_version, env) + + if vernum_int >= 141: + # VS2017 and later + is_supported = True + elif vernum_int == 140: + # VS2015: + # True: Develop, CmdLine + # False: Express, BTDispatch + is_supported = True + if kind_t.kind == VCVER_KIND_EXPRESS: + is_supported = False + elif kind_t.kind == VCVER_KIND_BTDISPATCH: + is_supported = False + else: + # VS2013 and earlier + is_supported = False + + debug( + 'is_supported=%s, msvc_version=%s, kind=%s', + is_supported, repr(msvc_version), repr(VCVER_KIND_STR[kind_t.kind]) + ) + return is_supported + +def msvc_version_uwp_is_supported(msvc_version, target_arch=None, env=None): + + vernum = float(Util.get_msvc_version_prefix(msvc_version)) + vernum_int = int(vernum * 10) + + kind_t = _msvc_version_kind_lookup(msvc_version, env) + + is_target = False + + if vernum_int >= 141: + # VS2017 and later + is_supported = True + elif vernum_int == 140: + # VS2015: + # True: Develop, CmdLine + # Maybe: Express + # False: BTDispatch + is_supported = True + if kind_t.kind == VCVER_KIND_EXPRESS: + uwp_is_supported = kind_t.extended.get('uwp_is_supported', {}) + is_supported = uwp_is_supported.get(target_arch, True) + is_target = True + elif kind_t.kind == VCVER_KIND_BTDISPATCH: + is_supported = False + else: + # VS2013 and earlier + is_supported = False + + debug( + 'is_supported=%s, is_target=%s, msvc_version=%s, kind=%s, target_arch=%s', + is_supported, is_target, repr(msvc_version), repr(VCVER_KIND_STR[kind_t.kind]), repr(target_arch) + ) + + return is_supported, is_target + +# reset cache + +def reset() -> None: + global _cache_vcver_kind_map + global _cache_pdir_vswhere_kind + global _cache_pdir_registry_kind + global _cache_pdir_registry_winsdk + + debug('') + + _cache_vcver_kind_map = {} + _cache_pdir_vswhere_kind = {} + _cache_pdir_registry_kind = {} + _cache_pdir_registry_winsdk = {} diff --git a/SCons/Tool/MSCommon/MSVC/Policy.py b/SCons/Tool/MSCommon/MSVC/Policy.py index fe8da3156b..7e11a63ba5 100644 --- a/SCons/Tool/MSCommon/MSVC/Policy.py +++ b/SCons/Tool/MSCommon/MSVC/Policy.py @@ -38,6 +38,10 @@ namedtuple, ) +from contextlib import ( + contextmanager, +) + import SCons.Warnings from ..common import ( @@ -215,6 +219,18 @@ def msvc_notfound_handler(env, msg): else: SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, msg) +@contextmanager +def msvc_notfound_policy_contextmanager(MSVC_NOTFOUND_POLICY=None): + """ Temporarily change the MSVC not found policy within a context. + + Args: + MSVC_NOTFOUND_POLICY: + string representing the policy behavior + when MSVC is not found or None + """ + prev_policy = msvc_set_notfound_policy(MSVC_NOTFOUND_POLICY) + yield + msvc_set_notfound_policy(prev_policy) def _msvc_scripterror_policy_lookup(symbol): @@ -299,3 +315,16 @@ def msvc_scripterror_handler(env, msg): else: SCons.Warnings.warn(MSVCScriptExecutionWarning, msg) +@contextmanager +def msvc_scripterror_policy_contextmanager(MSVC_SCRIPTERROR_POLICY=None): + """ Temporarily change the msvc batch execution errors policy within a context. + + Args: + MSVC_SCRIPTERROR_POLICY: + string representing the policy behavior + when msvc batch file execution errors are detected or None + """ + prev_policy = msvc_set_scripterror_policy(MSVC_SCRIPTERROR_POLICY) + yield + msvc_set_scripterror_policy(prev_policy) + diff --git a/SCons/Tool/MSCommon/MSVC/Registry.py b/SCons/Tool/MSCommon/MSVC/Registry.py index f9e544c6fc..b5b72c42fc 100644 --- a/SCons/Tool/MSCommon/MSVC/Registry.py +++ b/SCons/Tool/MSCommon/MSVC/Registry.py @@ -110,6 +110,9 @@ def windows_kit_query_paths(version): q = windows_kits(version) return microsoft_query_paths(q) +def vstudio_sxs_vs7(version): + return '\\'.join([r'VisualStudio\SxS\VS7', version]) + def vstudio_sxs_vc7(version): return '\\'.join([r'VisualStudio\SxS\VC7', version]) diff --git a/SCons/Tool/MSCommon/MSVC/ScriptArguments.py b/SCons/Tool/MSCommon/MSVC/ScriptArguments.py index 8848095cf9..2c766fed4e 100644 --- a/SCons/Tool/MSCommon/MSVC/ScriptArguments.py +++ b/SCons/Tool/MSCommon/MSVC/ScriptArguments.py @@ -42,6 +42,7 @@ from . import Config from . import Registry from . import WinSDK +from . import Kind from .Exceptions import ( MSVCInternalError, @@ -172,6 +173,12 @@ class SortOrder(enum.IntEnum): 'vs_def', ]) +TOOLSET_VERSION_ARGS_DEFINITION = namedtuple('ToolsetVersionArgsDefinition', [ + 'version', # full version (e.g., '14.1Exp', '14.32.31326') + 'vc_buildtools_def', + 'is_user', +]) + def _msvc_version(version): verstr = Util.get_msvc_version_prefix(version) @@ -184,19 +191,22 @@ def _msvc_version(version): return version_args -def _toolset_version(version): +def _toolset_version(version, is_user=False): - verstr = Util.get_msvc_version_prefix(version) - vs_def = Config.MSVC_VERSION_INTERNAL[verstr] + vc_series = Util.get_msvc_version_prefix(version) - version_args = MSVC_VERSION_ARGS_DEFINITION( + vc_buildseries_def = Config.MSVC_BUILDSERIES_EXTERNAL[vc_series] + vc_buildtools_def = Config.VC_BUILDTOOLS_MAP[vc_buildseries_def.vc_buildseries] + + version_args = TOOLSET_VERSION_ARGS_DEFINITION( version = version, - vs_def = vs_def, + vc_buildtools_def = vc_buildtools_def, + is_user = is_user, ) return version_args -def _msvc_script_argument_uwp(env, msvc, arglist): +def _msvc_script_argument_uwp(env, msvc, arglist, target_arch): uwp_app = env['MSVC_UWP_APP'] debug('MSVC_VERSION=%s, MSVC_UWP_APP=%s', repr(msvc.version), repr(uwp_app)) @@ -207,17 +217,34 @@ def _msvc_script_argument_uwp(env, msvc, arglist): if uwp_app not in _ARGUMENT_BOOLEAN_TRUE_LEGACY: return None - if msvc.vs_def.vc_buildtools_def.vc_version_numeric < VS2015.vc_buildtools_def.vc_version_numeric: + if msvc.vs_def.vc_buildtools_def.msvc_version_numeric < VS2015.vc_buildtools_def.msvc_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) + repr(msvc.vs_def.vc_buildtools_def.msvc_version_numeric), + repr(VS2015.vc_buildtools_def.msvc_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) + repr(uwp_app), repr(msvc.version), repr(VS2015.vc_buildtools_def.msvc_version) ) raise MSVCArgumentError(err_msg) + is_supported, is_target = Kind.msvc_version_uwp_is_supported(msvc.version, target_arch, env) + if not is_supported: + _, kind_str = Kind.get_msvc_version_kind(msvc.version) + debug( + 'invalid: msvc_version constraint: %s %s %s', + repr(msvc.version), repr(kind_str), repr(target_arch) + ) + if is_target and target_arch: + err_msg = "MSVC_UWP_APP ({}) TARGET_ARCH ({}) is not supported for MSVC_VERSION {} ({})".format( + repr(uwp_app), repr(target_arch), repr(msvc.version), repr(kind_str) + ) + else: + err_msg = "MSVC_UWP_APP ({}) is not supported for MSVC_VERSION {} ({})".format( + repr(uwp_app), repr(msvc.version), repr(kind_str) + ) + raise MSVCArgumentError(err_msg) + # VS2017+ rewrites uwp => store for 14.0 toolset uwp_arg = msvc.vs_def.vc_uwp @@ -250,16 +277,24 @@ def _user_script_argument_uwp(env, uwp, user_argstr) -> bool: raise MSVCArgumentError(err_msg) -def _msvc_script_argument_sdk_constraints(msvc, sdk_version): +def _msvc_script_argument_sdk_constraints(msvc, sdk_version, env): - if msvc.vs_def.vc_buildtools_def.vc_version_numeric < VS2015.vc_buildtools_def.vc_version_numeric: + if msvc.vs_def.vc_buildtools_def.msvc_version_numeric < VS2015.vc_buildtools_def.msvc_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) + repr(msvc.vs_def.vc_buildtools_def.msvc_version_numeric), + repr(VS2015.vc_buildtools_def.msvc_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) + repr(sdk_version), repr(msvc.version), repr(VS2015.vc_buildtools_def.msvc_version) + ) + return err_msg + + if not Kind.msvc_version_sdk_version_is_supported(msvc.version, env): + _, kind_str = Kind.get_msvc_version_kind(msvc.version) + debug('invalid: msvc_version constraint: %s %s', repr(msvc.version), repr(kind_str)) + err_msg = "MSVC_SDK_VERSION ({}) is not supported for MSVC_VERSION {} ({})".format( + repr(sdk_version), repr(msvc.version), repr(kind_str) ) return err_msg @@ -277,23 +312,23 @@ def _msvc_script_argument_sdk_platform_constraints(msvc, toolset, sdk_version, p if sdk_version == '8.1' and platform_def.is_uwp: - vs_def = toolset.vs_def if toolset else msvc.vs_def + vc_buildtools_def = toolset.vc_buildtools_def if toolset else msvc.vs_def.vc_buildtools_def - if vs_def.vc_buildtools_def.vc_version_numeric > VS2015.vc_buildtools_def.vc_version_numeric: + if vc_buildtools_def.msvc_version_numeric > VS2015.vc_buildtools_def.msvc_version_numeric: debug( 'invalid: uwp/store SDK 8.1 msvc_version constraint: %s > %s VS2015', - repr(vs_def.vc_buildtools_def.vc_version_numeric), - repr(VS2015.vc_buildtools_def.vc_version_numeric) + repr(vc_buildtools_def.msvc_version_numeric), + repr(VS2015.vc_buildtools_def.msvc_version_numeric) ) - if toolset and toolset.vs_def != msvc.vs_def: - err_msg = "MSVC_SDK_VERSION ({}) and platform type ({}) constraint violation: toolset version {} > {} VS2015".format( + if toolset and toolset.is_user: + err_msg = "MSVC_SDK_VERSION ({}) and platform type ({}) constraint violation: toolset {} MSVC_VERSION {} > {} VS2015".format( repr(sdk_version), repr(platform_def.vc_platform), - repr(toolset.version), repr(VS2015.vc_buildtools_def.vc_version) + repr(toolset.version), repr(msvc.version), repr(VS2015.vc_buildtools_def.msvc_version) ) else: err_msg = "MSVC_SDK_VERSION ({}) and platform type ({}) constraint violation: MSVC_VERSION {} > {} VS2015".format( repr(sdk_version), repr(platform_def.vc_platform), - repr(msvc.version), repr(VS2015.vc_buildtools_def.vc_version) + repr(msvc.version), repr(VS2015.vc_buildtools_def.msvc_version) ) return err_msg @@ -310,7 +345,7 @@ def _msvc_script_argument_sdk(env, msvc, toolset, platform_def, arglist): if not sdk_version: return None - err_msg = _msvc_script_argument_sdk_constraints(msvc, sdk_version) + err_msg = _msvc_script_argument_sdk_constraints(msvc, sdk_version, env) if err_msg: raise MSVCArgumentError(err_msg) @@ -333,7 +368,10 @@ def _msvc_script_argument_sdk(env, msvc, toolset, platform_def, arglist): def _msvc_script_default_sdk(env, msvc, platform_def, arglist, force_sdk: bool=False): - if msvc.vs_def.vc_buildtools_def.vc_version_numeric < VS2015.vc_buildtools_def.vc_version_numeric: + if msvc.vs_def.vc_buildtools_def.msvc_version_numeric < VS2015.vc_buildtools_def.msvc_version_numeric: + return None + + if not Kind.msvc_version_sdk_version_is_supported(msvc.version, env): return None sdk_list = WinSDK.get_sdk_version_list(msvc.vs_def, platform_def) @@ -415,10 +453,11 @@ def _msvc_sxs_toolset_folder(msvc, sxs_folder): if Util.is_toolset_sxs(sxs_folder): return sxs_folder, sxs_folder - key = (msvc.vs_def.vc_buildtools_def.vc_version, sxs_folder) - if key in _msvc_sxs_bugfix_folder: - sxs_version = _msvc_sxs_bugfix_folder[key] - return sxs_folder, sxs_version + for vc_buildseries_def in msvc.vs_def.vc_buildtools_def.vc_buildseries_list: + key = (vc_buildseries_def.vc_version, sxs_folder) + sxs_version = _msvc_sxs_bugfix_folder.get(key) + if sxs_version: + return sxs_folder, sxs_version debug('sxs folder: ignore version=%s', repr(sxs_folder)) return None, None @@ -574,49 +613,60 @@ def _msvc_version_toolset_vcvars(msvc, vc_dir, toolset_version): 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: + if msvc.vs_def.vc_buildtools_def.msvc_version_numeric < VS2017.vc_buildtools_def.msvc_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) + repr(msvc.vs_def.vc_buildtools_def.msvc_version_numeric), + repr(VS2017.vc_buildtools_def.msvc_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) + repr(toolset_version), repr(msvc.version), repr(VS2017.vc_buildtools_def.msvc_version) ) return err_msg - toolset_verstr = Util.get_msvc_version_prefix(toolset_version) + toolset_series = Util.get_msvc_version_prefix(toolset_version) - if not toolset_verstr: + if not toolset_series: debug('invalid: msvc version: toolset_version=%s', repr(toolset_version)) err_msg = 'MSVC_TOOLSET_VERSION {} format is not supported'.format( repr(toolset_version) ) return err_msg - toolset_vernum = float(toolset_verstr) + toolset_buildseries_def = Config.MSVC_BUILDSERIES_EXTERNAL.get(toolset_series) + if not toolset_buildseries_def: + debug('invalid: msvc version: toolset_version=%s', repr(toolset_version)) + err_msg = 'MSVC_TOOLSET_VERSION {} build series {} is not supported'.format( + repr(toolset_version), repr(toolset_series) + ) + return err_msg + + toolset_buildtools_def = Config.VC_BUILDTOOLS_MAP[toolset_buildseries_def.vc_buildseries] - if toolset_vernum < VS2015.vc_buildtools_def.vc_version_numeric: + toolset_verstr = toolset_buildtools_def.msvc_version + toolset_vernum = toolset_buildtools_def.msvc_version_numeric + + if toolset_vernum < VS2015.vc_buildtools_def.msvc_version_numeric: debug( 'invalid: toolset version constraint: %s < %s VS2015', - repr(toolset_vernum), repr(VS2015.vc_buildtools_def.vc_version_numeric) + repr(toolset_vernum), repr(VS2015.vc_buildtools_def.msvc_version_numeric) ) - err_msg = "MSVC_TOOLSET_VERSION ({}) constraint violation: toolset version {} < {} VS2015".format( - repr(toolset_version), repr(toolset_verstr), repr(VS2015.vc_buildtools_def.vc_version) + err_msg = "MSVC_TOOLSET_VERSION ({}) constraint violation: toolset msvc version {} < {} VS2015".format( + repr(toolset_version), repr(toolset_verstr), repr(VS2015.vc_buildtools_def.msvc_version) ) return err_msg - if toolset_vernum > msvc.vs_def.vc_buildtools_def.vc_version_numeric: + if toolset_vernum > msvc.vs_def.vc_buildtools_def.msvc_version_numeric: debug( 'invalid: toolset version constraint: toolset %s > %s msvc', - repr(toolset_vernum), repr(msvc.vs_def.vc_buildtools_def.vc_version_numeric) + repr(toolset_vernum), repr(msvc.vs_def.vc_buildtools_def.msvc_version_numeric) ) - err_msg = "MSVC_TOOLSET_VERSION ({}) constraint violation: toolset version {} > {} MSVC_VERSION".format( + err_msg = "MSVC_TOOLSET_VERSION ({}) constraint violation: toolset msvc version {} > {} MSVC_VERSION".format( repr(toolset_version), repr(toolset_verstr), repr(msvc.version) ) return err_msg - if toolset_vernum == VS2015.vc_buildtools_def.vc_version_numeric: + if toolset_vernum == VS2015.vc_buildtools_def.msvc_version_numeric: # tooset = 14.0 if Util.is_toolset_full(toolset_version): if not Util.is_toolset_140(toolset_version): @@ -624,7 +674,7 @@ def _msvc_script_argument_toolset_constraints(msvc, toolset_version): 'invalid: toolset version 14.0 constraint: %s != 14.0', repr(toolset_version) ) - err_msg = "MSVC_TOOLSET_VERSION ({}) constraint violation: toolset version {} != '14.0'".format( + err_msg = "MSVC_TOOLSET_VERSION ({}) constraint violation: toolset msvc version {} != '14.0'".format( repr(toolset_version), repr(toolset_version) ) return err_msg @@ -688,7 +738,7 @@ def _msvc_script_argument_toolset(env, msvc, vc_dir, arglist): def _msvc_script_default_toolset(env, msvc, vc_dir, arglist, force_toolset: bool=False): - if msvc.vs_def.vc_buildtools_def.vc_version_numeric < VS2017.vc_buildtools_def.vc_version_numeric: + if msvc.vs_def.vc_buildtools_def.msvc_version_numeric < VS2017.vc_buildtools_def.msvc_version_numeric: return None toolset_default = _msvc_default_toolset(msvc, vc_dir) @@ -729,26 +779,26 @@ def _user_script_argument_toolset(env, toolset_version, user_argstr): def _msvc_script_argument_spectre_constraints(msvc, toolset, spectre_libs, platform_def): - if msvc.vs_def.vc_buildtools_def.vc_version_numeric < VS2017.vc_buildtools_def.vc_version_numeric: + if msvc.vs_def.vc_buildtools_def.msvc_version_numeric < VS2017.vc_buildtools_def.msvc_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) + repr(msvc.vs_def.vc_buildtools_def.msvc_version_numeric), + repr(VS2017.vc_buildtools_def.msvc_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) + repr(spectre_libs), repr(msvc.version), repr(VS2017.vc_buildtools_def.msvc_version) ) return err_msg if toolset: - if toolset.vs_def.vc_buildtools_def.vc_version_numeric < VS2017.vc_buildtools_def.vc_version_numeric: + if toolset.vc_buildtools_def.msvc_version_numeric < VS2017.vc_buildtools_def.msvc_version_numeric: debug( 'invalid: toolset version constraint: %s < %s VS2017', - repr(toolset.vs_def.vc_buildtools_def.vc_version_numeric), - repr(VS2017.vc_buildtools_def.vc_version_numeric) + repr(toolset.vc_buildtools_def.msvc_version_numeric), + repr(VS2017.vc_buildtools_def.msvc_version_numeric) ) err_msg = "MSVC_SPECTRE_LIBS ({}) constraint violation: toolset version {} < {} VS2017".format( - repr(spectre_libs), repr(toolset.version), repr(VS2017.vc_buildtools_def.vc_version) + repr(spectre_libs), repr(toolset.version), repr(VS2017.vc_buildtools_def.msvc_version) ) return err_msg @@ -836,14 +886,14 @@ def _msvc_script_argument_user(env, msvc, arglist): if not script_args: return None - if msvc.vs_def.vc_buildtools_def.vc_version_numeric < VS2015.vc_buildtools_def.vc_version_numeric: + if msvc.vs_def.vc_buildtools_def.msvc_version_numeric < VS2015.vc_buildtools_def.msvc_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) + repr(msvc.vs_def.vc_buildtools_def.msvc_version_numeric), + repr(VS2015.vc_buildtools_def.msvc_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) + repr(script_args), repr(msvc.version), repr(VS2015.vc_buildtools_def.msvc_version) ) raise MSVCArgumentError(err_msg) @@ -873,7 +923,18 @@ def _msvc_process_construction_variables(env) -> bool: return False -def msvc_script_arguments(env, version, vc_dir, arg): +def msvc_script_arguments_has_uwp(env): + + if not _msvc_process_construction_variables(env): + return False + + uwp_app = env.get('MSVC_UWP_APP') + is_uwp = bool(uwp_app and uwp_app in _ARGUMENT_BOOLEAN_TRUE_LEGACY) + + debug('is_uwp=%s', is_uwp) + return is_uwp + +def msvc_script_arguments(env, version, vc_dir, arg=None): arguments = [arg] if arg else [] @@ -889,10 +950,12 @@ def msvc_script_arguments(env, version, vc_dir, arg): if _msvc_process_construction_variables(env): + target_arch = env.get('TARGET_ARCH') + # MSVC_UWP_APP if 'MSVC_UWP_APP' in env: - uwp = _msvc_script_argument_uwp(env, msvc, arglist) + uwp = _msvc_script_argument_uwp(env, msvc, arglist, target_arch) else: uwp = None @@ -926,7 +989,7 @@ def msvc_script_arguments(env, version, vc_dir, arg): if user_toolset: toolset = None elif toolset_version: - toolset = _toolset_version(toolset_version) + toolset = _toolset_version(toolset_version, is_user=True) elif default_toolset: toolset = _toolset_version(default_toolset) else: @@ -958,7 +1021,7 @@ def msvc_script_arguments(env, version, vc_dir, arg): if user_argstr: _user_script_argument_spectre(env, spectre, user_argstr) - if msvc.vs_def.vc_buildtools_def.vc_version == '14.0': + if msvc.vs_def.vc_buildtools_def.msvc_version == '14.0': if user_uwp and sdk_version and len(arglist) == 2: # VS2015 toolset argument order issue: SDK store => store SDK arglist_reverse = True @@ -1003,6 +1066,14 @@ def _msvc_toolset_versions_internal(msvc_version, vc_dir, full: bool=True, sxs: return toolset_versions +def _msvc_version_toolsets_internal(msvc_version, vc_dir): + + msvc = _msvc_version(msvc_version) + + toolsets_sxs, toolsets_full = _msvc_version_toolsets(msvc, vc_dir) + + return toolsets_sxs, toolsets_full + def _msvc_toolset_versions_spectre_internal(msvc_version, vc_dir): msvc = _msvc_version(msvc_version) diff --git a/SCons/Tool/MSCommon/MSVC/ScriptArgumentsTests.py b/SCons/Tool/MSCommon/MSVC/ScriptArgumentsTests.py index 670576b046..308e436976 100644 --- a/SCons/Tool/MSCommon/MSVC/ScriptArgumentsTests.py +++ b/SCons/Tool/MSCommon/MSVC/ScriptArgumentsTests.py @@ -36,6 +36,10 @@ from SCons.Tool.MSCommon.MSVC import Util from SCons.Tool.MSCommon.MSVC import WinSDK from SCons.Tool.MSCommon.MSVC import ScriptArguments +from SCons.Tool.MSCommon.MSVC.Kind import ( + msvc_version_is_express, + msvc_version_is_btdispatch, +) from SCons.Tool.MSCommon.MSVC.Exceptions import ( MSVCInternalError, @@ -127,7 +131,7 @@ class Data: for vcver in Config.MSVC_VERSION_SUFFIX.keys(): version_def = Util.msvc_version_components(vcver) - vc_dir = vc.find_vc_pdir(None, vcver) + vc_dir = vc.find_vc_pdir(vcver) t = (version_def, vc_dir) ALL_VERSIONS_PAIRS.append(t) if vc_dir: @@ -229,7 +233,13 @@ def test_msvc_script_arguments_defaults(self) -> None: for version_def, vc_dir in Data.INSTALLED_VERSIONS_PAIRS: for arg in ('', 'arch'): scriptargs = func(env, version_def.msvc_version, vc_dir, arg) - if version_def.msvc_vernum >= 14.0: + sdk_supported = True + if version_def.msvc_verstr == '14.0': + if msvc_version_is_express(version_def.msvc_version): + sdk_supported = False + elif msvc_version_is_btdispatch(version_def.msvc_version): + sdk_supported = False + if version_def.msvc_vernum >= 14.0 and sdk_supported: if arg and scriptargs.startswith(arg): testargs = scriptargs[len(arg):].lstrip() else: @@ -289,7 +299,7 @@ def run_msvc_script_args_none(self) -> None: {'MSVC_SCRIPT_ARGS': None, 'MSVC_SPECTRE_LIBS': None}, ]: env = Environment(**kwargs) - _ = func(env, version_def.msvc_version, vc_dir, '') + _ = func(env, version_def.msvc_version, vc_dir) def run_msvc_script_args(self) -> None: func = ScriptArguments.msvc_script_arguments @@ -307,12 +317,17 @@ def run_msvc_script_args(self) -> None: toolset_def = toolset_versions[0] if toolset_versions else Util.msvc_extended_version_components(version_def.msvc_verstr) - earlier_toolset_versions = [toolset_def for toolset_def in toolset_versions if toolset_def.msvc_vernum != version_def.msvc_vernum] + earlier_toolset_versions = [earlier_toolset_def for earlier_toolset_def in toolset_versions if earlier_toolset_def.msvc_vernum != version_def.msvc_vernum] earlier_toolset_def = earlier_toolset_versions[0] if earlier_toolset_versions else None + vc_buildtools_def = Config.MSVC_BUILDTOOLS_EXTERNAL[toolset_def.msvc_buildtools] + vc_buildseries_def = vc_buildtools_def.vc_buildseries_list[0] + + latest_buildseries_major, latest_buildseries_minor = [int(comp) for comp in vc_buildseries_def.vc_version.split('.')] + # should not raise exception (argument not validated) env = Environment(MSVC_SCRIPT_ARGS='undefinedsymbol') - _ = func(env, version_def.msvc_version, vc_dir, '') + _ = func(env, version_def.msvc_version, vc_dir) for kwargs in [ {'MSVC_UWP_APP': False, 'MSVC_SCRIPT_ARGS': None}, @@ -323,7 +338,7 @@ def run_msvc_script_args(self) -> None: {'MSVC_SPECTRE_LIBS': 'True', 'MSVC_SCRIPT_ARGS': '-vcvars_spectre_libs=spectre'}, # not boolean ignored ]: env = Environment(**kwargs) - _ = func(env, version_def.msvc_version, vc_dir, '') + _ = func(env, version_def.msvc_version, vc_dir) for msvc_uwp_app in (True, False): @@ -362,15 +377,15 @@ def run_msvc_script_args(self) -> None: (expect, { 'MSVC_SDK_VERSION': sdk_def.sdk_version, 'MSVC_UWP_APP': msvc_uwp_app, - 'MSVC_TOOLSET_VERSION': version_def.msvc_verstr + 'MSVC_TOOLSET_VERSION': toolset_def.msvc_toolset_version }), ] + more_tests: env = Environment(**kwargs) if exc: with self.assertRaises(MSVCArgumentError): - _ = func(env, version_def.msvc_version, vc_dir, '') + _ = func(env, version_def.msvc_version, vc_dir) else: - _ = func(env, version_def.msvc_version, vc_dir, '') + _ = func(env, version_def.msvc_version, vc_dir) else: @@ -379,14 +394,14 @@ def run_msvc_script_args(self) -> None: {'MSVC_SDK_VERSION': sdk_def.sdk_version, 'MSVC_UWP_APP': msvc_uwp_app}, ]: env = Environment(**kwargs) - _ = func(env, version_def.msvc_version, vc_dir, '') + _ = func(env, version_def.msvc_version, vc_dir) for kwargs in [ - {'MSVC_SCRIPT_ARGS': '-vcvars_ver={}'.format(version_def.msvc_verstr)}, - {'MSVC_TOOLSET_VERSION': version_def.msvc_verstr}, + {'MSVC_SCRIPT_ARGS': '-vcvars_ver={}'.format(toolset_def.msvc_toolset_version)}, + {'MSVC_TOOLSET_VERSION': toolset_def.msvc_toolset_version}, ]: env = Environment(**kwargs) - _ = func(env, version_def.msvc_version, vc_dir, '') + _ = func(env, version_def.msvc_version, vc_dir) msvc_toolset_notfound_version = Data.msvc_toolset_notfound_version(version_def.msvc_version) @@ -398,7 +413,7 @@ def run_msvc_script_args(self) -> None: ]: env = Environment(**kwargs) with self.assertRaises(MSVCToolsetVersionNotFound): - _ = func(env, version_def.msvc_version, vc_dir, '') + _ = func(env, version_def.msvc_version, vc_dir) msvc_sdk_notfound_version = Data.msvc_sdk_notfound_version(version_def.msvc_version) @@ -407,15 +422,15 @@ def run_msvc_script_args(self) -> None: ]: env = Environment(**kwargs) with self.assertRaises(MSVCSDKVersionNotFound): - _ = func(env, version_def.msvc_version, vc_dir, '') + _ = func(env, version_def.msvc_version, vc_dir) have_spectre = toolset_def.msvc_toolset_version in Data.SPECTRE_TOOLSET_VERSIONS.get(version_def.msvc_version,[]) env = Environment(MSVC_SPECTRE_LIBS=True, MSVC_TOOLSET_VERSION=toolset_def.msvc_toolset_version) if not have_spectre: with self.assertRaises(MSVCSpectreLibsNotFound): - _ = func(env, version_def.msvc_version, vc_dir, '') + _ = func(env, version_def.msvc_version, vc_dir) else: - _ = func(env, version_def.msvc_version, vc_dir, '') + _ = func(env, version_def.msvc_version, vc_dir) msvc_sdk_version = Data.msvc_sdk_version(version_def.msvc_version) @@ -443,14 +458,14 @@ def run_msvc_script_args(self) -> None: }, (MSVCArgumentError, ), ), # multiple definitions - ({'MSVC_TOOLSET_VERSION': version_def.msvc_verstr, - 'MSVC_SCRIPT_ARGS': "-vcvars_ver={}".format(version_def.msvc_verstr) + ({'MSVC_TOOLSET_VERSION': toolset_def.msvc_toolset_version, + 'MSVC_SCRIPT_ARGS': "-vcvars_ver={}".format(toolset_def.msvc_toolset_version) }, (MSVCArgumentError, ), ), # multiple definitions (args) - ({'MSVC_TOOLSET_VERSION': version_def.msvc_verstr, - 'MSVC_SCRIPT_ARGS': "-vcvars_ver={0} undefined -vcvars_ver={0}".format(version_def.msvc_verstr) + ({'MSVC_TOOLSET_VERSION': toolset_def.msvc_toolset_version, + 'MSVC_SCRIPT_ARGS': "-vcvars_ver={0} undefined -vcvars_ver={0}".format(toolset_def.msvc_toolset_version) }, (MSVCArgumentError, ), ), @@ -484,7 +499,7 @@ def run_msvc_script_args(self) -> None: (MSVCArgumentError, ), ), # toolset > msvc_version - ({'MSVC_TOOLSET_VERSION': '{}.{}'.format(version_def.msvc_major, version_def.msvc_minor+1), + ({'MSVC_TOOLSET_VERSION': '{}.{}'.format(latest_buildseries_major, latest_buildseries_minor+1), }, (MSVCArgumentError, ), ), @@ -519,25 +534,90 @@ def run_msvc_script_args(self) -> None: ] + more_tests: env = Environment(**kwargs) with self.assertRaises(exc_t): - _ = func(env, version_def.msvc_version, vc_dir, '') + _ = func(env, version_def.msvc_version, vc_dir) elif version_def.msvc_verstr == '14.0': - # VS2015: MSVC_SDK_VERSION and MSVC_UWP_APP + + if msvc_version_is_express(version_def.msvc_version): + sdk_supported = False + uwp_supported = True # based on target arch + elif msvc_version_is_btdispatch(version_def.msvc_version): + sdk_supported = False + uwp_supported = False + else: + sdk_supported = True + uwp_supported = True env = Environment(MSVC_SCRIPT_ARGS='undefinedsymbol') - _ = func(env, version_def.msvc_version, vc_dir, '') + _ = func(env, version_def.msvc_version, vc_dir) - for msvc_uwp_app in (True, False): + if sdk_supported: + # VS2015: MSVC_SDK_VERSION + + if uwp_supported: + # VS2015: MSVC_UWP_APP + + for msvc_uwp_app in (True, False): + + sdk_list = WinSDK.get_msvc_sdk_version_list(version_def.msvc_version, msvc_uwp_app=msvc_uwp_app) + for sdk_version in sdk_list: + + for kwargs in [ + {'MSVC_SCRIPT_ARGS': sdk_version, 'MSVC_UWP_APP': msvc_uwp_app}, + {'MSVC_SDK_VERSION': sdk_version, 'MSVC_UWP_APP': msvc_uwp_app}, + ]: + env = Environment(**kwargs) + _ = func(env, version_def.msvc_version, vc_dir) + + else: + # VS2015: MSVC_UWP_APP error + + for msvc_uwp_app in (True,): + + sdk_list = WinSDK.get_msvc_sdk_version_list(version_def.msvc_version, msvc_uwp_app=msvc_uwp_app) + for sdk_version in sdk_list: + + for kwargs in [ + {'MSVC_SCRIPT_ARGS': sdk_version, 'MSVC_UWP_APP': msvc_uwp_app}, + {'MSVC_SDK_VERSION': sdk_version, 'MSVC_UWP_APP': msvc_uwp_app}, + ]: + env = Environment(**kwargs) + with self.assertRaises(MSVCArgumentError): + _ = func(env, version_def.msvc_version, vc_dir) + + else: + # VS2015: MSVC_SDK_VERSION error sdk_list = WinSDK.get_msvc_sdk_version_list(version_def.msvc_version, msvc_uwp_app=msvc_uwp_app) for sdk_version in sdk_list: - for kwargs in [ - {'MSVC_SCRIPT_ARGS': sdk_version, 'MSVC_UWP_APP': msvc_uwp_app}, - {'MSVC_SDK_VERSION': sdk_version, 'MSVC_UWP_APP': msvc_uwp_app}, - ]: - env = Environment(**kwargs) - _ = func(env, version_def.msvc_version, vc_dir, '') + env = Environment(MSVC_SDK_VERSION=sdk_version) + with self.assertRaises(MSVCArgumentError): + _ = func(env, version_def.msvc_version, vc_dir) + + # MSVC_SCRIPT_ARGS sdk_version not validated + env = Environment(MSVC_SCRIPT_ARGS=sdk_version) + _ = func(env, version_def.msvc_version, vc_dir) + + if uwp_supported: + # VS2015: MSVC_UWP_APP + + for msvc_uwp_app in (True, False): + env = Environment(MSVC_UWP_APP=msvc_uwp_app) + _ = func(env, version_def.msvc_version, vc_dir) + + else: + # VS2015: MSVC_UWP_APP error + + for msvc_uwp_app in (True,): + + env = Environment(MSVC_UWP_APP=msvc_uwp_app) + with self.assertRaises(MSVCArgumentError): + _ = func(env, version_def.msvc_version, vc_dir) + + # MSVC_SCRIPT_ARGS store not validated + env = Environment(MSVC_SCRIPT_ARGS='store') + _ = func(env, version_def.msvc_version, vc_dir) for kwargs in [ {'MSVC_SPECTRE_LIBS': True, 'MSVC_SCRIPT_ARGS': None}, @@ -545,14 +625,14 @@ def run_msvc_script_args(self) -> None: ]: env = Environment(**kwargs) with self.assertRaises(MSVCArgumentError): - _ = func(env, version_def.msvc_version, vc_dir, '') + _ = func(env, version_def.msvc_version, vc_dir) else: # VS2013 and earlier: no arguments env = Environment(MSVC_SCRIPT_ARGS='undefinedsymbol') with self.assertRaises(MSVCArgumentError): - _ = func(env, version_def.msvc_version, vc_dir, '') + _ = func(env, version_def.msvc_version, vc_dir) for kwargs in [ {'MSVC_UWP_APP': True, 'MSVC_SCRIPT_ARGS': None}, @@ -563,7 +643,7 @@ def run_msvc_script_args(self) -> None: ]: env = Environment(**kwargs) with self.assertRaises(MSVCArgumentError): - _ = func(env, version_def.msvc_version, vc_dir, '') + _ = func(env, version_def.msvc_version, vc_dir) def test_msvc_script_args_none(self) -> None: force = ScriptArguments.msvc_force_default_arguments(force=False) diff --git a/SCons/Tool/MSCommon/MSVC/Util.py b/SCons/Tool/MSCommon/MSVC/Util.py index 6fd188bc35..0da58e1e4d 100644 --- a/SCons/Tool/MSCommon/MSVC/Util.py +++ b/SCons/Tool/MSCommon/MSVC/Util.py @@ -37,6 +37,15 @@ from . import Config + +# call _initialize method upon class definition completion + +class AutoInitialize: + def __init_subclass__(cls, **kwargs): + super().__init_subclass__(**kwargs) + if hasattr(cls, '_initialize') and callable(getattr(cls, '_initialize', None)): + cls._initialize() + # path utilities # windows drive specification (e.g., 'C:') @@ -234,6 +243,26 @@ def get_msvc_version_prefix(version): rval = m.group('version') return rval +def get_msvc_version_prefix_suffix(version): + """ + Get the msvc version number prefix and suffix from a string. + + Args: + version: str + version specification + + Returns: + (str, str): the msvc version prefix and suffix + + """ + prefix = suffix = '' + if version: + m = re_msvc_version.match(version) + if m: + prefix = m.group('msvc_version') + suffix = m.group('suffix') if m.group('suffix') else '' + return prefix, suffix + # toolset version query utilities def is_toolset_full(toolset_version) -> bool: @@ -322,15 +351,21 @@ def msvc_version_components(vcver): return msvc_version_components_def _MSVC_EXTENDED_VERSION_COMPONENTS_DEFINITION = namedtuple('MSVCExtendedVersionComponentsDefinition', [ - 'msvc_version', # msvc version (e.g., '14.1Exp') - 'msvc_verstr', # msvc version numeric string (e.g., '14.1') - 'msvc_suffix', # msvc version component type (e.g., 'Exp') - 'msvc_vernum', # msvc version floating point number (e.g, 14.1) - 'msvc_major', # msvc major version integer number (e.g., 14) - 'msvc_minor', # msvc minor version integer number (e.g., 1) - 'msvc_comps', # msvc version components tuple (e.g., ('14', '1')) + 'msvc_version', # msvc version (e.g., '14.1Exp') + 'msvc_verstr', # msvc version numeric string (e.g., '14.1') + 'msvc_suffix', # msvc version component type (e.g., 'Exp') + 'msvc_suffix_rank', # msvc version component rank (0, 1) + 'msvc_vernum', # msvc version floating point number (e.g, 14.1) + 'msvc_major', # msvc major version integer number (e.g., 14) + 'msvc_minor', # msvc minor version integer number (e.g., 1) + 'msvc_comps', # msvc version components tuple (e.g., ('14', '1')) + 'msvc_buildtools', # msvc build tools + 'msvc_buildtools_num', # msvc build tools integer number + 'msvc_buildseries', # msvc build series + 'msvc_buildseries_num', # msvc build series floating point number 'msvc_toolset_version', # msvc toolset version 'msvc_toolset_comps', # msvc toolset version components + 'msvc_toolset_is_sxs', # msvc toolset version is sxs 'version', # msvc version or msvc toolset version ]) @@ -355,11 +390,19 @@ def msvc_extended_version_components(version): msvc_toolset_version = m.group('version') msvc_toolset_comps = tuple(msvc_toolset_version.split('.')) + msvc_toolset_is_sxs = is_toolset_sxs(msvc_toolset_version) - msvc_verstr = get_msvc_version_prefix(msvc_toolset_version) - if not msvc_verstr: + vc_verstr = get_msvc_version_prefix(msvc_toolset_version) + if not vc_verstr: return None + vc_buildseries_def = Config.MSVC_BUILDSERIES_EXTERNAL.get(vc_verstr) + if not vc_buildseries_def: + return None + + vc_buildtools_def = Config.VC_BUILDTOOLS_MAP[vc_buildseries_def.vc_buildseries] + + msvc_verstr = vc_buildtools_def.msvc_version msvc_suffix = m.group('suffix') if m.group('suffix') else '' msvc_version = msvc_verstr + msvc_suffix @@ -376,12 +419,18 @@ def msvc_extended_version_components(version): msvc_version = msvc_version, msvc_verstr = msvc_verstr, msvc_suffix = msvc_suffix, + msvc_suffix_rank = 0 if not msvc_suffix else 1, msvc_vernum = msvc_vernum, msvc_major = msvc_major, msvc_minor = msvc_minor, msvc_comps = msvc_comps, + msvc_buildtools = vc_buildtools_def.msvc_version, + msvc_buildtools_num = vc_buildtools_def.msvc_version_numeric, + msvc_buildseries = vc_buildseries_def.vc_version, + msvc_buildseries_num = vc_buildseries_def.vc_version_numeric, msvc_toolset_version = msvc_toolset_version, msvc_toolset_comps = msvc_toolset_comps, + msvc_toolset_is_sxs = msvc_toolset_is_sxs, version = version, ) diff --git a/SCons/Tool/MSCommon/MSVC/__init__.py b/SCons/Tool/MSCommon/MSVC/__init__.py index 766894d9bb..f87b0f1d8c 100644 --- a/SCons/Tool/MSCommon/MSVC/__init__.py +++ b/SCons/Tool/MSCommon/MSVC/__init__.py @@ -40,6 +40,7 @@ from . import Config # noqa: F401 from . import Util # noqa: F401 from . import Registry # noqa: F401 +from . import Kind # noqa: F401 from . import SetupEnvDefault # noqa: F401 from . import Policy # noqa: F401 from . import WinSDK # noqa: F401 diff --git a/SCons/Tool/MSCommon/README.rst b/SCons/Tool/MSCommon/README.rst index 5ab07ad631..b37f409d3d 100644 --- a/SCons/Tool/MSCommon/README.rst +++ b/SCons/Tool/MSCommon/README.rst @@ -27,16 +27,176 @@ Design Notes ``MSCommon/vc.py`` and are available via the ``SCons.Tool.MSCommon`` namespace. +MSVC Detection Priority +======================= + +For msvc version specifications without an 'Exp' suffix, an express +installation is used only when no other installation is detected. + ++---------+---------+----------------------------------------------------------+ +| Product | VCVer | Priority | ++=========+=========+==========================================================+ +| VS2022 | 14.3 | Enterprise, Professional, Community, BuildTools | ++---------+---------+----------------------------------------------------------+ +| VS2019 | 14.2 | Enterprise, Professional, Community, BuildTools | ++---------+---------+----------------------------------------------------------+ +| VS2017 | 14.1 | Enterprise, Professional, Community, BuildTools, Express | ++---------+---------+----------------------------------------------------------+ +| VS2017 | 14.1Exp | Express | ++---------+---------+----------------------------------------------------------+ +| VS2015 | 14.0 | [Develop, BuildTools, CmdLine], Express | ++---------+---------+----------------------------------------------------------+ +| VS2015 | 14.0Exp | Express | ++---------+---------+----------------------------------------------------------+ +| VS2013 | 12.0 | Develop, Express | ++---------+---------+----------------------------------------------------------+ +| VS2013 | 12.0Exp | Express | ++---------+---------+----------------------------------------------------------+ +| VS2012 | 11.0 | Develop, Express | ++---------+---------+----------------------------------------------------------+ +| VS2012 | 11.0Exp | Express | ++---------+---------+----------------------------------------------------------+ +| VS2010 | 10.0 | Develop, Express | ++---------+---------+----------------------------------------------------------+ +| VS2010 | 10.0Exp | Express | ++---------+---------+----------------------------------------------------------+ +| VS2008 | 9.0 | Develop, VCForPython, Express | ++---------+---------+----------------------------------------------------------+ +| VS2008 | 9.0Exp | Express | ++---------+---------+----------------------------------------------------------+ +| VS2005 | 8.0 | Develop, Express | ++---------+---------+----------------------------------------------------------+ +| VS2005 | 8.0Exp | Express | ++---------+---------+----------------------------------------------------------+ +| VS2003 | 7.1 | Develop | ++---------+---------+----------------------------------------------------------+ +| VS2002 | 7.0 | Develop | ++---------+---------+----------------------------------------------------------+ +| VS6.0 | 6.0 | Develop | ++---------+---------+----------------------------------------------------------+ + +Legend: + + Develop + devenv.com or msdev.com is detected. + + Express + WDExpress.exe or VCExpress.exe is detected. + + BuildTools [VS2015] + The vcvarsall batch file dispatches to the buildtools batch file. + + CmdLine [VS2015] + Neither Develop, Express, or BuildTools. + +VS2015 Edition Limitations +========================== + +VS2015 BuildTools +----------------- + +The VS2015 BuildTools stand-alone batch file does not support the ``sdk version`` argument. + +The VS2015 BuildTools stand-alone batch file does not support the ``store`` argument. + +These arguments appear to be silently ignored and likely would result in compiler +and/or linker build failures. + +The VS2015 BuildTools ``vcvarsall.bat`` batch file dispatches to the stand-alone buildtools +batch file under certain circumstances. A fragment from the vcvarsall batch file is: + +:: + + if exist "%~dp0..\common7\IDE\devenv.exe" goto setup_VS + if exist "%~dp0..\common7\IDE\wdexpress.exe" goto setup_VS + if exist "%~dp0..\..\Microsoft Visual C++ Build Tools\vcbuildtools.bat" goto setup_buildsku + + :setup_VS + + ... + + :setup_buildsku + if not exist "%~dp0..\..\Microsoft Visual C++ Build Tools\vcbuildtools.bat" goto usage + set CurrentDir=%CD% + call "%~dp0..\..\Microsoft Visual C++ Build Tools\vcbuildtools.bat" %1 %2 + cd /d %CurrentDir% + goto :eof + +VS2015 Express +-------------- + +The VS2015 Express batch file does not support the ``sdk version`` argument. + +The VS2015 Express batch file does not support the ``store`` argument for the ``amd64`` and +``arm`` target architectures + +amd64 Target Architecture +^^^^^^^^^^^^^^^^^^^^^^^^^ + +As installed, VS2015 Express does not support the ``store`` argument for the ``amd64`` target +architecture. The generated ``store`` library paths include directories that do not exist. + +The store library paths appear in two places in the ``vcvarsx86_amd64`` batch file: + +:: + + :setstorelib + @if exist "%VCINSTALLDIR%LIB\amd64\store" set LIB=%VCINSTALLDIR%LIB\amd64\store;%LIB% + ... + :setstorelibpath + @if exist "%VCINSTALLDIR%LIB\amd64\store" set LIBPATH=%VCINSTALLDIR%LIB\amd64\store;%LIBPATH% + +The correct store library paths would be: + +:: + + :setstorelib + @if exist "%VCINSTALLDIR%LIB\store\amd64" set LIB=%VCINSTALLDIR%LIB\store\amd64;%LIB% + ... + :setstorelibpath + @if exist "%VCINSTALLDIR%LIB\store\amd64" set LIBPATH=%VCINSTALLDIR%LIB\store\amd64;%LIBPATH% + +arm Target Architecture +^^^^^^^^^^^^^^^^^^^^^^^ + +As installed, VS2015 Express does not support the ``store`` argument for the ``arm`` target +architecture. The generated ``store`` library paths include directories that do not exist. + +The store library paths appear in two places in the ``vcvarsx86_arm`` batch file: + +:: + + :setstorelib + @if exist "%VCINSTALLDIR%LIB\ARM\store" set LIB=%VCINSTALLDIR%LIB\ARM\store;%LIB% + ... + :setstorelibpath + @if exist "%VCINSTALLDIR%LIB\ARM\store" set LIBPATH=%VCINSTALLDIR%LIB\ARM\store;%LIBPATH% + +The correct store library paths would be file: + +:: + + :setstorelib + @if exist "%VCINSTALLDIR%LIB\store\ARM" set LIB=%VCINSTALLDIR%LIB\store\ARM;%LIB% + ... + :setstorelibpath + @if exist "%VCINSTALLDIR%LIB\store\ARM" set LIBPATH=%VCINSTALLDIR%LIB\store\ARM;%LIBPATH% + + Known Issues ============ The following issues are known to exist: * Using ``MSVC_USE_SCRIPT`` and ``MSVC_USE_SCRIPT_ARGS`` to call older Microsoft SDK - ``SetEnv.cmd`` batch files may result in build failures. Some of these batch files - require delayed expansion to be enabled which is not usually the Windows default. - One solution would be to launch the MSVC batch file command in a new command interpreter - instance with delayed expansion enabled via command-line options. + ``SetEnv.cmd`` batch files may result in build failures. + + Typically, the reasons for build failures with SDK batch files are one, or both, of: + + * The batch files require delayed expansion to be enabled which is not usually the Windows default. + + * The batch files inspect environment variables that are not defined in the minimal subprocess + environment in which the batch files are invoked. * The code to suppress the "No versions of the MSVC compiler were found" warning for the default environment was moved from ``MSCommon/vc.py`` to ``MSCommon/MSVC/SetupEnvDefault.py``. @@ -47,20 +207,24 @@ The following issues are known to exist: Experimental Features ===================== -msvc_query_version_toolset(version=None, prefer_newest=True) ------------------------------------------------------------- +msvc_query_version_toolset(version=None, prefer_newest=True, vswhere_exe=None) +------------------------------------------------------------------------------ The experimental function ``msvc_query_version_toolset`` was added to ``MSCommon/vc.py`` -and is available via the ``SCons.Tool.MSCommon`` namespace. This function takes a version -specification or a toolset version specification and a product preference as arguments and -returns the msvc version and the msvc toolset version for the corresponding version specification. +and is available via the ``SCons.Tool.MSCommon`` namespace. + +This function takes a version specification or a toolset version specification, an optional product +preference, and an optional vswhere executable location as arguments and returns the msvc version and +the msvc toolset version for the corresponding version specification. This is a proxy for using the toolset version for selection until that functionality can be added. Example usage: + :: for version in [ + '14.4', '14.3', '14.2', '14.1', @@ -90,6 +254,7 @@ Example usage: print('{}Query: {} version={}, prefer_newest={}'.format(newline, msg, version, prefer_newest)) Example output fragment + :: Build: _build003 {'MSVC_VERSION': '14.3', 'MSVC_TOOLSET_VERSION': '14.29.30133'} @@ -117,6 +282,7 @@ added to the batch file argument list. This is intended to make the cache more updates that may change the default toolset version and/or the default SDK version. Example usage: + :: @echo Enabling scons cache ... @@ -139,6 +305,7 @@ Enabling warnings to be produced for detected msvc batch file errors may provide for build failures. Refer to the documentation for details. Change the default policy: + :: from SCons.Tool.MSCommon import msvc_set_scripterror_policy @@ -146,6 +313,7 @@ Change the default policy: msvc_set_scripterror_policy('Warning') Specify the policy per-environment: + :: env = Environment(MSVC_VERSION='14.3', MSVC_SPECTRE_LIBS=True, MSVC_SCRIPTERROR_POLICY='Warning') @@ -171,6 +339,7 @@ On occasion, the raw vswhere output may prove useful especially if there are sus detection of installed msvc instances. Windows command-line sample invocations: + :: @rem 64-Bit Windows @@ -188,31 +357,37 @@ Batch File Arguments Supported MSVC batch file arguments by product: -======= === === ======= ======= -Product UWP SDK Toolset Spectre -======= === === ======= ======= -VS2022 X X X X -------- --- --- ------- ------- -VS2019 X X X X -------- --- --- ------- ------- -VS2017 X X X X -------- --- --- ------- ------- -VS2015 X X -======= === === ======= ======= ++---------+---------+--------+---------+---------+ +| Product | UWP | SDK | Toolset | Spectre | ++=========+=========+========+=========+=========+ +| VS2022 | X | X | X | X | ++---------+---------+--------+---------+---------+ +| VS2019 | X | X | X | X | ++---------+---------+--------+---------+---------+ +| VS2017 | X | X | X | X | ++---------+---------+--------+---------+---------+ +| VS2015 | X [1] | X [2] | | | ++---------+---------+--------+---------+---------+ + +Notes: + +1) The BuildTools edition does not support the ``store`` argument. The Express edition + supports the ``store`` argument for the ``x86`` target only. +2) The ``sdk version`` argument is not supported in the BuildTools and Express editions. Supported MSVC batch file arguments in SCons: -======== ====================================== =================================================== -Argument Construction Variable Script Argument Equivalent -======== ====================================== =================================================== -UWP ``MSVC_UWP_APP=True`` ``MSVC_SCRIPT_ARGS='store'`` --------- -------------------------------------- --------------------------------------------------- -SDK ``MSVC_SDK_VERSION='10.0.20348.0'`` ``MSVC_SCRIPT_ARGS='10.0.20348.0'`` --------- -------------------------------------- --------------------------------------------------- -Toolset ``MSVC_TOOLSET_VERSION='14.31.31103'`` ``MSVC_SCRIPT_ARGS='-vcvars_ver=14.31.31103'`` --------- -------------------------------------- --------------------------------------------------- -Spectre ``MSVC_SPECTRE_LIBS=True`` ``MSVC_SCRIPT_ARGS='-vcvars_spectre_libs=spectre'`` -======== ====================================== =================================================== ++----------+----------------------------------------+-----------------------------------------------------+ +| Argument | Construction Variable | Script Argument Equivalent | ++==========+========================================+=====================================================+ +| UWP | ``MSVC_UWP_APP=True`` | ``MSVC_SCRIPT_ARGS='store'`` | ++----------+----------------------------------------+-----------------------------------------------------+ +| SDK | ``MSVC_SDK_VERSION='10.0.20348.0'`` | ``MSVC_SCRIPT_ARGS='10.0.20348.0'`` | ++----------+----------------------------------------+-----------------------------------------------------+ +| Toolset | ``MSVC_TOOLSET_VERSION='14.31.31103'`` | ``MSVC_SCRIPT_ARGS='-vcvars_ver=14.31.31103'`` | ++----------+----------------------------------------+-----------------------------------------------------+ +| Spectre | ``MSVC_SPECTRE_LIBS=True`` | ``MSVC_SCRIPT_ARGS='-vcvars_spectre_libs=spectre'`` | ++----------+----------------------------------------+-----------------------------------------------------+ **MSVC_SCRIPT_ARGS contents are not validated. Utilizing script arguments that have construction variable equivalents is discouraged and may lead to difficult to diagnose build errors.** @@ -244,6 +419,7 @@ that the msvc batch files would return. When using ``MSVC_SCRIPT_ARGS``, the toolset specification should be omitted entirely. Local installation and summary test results: + :: VS2022\VC\Auxiliary\Build\Microsoft.VCToolsVersion.v143.default.txt @@ -253,6 +429,7 @@ Local installation and summary test results: 14.32.31326 Toolset version summary: + :: 14.31.31103 Environment() @@ -268,6 +445,7 @@ Toolset version summary: 14.32.31326 Environment(MSVC_SCRIPT_ARGS=['-vcvars_ver=14.32']) VS2022\\Common7\\Tools\\vsdevcmd\\ext\\vcvars.bat usage fragment: + :: @echo -vcvars_ver=version : Version of VC++ Toolset to select @@ -289,6 +467,7 @@ VS2022\\Common7\\Tools\\vsdevcmd\\ext\\vcvars.bat usage fragment: @echo SxS toolset to [VSInstallDir]\VC\MSVC\Tools\ directory. VS2022 batch file fragment to determine the default toolset version: + :: @REM Add MSVC @@ -315,75 +494,114 @@ Visual Studio Version Notes SDK Versions ------------ -==== ============ -SDK Format -==== ============ -10.0 10.0.XXXXX.Y ----- ------------ -8.1 8.1 -==== ============ ++------+-------------------+ +| SDK | Format | ++======+===================+ +| 10.0 | 10.0.XXXXX.Y [1] | ++------+-------------------+ +| 8.1 | 8.1 | ++------+-------------------+ + +Notes: + +1) The Windows 10 SDK version number is 10.0.20348.0 and earlier. + + The Windows 11 SDK version number is 10.0.22000.194 and later. + +BuildSeries Versions +-------------------- + ++-------------+-------+-------+ +| BuildSeries | VCVER | CLVER | ++=============+=======+=======+ +| 14.4 | 14.4X | 19.4 | ++-------------+-------+-------+ +| 14.3 | 14.3X | 19.3 | ++-------------+-------+-------+ +| 14.2 | 14.2X | 19.2 | ++-------------+-------+-------+ +| 14.1 | 14.1X | 19.1 | ++-------------+-------+-------+ +| 14.0 | 14.0 | 19.0 | ++-------------+-------+-------+ +| 12.0 | 12.0 | 18.0 | ++-------------+-------+-------+ +| 11.0 | 11.0 | 17.0 | ++-------------+-------+-------+ +| 10.0 | 10.0 | 16.0 | ++-------------+-------+-------+ +| 9.0 | 9.0 | 15.0 | ++-------------+-------+-------+ +| 8.0 | 8.0 | 14.0 | ++-------------+-------+-------+ +| 7.1 | 7.1 | 13.1 | ++-------------+-------+-------+ +| 7.0 | 7.0 | 13.0 | ++-------------+-------+-------+ +| 6.0 | 6.0 | 12.0 | ++-------------+-------+-------+ BuildTools Versions ------------------- -========== ===== ===== ======== -BuildTools VCVER CLVER MSVCRT -========== ===== ===== ======== -v143 14.3 19.3 140/ucrt ----------- ----- ----- -------- -v142 14.2 19.2 140/ucrt ----------- ----- ----- -------- -v141 14.1 19.1 140/ucrt ----------- ----- ----- -------- -v140 14.0 19.0 140/ucrt ----------- ----- ----- -------- -v120 12.0 18.0 120 ----------- ----- ----- -------- -v110 11.0 17.0 110 ----------- ----- ----- -------- -v100 10.0 16.0 100 ----------- ----- ----- -------- -v90 9.0 15.0 90 ----------- ----- ----- -------- -v80 8.0 14.0 80 ----------- ----- ----- -------- -v71 7.1 13.1 71 ----------- ----- ----- -------- -v70 7.0 13.0 70 ----------- ----- ----- -------- -v60 6.0 12.0 60 -========== ===== ===== ======== ++------------+-------------+----------+ +| BuildTools | BuildSeries | MSVCRT | ++============+=============+==========+ +| v143 | 14.4, 14.3 | 140/ucrt | ++------------+-------------+----------+ +| v142 | 14.2 | 140/ucrt | ++------------+-------------+----------+ +| v141 | 14.1 | 140/ucrt | ++------------+-------------+----------+ +| v140 | 14.0 | 140/ucrt | ++------------+-------------+----------+ +| v120 | 12.0 | 120 | ++------------+-------------+----------+ +| v110 | 11.0 | 110 | ++------------+-------------+----------+ +| v100 | 10.0 | 100 | ++------------+-------------+----------+ +| v90 | 9.0 | 90 | ++------------+-------------+----------+ +| v80 | 8.0 | 80 | ++------------+-------------+----------+ +| v71 | 7.1 | 71 | ++------------+-------------+----------+ +| v70 | 7.0 | 70 | ++------------+-------------+----------+ +| v60 | 6.0 | 60 | ++------------+-------------+----------+ Product Versions ---------------- -======== ===== ========= ====================== -Product VSVER SDK BuildTools -======== ===== ========= ====================== -2022 17.0 10.0, 8.1 v143, v142, v141, v140 --------- ----- --------- ---------------------- -2019 16.0 10.0, 8.1 v142, v141, v140 --------- ----- --------- ---------------------- -2017 15.0 10.0, 8.1 v141, v140 --------- ----- --------- ---------------------- -2015 14.0 10.0, 8.1 v140 --------- ----- --------- ---------------------- -2013 12.0 v120 --------- ----- --------- ---------------------- -2012 11.0 v110 --------- ----- --------- ---------------------- -2010 10.0 v100 --------- ----- --------- ---------------------- -2008 9.0 v90 --------- ----- --------- ---------------------- -2005 8.0 v80 --------- ----- --------- ---------------------- -2003.NET 7.1 v71 --------- ----- --------- ---------------------- -2002.NET 7.0 v70 --------- ----- --------- ---------------------- -6.0 6.0 v60 -======== ===== ========= ====================== ++----------+-------+-------+-----------+------------------------+ +| Product | VSVER | SCons | SDK | BuildTools | ++==========+=======+=======+===========+========================+ +| 2022 | 17.0 | 14.3 | 10.0, 8.1 | v143, v142, v141, v140 | ++----------+-------+-------+-----------+------------------------+ +| 2019 | 16.0 | 14.2 | 10.0, 8.1 | v142, v141, v140 | ++----------+-------+-------+-----------+------------------------+ +| 2017 | 15.0 | 14.1 | 10.0, 8.1 | v141, v140 | ++----------+-------+-------+-----------+------------------------+ +| 2015 | 14.0 | 14.0 | 10.0, 8.1 | v140 | ++----------+-------+-------+-----------+------------------------+ +| 2013 | 12.0 | 12.0 | | v120 | ++----------+-------+-------+-----------+------------------------+ +| 2012 | 11.0 | 11.0 | | v110 | ++----------+-------+-------+-----------+------------------------+ +| 2010 | 10.0 | 10.0 | | v100 | ++----------+-------+-------+-----------+------------------------+ +| 2008 | 9.0 | 9.0 | | v90 | ++----------+-------+-------+-----------+------------------------+ +| 2005 | 8.0 | 8.0 | | v80 | ++----------+-------+-------+-----------+------------------------+ +| 2003.NET | 7.1 | 7.1 | | v71 | ++----------+-------+-------+-----------+------------------------+ +| 2002.NET | 7.0 | 7.0 | | v70 | ++----------+-------+-------+-----------+------------------------+ +| 6.0 | 6.0 | 6.0 | | v60 | ++----------+-------+-------+-----------+------------------------+ SCons Implementation Notes diff --git a/SCons/Tool/MSCommon/__init__.py b/SCons/Tool/MSCommon/__init__.py index c3078ac630..63f9ae4de6 100644 --- a/SCons/Tool/MSCommon/__init__.py +++ b/SCons/Tool/MSCommon/__init__.py @@ -45,6 +45,9 @@ msvc_toolset_versions, msvc_toolset_versions_spectre, msvc_query_version_toolset, + vswhere_register_executable, + vswhere_get_executable, + vswhere_freeze_executable, ) from SCons.Tool.MSCommon.vs import ( # noqa: F401 @@ -60,6 +63,8 @@ msvc_get_notfound_policy, msvc_set_scripterror_policy, msvc_get_scripterror_policy, + msvc_notfound_policy_contextmanager, + msvc_scripterror_policy_contextmanager, ) from .MSVC.Exceptions import ( # noqa: F401 @@ -78,7 +83,9 @@ MSVCUnsupportedHostArch, MSVCUnsupportedTargetArch, MSVCScriptNotFound, + MSVCUseScriptError, MSVCUseSettingsError, + VSWhereUserError, ) from .MSVC.Util import ( # noqa: F401 diff --git a/SCons/Tool/MSCommon/sdk.py b/SCons/Tool/MSCommon/sdk.py index c6600f3a5b..a06dfacfc6 100644 --- a/SCons/Tool/MSCommon/sdk.py +++ b/SCons/Tool/MSCommon/sdk.py @@ -372,7 +372,7 @@ def mssdk_setup_env(env): return msvs_version = env.subst(msvs_version) from . import vs - msvs = vs.get_vs_by_version(msvs_version) + msvs = vs.get_vs_by_version(msvs_version, env) debug('mssdk_setup_env:msvs is :%s', msvs) if not msvs: debug('mssdk_setup_env: no VS version detected, bailingout:%s', msvs) diff --git a/SCons/Tool/MSCommon/vc.py b/SCons/Tool/MSCommon/vc.py index e43c618cbd..032dbf202e 100644 --- a/SCons/Tool/MSCommon/vc.py +++ b/SCons/Tool/MSCommon/vc.py @@ -25,9 +25,6 @@ MS Compilers: Visual C/C++ detection and configuration. # TODO: -# * gather all the information from a single vswhere call instead -# of calling repeatedly (use json format?) -# * support passing/setting location for vswhere in env. # * supported arch for versions: for old versions of batch file without # argument, giving bogus argument cannot be detected, so we have to hardcode # this here @@ -52,19 +49,26 @@ namedtuple, OrderedDict, ) +import json +from functools import cmp_to_key +import enum import SCons.Util import SCons.Warnings from SCons.Tool import find_program_path from . import common -from .common import CONFIG_CACHE, debug +from .common import ( + CONFIG_CACHE, + debug, +) from .sdk import get_installed_sdks from . import MSVC from .MSVC.Exceptions import ( VisualCException, + MSVCInternalError, MSVCUserError, MSVCArgumentError, MSVCToolsetVersionNotFound, @@ -81,9 +85,15 @@ class MSVCUnsupportedTargetArch(VisualCException): class MSVCScriptNotFound(MSVCUserError): pass +class MSVCUseScriptError(MSVCUserError): + pass + class MSVCUseSettingsError(MSVCUserError): pass +class VSWhereUserError(MSVCUserError): + pass + # internal exceptions class UnsupportedVersion(VisualCException): @@ -102,8 +112,8 @@ class BatchFileExecutionError(VisualCException): # MSVC 9.0 preferred query order: # True: VCForPython, VisualStudio -# FAlse: VisualStudio, VCForPython -_VC90_Prefer_VCForPython = True +# False: VisualStudio, VCForPython +_VC90_Prefer_VCForPython = False # Dict to 'canonalize' the arch _ARCH_TO_CANONICAL = { @@ -393,7 +403,7 @@ def _make_target_host_map(all_hosts, host_all_targets_map): # debug("_LE2019_HOST_TARGET_CFG: %s", _LE2019_HOST_TARGET_CFG) -# 14.0 (VS2015) to 8.0 (VS2005) +# 14.0 (VS2015) to 10.0 (VS2010) # Given a (host, target) tuple, return a tuple containing the argument for # the batch file and a tuple of the path components to find cl.exe. @@ -402,23 +412,23 @@ def _make_target_host_map(all_hosts, host_all_targets_map): # bin directory (i.e., /VC/bin). Any other tools are in subdirectory # named for the the host/target pair or a single name if the host==target. -_LE2015_HOST_TARGET_BATCHARG_CLPATHCOMPS = { +_LE2015_HOST_TARGET_BATCHARG_BATCHFILE_CLPATHCOMPS = { - ('amd64', 'amd64') : ('amd64', ('bin', 'amd64')), - ('amd64', 'x86') : ('amd64_x86', ('bin', 'amd64_x86')), - ('amd64', 'arm') : ('amd64_arm', ('bin', 'amd64_arm')), + ('amd64', 'amd64') : ('amd64', 'vcvars64.bat', ('bin', 'amd64')), + ('amd64', 'x86') : ('amd64_x86', 'vcvarsamd64_x86.bat', ('bin', 'amd64_x86')), + ('amd64', 'arm') : ('amd64_arm', 'vcvarsamd64_arm.bat', ('bin', 'amd64_arm')), - ('x86', 'amd64') : ('x86_amd64', ('bin', 'x86_amd64')), - ('x86', 'x86') : ('x86', ('bin', )), - ('x86', 'arm') : ('x86_arm', ('bin', 'x86_arm')), - ('x86', 'ia64') : ('x86_ia64', ('bin', 'x86_ia64')), + ('x86', 'amd64') : ('x86_amd64', 'vcvarsx86_amd64.bat', ('bin', 'x86_amd64')), + ('x86', 'x86') : ('x86', 'vcvars32.bat', ('bin', )), + ('x86', 'arm') : ('x86_arm', 'vcvarsx86_arm.bat', ('bin', 'x86_arm')), + ('x86', 'ia64') : ('x86_ia64', 'vcvarsx86_ia64.bat', ('bin', 'x86_ia64')), - ('arm64', 'amd64') : ('amd64', ('bin', 'amd64')), - ('arm64', 'x86') : ('amd64_x86', ('bin', 'amd64_x86')), - ('arm64', 'arm') : ('amd64_arm', ('bin', 'amd64_arm')), + ('arm64', 'amd64') : ('amd64', 'vcvars64.bat', ('bin', 'amd64')), + ('arm64', 'x86') : ('amd64_x86', 'vcvarsamd64_x86.bat', ('bin', 'amd64_x86')), + ('arm64', 'arm') : ('amd64_arm', 'vcvarsamd64_arm.bat', ('bin', 'amd64_arm')), - ('arm', 'arm') : ('arm', ('bin', 'arm')), - ('ia64', 'ia64') : ('ia64', ('bin', 'ia64')), + ('arm', 'arm') : ('arm', 'vcvarsarm.bat', ('bin', 'arm')), + ('ia64', 'ia64') : ('ia64', 'vcvars64.bat', ('bin', 'ia64')), } @@ -454,6 +464,53 @@ def _make_target_host_map(all_hosts, host_all_targets_map): # debug("_LE2015_HOST_TARGET_CFG: %s", _LE2015_HOST_TARGET_CFG) +# 9.0 (VS2008) to 8.0 (VS2005) + +_LE2008_HOST_TARGET_BATCHARG_BATCHFILE_CLPATHCOMPS = { + + ('amd64', 'amd64') : ('amd64', 'vcvarsamd64.bat', ('bin', 'amd64')), + ('amd64', 'x86') : ('x86', 'vcvars32.bat', ('bin', )), + + ('x86', 'amd64') : ('x86_amd64', 'vcvarsx86_amd64.bat', ('bin', 'x86_amd64')), + ('x86', 'x86') : ('x86', 'vcvars32.bat', ('bin', )), + ('x86', 'ia64') : ('x86_ia64', 'vcvarsx86_ia64.bat', ('bin', 'x86_ia64')), + + ('arm64', 'amd64') : ('amd64', 'vcvarsamd64.bat', ('bin', 'amd64')), + ('arm64', 'x86') : ('x86', 'vcvars32.bat', ('bin', )), + + ('ia64', 'ia64') : ('ia64', 'vcvarsia64.bat', ('bin', 'ia64')), + +} + +_LE2008_HOST_TARGET_CFG = _host_target_config_factory( + + label = 'LE2008', + + host_all_hosts = OrderedDict([ + ('amd64', ['amd64', 'x86']), + ('x86', ['x86']), + ('arm64', ['amd64', 'x86']), + ('ia64', ['ia64']), + ]), + + host_all_targets = { + 'amd64': ['amd64', 'x86'], + 'x86': ['x86', 'amd64', 'ia64'], + 'arm64': ['amd64', 'x86'], + 'ia64': ['ia64'], + }, + + host_def_targets = { + 'amd64': ['amd64', 'x86'], + 'x86': ['x86'], + 'arm64': ['amd64', 'x86'], + 'ia64': ['ia64'], + }, + +) + +# debug("_LE2008_HOST_TARGET_CFG: %s", _LE2008_HOST_TARGET_CFG) + # 7.1 (VS2003) and earlier # For 7.1 (VS2003) and earlier, there are only x86 targets and the batch files @@ -487,6 +544,8 @@ def _make_target_host_map(all_hosts, host_all_targets_map): _CL_EXE_NAME = 'cl.exe' +_VSWHERE_EXE = 'vswhere.exe' + def get_msvc_version_numeric(msvc_version): """Get the raw version numbers from a MSVC_VERSION string, so it could be cast to float or other numeric values. For example, '14.0Exp' @@ -566,9 +625,12 @@ def get_host_target(env, msvc_version, all_host_targets: bool=False): elif 143 > vernum_int >= 141: # 14.2 (VS2019) to 14.1 (VS2017) host_target_cfg = _LE2019_HOST_TARGET_CFG - elif 141 > vernum_int >= 80: - # 14.0 (VS2015) to 8.0 (VS2005) + elif 141 > vernum_int >= 100: + # 14.0 (VS2015) to 10.0 (VS2010) host_target_cfg = _LE2015_HOST_TARGET_CFG + elif 100 > vernum_int >= 80: + # 9.0 (VS2008) to 8.0 (VS2005) + host_target_cfg = _LE2008_HOST_TARGET_CFG else: # 80 > vernum_int # 7.1 (VS2003) and earlier host_target_cfg = _LE2003_HOST_TARGET_CFG @@ -687,49 +749,72 @@ def _skip_sendtelemetry(env): "7.0", "6.0"] -# if using vswhere, configure command line arguments to probe for installed VC editions -_VCVER_TO_VSWHERE_VER = { - '14.3': [ - ["-version", "[17.0, 18.0)"], # default: Enterprise, Professional, Community (order unpredictable?) - ["-version", "[17.0, 18.0)", "-products", "Microsoft.VisualStudio.Product.BuildTools"], # BuildTools - ], - '14.2': [ - ["-version", "[16.0, 17.0)"], # default: Enterprise, Professional, Community (order unpredictable?) - ["-version", "[16.0, 17.0)", "-products", "Microsoft.VisualStudio.Product.BuildTools"], # BuildTools - ], - '14.1': [ - ["-version", "[15.0, 16.0)"], # default: Enterprise, Professional, Community (order unpredictable?) - ["-version", "[15.0, 16.0)", "-products", "Microsoft.VisualStudio.Product.BuildTools"], # BuildTools - ], - '14.1Exp': [ - ["-version", "[15.0, 16.0)", "-products", "Microsoft.VisualStudio.Product.WDExpress"], # Express - ], -} - +# VS2017 and later: use a single vswhere json query to find all installations + +# vswhere query: +# map vs major version to vc version (no suffix) +# build set of supported vc versions (including suffix) + +_VSWHERE_VSMAJOR_TO_VCVERSION = {} +_VSWHERE_SUPPORTED_VCVER = set() + +for vs_major, vc_version, vc_ver_list in ( + ('17', '14.3', None), + ('16', '14.2', None), + ('15', '14.1', ['14.1Exp']), +): + _VSWHERE_VSMAJOR_TO_VCVERSION[vs_major] = vc_version + _VSWHERE_SUPPORTED_VCVER.add(vc_version) + if vc_ver_list: + for vc_ver in vc_ver_list: + _VSWHERE_SUPPORTED_VCVER.add(vc_ver) + +# vwhere query: +# build of set of candidate component ids +# preferred ranking: Enterprise, Professional, Community, BuildTools, Express +# Ent, Pro, Com, BT, Exp are in the same list +# Exp also has it's own list +# currently, only the express (Exp) suffix is expected + +_VSWHERE_COMPONENTID_CANDIDATES = set() +_VSWHERE_COMPONENTID_RANKING = {} +_VSWHERE_COMPONENTID_SUFFIX = {} +_VSWHERE_COMPONENTID_SCONS_SUFFIX = {} + +for component_id, component_rank, component_suffix, scons_suffix in ( + ('Enterprise', 140, 'Ent', ''), + ('Professional', 130, 'Pro', ''), + ('Community', 120, 'Com', ''), + ('BuildTools', 110, 'BT', ''), + ('WDExpress', 100, 'Exp', 'Exp'), +): + _VSWHERE_COMPONENTID_CANDIDATES.add(component_id) + _VSWHERE_COMPONENTID_RANKING[component_id] = component_rank + _VSWHERE_COMPONENTID_SUFFIX[component_id] = component_suffix + _VSWHERE_COMPONENTID_SCONS_SUFFIX[component_id] = scons_suffix + +# VS2015 and earlier: configure registry queries to probe for installed VC editions _VCVER_TO_PRODUCT_DIR = { - '14.3': [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'')], # not set by this version - '14.2': [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'')], # not set by this version - '14.1': [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'')], # not set by this version - '14.1Exp': [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'')], # not set by this version '14.0': [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\14.0\Setup\VC\ProductDir')], + (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\14.0\Setup\VC\ProductDir') + ], '14.0Exp': [ (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\WDExpress\14.0\Setup\VS\ProductDir'), - (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\14.0\Setup\VC\ProductDir')], + (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\14.0\Setup\VC\ProductDir'), + (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\14.0\Setup\VC\ProductDir') + ], '12.0': [ (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\12.0\Setup\VC\ProductDir'), ], '12.0Exp': [ + (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\WDExpress\12.0\Setup\VS\ProductDir'), (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\12.0\Setup\VC\ProductDir'), ], '11.0': [ (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\11.0\Setup\VC\ProductDir'), ], '11.0Exp': [ + (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\WDExpress\11.0\Setup\VS\ProductDir'), (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\11.0\Setup\VC\ProductDir'), ], '10.0': [ @@ -739,17 +824,20 @@ def _skip_sendtelemetry(env): (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\10.0\Setup\VC\ProductDir'), ], '9.0': [ - (SCons.Util.HKEY_CURRENT_USER, r'Microsoft\DevDiv\VCForPython\9.0\installdir',), + (SCons.Util.HKEY_CURRENT_USER, r'Microsoft\DevDiv\VCForPython\9.0\installdir',), # vs root + (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\DevDiv\VCForPython\9.0\installdir',), # vs root (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\9.0\Setup\VC\ProductDir',), ] if _VC90_Prefer_VCForPython else [ (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\9.0\Setup\VC\ProductDir',), - (SCons.Util.HKEY_CURRENT_USER, r'Microsoft\DevDiv\VCForPython\9.0\installdir',), + (SCons.Util.HKEY_CURRENT_USER, r'Microsoft\DevDiv\VCForPython\9.0\installdir',), # vs root + (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\DevDiv\VCForPython\9.0\installdir',), # vs root ], '9.0Exp': [ (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\9.0\Setup\VC\ProductDir'), ], '8.0': [ (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\8.0\Setup\VC\ProductDir'), + (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\8.0\Setup\VC\ProductDir'), ], '8.0Exp': [ (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\8.0\Setup\VC\ProductDir'), @@ -765,6 +853,62 @@ def _skip_sendtelemetry(env): ] } +# detect ide binaries + +VS2022_VS2002_DEV = ( + MSVC.Kind.IDE_PROGRAM_DEVENV_COM, # devenv.com +) + +VS1998_DEV = ( + MSVC.Kind.IDE_PROGRAM_MSDEV_COM, # MSDEV.COM +) + +VS2017_EXP = ( + MSVC.Kind.IDE_PROGRAM_WDEXPRESS_EXE, # WDExpress.exe +) + +VS2015_VS2012_EXP = ( + MSVC.Kind.IDE_PROGRAM_WDEXPRESS_EXE, # WDExpress.exe [Desktop] + MSVC.Kind.IDE_PROGRAM_VSWINEXPRESS_EXE, # VSWinExpress.exe [Windows] + MSVC.Kind.IDE_PROGRAM_VWDEXPRESS_EXE, # VWDExpress.exe [Web] +) + +VS2010_VS2005_EXP = ( + MSVC.Kind.IDE_PROGRAM_VCEXPRESS_EXE, +) + +# detect productdir kind + +_DETECT = MSVC.Kind.VCVER_KIND_DETECT + +_VCVER_KIND_DETECT = { + + # 'VCVer': (relpath from pdir to vsroot, path from vsroot to ide binaries, ide binaries) + + '14.3': _DETECT(root='..', path=r'Common7\IDE', programs=VS2022_VS2002_DEV), # 2022 + '14.2': _DETECT(root='..', path=r'Common7\IDE', programs=VS2022_VS2002_DEV), # 2019 + '14.1': _DETECT(root='..', path=r'Common7\IDE', programs=VS2022_VS2002_DEV + VS2017_EXP), # 2017 + '14.1Exp': _DETECT(root='..', path=r'Common7\IDE', programs=VS2022_VS2002_DEV + VS2017_EXP), # 2017 + + '14.0': _DETECT(root='..', path=r'Common7\IDE', programs=VS2022_VS2002_DEV + VS2015_VS2012_EXP), # 2015 + '14.0Exp': _DETECT(root='..', path=r'Common7\IDE', programs=VS2022_VS2002_DEV + VS2015_VS2012_EXP), # 2015 + '12.0': _DETECT(root='..', path=r'Common7\IDE', programs=VS2022_VS2002_DEV + VS2015_VS2012_EXP), # 2013 + '12.0Exp': _DETECT(root='..', path=r'Common7\IDE', programs=VS2022_VS2002_DEV + VS2015_VS2012_EXP), # 2013 + '11.0': _DETECT(root='..', path=r'Common7\IDE', programs=VS2022_VS2002_DEV + VS2015_VS2012_EXP), # 2012 + '11.0Exp': _DETECT(root='..', path=r'Common7\IDE', programs=VS2022_VS2002_DEV + VS2015_VS2012_EXP), # 2012 + + '10.0': _DETECT(root='..', path=r'Common7\IDE', programs=VS2022_VS2002_DEV + VS2010_VS2005_EXP), # 2010 + '10.0Exp': _DETECT(root='..', path=r'Common7\IDE', programs=VS2022_VS2002_DEV + VS2010_VS2005_EXP), # 2010 + '9.0': _DETECT(root='..', path=r'Common7\IDE', programs=VS2022_VS2002_DEV + VS2010_VS2005_EXP), # 2008 + '9.0Exp': _DETECT(root='..', path=r'Common7\IDE', programs=VS2022_VS2002_DEV + VS2010_VS2005_EXP), # 2008 + '8.0': _DETECT(root='..', path=r'Common7\IDE', programs=VS2022_VS2002_DEV + VS2010_VS2005_EXP), # 2005 + '8.0Exp': _DETECT(root='..', path=r'Common7\IDE', programs=VS2022_VS2002_DEV + VS2010_VS2005_EXP), # 2005 + + '7.1': _DETECT(root='..', path=r'Common7\IDE', programs=VS2022_VS2002_DEV), # 2003 + '7.0': _DETECT(root='..', path=r'Common7\IDE', programs=VS2022_VS2002_DEV), # 2001 + + '6.0': _DETECT(root='..', path=r'Common\MSDev98\Bin', programs=VS1998_DEV), # 1998 +} def msvc_version_to_maj_min(msvc_version): msvc_version_numeric = get_msvc_version_numeric(msvc_version) @@ -780,33 +924,581 @@ def msvc_version_to_maj_min(msvc_version): raise ValueError("Unrecognized version %s (%s)" % (msvc_version,msvc_version_numeric)) from None -VSWHERE_PATHS = [os.path.join(p,'vswhere.exe') for p in [ +_VSWHERE_EXEGROUP_MSVS = [os.path.join(p, _VSWHERE_EXE) for p in [ + # For bug 3333: support default location of vswhere for both + # 64 and 32 bit windows installs. + # For bug 3542: also accommodate not being on C: drive. os.path.expandvars(r"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer"), os.path.expandvars(r"%ProgramFiles%\Microsoft Visual Studio\Installer"), +]] + +_VSWHERE_EXEGROUP_PKGMGR = [os.path.join(p, _VSWHERE_EXE) for p in [ os.path.expandvars(r"%ChocolateyInstall%\bin"), + os.path.expandvars(r"%LOCALAPPDATA%\Microsoft\WinGet\Links"), + os.path.expanduser(r"~\scoop\shims"), + os.path.expandvars(r"%SCOOP%\shims"), ]] +_VSWhereBinary = namedtuple('_VSWhereBinary', [ + 'vswhere_exe', + 'vswhere_norm', +]) + +class VSWhereBinary(_VSWhereBinary, MSVC.Util.AutoInitialize): + + _UNDEFINED_VSWHERE_BINARY = None + + _cache_vswhere_paths = {} + + @classmethod + def _initialize(cls): + vswhere_binary = cls( + vswhere_exe=None, + vswhere_norm=None, + ) + cls._UNDEFINED_VSWHERE_BINARY = vswhere_binary + for symbol in (None, ''): + cls._cache_vswhere_paths[symbol] = vswhere_binary + + @classmethod + def factory(cls, vswhere_exe): + + if not vswhere_exe: + return cls._UNDEFINED_VSWHERE_BINARY + + vswhere_binary = cls._cache_vswhere_paths.get(vswhere_exe) + if vswhere_binary: + return vswhere_binary + + vswhere_norm = MSVC.Util.normalize_path(vswhere_exe) + + vswhere_binary = cls._cache_vswhere_paths.get(vswhere_norm) + if vswhere_binary: + return vswhere_binary + + vswhere_binary = cls( + vswhere_exe=vswhere_exe, + vswhere_norm=vswhere_norm, + ) + + cls._cache_vswhere_paths[vswhere_exe] = vswhere_binary + cls._cache_vswhere_paths[vswhere_norm] = vswhere_binary + + return vswhere_binary + +class _VSWhereExecutable(MSVC.Util.AutoInitialize): + + debug_extra = None + + class _VSWhereUserPriority(enum.IntEnum): + HIGH = 0 # before msvs locations + DEFAULT = enum.auto() # after msvs locations and before pkgmgr locations + LOW = enum.auto() # after pkgmgr locations + + priority_default = _VSWhereUserPriority.DEFAULT + priority_map = {} + priority_symbols = [] + priority_indmap = {} + + for e in _VSWhereUserPriority: + for symbol in (e.name.lower(), e.name.lower().capitalize(), e.name): + priority_map[symbol] = e.value + priority_symbols.append(symbol) + priority_indmap[e.value] = e.name + + UNDEFINED_VSWHERE_BINARY = VSWhereBinary.factory(None) + + @classmethod + def reset(cls): + + cls._vswhere_exegroups = None + cls._vswhere_exegroups_user = None + cls._vswhere_exegroup_msvs = None + cls._vswhere_exegroup_pkgmgr = None + + cls.vswhere_frozen_flag = False + cls.vswhere_frozen_binary = None + + @classmethod + def _initialize(cls): + cls.debug_extra = common.debug_extra(cls) + cls.reset() + + @classmethod + def _vswhere_exegroup_binaries(cls, vswhere_exe_list, label): + vswhere_binaries = [] + for vswhere_exe in vswhere_exe_list: + if not os.path.exists(vswhere_exe): + continue + vswhere_binary = VSWhereBinary.factory(vswhere_exe) + vswhere_binaries.append(vswhere_binary) + debug( + "insert exegroup=%s, vswhere_binary=%s", + label, vswhere_binary, extra=cls.debug_extra + ) + return vswhere_binaries + + @classmethod + def _get_vswhere_exegroups(cls): + + if cls._vswhere_exegroups is None: + + cls._vswhere_exegroup_msvs = cls._vswhere_exegroup_binaries(_VSWHERE_EXEGROUP_MSVS, 'MSVS') + cls._vswhere_exegroup_pkgmgr = cls._vswhere_exegroup_binaries(_VSWHERE_EXEGROUP_PKGMGR, 'PKGMGR') + + cls._vswhere_exegroups_user = [ + [] for e in cls._VSWhereUserPriority + ] + + vswhere_exegroups = [ + cls._vswhere_exegroups_user[cls._VSWhereUserPriority.HIGH], + cls._vswhere_exegroup_msvs, + cls._vswhere_exegroups_user[cls._VSWhereUserPriority.DEFAULT], + cls._vswhere_exegroup_pkgmgr, + cls._vswhere_exegroups_user[cls._VSWhereUserPriority.LOW], + ] + + cls._vswhere_exegroups = vswhere_exegroups + + return cls._vswhere_exegroups + + @classmethod + def _get_vswhere_exegroups_user(cls): + + if cls._vswhere_exegroups_user is None: + cls._get_vswhere_exegroups() + + return cls._vswhere_exegroups_user + + @classmethod + def _vswhere_exegroup_binary(cls, exegroup): + # first vswhere binary in group or UNDEFINED_VSWHERE_BINARY + vswhere_binary = exegroup[0] if exegroup else cls.UNDEFINED_VSWHERE_BINARY + return vswhere_binary + + @classmethod + def _vswhere_current_binary(cls): + # first vswhere binary in priority order or UNDEFINED_VSWHERE_BINARY + vswhere_binary = cls.UNDEFINED_VSWHERE_BINARY + vswhere_exegroups = cls._get_vswhere_exegroups() + for exegroup in vswhere_exegroups: + binary = cls._vswhere_exegroup_binary(exegroup) + if binary == cls.UNDEFINED_VSWHERE_BINARY: + continue + vswhere_binary = binary + break + return vswhere_binary + + @classmethod + def _vswhere_all_executables(cls): + # unique vswhere executables in priority order + vswhere_exe_list = [] + vswhere_norm_set = set() + vswhere_exegroups = cls._get_vswhere_exegroups() + for exegroup in vswhere_exegroups: + for vswhere_binary in exegroup: + if vswhere_binary.vswhere_norm in vswhere_norm_set: + continue + vswhere_norm_set.add(vswhere_binary.vswhere_norm) + vswhere_exe_list.append(vswhere_binary.vswhere_exe) + return vswhere_exe_list + + @classmethod + def is_frozen(cls) -> bool: + rval = bool(cls.vswhere_frozen_flag) + return rval + + @classmethod + def freeze_vswhere_binary(cls): + if not cls.vswhere_frozen_flag: + cls.vswhere_frozen_flag = True + cls.vswhere_frozen_binary = cls._vswhere_current_binary() + debug("freeze=%s", cls.vswhere_frozen_binary, extra=cls.debug_extra) + return cls.vswhere_frozen_binary + + @classmethod + def freeze_vswhere_executable(cls): + vswhere_binary = cls.freeze_vswhere_binary() + vswhere_exe = vswhere_binary.vswhere_exe + return vswhere_exe + + @classmethod + def get_vswhere_executable(cls): + if cls.vswhere_frozen_flag: + vswhere_binary = cls.vswhere_frozen_binary + else: + vswhere_binary = cls._vswhere_current_binary() + vswhere_exe = vswhere_binary.vswhere_exe + debug("vswhere_exe=%s", repr(vswhere_exe), extra=cls.debug_extra) + return vswhere_exe + + @classmethod + def _vswhere_priority_group(cls, priority): + if not priority: + group = cls.priority_default + else: + group = cls.priority_map.get(priority) + if group is None: + msg = f'Value specified for vswhere executable priority is not supported: {priority!r}:\n' \ + f' Valid values are: {cls.priority_symbols}' + debug(f'VSWhereUserError: {msg}', extra=cls.debug_extra) + raise VSWhereUserError(msg) + return group + + @classmethod + def register_vswhere_executable(cls, vswhere_exe, priority=None): + + vswhere_binary = cls.UNDEFINED_VSWHERE_BINARY + + if not vswhere_exe: + # ignore: None or empty + return vswhere_binary + + if not os.path.exists(vswhere_exe): + msg = f'Specified vswhere executable not found: {vswhere_exe!r}.' + debug(f'VSWhereUserError: {msg}', extra=cls.debug_extra) + raise VSWhereUserError(msg) + + group = cls._vswhere_priority_group(priority) + + vswhere_binary = VSWhereBinary.factory(vswhere_exe) + + if cls.vswhere_frozen_flag: + + if vswhere_binary.vswhere_norm == cls.vswhere_frozen_binary.vswhere_norm: + # ignore: user executable == frozen executable + return vswhere_binary + + msg = 'A different vswhere execuable cannot be requested after initial detetection:\n' \ + f' initial vswhere executable: {cls.vswhere_frozen_binary.vswhere_exe!r}\n' \ + f' request vswhere executable: {vswhere_binary.vswhere_exe!r}' + + debug(f'VSWhereUserError: {msg}', extra=cls.debug_extra) + raise VSWhereUserError(msg) + + vswhere_exegroups_user = cls._get_vswhere_exegroups_user() + + exegroup = vswhere_exegroups_user[group] + group_binary = cls._vswhere_exegroup_binary(exegroup) + + if vswhere_binary.vswhere_norm == group_binary.vswhere_norm: + # ignore: user executable == exegroup[0] executable + return vswhere_binary + + exegroup.insert(0, vswhere_binary) + debug( + "insert exegroup=user[%s], vswhere_binary=%s", + cls.priority_indmap[group], vswhere_binary, extra=cls.debug_extra + ) + + return vswhere_binary + + @classmethod + def vswhere_freeze_executable(cls, vswhere_exe): + vswhere_binary = cls.register_vswhere_executable(vswhere_exe, priority='high') + frozen_binary = cls.freeze_vswhere_binary() + return frozen_binary, vswhere_binary + + @classmethod + def vswhere_freeze_env(cls, env): + + if env is None: + # no environment, VSWHERE undefined + vswhere_exe = None + write_vswhere = False + elif not env.get('VSWHERE'): + # environment, VSWHERE undefined/none/empty + vswhere_exe = None + write_vswhere = True + else: + # environment, VSWHERE defined + vswhere_exe = env.subst('$VSWHERE') + write_vswhere = False + + frozen_binary, vswhere_binary = cls.vswhere_freeze_executable(vswhere_exe) + + if write_vswhere and frozen_binary.vswhere_norm != vswhere_binary.vswhere_norm: + env['VSWHERE'] = frozen_binary.vswhere_exe + debug( + "env['VSWHERE']=%s", + repr(frozen_binary.vswhere_exe), extra=cls.debug_extra + ) + + return frozen_binary, vswhere_binary + +# external use + +def vswhere_register_executable(vswhere_exe, priority=None, freeze=False): + debug( + 'register vswhere_exe=%s, priority=%s, freeze=%s', + repr(vswhere_exe), repr(priority), repr(freeze) + ) + _VSWhereExecutable.register_vswhere_executable(vswhere_exe, priority=priority) + if freeze: + _VSWhereExecutable.freeze_vswhere_executable() + rval = _VSWhereExecutable.get_vswhere_executable() + debug('current vswhere_exe=%s, is_frozen=%s', repr(rval), _VSWhereExecutable.is_frozen()) + return vswhere_exe + +def vswhere_get_executable(): + debug('') + vswhere_exe = _VSWhereExecutable.get_vswhere_executable() + return vswhere_exe + +def vswhere_freeze_executable(): + debug('') + vswhere_exe = _VSWhereExecutable.freeze_vswhere_executable() + return vswhere_exe + +# internal use only +vswhere_freeze_env = _VSWhereExecutable.vswhere_freeze_env + def msvc_find_vswhere(): """ Find the location of vswhere """ - # For bug 3333: support default location of vswhere for both - # 64 and 32 bit windows installs. - # For bug 3542: also accommodate not being on C: drive. # NB: this gets called from testsuite on non-Windows platforms. # Whether that makes sense or not, don't break it for those. - vswhere_path = None - for pf in VSWHERE_PATHS: - if os.path.exists(pf): - vswhere_path = pf + vswhere_path = _VSWhereExecutable.get_vswhere_executable() + return vswhere_path + +_MSVCInstance = namedtuple('_MSVCInstance', [ + 'vc_path', + 'vc_version', + 'vc_version_numeric', + 'vc_version_scons', + 'vc_release', + 'vc_component_id', + 'vc_component_rank', + 'vc_component_suffix', +]) + +class MSVCInstance(_MSVCInstance): + + @staticmethod + def msvc_instances_default_order(a, b): + # vc version numeric: descending order + if a.vc_version_numeric != b.vc_version_numeric: + return 1 if a.vc_version_numeric < b.vc_version_numeric else -1 + # vc release: descending order (release, preview) + if a.vc_release != b.vc_release: + return 1 if a.vc_release < b.vc_release else -1 + # component rank: descending order + if a.vc_component_rank != b.vc_component_rank: + return 1 if a.vc_component_rank < b.vc_component_rank else -1 + return 0 + +class _VSWhere(MSVC.Util.AutoInitialize): + + debug_extra = None + + @classmethod + def reset(cls): + + cls.seen_vswhere = set() + cls.seen_root = set() + + cls.msvc_instances = [] + cls.msvc_map = {} + + @classmethod + def _initialize(cls): + cls.debug_extra = common.debug_extra(cls) + cls.reset() + + @classmethod + def _filter_vswhere_binary(cls, vswhere_binary): + + vswhere_norm = None + + if vswhere_binary.vswhere_norm not in cls.seen_vswhere: + cls.seen_vswhere.add(vswhere_binary.vswhere_norm) + vswhere_norm = vswhere_binary.vswhere_norm + + return vswhere_norm + + @classmethod + def _new_roots_discovered(cls): + if len(cls.seen_vswhere) > 1: + raise MSVCInternalError(f'vswhere discovered new msvc installations after initial detection') + + @classmethod + def _vswhere_query_json_output(cls, vswhere_exe, vswhere_args): + + vswhere_json = None + + once = True + while once: + once = False + # using break for single exit (unless exception) + + vswhere_cmd = [vswhere_exe] + vswhere_args + ['-format', 'json', '-utf8'] + debug("running: %s", vswhere_cmd, extra=cls.debug_extra) + + try: + cp = subprocess.run(vswhere_cmd, stdout=PIPE, stderr=PIPE, check=True) + except OSError as e: + errmsg = str(e) + debug("%s: %s", type(e).__name__, errmsg, extra=cls.debug_extra) + break + except Exception as e: + errmsg = str(e) + debug("%s: %s", type(e).__name__, errmsg, extra=cls.debug_extra) + raise + + if not cp.stdout: + debug("no vswhere information returned", extra=cls.debug_extra) + break + + vswhere_output = cp.stdout.decode('utf8', errors='replace') + if not vswhere_output: + debug("no vswhere information output", extra=cls.debug_extra) + break + + try: + vswhere_output_json = json.loads(vswhere_output) + except json.decoder.JSONDecodeError: + debug("json decode exception loading vswhere output", extra=cls.debug_extra) + break + + vswhere_json = vswhere_output_json break - return vswhere_path + debug( + 'vswhere_json=%s, vswhere_exe=%s', + bool(vswhere_json), repr(vswhere_exe), extra=cls.debug_extra + ) + + return vswhere_json + + @classmethod + def vswhere_update_msvc_map(cls, vswhere_exe): + + frozen_binary, vswhere_binary = _VSWhereExecutable.vswhere_freeze_executable(vswhere_exe) + + vswhere_norm = cls._filter_vswhere_binary(frozen_binary) + if not vswhere_norm: + return cls.msvc_map + + debug('vswhere_norm=%s', repr(vswhere_norm), extra=cls.debug_extra) + + vswhere_json = cls._vswhere_query_json_output( + vswhere_norm, + ['-all', '-products', '*'] + ) + + if not vswhere_json: + return cls.msvc_map + + n_instances = len(cls.msvc_instances) + + for instance in vswhere_json: + + #print(json.dumps(instance, indent=4, sort_keys=True)) + + installation_path = instance.get('installationPath') + if not installation_path or not os.path.exists(installation_path): + continue + + vc_path = os.path.join(installation_path, 'VC') + if not os.path.exists(vc_path): + continue + + vc_root = MSVC.Util.normalize_path(vc_path) + if vc_root in cls.seen_root: + continue + cls.seen_root.add(vc_root) + + installation_version = instance.get('installationVersion') + if not installation_version: + continue + + vs_major = installation_version.split('.')[0] + if not vs_major in _VSWHERE_VSMAJOR_TO_VCVERSION: + debug('ignore vs_major: %s', vs_major, extra=cls.debug_extra) + continue + + vc_version = _VSWHERE_VSMAJOR_TO_VCVERSION[vs_major] + + product_id = instance.get('productId') + if not product_id: + continue + + component_id = product_id.split('.')[-1] + if component_id not in _VSWHERE_COMPONENTID_CANDIDATES: + debug('ignore component_id: %s', component_id, extra=cls.debug_extra) + continue + + component_rank = _VSWHERE_COMPONENTID_RANKING.get(component_id,0) + if component_rank == 0: + raise MSVCInternalError(f'unknown component_rank for component_id: {component_id!r}') + + scons_suffix = _VSWHERE_COMPONENTID_SCONS_SUFFIX[component_id] + + if scons_suffix: + vc_version_scons = vc_version + scons_suffix + else: + vc_version_scons = vc_version + + is_prerelease = True if instance.get('isPrerelease', False) else False + is_release = False if is_prerelease else True + + msvc_instance = MSVCInstance( + vc_path = vc_path, + vc_version = vc_version, + vc_version_numeric = float(vc_version), + vc_version_scons = vc_version_scons, + vc_release = is_release, + vc_component_id = component_id, + vc_component_rank = component_rank, + vc_component_suffix = component_suffix, + ) + + cls.msvc_instances.append(msvc_instance) + + new_roots = bool(len(cls.msvc_instances) > n_instances) + if new_roots: + + cls.msvc_instances = sorted( + cls.msvc_instances, + key=cmp_to_key(MSVCInstance.msvc_instances_default_order) + ) + + cls.msvc_map = {} + + for msvc_instance in cls.msvc_instances: + + debug( + 'msvc instance: msvc_version=%s, is_release=%s, component_id=%s, vc_path=%s', + repr(msvc_instance.vc_version_scons), msvc_instance.vc_release, + repr(msvc_instance.vc_component_id), repr(msvc_instance.vc_path), + extra=cls.debug_extra + ) -def find_vc_pdir_vswhere(msvc_version, env=None): + key = (msvc_instance.vc_version_scons, msvc_instance.vc_release) + cls.msvc_map.setdefault(key,[]).append(msvc_instance) + + if msvc_instance.vc_version_scons == msvc_instance.vc_version: + continue + + key = (msvc_instance.vc_version, msvc_instance.vc_release) + cls.msvc_map.setdefault(key,[]).append(msvc_instance) + + cls._new_roots_discovered() + + debug( + 'new_roots=%s, msvc_instances=%s', + new_roots, len(cls.msvc_instances), extra=cls.debug_extra + ) + + return cls.msvc_map + +_cache_pdir_vswhere_queries = {} + +def _find_vc_pdir_vswhere(msvc_version, vswhere_exe): """ Find the MSVC product directory using the vswhere program. Args: msvc_version: MSVC version to search for - env: optional to look up VSWHERE variable + vswhere_exe: vswhere executable or None Returns: MSVC install dir or None @@ -815,57 +1507,178 @@ def find_vc_pdir_vswhere(msvc_version, env=None): UnsupportedVersion: if the version is not known by this file """ + global _cache_pdir_vswhere_queries + + msvc_map = _VSWhere.vswhere_update_msvc_map(vswhere_exe) + if not msvc_map: + return None + + rval = _cache_pdir_vswhere_queries.get(msvc_version, UNDEFINED) + if rval != UNDEFINED: + debug('msvc_version=%s, pdir=%s', repr(msvc_version), repr(rval)) + return rval + + if msvc_version not in _VSWHERE_SUPPORTED_VCVER: + debug("Unknown version of MSVC: %s", msvc_version) + raise UnsupportedVersion("Unknown version %s" % msvc_version) + + is_release = True + key = (msvc_version, is_release) + + msvc_instances = msvc_map.get(key, UNDEFINED) + if msvc_instances == UNDEFINED: + debug( + 'msvc instances lookup failed: msvc_version=%s, is_release=%s', + repr(msvc_version), repr(is_release) + ) + msvc_instances = [] + + save_pdir_kind = [] + + pdir = None + kind_t = None + + for msvc_instance in msvc_instances: + + vcdir = msvc_instance.vc_path + + vckind_t = MSVC.Kind.msvc_version_pdir_vswhere_kind(msvc_version, vcdir, _VCVER_KIND_DETECT[msvc_version]) + if vckind_t.skip: + if vckind_t.save: + debug('save kind: msvc_version=%s, pdir=%s', repr(msvc_version), repr(vcdir)) + save_pdir_kind.append((vcdir, vckind_t)) + else: + debug('skip kind: msvc_version=%s, pdir=%s', repr(msvc_version), repr(vcdir)) + continue + + pdir = vcdir + kind_t = vckind_t + break + + if not pdir and not kind_t: + if save_pdir_kind: + pdir, kind_t = save_pdir_kind[0] + + MSVC.Kind.msvc_version_register_kind(msvc_version, kind_t) + + debug('msvc_version=%s, pdir=%s', repr(msvc_version), repr(pdir)) + _cache_pdir_vswhere_queries[msvc_version] = pdir + + return pdir + +_cache_pdir_registry_queries = {} + +def _find_vc_pdir_registry(msvc_version): + """ Find the MSVC product directory using the registry. + + Args: + msvc_version: MSVC version to search for + + Returns: + MSVC install dir or None + + Raises: + UnsupportedVersion: if the version is not known by this file + + """ + global _cache_pdir_registry_queries + + rval = _cache_pdir_registry_queries.get(msvc_version, UNDEFINED) + if rval != UNDEFINED: + debug('msvc_version=%s, pdir=%s', repr(msvc_version), repr(rval)) + return rval + try: - vswhere_version = _VCVER_TO_VSWHERE_VER[msvc_version] + regkeys = _VCVER_TO_PRODUCT_DIR[msvc_version] except KeyError: debug("Unknown version of MSVC: %s", msvc_version) raise UnsupportedVersion("Unknown version %s" % msvc_version) from None - if env is None or not env.get('VSWHERE'): - vswhere_path = msvc_find_vswhere() - else: - vswhere_path = env.subst('$VSWHERE') - - if vswhere_path is None: - return None + save_pdir_kind = [] - debug('VSWHERE: %s', vswhere_path) - for vswhere_version_args in vswhere_version: + is_win64 = common.is_win64() - vswhere_cmd = [vswhere_path] + vswhere_version_args + ["-property", "installationPath"] + pdir = None + kind_t = None - debug("running: %s", vswhere_cmd) + root = 'Software\\' + for hkroot, key in regkeys: - # TODO: Python 3.7 - # cp = subprocess.run(vswhere_cmd, capture_output=True, check=True) # 3.7+ only - cp = subprocess.run(vswhere_cmd, stdout=PIPE, stderr=PIPE, check=True) + if not hkroot or not key: + continue - if cp.stdout: - # vswhere could return multiple lines, e.g. if Build Tools - # and {Community,Professional,Enterprise} are both installed. - # We could define a way to pick the one we prefer, but since - # this data is currently only used to make a check for existence, - # returning the first hit should be good enough. - lines = cp.stdout.decode("mbcs").splitlines() - return os.path.join(lines[0], 'VC') + if is_win64: + msregkeys = [root + 'Wow6432Node\\' + key, root + key] else: - # We found vswhere, but no install info available for this version - pass + msregkeys = [root + key] - return None + vcdir = None + for msregkey in msregkeys: + debug('trying VC registry key %s', repr(msregkey)) + try: + vcdir = common.read_reg(msregkey, hkroot) + except OSError: + continue + if vcdir: + break + if not vcdir: + debug('no VC registry key %s', repr(key)) + continue -def find_vc_pdir(env, msvc_version): - """Find the MSVC product directory for the given version. + is_vcforpython = False + + is_vsroot = False + if msvc_version == '9.0' and key.lower().endswith('\\vcforpython\\9.0\\installdir'): + # Visual C++ for Python registry key is VS installdir (root) not VC productdir + is_vsroot = True + is_vcforpython = True + elif msvc_version in ('14.0Exp', '12.0Exp', '11.0Exp') and key.lower().endswith('\\setup\\vs\\productdir'): + # Visual Studio 2015/2013/2012 Express is VS productdir (root) not VC productdir + is_vsroot = True + + if is_vsroot: + vcdir = os.path.join(vcdir, 'VC') + debug('convert vs root to vc dir: %s', repr(vcdir)) + + if not os.path.exists(vcdir): + debug('reg says dir is %s, but it does not exist. (ignoring)', repr(vcdir)) + continue + + vckind_t = MSVC.Kind.msvc_version_pdir_registry_kind(msvc_version, vcdir, _VCVER_KIND_DETECT[msvc_version], is_vcforpython) + if vckind_t.skip: + if vckind_t.save: + debug('save kind: msvc_version=%s, pdir=%s', repr(msvc_version), repr(vcdir)) + save_pdir_kind.append((vcdir, vckind_t)) + else: + debug('skip kind: msvc_version=%s, pdir=%s', repr(msvc_version), repr(vcdir)) + continue + + pdir = vcdir + kind_t = vckind_t + break - Tries to look up the path using a registry key from the table - _VCVER_TO_PRODUCT_DIR; if there is no key, calls find_vc_pdir_wshere - for help instead. + if not pdir and not kind_t: + if save_pdir_kind: + pdir, kind_t = save_pdir_kind[0] + + MSVC.Kind.msvc_version_register_kind(msvc_version, kind_t) + + debug('msvc_version=%s, pdir=%s', repr(msvc_version), repr(pdir)) + _cache_pdir_registry_queries[msvc_version] = pdir + + return pdir + +def _find_vc_pdir(msvc_version, vswhere_exe): + """Find the MSVC product directory for the given version. Args: msvc_version: str msvc version (major.minor, e.g. 10.0) + vswhere_exe: str + vswhere executable or None + Returns: str: Path found in registry, or None @@ -875,52 +1688,66 @@ def find_vc_pdir(env, msvc_version): UnsupportedVersion inherits from VisualCException. """ - root = 'Software\\' - try: - hkeys = _VCVER_TO_PRODUCT_DIR[msvc_version] - except KeyError: - debug("Unknown version of MSVC: %s", msvc_version) - raise UnsupportedVersion("Unknown version %s" % msvc_version) from None - for hkroot, key in hkeys: - try: - comps = None - if not key: - comps = find_vc_pdir_vswhere(msvc_version, env) - if not comps: - debug('no VC found for version %s', repr(msvc_version)) - raise OSError - debug('VC found: %s', repr(msvc_version)) - return comps - else: - if common.is_win64(): - try: - # ordinarily at win64, try Wow6432Node first. - comps = common.read_reg(root + 'Wow6432Node\\' + key, hkroot) - except OSError: - # at Microsoft Visual Studio for Python 2.7, value is not in Wow6432Node - pass - if not comps: - # not Win64, or Microsoft Visual Studio for Python 2.7 - comps = common.read_reg(root + key, hkroot) - except OSError: - debug('no VC registry key %s', repr(key)) - else: - if msvc_version == '9.0': - if key.lower().endswith('\\vcforpython\\9.0\\installdir'): - # Visual C++ for Python registry key is installdir (root) not productdir (vc) - comps = os.path.join(comps, 'VC') - elif msvc_version == '14.0Exp': - if key.lower().endswith('\\setup\\vs\\productdir'): - # Visual Studio 14.0 Express registry key is installdir (root) not productdir (vc) - comps = os.path.join(comps, 'VC') - debug('found VC in registry: %s', comps) - if os.path.exists(comps): - return comps - else: - debug('reg says dir is %s, but it does not exist. (ignoring)', comps) + if msvc_version in _VSWHERE_SUPPORTED_VCVER: + + pdir = _find_vc_pdir_vswhere(msvc_version, vswhere_exe) + if pdir: + debug('VC found: %s, dir=%s', repr(msvc_version), repr(pdir)) + return pdir + + elif msvc_version in _VCVER_TO_PRODUCT_DIR: + + pdir = _find_vc_pdir_registry(msvc_version) + if pdir: + debug('VC found: %s, dir=%s', repr(msvc_version), repr(pdir)) + return pdir + + else: + + debug("Unknown version of MSVC: %s", repr(msvc_version)) + raise UnsupportedVersion("Unknown version %s" % repr(msvc_version)) from None + + debug('no VC found for version %s', repr(msvc_version)) return None +def find_vc_pdir(msvc_version, env=None): + """Find the MSVC product directory for the given version. + + Args: + msvc_version: str + msvc version (major.minor, e.g. 10.0) + env: + optional to look up VSWHERE variable + + Returns: + str: Path found in registry, or None + + Raises: + UnsupportedVersion: if the version is not known by this file. + + UnsupportedVersion inherits from VisualCException. + + """ + + frozen_binary, _ = _VSWhereExecutable.vswhere_freeze_env(env) + + pdir = _find_vc_pdir(msvc_version, frozen_binary.vswhere_exe) + debug('pdir=%s', repr(pdir)) + + return pdir + +# register find_vc_pdir function with Kind routines +# in case of unknown msvc_version (just in case) +MSVC.Kind.register_msvc_version_pdir_func(find_vc_pdir) + +def _reset_vc_pdir(): + debug('reset pdir caches') + global _cache_pdir_vswhere_queries + global _cache_pdir_registry_queries + _cache_pdir_vswhere_queries = {} + _cache_pdir_registry_queries = {} + def find_batch_file(msvc_version, host_arch, target_arch, pdir): """ Find the location of the batch script which should set up the compiler @@ -940,6 +1767,7 @@ def find_batch_file(msvc_version, host_arch, target_arch, pdir): arg = '' vcdir = None clexe = None + depbat = None if vernum_int >= 143: # 14.3 (VS2022) and later @@ -953,15 +1781,26 @@ def find_batch_file(msvc_version, host_arch, target_arch, pdir): batfile, _ = _LE2019_HOST_TARGET_BATCHFILE_CLPATHCOMPS[(host_arch, target_arch)] batfilename = os.path.join(batfiledir, batfile) vcdir = pdir - elif 141 > vernum_int >= 80: - # 14.0 (VS2015) to 8.0 (VS2005) - arg, cl_path_comps = _LE2015_HOST_TARGET_BATCHARG_CLPATHCOMPS[(host_arch, target_arch)] + elif 141 > vernum_int >= 100: + # 14.0 (VS2015) to 10.0 (VS2010) + arg, batfile, cl_path_comps = _LE2015_HOST_TARGET_BATCHARG_BATCHFILE_CLPATHCOMPS[(host_arch, target_arch)] batfilename = os.path.join(pdir, "vcvarsall.bat") - if msvc_version == '9.0' and not os.path.exists(batfilename): - # Visual C++ for Python batch file is in installdir (root) not productdir (vc) - batfilename = os.path.normpath(os.path.join(pdir, os.pardir, "vcvarsall.bat")) - # Visual C++ for Python sdk batch files do not point to the VCForPython installation + depbat = os.path.join(pdir, *cl_path_comps, batfile) + clexe = os.path.join(pdir, *cl_path_comps, _CL_EXE_NAME) + elif 100 > vernum_int >= 80: + # 9.0 (VS2008) to 8.0 (VS2005) + arg, batfile, cl_path_comps = _LE2008_HOST_TARGET_BATCHARG_BATCHFILE_CLPATHCOMPS[(host_arch, target_arch)] + if vernum_int == 90 and MSVC.Kind.msvc_version_is_vcforpython(msvc_version): + # 9.0 (VS2008) Visual C++ for Python: + # sdk batch files do not point to the VCForPython installation + # vcvarsall batch file is in installdir not productdir (e.g., vc\..\vcvarsall.bat) + # dependency batch files are not called from vcvarsall.bat sdk_pdir = None + batfilename = os.path.join(pdir, os.pardir, "vcvarsall.bat") + depbat = None + else: + batfilename = os.path.join(pdir, "vcvarsall.bat") + depbat = os.path.join(pdir, *cl_path_comps, batfile) clexe = os.path.join(pdir, *cl_path_comps, _CL_EXE_NAME) else: # 80 > vernum_int # 7.1 (VS2003) and earlier @@ -974,7 +1813,11 @@ def find_batch_file(msvc_version, host_arch, target_arch, pdir): batfilename = None if clexe and not os.path.exists(clexe): - debug("cl.exe not found: %s", clexe) + debug("%s not found: %s", _CL_EXE_NAME, clexe) + batfilename = None + + if depbat and not os.path.exists(depbat): + debug("dependency batch file not found: %s", depbat) batfilename = None return batfilename, arg, vcdir, sdk_pdir @@ -999,16 +1842,23 @@ def find_batch_file_sdk(host_arch, target_arch, sdk_pdir): return None __INSTALLED_VCS_RUN = None + +def _reset_installed_vcs(): + global __INSTALLED_VCS_RUN + debug('reset __INSTALLED_VCS_RUN') + __INSTALLED_VCS_RUN = None + _VC_TOOLS_VERSION_FILE_PATH = ['Auxiliary', 'Build', 'Microsoft.VCToolsVersion.default.txt'] _VC_TOOLS_VERSION_FILE = os.sep.join(_VC_TOOLS_VERSION_FILE_PATH) -def _check_cl_exists_in_vc_dir(env, vc_dir, msvc_version) -> bool: - """Return status of finding a cl.exe to use. +def _check_files_exist_in_vc_dir(env, vc_dir, msvc_version) -> bool: + """Return status of finding batch file and cl.exe to use. - Locates cl in the vc_dir depending on TARGET_ARCH, HOST_ARCH and the - msvc version. TARGET_ARCH and HOST_ARCH can be extracted from the - passed env, unless the env is None, in which case the native platform is - assumed for the host and all associated targets. + Locates required vcvars batch files and cl in the vc_dir depending on + TARGET_ARCH, HOST_ARCH and the msvc version. TARGET_ARCH and HOST_ARCH + can be extracted from the passed env, unless the env is None, in which + case the native platform is assumed for the host and all associated + targets. Args: env: Environment @@ -1067,33 +1917,72 @@ def _check_cl_exists_in_vc_dir(env, vc_dir, msvc_version) -> bool: debug('unsupported host/target platform combo: (%s,%s)', host_platform, target_platform) continue - _, cl_path_comps = batchfile_clpathcomps + batfile, cl_path_comps = batchfile_clpathcomps + + batfile_path = os.path.join(vc_dir, "Auxiliary", "Build", batfile) + if not os.path.exists(batfile_path): + debug("batch file not found: %s", batfile_path) + continue + cl_path = os.path.join(vc_dir, 'Tools', 'MSVC', vc_specific_version, *cl_path_comps, _CL_EXE_NAME) - debug('checking for %s at %s', _CL_EXE_NAME, cl_path) + if not os.path.exists(cl_path): + debug("%s not found: %s", _CL_EXE_NAME, cl_path) + continue - if os.path.exists(cl_path): - debug('found %s!', _CL_EXE_NAME) - return True + debug('%s found: %s', _CL_EXE_NAME, cl_path) + return True elif 141 > vernum_int >= 80: # 14.0 (VS2015) to 8.0 (VS2005) + if vernum_int >= 100: + # 14.0 (VS2015) to 10.0 (VS2010) + host_target_batcharg_batchfile_clpathcomps = _LE2015_HOST_TARGET_BATCHARG_BATCHFILE_CLPATHCOMPS + else: + # 9.0 (VS2008) to 8.0 (VS2005) + host_target_batcharg_batchfile_clpathcomps = _LE2008_HOST_TARGET_BATCHARG_BATCHFILE_CLPATHCOMPS + + if vernum_int == 90 and MSVC.Kind.msvc_version_is_vcforpython(msvc_version): + # 9.0 (VS2008) Visual C++ for Python: + # vcvarsall batch file is in installdir not productdir (e.g., vc\..\vcvarsall.bat) + # dependency batch files are not called from vcvarsall.bat + batfile_path = os.path.join(vc_dir, os.pardir, "vcvarsall.bat") + check_depbat = False + else: + batfile_path = os.path.join(vc_dir, "vcvarsall.bat") + check_depbat = True + + if not os.path.exists(batfile_path): + debug("batch file not found: %s", batfile_path) + return False + for host_platform, target_platform in host_target_list: debug('host platform %s, target platform %s for version %s', host_platform, target_platform, msvc_version) - batcharg_clpathcomps = _LE2015_HOST_TARGET_BATCHARG_CLPATHCOMPS.get((host_platform, target_platform), None) - if batcharg_clpathcomps is None: + batcharg_batchfile_clpathcomps = host_target_batcharg_batchfile_clpathcomps.get( + (host_platform, target_platform), None + ) + + if batcharg_batchfile_clpathcomps is None: debug('unsupported host/target platform combo: (%s,%s)', host_platform, target_platform) continue - _, cl_path_comps = batcharg_clpathcomps + _, batfile, cl_path_comps = batcharg_batchfile_clpathcomps + + if check_depbat: + batfile_path = os.path.join(vc_dir, *cl_path_comps, batfile) + if not os.path.exists(batfile_path): + debug("batch file not found: %s", batfile_path) + continue + cl_path = os.path.join(vc_dir, *cl_path_comps, _CL_EXE_NAME) - debug('checking for %s at %s', _CL_EXE_NAME, cl_path) + if not os.path.exists(cl_path): + debug("%s not found: %s", _CL_EXE_NAME, cl_path) + continue - if os.path.exists(cl_path): - debug('found %s', _CL_EXE_NAME) - return True + debug('%s found: %s', _CL_EXE_NAME, cl_path) + return True elif 80 > vernum_int >= 60: # 7.1 (VS2003) to 6.0 (VS6) @@ -1110,7 +1999,7 @@ def _check_cl_exists_in_vc_dir(env, vc_dir, msvc_version) -> bool: for cl_dir in cl_dirs: cl_path = os.path.join(cl_root, cl_dir, _CL_EXE_NAME) if os.path.exists(cl_path): - debug('%s found %s', _CL_EXE_NAME, cl_path) + debug('%s found: %s', _CL_EXE_NAME, cl_path) return True return False @@ -1123,6 +2012,8 @@ def _check_cl_exists_in_vc_dir(env, vc_dir, msvc_version) -> bool: def get_installed_vcs(env=None): global __INSTALLED_VCS_RUN + _VSWhereExecutable.vswhere_freeze_env(env) + if __INSTALLED_VCS_RUN is not None: return __INSTALLED_VCS_RUN @@ -1138,17 +2029,17 @@ def get_installed_vcs(env=None): for ver in _VCVER: debug('trying to find VC %s', ver) try: - VC_DIR = find_vc_pdir(env, ver) + VC_DIR = find_vc_pdir(ver, env) if VC_DIR: debug('found VC %s', ver) - if _check_cl_exists_in_vc_dir(env, VC_DIR, ver): + if _check_files_exist_in_vc_dir(env, VC_DIR, ver): installed_versions.append(ver) else: debug('no compiler found %s', ver) else: debug('return None for ver %s', ver) - except (MSVCUnsupportedTargetArch, MSVCUnsupportedHostArch): - # Allow this exception to propagate further as it should cause + except (MSVCUnsupportedTargetArch, MSVCUnsupportedHostArch, VSWhereUserError): + # Allow these exceptions to propagate further as it should cause # SCons to exit with an error code raise except VisualCException as e: @@ -1164,8 +2055,10 @@ def get_installed_vcs(env=None): def reset_installed_vcs() -> None: """Make it try again to find VC. This is just for the tests.""" - global __INSTALLED_VCS_RUN - __INSTALLED_VCS_RUN = None + _reset_installed_vcs() + _reset_vc_pdir() + _VSWhereExecutable.reset() + _VSWhere.reset() MSVC._reset() def msvc_default_version(env=None): @@ -1341,7 +2234,7 @@ def msvc_find_valid_batch_script(env, version): # Find the product directory pdir = None try: - pdir = find_vc_pdir(env, version) + pdir = find_vc_pdir(version, env) except UnsupportedVersion: # Unsupported msvc version (raise MSVCArgumentError?) pass @@ -1386,6 +2279,13 @@ def msvc_find_valid_batch_script(env, version): if not vc_script: continue + if not target_platform and MSVC.ScriptArguments.msvc_script_arguments_has_uwp(env): + # no target arch specified and is a store/uwp build + if MSVC.Kind.msvc_version_skip_uwp_target(env, version): + # store/uwp may not be supported for all express targets (prevent constraint error) + debug('skip uwp target arch: version=%s, target=%s', repr(version), repr(target_arch)) + continue + # Try to use the located batch file for this host/target platform combo arg = MSVC.ScriptArguments.msvc_script_arguments(env, version, vc_dir, arg) debug('trying vc_script:%s, vc_script_args:%s', repr(vc_script), arg) @@ -1483,6 +2383,9 @@ def get_use_script_use_settings(env): def msvc_setup_env(env): debug('called') + + _VSWhereExecutable.vswhere_freeze_env(env) + version = get_default_version(env) if version is None: if not msvc_setup_env_user(env): @@ -1616,13 +2519,17 @@ def msvc_sdk_versions(version=None, msvc_uwp_app: bool=False): msg = f'Unsupported version {version!r}' raise MSVCArgumentError(msg) - rval = MSVC.WinSDK.get_msvc_sdk_version_list(version, msvc_uwp_app) + rval = MSVC.WinSDK.get_msvc_sdk_version_list(version_def.msvc_version, msvc_uwp_app) return rval -def msvc_toolset_versions(msvc_version=None, full: bool=True, sxs: bool=False): - debug('msvc_version=%s, full=%s, sxs=%s', repr(msvc_version), repr(full), repr(sxs)) +def msvc_toolset_versions(msvc_version=None, full: bool=True, sxs: bool=False, vswhere_exe=None): + debug( + 'msvc_version=%s, full=%s, sxs=%s, vswhere_exe=%s', + repr(msvc_version), repr(full), repr(sxs), repr(vswhere_exe) + ) + + _VSWhereExecutable.vswhere_freeze_executable(vswhere_exe) - env = None rval = [] if not msvc_version: @@ -1636,7 +2543,7 @@ def msvc_toolset_versions(msvc_version=None, full: bool=True, sxs: bool=False): msg = f'Unsupported msvc version {msvc_version!r}' raise MSVCArgumentError(msg) - vc_dir = find_vc_pdir(env, msvc_version) + vc_dir = _find_vc_pdir(msvc_version, vswhere_exe) if not vc_dir: debug('VC folder not found for version %s', repr(msvc_version)) return rval @@ -1644,10 +2551,11 @@ def msvc_toolset_versions(msvc_version=None, full: bool=True, sxs: bool=False): rval = MSVC.ScriptArguments._msvc_toolset_versions_internal(msvc_version, vc_dir, full=full, sxs=sxs) return rval -def msvc_toolset_versions_spectre(msvc_version=None): - debug('msvc_version=%s', repr(msvc_version)) +def msvc_toolset_versions_spectre(msvc_version=None, vswhere_exe=None): + debug('msvc_version=%s, vswhere_exe=%s', repr(msvc_version), repr(vswhere_exe)) + + _VSWhereExecutable.vswhere_freeze_executable(vswhere_exe) - env = None rval = [] if not msvc_version: @@ -1661,7 +2569,7 @@ def msvc_toolset_versions_spectre(msvc_version=None): msg = f'Unsupported msvc version {msvc_version!r}' raise MSVCArgumentError(msg) - vc_dir = find_vc_pdir(env, msvc_version) + vc_dir = _find_vc_pdir(msvc_version, vswhere_exe) if not vc_dir: debug('VC folder not found for version %s', repr(msvc_version)) return rval @@ -1669,30 +2577,91 @@ def msvc_toolset_versions_spectre(msvc_version=None): rval = MSVC.ScriptArguments._msvc_toolset_versions_spectre_internal(msvc_version, vc_dir) return rval -def msvc_query_version_toolset(version=None, prefer_newest: bool=True): +_InstalledVersionToolset = namedtuple('_InstalledVersionToolset', [ + 'msvc_version_def', + 'toolset_version_def', +]) + +_InstalledVCSToolsetsComponents = namedtuple('_InstalledVCSToolsetComponents', [ + 'sxs_map', + 'toolset_vcs', + 'msvc_toolset_component_defs', +]) + +def get_installed_vcs_toolsets_components(vswhere_exe=None): + debug('vswhere_exe=%s', repr(vswhere_exe)) + + _VSWhereExecutable.vswhere_freeze_executable(vswhere_exe) + + sxs_map = {} + toolset_vcs = set() + msvc_toolset_component_defs = [] + + vcs = get_installed_vcs() + for msvc_version in vcs: + + vc_dir = _find_vc_pdir(msvc_version, vswhere_exe) + if not vc_dir: + continue + + msvc_version_def = MSVC.Util.msvc_version_components(msvc_version) + toolset_vcs.add(msvc_version_def.msvc_version) + + if msvc_version_def.msvc_vernum > 14.0: + + toolsets_sxs, toolsets_full = MSVC.ScriptArguments._msvc_version_toolsets_internal( + msvc_version, vc_dir + ) + + debug('msvc_version=%s, toolset_sxs=%s', repr(msvc_version), repr(toolsets_sxs)) + sxs_map.update(toolsets_sxs) + + else: + + toolsets_full = [msvc_version_def.msvc_verstr] + + for toolset_version in toolsets_full: + debug('msvc_version=%s, toolset_version=%s', repr(msvc_version), repr(toolset_version)) + + toolset_version_def = MSVC.Util.msvc_extended_version_components(toolset_version) + if not toolset_version_def: + continue + toolset_vcs.add(toolset_version_def.msvc_version) + + rval = _InstalledVersionToolset( + msvc_version_def=msvc_version_def, + toolset_version_def=toolset_version_def, + ) + msvc_toolset_component_defs.append(rval) + + installed_vcs_toolsets_components = _InstalledVCSToolsetsComponents( + sxs_map=sxs_map, + toolset_vcs=toolset_vcs, + msvc_toolset_component_defs=msvc_toolset_component_defs, + ) + + return installed_vcs_toolsets_components + +def msvc_query_version_toolset(version=None, prefer_newest: bool=True, vswhere_exe=None): """ - Returns an msvc version and a toolset version given a version + Return an msvc version and a toolset version given a version specification. This is an EXPERIMENTAL proxy for using a toolset version to perform msvc instance selection. This function will be removed when toolset version is taken into account during msvc instance selection. - Search for an installed Visual Studio instance that supports the - specified version. + This function searches for an installed Visual Studio instance that + contains the requested version. A component suffix (e.g., Exp) is not + supported for toolset version specifications (e.g., 14.39). - When the specified version contains a component suffix (e.g., Exp), - the msvc version is returned and the toolset version is None. No - search if performed. + An MSVCArgumentError is raised when argument validation fails. An + MSVCToolsetVersionNotFound exception is raised when the requested + version is not found. - When the specified version does not contain a component suffix, the - version is treated as a toolset version specification. A search is - performed for the first msvc instance that contains the toolset - version. - - Only Visual Studio 2017 and later support toolset arguments. For - Visual Studio 2015 and earlier, the msvc version is returned and - the toolset version is None. + For Visual Studio 2015 and earlier, the msvc version is returned and + the toolset version is None. For Visual Studio 2017 and later, the + selected toolset version is returned. Args: @@ -1706,6 +2675,9 @@ def msvc_query_version_toolset(version=None, prefer_newest: bool=True): the native Visual Studio instance is not detected, prefer newer Visual Studio instances. + vswhere_exe: str + A vswhere executable location or None. + Returns: tuple: A tuple containing the msvc version and the msvc toolset version. The msvc toolset version may be None. @@ -1714,112 +2686,140 @@ def msvc_query_version_toolset(version=None, prefer_newest: bool=True): MSVCToolsetVersionNotFound: when the specified version is not found. MSVCArgumentError: when argument validation fails. """ - debug('version=%s, prefer_newest=%s', repr(version), repr(prefer_newest)) + debug( + 'version=%s, prefer_newest=%s, vswhere_exe=%s', + repr(version), repr(prefer_newest), repr(vswhere_exe) + ) + + _VSWhereExecutable.vswhere_freeze_executable(vswhere_exe) - env = None msvc_version = None msvc_toolset_version = None - if not version: - version = msvc_default_version() + with MSVC.Policy.msvc_notfound_policy_contextmanager('suppress'): - if not version: - debug('no msvc versions detected') - return msvc_version, msvc_toolset_version + vcs = get_installed_vcs() - version_def = MSVC.Util.msvc_extended_version_components(version) + if not version: + version = msvc_default_version() - if not version_def: - msg = f'Unsupported msvc version {version!r}' - raise MSVCArgumentError(msg) + if not version: + msg = f'No versions of the MSVC compiler were found' + debug(f'MSVCToolsetVersionNotFound: {msg}') + raise MSVCToolsetVersionNotFound(msg) - if version_def.msvc_suffix: - if version_def.msvc_verstr != version_def.msvc_toolset_version: - # toolset version with component suffix - msg = f'Unsupported toolset version {version!r}' - raise MSVCArgumentError(msg) + version_def = MSVC.Util.msvc_extended_version_components(version) - if version_def.msvc_vernum > 14.0: - # VS2017 and later - force_toolset_msvc_version = False - else: - # VS2015 and earlier - force_toolset_msvc_version = True - extended_version = version_def.msvc_verstr + '0.00000' - if not extended_version.startswith(version_def.msvc_toolset_version): - # toolset not equivalent to msvc version - msg = 'Unsupported toolset version {} (expected {})'.format( - repr(version), repr(extended_version) - ) + if not version_def: + msg = f'Unsupported MSVC version {version!r}' + debug(f'MSVCArgumentError: {msg}') raise MSVCArgumentError(msg) - msvc_version = version_def.msvc_version + msvc_version = version_def.msvc_version - if msvc_version not in MSVC.Config.MSVC_VERSION_TOOLSET_SEARCH_MAP: - # VS2013 and earlier - debug( - 'ignore: msvc_version=%s, msvc_toolset_version=%s', - repr(msvc_version), repr(msvc_toolset_version) - ) - return msvc_version, msvc_toolset_version + if version_def.msvc_suffix: - if force_toolset_msvc_version: - query_msvc_toolset_version = version_def.msvc_verstr - else: - query_msvc_toolset_version = version_def.msvc_toolset_version + if version_def.msvc_verstr != version_def.msvc_toolset_version: + # toolset version with component suffix + msg = f'Unsupported MSVC toolset version {version!r}' + debug(f'MSVCArgumentError: {msg}') + raise MSVCArgumentError(msg) - if prefer_newest: - query_version_list = MSVC.Config.MSVC_VERSION_TOOLSET_SEARCH_MAP[msvc_version] - else: - query_version_list = MSVC.Config.MSVC_VERSION_TOOLSET_DEFAULTS_MAP[msvc_version] + \ - MSVC.Config.MSVC_VERSION_TOOLSET_SEARCH_MAP[msvc_version] + if msvc_version not in vcs: + msg = f'MSVC version {msvc_version!r} not found' + debug(f'MSVCToolsetVersionNotFound: {msg}') + raise MSVCToolsetVersionNotFound(msg) + + if msvc_version not in MSVC.Config.MSVC_VERSION_TOOLSET_SEARCH_MAP: + debug( + 'suffix: msvc_version=%s, msvc_toolset_version=%s', + repr(msvc_version), repr(msvc_toolset_version) + ) + return msvc_version, msvc_toolset_version - seen_msvc_version = set() - for query_msvc_version in query_version_list: + if version_def.msvc_vernum > 14.0: + # VS2017 and later + force_toolset_msvc_version = False + else: + # VS2015 and earlier + force_toolset_msvc_version = True + extended_version = version_def.msvc_verstr + '0.00000' + if not extended_version.startswith(version_def.msvc_toolset_version): + # toolset not equivalent to msvc version + msg = 'Unsupported MSVC toolset version {} (expected {})'.format( + repr(version), repr(extended_version) + ) + debug(f'MSVCArgumentError: {msg}') + raise MSVCArgumentError(msg) - if query_msvc_version in seen_msvc_version: - continue - seen_msvc_version.add(query_msvc_version) + if msvc_version not in MSVC.Config.MSVC_VERSION_TOOLSET_SEARCH_MAP: - vc_dir = find_vc_pdir(env, query_msvc_version) - if not vc_dir: - continue + # VS2013 and earlier + + if msvc_version not in vcs: + msg = f'MSVC version {version!r} not found' + debug(f'MSVCToolsetVersionNotFound: {msg}') + raise MSVCToolsetVersionNotFound(msg) - if query_msvc_version.startswith('14.0'): - # VS2015 does not support toolset version argument - msvc_toolset_version = None debug( - 'found: msvc_version=%s, msvc_toolset_version=%s', - repr(query_msvc_version), repr(msvc_toolset_version) + 'earlier: msvc_version=%s, msvc_toolset_version=%s', + repr(msvc_version), repr(msvc_toolset_version) ) - return query_msvc_version, msvc_toolset_version + return msvc_version, msvc_toolset_version - try: - toolset_vcvars = MSVC.ScriptArguments._msvc_toolset_internal(query_msvc_version, query_msvc_toolset_version, vc_dir) - if toolset_vcvars: - msvc_toolset_version = toolset_vcvars + if force_toolset_msvc_version: + query_msvc_toolset_version = version_def.msvc_verstr + else: + query_msvc_toolset_version = version_def.msvc_toolset_version + + if prefer_newest: + query_version_list = MSVC.Config.MSVC_VERSION_TOOLSET_SEARCH_MAP[msvc_version] + else: + query_version_list = MSVC.Config.MSVC_VERSION_TOOLSET_DEFAULTS_MAP[msvc_version] + \ + MSVC.Config.MSVC_VERSION_TOOLSET_SEARCH_MAP[msvc_version] + + seen_msvc_version = set() + for query_msvc_version in query_version_list: + + if query_msvc_version in seen_msvc_version: + continue + seen_msvc_version.add(query_msvc_version) + + vc_dir = _find_vc_pdir(query_msvc_version, vswhere_exe) + if not vc_dir: + continue + + if query_msvc_version.startswith('14.0'): + # VS2015 does not support toolset version argument + msvc_toolset_version = None debug( 'found: msvc_version=%s, msvc_toolset_version=%s', repr(query_msvc_version), repr(msvc_toolset_version) ) return query_msvc_version, msvc_toolset_version - except MSVCToolsetVersionNotFound: - pass + try: + toolset_vcvars = MSVC.ScriptArguments._msvc_toolset_internal(query_msvc_version, query_msvc_toolset_version, vc_dir) + if toolset_vcvars: + msvc_toolset_version = toolset_vcvars + debug( + 'found: msvc_version=%s, msvc_toolset_version=%s', + repr(query_msvc_version), repr(msvc_toolset_version) + ) + return query_msvc_version, msvc_toolset_version + + except MSVCToolsetVersionNotFound: + pass - msvc_toolset_version = query_msvc_toolset_version + msvc_toolset_version = query_msvc_toolset_version debug( 'not found: msvc_version=%s, msvc_toolset_version=%s', repr(msvc_version), repr(msvc_toolset_version) ) - if version_def.msvc_verstr == msvc_toolset_version: - msg = f'MSVC version {version!r} was not found' - MSVC.Policy.msvc_notfound_handler(None, msg) - return msvc_version, msvc_toolset_version - msg = f'MSVC toolset version {version!r} not found' + debug(f'MSVCToolsetVersionNotFound: {msg}') raise MSVCToolsetVersionNotFound(msg) diff --git a/SCons/Tool/MSCommon/vcTests.py b/SCons/Tool/MSCommon/vcTests.py index 9c2bd0ac8c..e1146e89be 100644 --- a/SCons/Tool/MSCommon/vcTests.py +++ b/SCons/Tool/MSCommon/vcTests.py @@ -57,12 +57,22 @@ def testDefaults(self) -> None: Verify that msvc_find_vswhere() find's files in the specified paths """ # import pdb; pdb.set_trace() - vswhere_dirs = [os.path.splitdrive(p)[1] for p in SCons.Tool.MSCommon.vc.VSWHERE_PATHS] + test_all_dirs = [] + base_dir = test.workpath('fake_vswhere') + + vswhere_dirs = [os.path.splitdrive(p)[1] for p in SCons.Tool.MSCommon.vc._VSWHERE_EXEGROUP_MSVS] + test_vswhere_dirs = [os.path.join(base_dir,d[1:]) for d in vswhere_dirs] + SCons.Tool.MSCommon.vc._VSWHERE_EXEGROUP_MSVS = test_vswhere_dirs + test_all_dirs += test_vswhere_dirs + + vswhere_dirs = [os.path.splitdrive(p)[1] for p in SCons.Tool.MSCommon.vc._VSWHERE_EXEGROUP_PKGMGR] test_vswhere_dirs = [os.path.join(base_dir,d[1:]) for d in vswhere_dirs] + SCons.Tool.MSCommon.vc._VSWHERE_EXEGROUP_PKGMGR = test_vswhere_dirs + test_all_dirs += test_vswhere_dirs - SCons.Tool.MSCommon.vc.VSWHERE_PATHS = test_vswhere_dirs - for vsw in test_vswhere_dirs: + for vsw in test_all_dirs: + SCons.Tool.MSCommon.vc._VSWhereExecutable.reset() VswhereTestCase._createVSWhere(vsw) find_path = SCons.Tool.MSCommon.vc.msvc_find_vswhere() self.assertTrue(vsw == find_path, "Didn't find vswhere in %s found in %s" % (vsw, find_path)) @@ -75,7 +85,7 @@ def testDefaults(self) -> None: class MSVcTestCase(unittest.TestCase): @staticmethod - def _createDummyCl(path, add_bin: bool=True) -> None: + def _createDummyFile(path, filename, add_bin: bool=True) -> None: """ Creates a dummy cl.exe in the correct directory. It will create all missing parent directories as well @@ -94,7 +104,7 @@ def _createDummyCl(path, add_bin: bool=True) -> None: if create_path and not os.path.isdir(create_path): os.makedirs(create_path) - create_this = os.path.join(create_path,'cl.exe') + create_this = os.path.join(create_path, filename) # print("Creating: %s"%create_this) with open(create_this,'w') as ct: @@ -105,18 +115,10 @@ def runTest(self) -> None: """ Check that all proper HOST_PLATFORM and TARGET_PLATFORM are handled. Verify that improper HOST_PLATFORM and/or TARGET_PLATFORM are properly handled. - by SCons.Tool.MSCommon.vc._check_cl_exists_in_vc_dir() + by SCons.Tool.MSCommon.vc._check_files_exist_in_vc_dir() """ - check = SCons.Tool.MSCommon.vc._check_cl_exists_in_vc_dir - - env={'TARGET_ARCH':'x86'} - _, clpathcomps = SCons.Tool.MSCommon.vc._LE2015_HOST_TARGET_BATCHARG_CLPATHCOMPS[('x86','x86')] - path = os.path.join('.', *clpathcomps) - MSVcTestCase._createDummyCl(path, add_bin=False) - - # print("retval:%s"%check(env, '.', '8.0')) - + check = SCons.Tool.MSCommon.vc._check_files_exist_in_vc_dir # Setup for 14.1 (VS2017) and later tests @@ -131,122 +133,70 @@ def runTest(self) -> None: except IOError as e: print("Failed trying to write :%s :%s" % (tools_version_file, e)) - # Test 14.3 (VS2022) and later vc_ge2022_list = SCons.Tool.MSCommon.vc._GE2022_HOST_TARGET_CFG.all_pairs - for host, target in vc_ge2022_list: batfile, clpathcomps = SCons.Tool.MSCommon.vc._GE2022_HOST_TARGET_BATCHFILE_CLPATHCOMPS[(host,target)] # print("GE 14.3 Got: (%s, %s) -> (%s, %s)"%(host,target,batfile,clpathcomps)) env={'TARGET_ARCH':target, 'HOST_ARCH':host} + path = os.path.join('.', "Auxiliary", "Build", batfile) + MSVcTestCase._createDummyFile(path, batfile, add_bin=False) path = os.path.join('.', 'Tools', 'MSVC', MS_TOOLS_VERSION, *clpathcomps) - MSVcTestCase._createDummyCl(path, add_bin=False) + MSVcTestCase._createDummyFile(path, 'cl.exe', add_bin=False) result=check(env, '.', '14.3') # print("for:(%s, %s) got :%s"%(host, target, result)) self.assertTrue(result, "Checking host: %s target: %s" % (host, target)) - # Now test bogus value for HOST_ARCH - env={'TARGET_ARCH':'x86', 'HOST_ARCH':'GARBAGE'} - try: - result=check(env, '.', '14.3') - # print("for:%s got :%s"%(env, result)) - self.assertFalse(result, "Did not fail with bogus HOST_ARCH host: %s target: %s" % (env['HOST_ARCH'], env['TARGET_ARCH'])) - except MSVCUnsupportedHostArch: - pass - else: - self.fail('Did not fail when HOST_ARCH specified as: %s' % env['HOST_ARCH']) - - # Now test bogus value for TARGET_ARCH - env={'TARGET_ARCH':'GARBAGE', 'HOST_ARCH':'x86'} - try: - result=check(env, '.', '14.3') - # print("for:%s got :%s"%(env, result)) - self.assertFalse(result, "Did not fail with bogus TARGET_ARCH host: %s target: %s" % (env['HOST_ARCH'], env['TARGET_ARCH'])) - except MSVCUnsupportedTargetArch: - pass - else: - self.fail('Did not fail when TARGET_ARCH specified as: %s' % env['TARGET_ARCH']) - # Test 14.2 (VS2019) to 14.1 (VS2017) versions vc_le2019_list = SCons.Tool.MSCommon.vc._LE2019_HOST_TARGET_CFG.all_pairs - for host, target in vc_le2019_list: batfile, clpathcomps = SCons.Tool.MSCommon.vc._LE2019_HOST_TARGET_BATCHFILE_CLPATHCOMPS[(host,target)] # print("LE 14.2 Got: (%s, %s) -> (%s, %s)"%(host,target,batfile,clpathcomps)) env={'TARGET_ARCH':target, 'HOST_ARCH':host} path = os.path.join('.', 'Tools', 'MSVC', MS_TOOLS_VERSION, *clpathcomps) - MSVcTestCase._createDummyCl(path, add_bin=False) + MSVcTestCase._createDummyFile(path, 'cl.exe', add_bin=False) result=check(env, '.', '14.1') # print("for:(%s, %s) got :%s"%(host, target, result)) self.assertTrue(result, "Checking host: %s target: %s" % (host, target)) - # Now test bogus value for HOST_ARCH - env={'TARGET_ARCH':'x86', 'HOST_ARCH':'GARBAGE'} - try: - result=check(env, '.', '14.1') - # print("for:%s got :%s"%(env, result)) - self.assertFalse(result, "Did not fail with bogus HOST_ARCH host: %s target: %s" % (env['HOST_ARCH'], env['TARGET_ARCH'])) - except MSVCUnsupportedHostArch: - pass - else: - self.fail('Did not fail when HOST_ARCH specified as: %s' % env['HOST_ARCH']) - - # Now test bogus value for TARGET_ARCH - env={'TARGET_ARCH':'GARBAGE', 'HOST_ARCH':'x86'} - try: - result=check(env, '.', '14.1') - # print("for:%s got :%s"%(env, result)) - self.assertFalse(result, "Did not fail with bogus TARGET_ARCH host: %s target: %s" % (env['HOST_ARCH'], env['TARGET_ARCH'])) - except MSVCUnsupportedTargetArch: - pass - else: - self.fail('Did not fail when TARGET_ARCH specified as: %s' % env['TARGET_ARCH']) - - # Test 14.0 (VS2015) to 8.0 (VS2005) versions + # Test 14.0 (VS2015) to 10.0 (VS2010) versions vc_le2015_list = SCons.Tool.MSCommon.vc._LE2015_HOST_TARGET_CFG.all_pairs - for host, target in vc_le2015_list: - batarg, clpathcomps = SCons.Tool.MSCommon.vc._LE2015_HOST_TARGET_BATCHARG_CLPATHCOMPS[(host, target)] - # print("LE 14.0 Got: (%s, %s) -> (%s, %s)"%(host,target,batarg,clpathcomps)) + batarg, batfile, clpathcomps = SCons.Tool.MSCommon.vc._LE2015_HOST_TARGET_BATCHARG_BATCHFILE_CLPATHCOMPS[(host, target)] + # print("LE 14.0 Got: (%s, %s) -> (%s, %s, %s)"%(host,target,batarg,batfile,clpathcomps)) env={'TARGET_ARCH':target, 'HOST_ARCH':host} + MSVcTestCase._createDummyFile('.', 'vcvarsall.bat', add_bin=False) path = os.path.join('.', *clpathcomps) - MSVcTestCase._createDummyCl(path, add_bin=False) - result=check(env, '.', '9.0') + MSVcTestCase._createDummyFile(path, batfile, add_bin=False) + MSVcTestCase._createDummyFile(path, 'cl.exe', add_bin=False) + result=check(env, '.', '10.0') # print("for:(%s, %s) got :%s"%(host, target, result)) self.assertTrue(result, "Checking host: %s target: %s" % (host, target)) - # Now test bogus value for HOST_ARCH - env={'TARGET_ARCH':'x86', 'HOST_ARCH':'GARBAGE'} - try: - result=check(env, '.', '9.0') - # print("for:%s got :%s"%(env, result)) - self.assertFalse(result, "Did not fail with bogus HOST_ARCH host: %s target: %s" % (env['HOST_ARCH'], env['TARGET_ARCH'])) - except MSVCUnsupportedHostArch: - pass - else: - self.fail('Did not fail when HOST_ARCH specified as: %s' % env['HOST_ARCH']) - - # Now test bogus value for TARGET_ARCH - env={'TARGET_ARCH':'GARBAGE', 'HOST_ARCH':'x86'} - try: - result=check(env, '.', '9.0') - # print("for:%s got :%s"%(env, result)) - self.assertFalse(result, "Did not fail with bogus TARGET_ARCH host: %s target: %s" % (env['HOST_ARCH'], env['TARGET_ARCH'])) - except MSVCUnsupportedTargetArch: - pass - else: - self.fail('Did not fail when TARGET_ARCH specified as: %s' % env['TARGET_ARCH']) + # Test 9.0 (VC2008) to 8.0 (VS2005) + vc_le2008_list = SCons.Tool.MSCommon.vc._LE2008_HOST_TARGET_CFG.all_pairs + for host, target in vc_le2008_list: + batarg, batfile, clpathcomps = SCons.Tool.MSCommon.vc._LE2008_HOST_TARGET_BATCHARG_BATCHFILE_CLPATHCOMPS[(host, target)] + # print("LE 9.0 Got: (%s, %s) -> (%s, %s, %s)"%(host,target,batarg,batfile,clpathcomps)) + env={'TARGET_ARCH':target, 'HOST_ARCH':host} + MSVcTestCase._createDummyFile('.', 'vcvarsall.bat', add_bin=False) + path = os.path.join('.', *clpathcomps) + MSVcTestCase._createDummyFile(path, batfile, add_bin=False) + MSVcTestCase._createDummyFile(path, 'cl.exe', add_bin=False) + # check will fail if '9.0' and VCForPython (layout different) + result=check(env, '.', '8.0') + # print("for:(%s, %s) got :%s"%(host, target, result)) + self.assertTrue(result, "Checking host: %s target: %s" % (host, target)) # Test 7.1 (VS2003) and earlier vc_le2003_list = SCons.Tool.MSCommon.vc._LE2003_HOST_TARGET_CFG.all_pairs - for host, target in vc_le2003_list: # print("LE 7.1 Got: (%s, %s)"%(host,target)) env={'TARGET_ARCH':target, 'HOST_ARCH':host} path = os.path.join('.') - MSVcTestCase._createDummyCl(path) + MSVcTestCase._createDummyFile(path, 'cl.exe') result=check(env, '.', '6.0') # print("for:(%s, %s) got :%s"%(host, target, result)) self.assertTrue(result, "Checking host: %s target: %s" % (host, target)) @@ -254,7 +204,7 @@ def runTest(self) -> None: # Now test bogus value for HOST_ARCH env={'TARGET_ARCH':'x86', 'HOST_ARCH':'GARBAGE'} try: - result=check(env, '.', '6.0') + result=check(env, '.', '14.3') # print("for:%s got :%s"%(env, result)) self.assertFalse(result, "Did not fail with bogus HOST_ARCH host: %s target: %s" % (env['HOST_ARCH'], env['TARGET_ARCH'])) except MSVCUnsupportedHostArch: @@ -265,7 +215,7 @@ def runTest(self) -> None: # Now test bogus value for TARGET_ARCH env={'TARGET_ARCH':'GARBAGE', 'HOST_ARCH':'x86'} try: - result=check(env, '.', '6.0') + result=check(env, '.', '14.3') # print("for:%s got :%s"%(env, result)) self.assertFalse(result, "Did not fail with bogus TARGET_ARCH host: %s target: %s" % (env['HOST_ARCH'], env['TARGET_ARCH'])) except MSVCUnsupportedTargetArch: @@ -273,12 +223,29 @@ def runTest(self) -> None: else: self.fail('Did not fail when TARGET_ARCH specified as: %s' % env['TARGET_ARCH']) - class Data: - HAVE_MSVC = True if MSCommon.vc.msvc_default_version() else False + DEFAULT_VERSION = MSCommon.vc.msvc_default_version() + + if DEFAULT_VERSION: + HAVE_MSVC = True + DEFAULT_VERSION_DEF = MSCommon.msvc_version_components(DEFAULT_VERSION) + else: + HAVE_MSVC = False + DEFAULT_VERSION_DEF = None + INSTALLED_VCS = MSCommon.vc.get_installed_vcs() INSTALLED_VCS_COMPONENTS = MSCommon.vc.get_installed_vcs_components() + INSTALLED_VCS_TOOLSETS_COMPONENTS = MSCommon.vc.get_installed_vcs_toolsets_components() + + @classmethod + def query_version_list(cls, vcver): + # VS 2022 (14.3) can have either/both toolset versions 14.3X and 14.4X + if vcver == '14.3' or (vcver is None and cls.DEFAULT_VERSION == '14.3'): + vcver_list = ['14.4', '14.3'] + else: + vcver_list = [vcver] + return vcver_list @classmethod def _msvc_toolset_notfound_list(cls, toolset_seen, toolset_list): @@ -291,11 +258,11 @@ def _msvc_toolset_notfound_list(cls, toolset_seen, toolset_list): if len(comps) != 3: continue # full versions only + ival = int(comps[-1]) nloop = 0 while nloop < 10: - ival = int(comps[-1]) if ival == 0: - ival = 1000000 + ival = 100000 ival -= 1 version = '{}.{}.{:05d}'.format(comps[0], comps[1], ival) if version not in toolset_seen: @@ -371,10 +338,18 @@ def test_valid_vcver(self) -> None: version_def = MSCommon.msvc_version_components(symbol) for msvc_uwp_app in (True, False): sdk_list = MSCommon.vc.msvc_sdk_versions(version=symbol, msvc_uwp_app=msvc_uwp_app) - if Data.HAVE_MSVC and version_def.msvc_vernum >= 14.0: + if version_def.msvc_vernum < 14.0: + # version < VS2015 + # does not accept sdk version argument + self.assertFalse(sdk_list, "SDK list is not empty for msvc version {}".format(repr(symbol))) + elif Data.HAVE_MSVC and Data.DEFAULT_VERSION_DEF.msvc_vernum >= 14.0: + # version >= VS2015 and default_version >= VS2015 + # no guarantee test is valid: compatible sdks may not be installed as version gap grows self.assertTrue(sdk_list, "SDK list is empty for msvc version {}".format(repr(symbol))) else: - self.assertFalse(sdk_list, "SDK list is not empty for msvc version {}".format(repr(symbol))) + # version >= VS2015 and default_version < VS2015 + # skip test: version is not installed; compatible windows sdks may not be installed + pass def test_valid_vcver_toolsets(self) -> None: for symbol in MSCommon.vc._VCVER: @@ -490,15 +465,26 @@ class MsvcQueryVersionToolsetTests(unittest.TestCase): def run_valid_default_msvc(self, have_msvc) -> None: for prefer_newest in (True, False): - msvc_version, msvc_toolset_version = MSCommon.vc.msvc_query_version_toolset( - version=None, prefer_newest=prefer_newest - ) - expect = (have_msvc and msvc_version) or (not have_msvc and not msvc_version) - self.assertTrue(expect, "unexpected msvc_version {} for for msvc version {}".format( + if not have_msvc: + with self.assertRaises(MSCommon.vc.MSVCToolsetVersionNotFound): + msvc_version, msvc_toolset_version = MSCommon.vc.msvc_query_version_toolset( + version=None, prefer_newest=prefer_newest + ) + continue + msvc_version = msvc_toolset_version = None + for vcver in Data.query_version_list(None): + try: + msvc_version, msvc_toolset_version = MSCommon.vc.msvc_query_version_toolset( + version=vcver, prefer_newest=prefer_newest + ) + break + except MSCommon.vc.MSVCToolsetVersionNotFound: + pass + self.assertTrue(msvc_version, "unexpected msvc_version {} for for msvc version {}".format( repr(msvc_version), repr(None) )) version_def = MSCommon.msvc_version_components(msvc_version) - if have_msvc and version_def.msvc_vernum > 14.0: + if version_def.msvc_vernum > 14.0: # VS2017 and later for toolset version self.assertTrue(msvc_toolset_version, "msvc_toolset_version is undefined for msvc version {}".format( repr(None) @@ -513,11 +499,24 @@ def test_valid_default_msvc(self) -> None: def test_valid_vcver(self) -> None: for symbol in MSCommon.vc._VCVER: + have_msvc = bool(symbol in Data.INSTALLED_VCS_TOOLSETS_COMPONENTS.toolset_vcs) version_def = MSCommon.msvc_version_components(symbol) for prefer_newest in (True, False): - msvc_version, msvc_toolset_version = MSCommon.vc.msvc_query_version_toolset( - version=symbol, prefer_newest=prefer_newest - ) + if not have_msvc: + with self.assertRaises(MSCommon.vc.MSVCToolsetVersionNotFound): + msvc_version, msvc_toolset_version = MSCommon.vc.msvc_query_version_toolset( + version=symbol, prefer_newest=prefer_newest + ) + continue + msvc_version = msvc_toolset_version = None + for vcver in Data.query_version_list(symbol): + try: + msvc_version, msvc_toolset_version = MSCommon.vc.msvc_query_version_toolset( + version=vcver, prefer_newest=prefer_newest + ) + break + except: + pass self.assertTrue(msvc_version, "msvc_version is undefined for msvc version {}".format(repr(symbol))) if version_def.msvc_vernum > 14.0: # VS2017 and later for toolset version diff --git a/SCons/Tool/MSCommon/vs.py b/SCons/Tool/MSCommon/vs.py index af0fd26e5a..34fb670b5f 100644 --- a/SCons/Tool/MSCommon/vs.py +++ b/SCons/Tool/MSCommon/vs.py @@ -28,7 +28,6 @@ import os import SCons.Errors -import SCons.Tool.MSCommon.vc import SCons.Util from .common import ( @@ -40,6 +39,19 @@ read_reg, ) +from .vc import ( + find_vc_pdir, + get_msvc_version_numeric, + reset_installed_vcs, + vswhere_freeze_env, +) + + +# Visual Studio express version policy when unqualified version is not installed: +# True: use express version for unqualified version (e.g., use 12.0Exp for 12.0) +# False: do not use express version for unqualified version +_VSEXPRESS_USE_VERSTR = True + class VisualStudio: """ @@ -48,6 +60,9 @@ class VisualStudio: """ def __init__(self, version, **kw) -> None: self.version = version + self.verstr = get_msvc_version_numeric(version) + self.vernum = float(self.verstr) + self.is_express = True if self.verstr != self.version else False kw['vc_version'] = kw.get('vc_version', version) kw['sdk_version'] = kw.get('sdk_version', version) self.__dict__.update(kw) @@ -66,7 +81,7 @@ def find_batch_file(self): return batch_file def find_vs_dir_by_vc(self, env): - dir = SCons.Tool.MSCommon.vc.find_vc_pdir(env, self.vc_version) + dir = find_vc_pdir(self.vc_version, env) if not dir: debug('no installed VC %s', self.vc_version) return None @@ -428,6 +443,7 @@ def reset(self) -> None: def get_installed_visual_studios(env=None): global InstalledVSList global InstalledVSMap + vswhere_freeze_env(env) if InstalledVSList is None: InstalledVSList = [] InstalledVSMap = {} @@ -436,12 +452,21 @@ def get_installed_visual_studios(env=None): if vs.get_executable(env): debug('found VS %s', vs.version) InstalledVSList.append(vs) + if vs.is_express and vs.verstr not in InstalledVSMap: + if _VSEXPRESS_USE_VERSTR: + InstalledVSMap[vs.verstr] = vs InstalledVSMap[vs.version] = vs return InstalledVSList +def _get_installed_vss(env=None): + get_installed_visual_studios(env) + versions = list(InstalledVSMap.keys()) + return versions + def reset_installed_visual_studios() -> None: global InstalledVSList global InstalledVSMap + debug('') InstalledVSList = None InstalledVSMap = None for vs in SupportedVSList: @@ -449,7 +474,7 @@ def reset_installed_visual_studios() -> None: # Need to clear installed VC's as well as they are used in finding # installed VS's - SCons.Tool.MSCommon.vc.reset_installed_vcs() + reset_installed_vcs() # We may be asked to update multiple construction environments with @@ -487,7 +512,7 @@ def reset_installed_visual_studios() -> None: def msvs_exists(env=None) -> bool: return len(get_installed_visual_studios(env)) > 0 -def get_vs_by_version(msvs): +def get_vs_by_version(msvs, env=None): global InstalledVSMap global SupportedVSMap @@ -495,7 +520,7 @@ def get_vs_by_version(msvs): if msvs not in SupportedVSMap: msg = "Visual Studio version %s is not supported" % repr(msvs) raise SCons.Errors.UserError(msg) - get_installed_visual_studios() + get_installed_visual_studios(env) vs = InstalledVSMap.get(msvs) debug('InstalledVSMap:%s', InstalledVSMap) debug('found vs:%s', vs) @@ -524,7 +549,7 @@ def get_default_version(env): """ if 'MSVS' not in env or not SCons.Util.is_Dict(env['MSVS']): # get all versions, and remember them for speed later - versions = [vs.version for vs in get_installed_visual_studios()] + versions = _get_installed_vss(env) env['MSVS'] = {'VERSIONS' : versions} else: versions = env['MSVS'].get('VERSIONS', []) @@ -570,7 +595,7 @@ def merge_default_version(env) -> None: # TODO: refers to versions and arch which aren't defined; called nowhere. Drop? def msvs_setup_env(env) -> None: - msvs = get_vs_by_version(version) + msvs = get_vs_by_version(version, env) if msvs is None: return batfilename = msvs.get_batch_file() @@ -582,7 +607,7 @@ def msvs_setup_env(env) -> None: vars = ('LIB', 'LIBPATH', 'PATH', 'INCLUDE') - msvs_list = get_installed_visual_studios() + msvs_list = get_installed_visual_studios(env) vscommonvarnames = [vs.common_tools_var for vs in msvs_list] save_ENV = env['ENV'] nenv = normalize_env(env['ENV'], @@ -597,11 +622,10 @@ def msvs_setup_env(env) -> None: for k, v in vars.items(): env.PrependENVPath(k, v, delete_existing=1) -def query_versions(): +def query_versions(env=None): """Query the system to get available versions of VS. A version is considered when a batfile is found.""" - msvs_list = get_installed_visual_studios() - versions = [msvs.version for msvs in msvs_list] + versions = _get_installed_vss(env) return versions # Local Variables: diff --git a/SCons/Tool/msvc.py b/SCons/Tool/msvc.py index 6afa171c97..b823752b50 100644 --- a/SCons/Tool/msvc.py +++ b/SCons/Tool/msvc.py @@ -296,9 +296,6 @@ def generate(env) -> None: # without it for lex generation env["LEXUNISTD"] = SCons.Util.CLVar("--nounistd") - # Get user specified vswhere location or locate. - env['VSWHERE'] = env.get('VSWHERE', msvc_find_vswhere()) - # Set-up ms tools paths msvc_setup_env_once(env, tool=tool_name) diff --git a/SCons/Tool/msvc.xml b/SCons/Tool/msvc.xml index 9f433a2453..1609bd7885 100644 --- a/SCons/Tool/msvc.xml +++ b/SCons/Tool/msvc.xml @@ -395,26 +395,30 @@ a warning is issued showing the versions actually discovered, and the build will eventually fail indicating a missing compiler binary. If &cv-MSVC_VERSION; is not set, &SCons; will (by default) select the latest version of &MSVC; installed on your system. + + + The valid values for &cv-MSVC_VERSION; represent major versions of the compiler, except that versions ending in Exp -refer to "Express" or "Express for Desktop" Visual Studio editions, -which require distinct entries because they use a different -filesystem layout and have feature limitations compared to -the full version. +refer to "Express" or "Express for Desktop" Visual Studio editions. Values that do not look like a valid compiler version string are not supported. -To have the desired effect, &cv-MSVC_VERSION; must be set by the -time compiler discovery takes place. -If the default tools list -or an explicit tools list including &t-link-msvc; is used, -discovery takes place as the &consenv; is created, -so passing it as an argument in the the &f-link-Environment; call +To have the desired effect, &cv-MSVC_VERSION; must be set before an msvc &f-link-Tool; +(e.g., &t-link-msvc;) or an msvc-dependent &f-link-Tool; (e.g., &t-link-midl;) is loaded +into the &consenv;. + + + +If the default tools list or an explicit tools list is used that includes an +msvc &f-link-Tool; (e.g., &t-link-msvc;) or an msvc-dependent &f-link-Tool; (e.g., &t-link-midl;); +the &cv-MSVC_VERSION; is resolved when the &consenv; is created. +In this case, passing &cv-MSVC_VERSION; as an argument in the &f-link-Environment; call is the effective solution. -Otherwise, &cv-MSVC_VERSION; must be set before the first msvc tool is -loaded into the environment. +Otherwise, &cv-MSVC_VERSION; must be set before the first msvc &f-link-Tool; or +msvc-dependent &f-link-Tool; is loaded into the environment. See the manpage section "Construction Environments" for an example. @@ -423,12 +427,6 @@ The following table shows the correspondence of &cv-MSVC_VERSION; values to various version indicators ('x' is used as a placeholder for a single digit that can vary). -Note that it is not necessary to install Visual Studio -to build with &SCons; (for example, you can install only -Build Tools), but if Visual Studio is installed, -additional builders such as &b-link-MSVSSolution; and -&b-link-MSVSProject; become available and will -correspond to the indicated versions. @@ -478,10 +476,10 @@ Visual Studio "14.1Exp" - 14.1 - 1910 + 14.1 or 14.1x + 191x Visual Studio 2017 Express - 15.0 + 15.x "14.0" @@ -592,6 +590,31 @@ Visual Studio + + + + + It is not necessary to install a Visual Studio IDE + to build with &SCons; (for example, you can install only + Build Tools), but when a Visual Studio IDE is installed, + additional builders such as &b-link-MSVSSolution; and + &b-link-MSVSProject; become available and correspond to + the specified versions. + + + + Versions ending in Exp refer to historical + "Express" or "Express for Desktop" Visual Studio editions, + which had feature limitations compared to the full editions. + It is only necessary to specify the Exp + suffix to select the express edition when both express and + non-express editions of the same product are installed + simulaneously. The Exp suffix is unnecessary, + but accepted, when only the express edition is installed. + + + + The compilation environment can be further or more precisely specified through the use of several other &consvars;: see the descriptions of @@ -842,34 +865,42 @@ Specify the location of vswhere.exe. The vswhere.exe executable is distributed with Microsoft Visual Studio and Build - Tools since the 2017 edition, but is also available standalone. + Tools since the 2017 edition, but is also available as a standalone installation. It provides full information about installations of 2017 and later editions. With the argument, vswhere.exe can detect installations of the 2010 through 2015 editions with limited data returned. -If VSWHERE is set, &SCons; will use that location. - Otherwise &SCons; will look in the following locations and set VSWHERE to the path of the first vswhere.exe -located. + If &cv-VSWHERE; is set to a vswhere.exe location, &SCons; will use that location. + When &cv-VSWHERE; is undefined, &SCons; will look in the following locations and set &cv-VSWHERE; to the path + of the first vswhere.exe located: %ProgramFiles(x86)%\Microsoft Visual Studio\Installer %ProgramFiles%\Microsoft Visual Studio\Installer %ChocolateyInstall%\bin +%LOCALAPPDATA%\Microsoft\WinGet\Links +~\scoop\shims +%SCOOP%\shims - Note that VSWHERE must be set at the same time or prior to any of &t-link-msvc;, &t-link-msvs; , and/or &t-link-mslink; &f-link-Tool; being initialized. + Note that &cv-VSWHERE; must be set prior to the initial &MSVC; compiler discovery. + For example, &cv-VSWHERE; must be set at the same time or before the first msvc &f-link-Tool; + (e.g., &t-link-msvc;) or msvc-dependent &f-link-Tool; (e.g., &t-link-midl;) is initialized. + - Either set it as follows + + Either set it as follows: env = Environment(VSWHERE='c:/my/path/to/vswhere') -or if your &consenv; is created specifying an empty tools list -(or a list of tools which omits all of default, msvs, msvc, and mslink), -and also before &f-link-env-Tool; is called to ininitialize any of those tools: +Or, if your &consenv; is created specifying: (a) an empty tools list, or (b) +a list of tools which omits all of default, msvc (e.g., &t-link-msvc;), and +msvc-dependent tools (e.g., &t-link-midl;); and before &f-link-env-Tool; +is called to initialize any of those tools: env = Environment(tools=[]) diff --git a/SCons/Tool/msvsTests.py b/SCons/Tool/msvsTests.py index dd708d0a7e..7893cafdcc 100644 --- a/SCons/Tool/msvsTests.py +++ b/SCons/Tool/msvsTests.py @@ -420,6 +420,11 @@ def get(self, name, value=None): def Dir(self, name): return self.fs.Dir(name) + def subst(self, key): + if key[0] == '$': + key = key[1:] + return self[key] + class RegKey: """key class for storing an 'open' registry key""" @@ -579,7 +584,7 @@ def DummyQueryValue(key, value): def DummyExists(path) -> bool: return True -def DummyVsWhere(msvc_version, env): +def DummyVsWhere(msvc_version, vswhere_exe): # not testing versions with vswhere, so return none return None @@ -635,9 +640,9 @@ def _TODO_test_merge_default_version(self) -> None: """Test the merge_default_version() function""" pass - def test_query_versions(self) -> None: + def test_query_versions(self, env=None) -> None: """Test retrieval of the list of visual studio versions""" - v1 = query_versions() + v1 = query_versions(env) assert not v1 or str(v1[0]) == self.highest_version, \ (v1, self.highest_version) assert len(v1) == self.number_of_versions, v1 @@ -880,9 +885,9 @@ class msvs71TestCase(msvsTestCase): class msvs8ExpTestCase(msvsTestCase): # XXX: only one still not working """Test MSVS 8 Express Registry""" registry = DummyRegistry(regdata_8exp + regdata_cv) - default_version = '8.0Exp' - highest_version = '8.0Exp' - number_of_versions = 1 + default_version = '8.0' + highest_version = '8.0' + number_of_versions = 2 install_locs = { '6.0' : {}, '7.0' : {}, @@ -947,7 +952,7 @@ class msvsEmptyTestCase(msvsTestCase): SCons.Util.RegEnumKey = DummyEnumKey SCons.Util.RegEnumValue = DummyEnumValue SCons.Util.RegQueryValueEx = DummyQueryValue - SCons.Tool.MSCommon.vc.find_vc_pdir_vswhere = DummyVsWhere + SCons.Tool.MSCommon.vc._find_vc_pdir_vswhere = DummyVsWhere os.path.exists = DummyExists # make sure all files exist :-) os.path.isfile = DummyExists # make sure all files are files :-) diff --git a/test/MSVC/MSVC_SDK_VERSION.py b/test/MSVC/MSVC_SDK_VERSION.py index e5eae6762c..0f0e25a65b 100644 --- a/test/MSVC/MSVC_SDK_VERSION.py +++ b/test/MSVC/MSVC_SDK_VERSION.py @@ -31,6 +31,10 @@ from SCons.Tool.MSCommon.vc import get_installed_vcs_components from SCons.Tool.MSCommon import msvc_sdk_versions from SCons.Tool.MSCommon import msvc_toolset_versions +from SCons.Tool.MSCommon.MSVC.Kind import ( + msvc_version_is_express, + msvc_version_is_btdispatch, +) import TestSCons test = TestSCons.TestSCons() @@ -38,12 +42,28 @@ installed_versions = get_installed_vcs_components() default_version = installed_versions[0] -GE_VS2015_versions = [v for v in installed_versions if v.msvc_vernum >= 14.0] -LT_VS2015_versions = [v for v in installed_versions if v.msvc_vernum < 14.0] + +GE_VS2015_supported_versions = [] +GE_VS2015_unsupported_versions = [] +LT_VS2015_unsupported_versions = [] + +for v in installed_versions: + if v.msvc_vernum > 14.0: + GE_VS2015_supported_versions.append(v) + elif v.msvc_verstr == '14.0': + if msvc_version_is_express(v.msvc_version): + GE_VS2015_unsupported_versions.append((v, 'Express')) + elif msvc_version_is_btdispatch(v.msvc_version): + GE_VS2015_unsupported_versions.append((v, 'BTDispatch')) + else: + GE_VS2015_supported_versions.append(v) + else: + LT_VS2015_unsupported_versions.append(v) + default_sdk_versions_uwp = msvc_sdk_versions(version=None, msvc_uwp_app=True) default_sdk_versions_def = msvc_sdk_versions(version=None, msvc_uwp_app=False) -have_140 = any(v.msvc_verstr == '14.0' for v in GE_VS2015_versions) +have_140 = any(v.msvc_verstr == '14.0' for v in installed_versions) def version_major(version): components = version.split('.') @@ -64,9 +84,10 @@ def version_major_list(version_list): seen_major.add(major) return versions -if GE_VS2015_versions: +if GE_VS2015_supported_versions: - for supported in GE_VS2015_versions: + for supported in GE_VS2015_supported_versions: + # VS2017+ and VS2015 ('14.0') sdk_versions_uwp = msvc_sdk_versions(version=supported.msvc_version, msvc_uwp_app=True) sdk_versions_def = msvc_sdk_versions(version=supported.msvc_version, msvc_uwp_app=False) @@ -176,8 +197,8 @@ def version_major_list(version_list): """.format(repr(supported.msvc_version), repr(toolset_version)) )) test.run(arguments='-Q -s', status=2, stderr=None) - expect = "MSVCArgumentError: MSVC_SDK_VERSION ('8.1') and platform type ('UWP') constraint violation: toolset version {} > '14.0' VS2015:".format( - repr(toolset_version) + expect = "MSVCArgumentError: MSVC_SDK_VERSION ('8.1') and platform type ('UWP') constraint violation: toolset {} MSVC_VERSION {} > '14.0' VS2015:".format( + repr(toolset_version), repr(supported.msvc_version) ) test.must_contain_all(test.stderr(), expect) @@ -203,9 +224,37 @@ def version_major_list(version_list): )) test.run(arguments='-Q -s', stdout='') -if LT_VS2015_versions: +if GE_VS2015_unsupported_versions: + + for unsupported, kind_str in GE_VS2015_unsupported_versions: + # VS2015 Express + + sdk_version = default_sdk_versions_def[0] if default_sdk_versions_def else '8.1' + + test.write('SConstruct', textwrap.dedent( + """ + DefaultEnvironment(tools=[]) + env = Environment(MSVC_VERSION={}, MSVC_SDK_VERSION={}, tools=['msvc']) + """.format(repr(unsupported.msvc_version), repr(sdk_version)) + )) + test.run(arguments='-Q -s', status=2, stderr=None) + expect = "MSVCArgumentError: MSVC_SDK_VERSION ({}) is not supported for MSVC_VERSION {} ({}):".format( + repr(sdk_version), repr(unsupported.msvc_version), repr(kind_str) + ) + test.must_contain_all(test.stderr(), expect) + + # MSVC_SCRIPT_ARGS sdk_version is not validated + test.write('SConstruct', textwrap.dedent( + """ + DefaultEnvironment(tools=[]) + env = Environment(MSVC_VERSION={}, MSVC_SCRIPT_ARGS={}, tools=['msvc']) + """.format(repr(unsupported.msvc_version), repr(sdk_version)) + )) + test.run(arguments='-Q -s', stdout='') + +if LT_VS2015_unsupported_versions: - for unsupported in LT_VS2015_versions: + for unsupported in LT_VS2015_unsupported_versions: # must be VS2015 or later sdk_version = default_sdk_versions_def[0] if default_sdk_versions_def else '8.1' diff --git a/test/MSVC/MSVC_TOOLSET_VERSION.py b/test/MSVC/MSVC_TOOLSET_VERSION.py index a20cf8a319..e43f4d7f80 100644 --- a/test/MSVC/MSVC_TOOLSET_VERSION.py +++ b/test/MSVC/MSVC_TOOLSET_VERSION.py @@ -28,6 +28,7 @@ """ import textwrap +import SCons.Tool.MSCommon.MSVC.Config as Config from SCons.Tool.MSCommon.vc import get_installed_vcs_components from SCons.Tool.MSCommon import msvc_toolset_versions @@ -38,10 +39,31 @@ installed_versions = get_installed_vcs_components() default_version = installed_versions[0] + GE_VS2017_versions = [v for v in installed_versions if v.msvc_vernum >= 14.1] LT_VS2017_versions = [v for v in installed_versions if v.msvc_vernum < 14.1] LT_VS2015_versions = [v for v in LT_VS2017_versions if v.msvc_vernum < 14.0] +known_buildseries = set(Config.MSVC_BUILDSERIES_EXTERNAL.keys()) + +def get_toolset_buildseries_version(toolset_version): + comps = toolset_version.split('.') + buildseries_version = comps[0] + '.' + comps[1][0] + vc_buildseries_def = Config.MSVC_BUILDSERIES_EXTERNAL[buildseries_version] + vc_version = vc_buildseries_def.vc_version + return vc_version + +def get_latest_buildseries_version(v): + vs_def = Config.MSVC_VERSION_EXTERNAL[v.msvc_verstr] + vc_version = vs_def.vc_buildtools_def.vc_buildseries_list[0].vc_version + return vc_version + +def is_buildseries_known(toolset_version): + comps = toolset_version.split('.') + buildseries_version = comps[0] + '.' + comps[1][0] + rval = bool(buildseries_version in known_buildseries) + return rval + if GE_VS2017_versions: # VS2017 and later for toolset argument @@ -53,6 +75,11 @@ toolset_sxs_versions = msvc_toolset_versions(supported.msvc_version, full=False, sxs=True) toolset_sxs_version = toolset_sxs_versions[0] if toolset_sxs_versions else None + toolset_version = toolset_full_version if toolset_full_version else supported.msvc_version + buildseries_version = get_toolset_buildseries_version(toolset_version) + + latest_buildseries_version = get_latest_buildseries_version(supported) + if toolset_full_version: # toolset version using construction variable @@ -90,21 +117,21 @@ )) test.run(arguments='-Q -s', stdout='') - # msvc_version as toolset version + # build series version as toolset version test.write('SConstruct', textwrap.dedent( """ DefaultEnvironment(tools=[]) env = Environment(MSVC_VERSION={}, MSVC_TOOLSET_VERSION={}, tools=['msvc']) - """.format(repr(supported.msvc_version), repr(supported.msvc_verstr)) + """.format(repr(supported.msvc_version), repr(buildseries_version)) )) test.run(arguments='-Q -s', stdout='') - # msvc_version as toolset version using script argument + # build series version as toolset version using script argument test.write('SConstruct', textwrap.dedent( """ DefaultEnvironment(tools=[]) env = Environment(MSVC_VERSION={}, MSVC_SCRIPT_ARGS='-vcvars_ver={}', tools=['msvc']) - """.format(repr(supported.msvc_version), supported.msvc_verstr) + """.format(repr(supported.msvc_version), buildseries_version) )) test.run(arguments='-Q -s', stdout='') @@ -113,11 +140,11 @@ """ DefaultEnvironment(tools=[]) env = Environment(MSVC_VERSION={}, MSVC_TOOLSET_VERSION={}, MSVC_SCRIPT_ARGS='-vcvars_ver={}', tools=['msvc']) - """.format(repr(supported.msvc_version), repr(supported.msvc_verstr), supported.msvc_verstr) + """.format(repr(supported.msvc_version), repr(buildseries_version), buildseries_version) )) test.run(arguments='-Q -s', status=2, stderr=None) expect = "MSVCArgumentError: multiple toolset version declarations: MSVC_TOOLSET_VERSION={} and MSVC_SCRIPT_ARGS='-vcvars_ver={}':".format( - repr(supported.msvc_verstr), supported.msvc_verstr + repr(buildseries_version), buildseries_version ) test.must_contain_all(test.stderr(), expect) @@ -149,9 +176,19 @@ ) test.must_contain_all(test.stderr(), expect) - # msvc_toolset_version is invalid (version greater than msvc version) - invalid_toolset_vernum = round(supported.msvc_vernum + 0.1, 1) + # msvc_toolset_version is invalid (version not supported for msvc version) + invalid_toolset_vernum = round(float(latest_buildseries_version) + 0.1, 1) invalid_toolset_version = str(invalid_toolset_vernum) + + if is_buildseries_known(invalid_toolset_version): + expect = "MSVCArgumentError: MSVC_TOOLSET_VERSION ({}) constraint violation: toolset msvc version {} > {} MSVC_VERSION:".format( + repr(invalid_toolset_version), repr(invalid_toolset_version), repr(supported.msvc_version) + ) + else: + expect = "MSVCArgumentError: MSVC_TOOLSET_VERSION {} build series {} is not supported:".format( + repr(invalid_toolset_version), repr(invalid_toolset_version) + ) + test.write('SConstruct', textwrap.dedent( """ DefaultEnvironment(tools=[]) @@ -159,9 +196,6 @@ """.format(repr(supported.msvc_version), repr(invalid_toolset_version)) )) test.run(arguments='-Q -s', status=2, stderr=None) - expect = "MSVCArgumentError: MSVC_TOOLSET_VERSION ({}) constraint violation: toolset version {} > {} MSVC_VERSION:".format( - repr(invalid_toolset_version), repr(invalid_toolset_version), repr(supported.msvc_version) - ) test.must_contain_all(test.stderr(), expect) # msvc_toolset_version is invalid (version less than 14.0) @@ -173,7 +207,7 @@ """.format(repr(supported.msvc_version), repr(invalid_toolset_version)) )) test.run(arguments='-Q -s', status=2, stderr=None) - expect = "MSVCArgumentError: MSVC_TOOLSET_VERSION ({}) constraint violation: toolset version {} < '14.0' VS2015:".format( + expect = "MSVCArgumentError: MSVC_TOOLSET_VERSION ({}) constraint violation: toolset msvc version {} < '14.0' VS2015:".format( repr(invalid_toolset_version), repr(invalid_toolset_version) ) test.must_contain_all(test.stderr(), expect) @@ -187,7 +221,7 @@ """.format(repr(supported.msvc_version), repr(invalid_toolset_version)) )) test.run(arguments='-Q -s', status=2, stderr=None) - expect = "MSVCArgumentError: MSVC_TOOLSET_VERSION ({}) constraint violation: toolset version {} != '14.0':".format( + expect = "MSVCArgumentError: MSVC_TOOLSET_VERSION ({}) constraint violation: toolset msvc version {} != '14.0':".format( repr(invalid_toolset_version), repr(invalid_toolset_version) ) test.must_contain_all(test.stderr(), expect) diff --git a/test/MSVC/MSVC_UWP_APP.py b/test/MSVC/MSVC_UWP_APP.py index 30b07ef771..fd52fd8f5e 100644 --- a/test/MSVC/MSVC_UWP_APP.py +++ b/test/MSVC/MSVC_UWP_APP.py @@ -30,14 +30,33 @@ import re from SCons.Tool.MSCommon.vc import get_installed_vcs_components +from SCons.Tool.MSCommon.vc import get_native_host_platform +from SCons.Tool.MSCommon.vc import _GE2022_HOST_TARGET_CFG +from SCons.Tool.MSCommon.MSVC.Kind import ( + msvc_version_is_express, + msvc_version_is_btdispatch, +) import TestSCons test = TestSCons.TestSCons() test.skip_if_not_msvc() installed_versions = get_installed_vcs_components() -GE_VS2015_versions = [v for v in installed_versions if v.msvc_vernum >= 14.0] -LT_VS2015_versions = [v for v in installed_versions if v.msvc_vernum < 14.0] + +GE_VS2015_supported_versions = [] +GE_VS2015_unsupported_versions = [] +LT_VS2015_unsupported_versions = [] + +for v in installed_versions: + if v.msvc_vernum > 14.0: + GE_VS2015_supported_versions.append(v) + elif v.msvc_verstr == '14.0': + if msvc_version_is_btdispatch(v.msvc_version): + GE_VS2015_unsupported_versions.append((v, 'BTDispatch')) + else: + GE_VS2015_supported_versions.append(v) + else: + LT_VS2015_unsupported_versions.append(v) # Look for the Store VC Lib paths in the LIBPATH: # [VS install path]\VC\LIB\store[\arch] and @@ -46,32 +65,52 @@ # C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\LIB\store\amd64 # C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\LIB\store\references +# By default, 2015 Express supports the store argument only for x86 targets. +# Using MSVC_SCRIPT_ARGS to set the store argument is not validated and +# will result in the store paths not found on 64-bit hosts when using the +# default target architecture. + +# By default, 2015 BTDispatch silently ignores the store argument. +# Using MSVC_SCRIPT_ARGS to set the store argument is not validated and +# will result in the store paths not found. + +re_lib_eq2015exp_1 = re.compile(r'\\vc\\lib\\store', re.IGNORECASE) + re_lib_eq2015_1 = re.compile(r'\\vc\\lib\\store\\references', re.IGNORECASE) re_lib_eq2015_2 = re.compile(r'\\vc\\lib\\store', re.IGNORECASE) re_lib_ge2017_1 = re.compile(r'\\lib\\x86\\store\\references', re.IGNORECASE) re_lib_ge2017_2 = re.compile(r'\\lib\\x64\\store', re.IGNORECASE) - def check_libpath(msvc, active, output): + def _check_libpath(msvc, output): + is_supported = True outdict = {key.strip(): val.strip() for key, val in [line.split('|') for line in output.splitlines()]} platform = outdict.get('PLATFORM', '') libpath = outdict.get('LIBPATH', '') + uwpsupported = outdict.get('UWPSUPPORTED', '') + if uwpsupported and uwpsupported.split('|')[-1] == '0': + is_supported = False n_matches = 0 if msvc.msvc_verstr == '14.0': + if msvc_version_is_express(msvc.msvc_version): + for regex in (re_lib_eq2015exp_1,): + if regex.search(libpath): + n_matches += 1 + return n_matches > 0, 'store', libpath, is_supported for regex in (re_lib_eq2015_1, re_lib_eq2015_2): if regex.search(libpath): n_matches += 1 - return n_matches >= 2, 'store', libpath + return n_matches >= 2, 'store', libpath, is_supported elif platform == 'UWP': for regex in (re_lib_ge2017_1, re_lib_ge2017_2): if regex.search(libpath): n_matches += 1 - return n_matches > 0, 'uwp', libpath - return False, 'uwp', libpath + return n_matches > 0, 'uwp', libpath, is_supported + return False, 'uwp', libpath, is_supported - found, kind, libpath = _check_libpath(msvc, output) + found, kind, libpath, is_supported = _check_libpath(msvc, output) failmsg = None @@ -84,11 +123,13 @@ def _check_libpath(msvc, output): repr(msvc.msvc_version), repr(kind), repr(libpath) ) - return failmsg + return failmsg, is_supported -if GE_VS2015_versions: +if GE_VS2015_supported_versions: # VS2015 and later for uwp/store argument - for supported in GE_VS2015_versions: + + for supported in GE_VS2015_supported_versions: + for msvc_uwp_app in (True, '1', False, '0', None): active = msvc_uwp_app in (True, '1') @@ -102,7 +143,7 @@ def _check_libpath(msvc, output): """.format(repr(supported.msvc_version), repr(msvc_uwp_app)) )) test.run(arguments='-Q -s', stdout=None) - failmsg = check_libpath(supported, active, test.stdout()) + failmsg, _ = check_libpath(supported, active, test.stdout()) if failmsg: test.fail_test(message=failmsg) @@ -120,23 +161,77 @@ def _check_libpath(msvc, output): if not test.stderr().strip().startswith("MSVCArgumentError: multiple uwp declarations:"): test.fail_test(message='Expected MSVCArgumentError') - # uwp using script argument + if supported.msvc_verstr == '14.0' and msvc_version_is_express(supported.msvc_version): + + # uwp using script argument may not be supported for default target architecture + test.write('SConstruct', textwrap.dedent( + """ + from SCons.Tool.MSCommon.MSVC.Kind import msvc_version_uwp_is_supported + DefaultEnvironment(tools=[]) + env = Environment(MSVC_VERSION={}, MSVC_SCRIPT_ARGS='store', tools=['msvc']) + is_supported, _ = msvc_version_uwp_is_supported(env['MSVC_VERSION'], env['TARGET_ARCH']) + uwpsupported = '1' if is_supported else '0' + print('LIBPATH|' + env['ENV'].get('LIBPATH', '')) + print('PLATFORM|' + env['ENV'].get('VSCMD_ARG_app_plat','')) + print('UWPSUPPORTED|' + uwpsupported) + """.format(repr(supported.msvc_version)) + )) + test.run(arguments='-Q -s', stdout=None) + + else: + + # uwp using script argument + test.write('SConstruct', textwrap.dedent( + """ + DefaultEnvironment(tools=[]) + env = Environment(MSVC_VERSION={}, MSVC_SCRIPT_ARGS='store', tools=['msvc']) + print('LIBPATH|' + env['ENV'].get('LIBPATH', '')) + print('PLATFORM|' + env['ENV'].get('VSCMD_ARG_app_plat','')) + """.format(repr(supported.msvc_version)) + )) + test.run(arguments='-Q -s', stdout=None) + + failmsg, is_supported = check_libpath(supported, True, test.stdout()) + if failmsg and is_supported: + test.fail_test(message=failmsg) + +if GE_VS2015_unsupported_versions: + # VS2015 and later for uwp/store error + + for unsupported, kind_str in GE_VS2015_unsupported_versions: + + for msvc_uwp_app in (True, '1'): + + # uwp using construction variable + test.write('SConstruct', textwrap.dedent( + """ + DefaultEnvironment(tools=[]) + env = Environment(MSVC_VERSION={}, MSVC_UWP_APP={}, tools=['msvc']) + """.format(repr(unsupported.msvc_version), repr(msvc_uwp_app)) + )) + test.run(arguments='-Q -s', status=2, stderr=None) + expect = "MSVCArgumentError: MSVC_UWP_APP ({}) is not supported for MSVC_VERSION {} ({}):".format( + repr(msvc_uwp_app), repr(unsupported.msvc_version), repr(kind_str) + ) + test.must_contain_all(test.stderr(), expect) + + # MSVC_SCRIPT_ARGS store is not validated test.write('SConstruct', textwrap.dedent( """ DefaultEnvironment(tools=[]) env = Environment(MSVC_VERSION={}, MSVC_SCRIPT_ARGS='store', tools=['msvc']) - print('LIBPATH|' + env['ENV'].get('LIBPATH', '')) - print('PLATFORM|' + env['ENV'].get('VSCMD_ARG_app_plat','')) - """.format(repr(supported.msvc_version)) + """.format(repr(unsupported.msvc_version)) )) - test.run(arguments='-Q -s', stdout=None) - failmsg = check_libpath(supported, True, test.stdout()) - if failmsg: - test.fail_test(message=failmsg) + test.run(arguments='-Q -s', stdout='') + failmsg, _ = check_libpath(unsupported, True, test.stdout()) + if not failmsg: + test.fail_test(message='unexpected: store found in libpath') -if LT_VS2015_versions: +if LT_VS2015_unsupported_versions: # VS2013 and earlier for uwp/store error - for unsupported in LT_VS2015_versions: + + for unsupported in LT_VS2015_unsupported_versions: + for msvc_uwp_app in (True, '1', False, '0', None): active = msvc_uwp_app in (True, '1') diff --git a/test/MSVC/VSWHERE-fixture/SConstruct b/test/MSVC/VSWHERE-fixture/SConstruct index 74eea18c16..0346f802e0 100644 --- a/test/MSVC/VSWHERE-fixture/SConstruct +++ b/test/MSVC/VSWHERE-fixture/SConstruct @@ -5,12 +5,16 @@ import os import os.path -from SCons.Tool.MSCommon.vc import VSWHERE_PATHS +import SCons.Tool.MSCommon as mscommon +import SCons.Tool.MSCommon.vc as vc # Dump out expected paths -for vw_path in VSWHERE_PATHS: - print("VSWHERE_PATH=%s" % vw_path) - +for exegroup in ( + vc._VSWHERE_EXEGROUP_MSVS, + vc._VSWHERE_EXEGROUP_PKGMGR +): + for vw_path in exegroup: + print("VSWHERE_PATH=%s" % vw_path) # Allow normal detection logic to find vswhere.exe DefaultEnvironment(tools=[]) @@ -21,6 +25,19 @@ print("VSWHERE-detect=%s" % env1['VSWHERE']) v_local = os.path.join(os.getcwd(), 'vswhere.exe') Execute(Copy(os.path.join(os.getcwd(), 'vswhere.exe'), env1['VSWHERE'])) +# Reset vswhere executable manager +# A vswhere.exe not equal to the vswhere.exe for initial detection is an error +vc._VSWhereExecutable.reset() + # With VSWHERE set to copied vswhere.exe (see above), find vswhere.exe env = Environment(VSWHERE=v_local) print("VSWHERE-env=%s" % env['VSWHERE']) + +# Reset vswhere executable manager +# A vswhere.exe not equal to the vswhere.exe for initial detection is an error +vc._VSWhereExecutable.reset() + +# With VSWHERE set to copied vswhere.exe (see above), find vswhere.exe +mscommon.vswhere_register_executable(v_local, priority='high') +env = Environment() +print("VSWHERE-util=%s" % env['VSWHERE']) diff --git a/test/MSVC/VSWHERE.py b/test/MSVC/VSWHERE.py index 8212415f37..b38ae5f2da 100644 --- a/test/MSVC/VSWHERE.py +++ b/test/MSVC/VSWHERE.py @@ -28,6 +28,7 @@ Also test that vswhere.exe is found and sets VSWHERE to the correct values """ import os.path +import SCons.Tool.MSCommon import TestSCons _python_ = TestSCons._python_ @@ -36,6 +37,10 @@ test.skip_if_not_msvc() test.verbose_set(1) +_default_vc = SCons.Tool.MSCommon.vc.get_installed_vcs_components()[0] +if _default_vc.msvc_vernum < 14.1: + test.skip_test("no installed msvc requires vswhere.exe; skipping test\n") + test.dir_fixture('VSWHERE-fixture') test.run(arguments=".") @@ -57,11 +62,14 @@ detected_path = l.strip().split('=')[-1] elif 'VSWHERE-env' in l: env_path = l.strip().split('=')[-1] + elif 'VSWHERE-util' in l: + util_path = l.strip().split('=')[-1] # Debug code # print("VPP:%s" % default_locations) # print("V-D:%s" % detected_path) # print("V-E:%s" % env_path) +# print("V-U:%s" % util_path) test.fail_test( @@ -82,6 +90,12 @@ message='VSWHERE not\n\t%s\n\t but\n\t%s' % (expected_env_path, env_path), ) +expected_util_path = os.path.join(test.workdir, 'vswhere.exe') +test.fail_test( + util_path != expected_env_path, + message='VSWHERE not\n\t%s\n\t but\n\t%s' % (expected_util_path, util_path), +) + test.pass_test() # here for reference, unused @@ -94,6 +108,7 @@ VSWHERE-detect=C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe Copy("C:\Users\Bill\AppData\Local\Temp\testcmd.11256.1ae1_as5\vswhere.exe", "C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe") VSWHERE-env=C:\Users\Bill\AppData\Local\Temp\testcmd.11256.1ae1_as5\vswhere.exe +VSWHERE-util=C:\Users\Bill\AppData\Local\Temp\testcmd.11256.1ae1_as5\vswhere.exe scons: done reading SConscript files. scons: Building targets ... scons: `.' is up to date. diff --git a/test/MSVC/msvc.py b/test/MSVC/msvc.py index 6bc36759a2..e81ecea7e8 100644 --- a/test/MSVC/msvc.py +++ b/test/MSVC/msvc.py @@ -116,7 +116,7 @@ # TODO: Reevaluate if having this part of the test makes sense any longer # using precompiled headers should be faster -limit = slow*0.90 +limit = slow*1.00 if fast >= limit: print("Using precompiled headers was not fast enough:") print("slow.obj: %.3fs" % slow) diff --git a/test/MSVC/msvc_cache_force_defaults.py b/test/MSVC/msvc_cache_force_defaults.py index e0ed1c3543..ad67304d56 100644 --- a/test/MSVC/msvc_cache_force_defaults.py +++ b/test/MSVC/msvc_cache_force_defaults.py @@ -30,6 +30,10 @@ import textwrap from SCons.Tool.MSCommon.vc import get_installed_vcs_components +from SCons.Tool.MSCommon.MSVC.Kind import ( + msvc_version_is_express, + msvc_version_is_btdispatch, +) import TestSCons test = TestSCons.TestSCons() @@ -68,8 +72,15 @@ test.run(arguments="-Q -s", status=0, stdout=None) cache_arg = test.stdout().strip() if default_version.msvc_verstr == '14.0': - # VS2015: target_arch msvc_sdk_version - expect = r'^SCRIPT_ARGS: .* [0-9.]+$' + if msvc_version_is_express(default_version.msvc_version): + # VS2015 Express: target_arch + expect = r'^SCRIPT_ARGS: [a-zA-Z0-9_]+$' + elif msvc_version_is_btdispatch(default_version.msvc_version): + # VS2015 BTDispatch: target_arch + expect = r'^SCRIPT_ARGS: [a-zA-Z0-9_]+$' + else: + # VS2015: target_arch msvc_sdk_version + expect = r'^SCRIPT_ARGS: [a-zA-Z0-9_]+ [0-9.]+$' else: # VS2017+ msvc_sdk_version msvc_toolset_version expect = r'^SCRIPT_ARGS: [0-9.]+ -vcvars_ver=[0-9.]+$' diff --git a/test/MSVC/no_msvc.py b/test/MSVC/no_msvc.py index 35cce9258d..aa9e2b8e57 100644 --- a/test/MSVC/no_msvc.py +++ b/test/MSVC/no_msvc.py @@ -75,8 +75,7 @@ def exists(env): # test no msvc's and msvc_query_version_toolset() call test.file_fixture('no_msvc/no_msvcs_sconstruct_msvc_query_toolset_version.py', 'SConstruct') -test.run(arguments='-Q -s') -test.must_contain_all(test.stdout(), 'msvc_version=None, msvc_toolset_version=None') +test.run(arguments='-Q -s', status=2, stderr=r"^.*MSVCToolsetVersionNotFound.+", match=TestSCons.match_re_dotall) test.pass_test() diff --git a/test/MSVC/query_vcbat.py b/test/MSVC/query_vcbat.py index 6e30706b89..28bbd02dc3 100644 --- a/test/MSVC/query_vcbat.py +++ b/test/MSVC/query_vcbat.py @@ -51,7 +51,7 @@ # print k, v #MergeMSVSBatFile(env, 9.0) #print(env['ENV']['PATH']) -print(query_versions()) +print(query_versions(env=None)) """) test.run(stderr=None) diff --git a/test/fixture/no_msvc/no_msvcs_sconstruct.py b/test/fixture/no_msvc/no_msvcs_sconstruct.py index 1aed9ecc1c..8dba65acba 100644 --- a/test/fixture/no_msvc/no_msvcs_sconstruct.py +++ b/test/fixture/no_msvc/no_msvcs_sconstruct.py @@ -7,7 +7,7 @@ DefaultEnvironment(tools=[]) -def DummyVsWhere(msvc_version, env): +def DummyVsWhere(msvc_version, vswhere_exe): # not testing versions with vswhere, so return none return None @@ -16,6 +16,6 @@ def DummyVsWhere(msvc_version, env): (SCons.Util.HKEY_LOCAL_MACHINE, r'') ] -SCons.Tool.MSCommon.vc.find_vc_pdir_vswhere = DummyVsWhere +SCons.Tool.MSCommon.vc._find_vc_pdir_vswhere = DummyVsWhere env = SCons.Environment.Environment() print('MSVC_VERSION=' + str(env.get('MSVC_VERSION'))) diff --git a/test/fixture/no_msvc/no_msvcs_sconstruct_msvc_query_toolset_version.py b/test/fixture/no_msvc/no_msvcs_sconstruct_msvc_query_toolset_version.py index fc5558b54a..9fe9440321 100644 --- a/test/fixture/no_msvc/no_msvcs_sconstruct_msvc_query_toolset_version.py +++ b/test/fixture/no_msvc/no_msvcs_sconstruct_msvc_query_toolset_version.py @@ -7,7 +7,7 @@ DefaultEnvironment(tools=[]) -def DummyVsWhere(msvc_version, env): +def DummyVsWhere(msvc_version, vswhere_exe): # not testing versions with vswhere, so return none return None @@ -16,6 +16,6 @@ def DummyVsWhere(msvc_version, env): (SCons.Util.HKEY_LOCAL_MACHINE, r'') ] -SCons.Tool.MSCommon.vc.find_vc_pdir_vswhere = DummyVsWhere +SCons.Tool.MSCommon.vc._find_vc_pdir_vswhere = DummyVsWhere msvc_version, msvc_toolset_version = SCons.Tool.MSCommon.msvc_query_version_toolset() print(f'msvc_version={msvc_version!r}, msvc_toolset_version={msvc_toolset_version!r}') diff --git a/test/fixture/no_msvc/no_msvcs_sconstruct_msvc_sdk_versions.py b/test/fixture/no_msvc/no_msvcs_sconstruct_msvc_sdk_versions.py index 6e7562d390..1880b8bf7a 100644 --- a/test/fixture/no_msvc/no_msvcs_sconstruct_msvc_sdk_versions.py +++ b/test/fixture/no_msvc/no_msvcs_sconstruct_msvc_sdk_versions.py @@ -7,7 +7,7 @@ DefaultEnvironment(tools=[]) -def DummyVsWhere(msvc_version, env): +def DummyVsWhere(msvc_version, vswhere_exe): # not testing versions with vswhere, so return none return None @@ -16,6 +16,6 @@ def DummyVsWhere(msvc_version, env): (SCons.Util.HKEY_LOCAL_MACHINE, r'') ] -SCons.Tool.MSCommon.vc.find_vc_pdir_vswhere = DummyVsWhere +SCons.Tool.MSCommon.vc._find_vc_pdir_vswhere = DummyVsWhere sdk_version_list = SCons.Tool.MSCommon.msvc_sdk_versions() print('sdk_version_list=' + repr(sdk_version_list)) diff --git a/test/fixture/no_msvc/no_msvcs_sconstruct_msvc_toolset_versions.py b/test/fixture/no_msvc/no_msvcs_sconstruct_msvc_toolset_versions.py index c2208cd393..35a45c3b62 100644 --- a/test/fixture/no_msvc/no_msvcs_sconstruct_msvc_toolset_versions.py +++ b/test/fixture/no_msvc/no_msvcs_sconstruct_msvc_toolset_versions.py @@ -7,7 +7,7 @@ DefaultEnvironment(tools=[]) -def DummyVsWhere(msvc_version, env): +def DummyVsWhere(msvc_version, vswhere_exe): # not testing versions with vswhere, so return none return None @@ -16,6 +16,6 @@ def DummyVsWhere(msvc_version, env): (SCons.Util.HKEY_LOCAL_MACHINE, r'') ] -SCons.Tool.MSCommon.vc.find_vc_pdir_vswhere = DummyVsWhere +SCons.Tool.MSCommon.vc._find_vc_pdir_vswhere = DummyVsWhere toolset_version_list = SCons.Tool.MSCommon.msvc_toolset_versions() print('toolset_version_list=' + repr(toolset_version_list)) diff --git a/test/fixture/no_msvc/no_msvcs_sconstruct_tools.py b/test/fixture/no_msvc/no_msvcs_sconstruct_tools.py index 2f2f07aedc..baac01bb2c 100644 --- a/test/fixture/no_msvc/no_msvcs_sconstruct_tools.py +++ b/test/fixture/no_msvc/no_msvcs_sconstruct_tools.py @@ -7,7 +7,7 @@ DefaultEnvironment(tools=[]) -def DummyVsWhere(msvc_version, env): +def DummyVsWhere(msvc_version, vswhere_exe): # not testing versions with vswhere, so return none return None @@ -16,6 +16,6 @@ def DummyVsWhere(msvc_version, env): (SCons.Util.HKEY_LOCAL_MACHINE, r'') ] -SCons.Tool.MSCommon.vc.find_vc_pdir_vswhere = DummyVsWhere +SCons.Tool.MSCommon.vc._find_vc_pdir_vswhere = DummyVsWhere env = SCons.Environment.Environment(tools=['myignoredefaultmsvctool']) diff --git a/test/fixture/no_msvc/no_msvcs_sconstruct_version.py b/test/fixture/no_msvc/no_msvcs_sconstruct_version.py index bd81688e35..3bf15d35e9 100644 --- a/test/fixture/no_msvc/no_msvcs_sconstruct_version.py +++ b/test/fixture/no_msvc/no_msvcs_sconstruct_version.py @@ -7,7 +7,7 @@ DefaultEnvironment(tools=[]) -def DummyVsWhere(msvc_version, env): +def DummyVsWhere(msvc_version, vswhere_exe): # not testing versions with vswhere, so return none return None @@ -16,6 +16,6 @@ def DummyVsWhere(msvc_version, env): (SCons.Util.HKEY_LOCAL_MACHINE, r'') ] -SCons.Tool.MSCommon.vc.find_vc_pdir_vswhere = DummyVsWhere +SCons.Tool.MSCommon.vc._find_vc_pdir_vswhere = DummyVsWhere SCons.Tool.MSCommon.msvc_set_notfound_policy('error') env = SCons.Environment.Environment(MSVC_VERSION='14.3') diff --git a/testing/framework/TestSConsMSVS.py b/testing/framework/TestSConsMSVS.py index 8c34372b85..91aa329d66 100644 --- a/testing/framework/TestSConsMSVS.py +++ b/testing/framework/TestSConsMSVS.py @@ -680,7 +680,7 @@ def msvs_versions(self): import SCons import SCons.Tool.MSCommon print("self.scons_version =%%s"%%repr(SCons.__%s__)) -print("self._msvs_versions =%%s"%%str(SCons.Tool.MSCommon.query_versions())) +print("self._msvs_versions =%%s"%%str(SCons.Tool.MSCommon.query_versions(env=None))) """ % 'version' self.run(arguments = '-n -q -Q -f -', stdin = input) @@ -738,13 +738,13 @@ def msvs_substitute(self, input, msvs_ver, result = result.replace('\n', sln_sccinfo) return result - def get_msvs_executable(self, version): + def get_msvs_executable(self, version, env=None): """Returns a full path to the executable (MSDEV or devenv) for the specified version of Visual Studio. """ from SCons.Tool.MSCommon import get_vs_by_version - msvs = get_vs_by_version(version) + msvs = get_vs_by_version(version, env) if not msvs: return None return msvs.get_executable()