From 7d0f80485e5629bed900d9145e16749d6df1a89c Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Thu, 7 Sep 2023 21:50:10 -0400 Subject: [PATCH 01/24] MSVC: detection fixes and changes Changes: * VS/VC roots are classified by their installed features (e.g, devenv.com, vcexpress.com, etc.). This provides for special-case verification of batch file arguments based on the internal classification. * Consistent with earlier behavior, express versions are used for the non-express msvc version symbol for 14.1 and 8.0 when the express version is the only version installed. * There is a strong possibility that 14.0Exp may not have been detected correctly. It appears that registry keys for earlier versions of msvc are not populated. Refined detection of 14.0Exp was added. * Special case handling of VS2015 BuildTools was added. The msvc batch files restrict the arguments available as compared to full versions. The arguments may be accepted and ignored possibly resulting in build failures that are likely not easy to diagnose. * Special case handling og VS2015 Express was added. The msvc batch files restrict the arguments available as compared to full versions. For example, store/UWP build are only available for x86 targets. * Windows/Platform SDK installations of 7.1, 7.0, and 6.1 populate registry keys and installation folders that were detected by scons (versions 10.0 and 9.0). Unfortunately, the generated files are intended to be used via SetEnv.cmd and result in errors. The detection of sdk-only installations was added and the roots are ignored. * The relative imports of the MSCommon module were changed to top-level absolute imports in a number of microsoft tools. Moving any of the tools to the site tools folder failed on import (i.e., the relative paths become invalid when moved). * VS2005 to VS2015 vcvarsall.bat dispatches to a dependent batch file when configuring the msvc environment. In certain installation scenarios, the dependent batch file (e.g., vcvars64.bat) may not exist. The existence of vcvarsall.bat, the dependent batch file, and the compiler executable are now verified. * MSVC configuration data specific to versions VS2005 to VS2008 was added as the dependent batch files have different names than the batch files for VS2010 and later. VC++ For Python is handled as a special case as the dependent batch files: are not used and are in different locations. * When VC++ For Python is installed using the ALLUSERS=1 command-line option, the registry keys written are under HKLM rather than HKCU. VC++ For Python installed for all users is now correctly detected. * The existing detection configuration for vswhere and the registry was refactored to separate the two methods of detection. * The detection of the msvc compiler executable has been modified and no longer considers the os environment. The detection of the msvc compiler executable was modified to provide more detailed warning messages. --- SCons/Tool/MSCommon/MSVC/Kind.py | 671 +++++++++++ SCons/Tool/MSCommon/MSVC/Registry.py | 3 + SCons/Tool/MSCommon/MSVC/ScriptArguments.py | 52 +- .../MSCommon/MSVC/ScriptArgumentsTests.py | 127 +- SCons/Tool/MSCommon/MSVC/Util.py | 20 + SCons/Tool/MSCommon/MSVC/Warnings.py | 3 + SCons/Tool/MSCommon/MSVC/__init__.py | 1 + SCons/Tool/MSCommon/vc.py | 1033 ++++++++++++++--- SCons/Tool/MSCommon/vcTests.py | 125 +- SCons/Tool/MSCommon/vs.py | 2 +- SCons/Tool/midl.py | 2 +- SCons/Tool/mslib.py | 5 +- SCons/Tool/mslink.py | 7 +- SCons/Tool/mssdk.py | 6 +- SCons/Tool/msvc.py | 9 +- SCons/Tool/msvs.py | 5 +- SCons/Tool/msvsTests.py | 6 +- test/MSVC/MSVC_SDK_VERSION.py | 63 +- test/MSVC/MSVC_TOOLSET_VERSION.py | 1 + test/MSVC/MSVC_USE_SETTINGS.py | 2 +- test/MSVC/MSVC_UWP_APP.py | 137 ++- test/MSVC/VSWHERE.py | 5 + test/MSVC/msvc_cache_force_defaults.py | 15 +- 23 files changed, 1956 insertions(+), 344 deletions(-) create mode 100644 SCons/Tool/MSCommon/MSVC/Kind.py diff --git a/SCons/Tool/MSCommon/MSVC/Kind.py b/SCons/Tool/MSCommon/MSVC/Kind.py new file mode 100644 index 0000000000..8b254a23b4 --- /dev/null +++ b/SCons/Tool/MSCommon/MSVC/Kind.py @@ -0,0 +1,671 @@ +# 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 + # print('register_kind: msvc_version=%s, kind=%s' % (repr(msvc_version), repr(VCVER_KIND_STR[kind_t.kind]))) + 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_installed_vcs + global _cache_vcver_kind_map + global _cache_pdir_vswhere_kind + global _cache_pdir_registry_kind + global _cache_pdir_registry_winsdk + + debug('') + + _cache_installed_vcs = None + _cache_vcver_kind_map = {} + _cache_pdir_vswhere_kind = {} + _cache_pdir_registry_kind = {} + _cache_pdir_registry_winsdk = {} diff --git a/SCons/Tool/MSCommon/MSVC/Registry.py b/SCons/Tool/MSCommon/MSVC/Registry.py index eee20ccbc7..970b4d4412 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..c689d7a0b5 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, @@ -196,7 +197,7 @@ def _toolset_version(version): 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)) @@ -218,6 +219,23 @@ def _msvc_script_argument_uwp(env, msvc, arglist): ) 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,7 +268,7 @@ 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: debug( @@ -263,6 +281,14 @@ def _msvc_script_argument_sdk_constraints(msvc, sdk_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 + for msvc_sdk_version in msvc.vs_def.vc_sdk_versions: re_sdk_version = re_sdk_dispatch_map[msvc_sdk_version] if re_sdk_version.match(sdk_version): @@ -310,7 +336,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) @@ -336,6 +362,9 @@ def _msvc_script_default_sdk(env, msvc, platform_def, arglist, force_sdk: bool=F if msvc.vs_def.vc_buildtools_def.vc_version_numeric < VS2015.vc_buildtools_def.vc_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) if not len(sdk_list): return None @@ -873,7 +902,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 +929,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 diff --git a/SCons/Tool/MSCommon/MSVC/ScriptArgumentsTests.py b/SCons/Tool/MSCommon/MSVC/ScriptArgumentsTests.py index 670576b046..c79f044c55 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 @@ -312,7 +322,7 @@ def run_msvc_script_args(self) -> None: # 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 +333,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): @@ -368,9 +378,9 @@ def run_msvc_script_args(self) -> None: 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 +389,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}, ]: 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 +408,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 +417,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) @@ -519,25 +529,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 +620,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 +638,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 64b8d673eb..d41ff7d9f6 100644 --- a/SCons/Tool/MSCommon/MSVC/Util.py +++ b/SCons/Tool/MSCommon/MSVC/Util.py @@ -155,6 +155,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: diff --git a/SCons/Tool/MSCommon/MSVC/Warnings.py b/SCons/Tool/MSCommon/MSVC/Warnings.py index cab5145a99..902de299a6 100644 --- a/SCons/Tool/MSCommon/MSVC/Warnings.py +++ b/SCons/Tool/MSCommon/MSVC/Warnings.py @@ -30,6 +30,9 @@ class VisualCWarning(SCons.Warnings.WarningOnByDefault): pass +class VSWherePathWarning(VisualCWarning): + pass + class MSVCScriptExecutionWarning(VisualCWarning): pass 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/vc.py b/SCons/Tool/MSCommon/vc.py index fd806bdd0a..9891ac3506 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,10 +49,13 @@ namedtuple, OrderedDict, ) +import json +from functools import cmp_to_key import SCons.Util import SCons.Warnings from SCons.Tool import find_program_path +import SCons.Script from . import common from .common import CONFIG_CACHE, debug @@ -65,11 +65,28 @@ from .MSVC.Exceptions import ( VisualCException, + MSVCInternalError, MSVCUserError, MSVCArgumentError, MSVCToolsetVersionNotFound, ) +# user vswhere.exe location as command-line option + +_vswhere_cmdline_arg = '--vswhere-path' +_vswhere_cmdline_var = 'vswhere_path' + +SCons.Script.AddOption( + _vswhere_cmdline_arg, + dest=_vswhere_cmdline_var, + type="string", + nargs=1, + action="store", + metavar='PATH', + default=None, + help='Fully qualified path to vswhere.exe.', +) + # external exceptions class MSVCUnsupportedHostArch(VisualCException): @@ -89,9 +106,6 @@ class MSVCUseSettingsError(MSVCUserError): class UnsupportedVersion(VisualCException): pass -class MissingConfiguration(VisualCException): - pass - class BatchFileExecutionError(VisualCException): pass @@ -105,7 +119,7 @@ class BatchFileExecutionError(VisualCException): # MSVC 9.0 preferred query order: # True: VCForPython, VisualStudio -# FAlse: VisualStudio, VCForPython +# False: VisualStudio, VCForPython _VC90_Prefer_VCForPython = True # Dict to 'canonalize' the arch @@ -396,7 +410,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. @@ -405,23 +419,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')), } @@ -457,6 +471,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 @@ -490,6 +551,11 @@ def _make_target_host_map(all_hosts, host_all_targets_map): _CL_EXE_NAME = 'cl.exe' +_VSWHERE_EXE = 'vswhere.exe' + +# case-insensitive endswith vswhere.exe +_re_match_vswhere = re.compile('^.*' + re.escape(_VSWHERE_EXE) + '$', re.IGNORECASE) + 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' @@ -569,9 +635,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 @@ -690,38 +759,58 @@ 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\VCExpress\14.0\Setup\VC\ProductDir')], + (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\WDExpress\14.0\Setup\VS\ProductDir'), # vs root + (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\14.0\Setup\VC\ProductDir'), # not populated? + (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\14.0\Setup\VC\ProductDir')], # kind detection '12.0': [ (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\12.0\Setup\VC\ProductDir'), ], @@ -741,17 +830,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'), @@ -767,6 +859,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) @@ -788,6 +936,88 @@ def msvc_version_to_maj_min(msvc_version): os.path.expandvars(r"%ChocolateyInstall%\bin"), ]] +# normalize user-specified vswhere paths + +_cache_user_vswhere_paths = {} + +def _vswhere_user_path(pval): + global _cache_user_vswhere_path + + rval = _cache_user_vswhere_paths.get(pval, UNDEFINED) + if rval != UNDEFINED: + debug('vswhere_path=%s', rval) + return rval + + vswhere_path = None + if pval: + + if not os.path.exists(pval): + + warn_msg = f'vswhere path not found: {pval!r}' + SCons.Warnings.warn(MSVC.Warnings.VSWherePathWarning, warn_msg) + debug(warn_msg) + + elif not _re_match_vswhere.match(pval): + + warn_msg = f'vswhere.exe not found in vswhere path: {pval!r}' + SCons.Warnings.warn(MSVC.Warnings.VSWherePathWarning, warn_msg) + debug(warn_msg) + + else: + + vswhere_path = MSVC.Util.process_path(pval) + debug('vswhere_path=%s', vswhere_path) + + _cache_user_vswhere_paths[pval] = vswhere_path + + return vswhere_path + +# normalized user-specified command-line vswhere path + +_vswhere_path_cmdline = UNDEFINED + +def _msvc_cmdline_vswhere(): + global _vswhere_path_cmdline + + if _vswhere_path_cmdline == UNDEFINED: + + vswhere_path = None + vswhere_user = SCons.Script.GetOption(_vswhere_cmdline_var) + + if vswhere_user: + vswhere_path = _vswhere_user_path(vswhere_user) + + _vswhere_path_cmdline = vswhere_path + debug('vswhere_path=%s', vswhere_path) + + return _vswhere_path_cmdline + +# normalized default vswhere path + +_vswhere_paths_processed = [ + MSVC.Util.process_path(pval) + for pval in VSWHERE_PATHS + if os.path.exists(pval) +] + +_vswhere_path_default = UNDEFINED + +def _msvc_default_vswhere(): + global _vswhere_paths_processed + global _vswhere_path_default + + if _vswhere_path_default == UNDEFINED: + + if _vswhere_paths_processed: + vswhere_path = _vswhere_paths_processed[0] + else: + vswhere_path = None + + _vswhere_path_default = vswhere_path + debug('vswhere_path=%s', vswhere_path) + + return _vswhere_path_default + def msvc_find_vswhere(): """ Find the location of vswhere """ # For bug 3333: support default location of vswhere for both @@ -795,14 +1025,265 @@ def msvc_find_vswhere(): # 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 - break + vswhere_path = _msvc_cmdline_vswhere() + if not vswhere_path: + for pf in VSWHERE_PATHS: + if os.path.exists(pf): + vswhere_path = pf + break return vswhere_path +class _VSWhere: + + reset_funcs = [] + + @classmethod + def reset(cls): + + cls.seen_vswhere = set() + cls.seen_root = set() + + cls.vswhere_executables = [] + + cls.msvc_instances = [] + cls.msvc_map = {} + + @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 + + @classmethod + def register_reset_func(cls, func): + cls.reset_funcs.append(func) + + @classmethod + def call_reset_funcs(cls): + for func in cls.reset_funcs: + func() + +_VSWhere.reset() + +def _filter_vswhere_paths(env): + + vswhere_paths = [] + + if env and 'VSWHERE' in env: + vswhere_environ = _vswhere_user_path(env.subst('$VSWHERE')) + if vswhere_environ and vswhere_environ not in _VSWhere.seen_vswhere: + vswhere_paths.append(vswhere_environ) + + vswhere_cmdline = _msvc_cmdline_vswhere() + if vswhere_cmdline and vswhere_cmdline not in _VSWhere.seen_vswhere: + vswhere_paths.append(vswhere_cmdline) + + vswhere_default = _msvc_default_vswhere() + if vswhere_default and vswhere_default not in _VSWhere.seen_vswhere: + vswhere_paths.append(vswhere_default) + + debug('vswhere_paths=%s', vswhere_paths) + return vswhere_paths + +def _vswhere_query_json_output(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) + + 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) + break + except Exception as e: + errmsg = str(e) + debug("%s: %s", type(e).__name__, errmsg) + raise + + if not cp.stdout: + debug("no vswhere information returned") + break + + vswhere_output = cp.stdout.decode('utf8', errors='replace') + if not vswhere_output: + debug("no vswhere information output") + break + + try: + vswhere_output_json = json.loads(vswhere_output) + except json.decoder.JSONDecodeError: + debug("json decode exception loading vswhere output") + break + + vswhere_json = vswhere_output_json + break + + debug('vswhere_json=%s, vswhere_exe=%s', bool(vswhere_json), repr(vswhere_exe)) + + return vswhere_json + +MSVC_INSTANCE = namedtuple('MSVCInstance', [ + 'vc_path', + 'vc_version', + 'vc_version_numeric', + 'vc_version_scons', + 'vc_release', + 'vc_component_id', + 'vc_component_rank', + 'vc_component_suffix', +]) + +def _update_vswhere_msvc_map(env): + + vswhere_paths = _filter_vswhere_paths(env) + if not vswhere_paths: + debug('new_roots=False, msvc_instances=%s', len(_VSWhere.msvc_instances)) + return _VSWhere.msvc_map + + n_instances = len(_VSWhere.msvc_instances) + + for vswhere_exe in vswhere_paths: + + if vswhere_exe in _VSWhere.seen_vswhere: + continue + + _VSWhere.seen_vswhere.add(vswhere_exe) + _VSWhere.vswhere_executables.append(vswhere_exe) + + debug('vswhere_exe=%s', repr(vswhere_exe)) + + vswhere_json = _vswhere_query_json_output( + vswhere_exe, + ['-all', '-products', '*'] + ) + + if not vswhere_json: + continue + + 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_root = os.path.join(installation_path, 'VC') + if not os.path.exists(vc_root): + continue + + vc_root = MSVC.Util.process_path(vc_root) + if vc_root in _VSWhere.seen_root: + continue + + _VSWhere.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) + 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) + 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 = MSVC_INSTANCE( + vc_path = vc_root, + 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, + ) + + _VSWhere.msvc_instances.append(msvc_instance) + + new_roots = bool(len(_VSWhere.msvc_instances) > n_instances) + if new_roots: + + _VSWhere.msvc_instances = sorted( + _VSWhere.msvc_instances, + key=cmp_to_key(_VSWhere.msvc_instances_default_order) + ) + + _VSWhere.msvc_map = {} + + for msvc_instance in _VSWhere.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) + ) + + key = (msvc_instance.vc_version_scons, msvc_instance.vc_release) + _VSWhere.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) + _VSWhere.msvc_map.setdefault(key,[]).append(msvc_instance) + + _VSWhere.call_reset_funcs() + + debug('new_roots=%s, msvc_instances=%s', new_roots, len(_VSWhere.msvc_instances)) + + return _VSWhere.msvc_map + +_cache_pdir_vswhere_queries = {} + +def _reset_pdir_vswhere_queries(): + global _cache_pdir_vswhere_queries + _cache_pdir_vswhere_queries = {} + debug('reset _cache_pdir_vswhere_queries') + +# register pdir vswhere cache reset function with vswhere state manager +_VSWhere.register_reset_func(_reset_pdir_vswhere_queries) + def find_vc_pdir_vswhere(msvc_version, env=None): """ Find the MSVC product directory using the vswhere program. @@ -817,109 +1298,225 @@ 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 = _update_vswhere_msvc_map(env) + 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') + save_pdir_kind = [] - if vswhere_path is None: - return None + is_win64 = common.is_win64() - debug('VSWHERE: %s', vswhere_path) - for vswhere_version_args in vswhere_version: + pdir = None + kind_t = None - vswhere_cmd = [vswhere_path] + vswhere_version_args + ["-property", "installationPath"] + root = 'Software\\' + for hkroot, key in regkeys: - debug("running: %s", vswhere_cmd) + if not hkroot or not key: + continue - # TODO: Python 3.7 - # cp = subprocess.run(vswhere_cmd, capture_output=True, check=True) # 3.7+ only - cp = subprocess.run(vswhere_cmd, stdout=PIPE, stderr=PIPE, check=True) - - if cp.stdout: - # 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 + + 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 == '14.0Exp' and key.lower().endswith('\\14.0\\setup\\vs\\productdir'): + # 2015Exp 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 + + 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 -def find_vc_pdir(env, msvc_version): + return pdir + +def find_vc_pdir(msvc_version, env=None): """Find the MSVC product directory for the given version. - 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. + Use find_vc_pdir_vsvwhere for msvc versions 14.1 and later. + Use find_vc_pdir_registry for msvc versions 14.0 and earlier. 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. - MissingConfiguration: found version but the directory is missing. - Both exceptions inherit from VisualCException. + 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' and key.lower().endswith('\\vcforpython\\9.0\\installdir'): - # Visual C++ for Python registry key is installdir (root) not productdir (vc) - comps = os.path.join(comps, 'VC') - debug('found VC in registry: %s', comps) - if os.path.exists(comps): - return comps - else: - debug('reg says dir is %s, but it does not exist. (ignoring)', comps) - raise MissingConfiguration(f"registry dir {comps} not found on the filesystem") + if msvc_version in _VSWHERE_SUPPORTED_VCVER: + + pdir = find_vc_pdir_vswhere(msvc_version, env) + 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 +# 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_user_vswhere_path + global _cache_pdir_vswhere_queries + global _cache_pdir_registry_queries + _cache_user_vswhere_paths = {} + _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 @@ -939,6 +1536,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 @@ -952,15 +1550,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 @@ -973,7 +1582,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 @@ -998,16 +1611,26 @@ 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 + +# register vcs cache reset function with vswhere state manager +_VSWhere.register_reset_func(_reset_installed_vcs) + _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 @@ -1066,33 +1689,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) @@ -1109,7 +1771,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 @@ -1122,6 +1784,10 @@ def _check_cl_exists_in_vc_dir(env, vc_dir, msvc_version) -> bool: def get_installed_vcs(env=None): global __INSTALLED_VCS_RUN + # the installed vcs cache is cleared + # if new vc roots are discovered + _update_vswhere_msvc_map(env) + if __INSTALLED_VCS_RUN is not None: return __INSTALLED_VCS_RUN @@ -1137,10 +1803,10 @@ 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) @@ -1163,8 +1829,9 @@ 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() + _VSWhere.reset() MSVC._reset() def msvc_default_version(env=None): @@ -1340,13 +2007,10 @@ 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 - except MissingConfiguration: - # Found version, directory missing - pass debug('product directory: version=%s, pdir=%s', version, pdir) # Find the host, target, and all candidate (host, target) platform combinations: @@ -1388,6 +2052,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) @@ -1523,19 +2194,32 @@ def msvc_setup_env(env): SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, warn_msg) return None + found_cl_path = None + found_cl_envpath = None + + seen_path = False for k, v in d.items(): + if not seen_path and k.upper() == 'PATH': + seen_path = True + found_cl_path = SCons.Util.WhereIs('cl', v) + found_cl_envpath = SCons.Util.WhereIs('cl', env['ENV'].get(k, [])) env.PrependENVPath(k, v, delete_existing=True) debug("env['ENV']['%s'] = %s", k, env['ENV'][k]) - # final check to issue a warning if the compiler is not present - if not find_program_path(env, 'cl'): - debug("did not find %s", _CL_EXE_NAME) + debug("cl paths: d['PATH']=%s, ENV['PATH']=%s", repr(found_cl_path), repr(found_cl_envpath)) + + # final check to issue a warning if the requested compiler is not present + if not found_cl_path: if CONFIG_CACHE: propose = f"SCONS_CACHE_MSVC_CONFIG caching enabled, remove cache file {CONFIG_CACHE} if out of date." else: propose = "It may need to be installed separately with Visual Studio." - warn_msg = f"Could not find MSVC compiler 'cl'. {propose}" - SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, warn_msg) + warn_msg = "Could not find requested MSVC compiler 'cl'." + if found_cl_envpath: + warn_msg += " A 'cl' was found on the scons ENV path which may be erroneous." + warn_msg += " %s" + debug(warn_msg, propose) + SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, warn_msg % propose) def msvc_exists(env=None, version=None): vcs = get_installed_vcs(env) @@ -1612,7 +2296,6 @@ def msvc_sdk_versions(version=None, msvc_uwp_app: bool=False): 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)) - env = None rval = [] if not msvc_version: @@ -1626,7 +2309,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) if not vc_dir: debug('VC folder not found for version %s', repr(msvc_version)) return rval @@ -1637,7 +2320,6 @@ def msvc_toolset_versions(msvc_version=None, full: bool=True, sxs: bool=False): def msvc_toolset_versions_spectre(msvc_version=None): debug('msvc_version=%s', repr(msvc_version)) - env = None rval = [] if not msvc_version: @@ -1651,7 +2333,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) if not vc_dir: debug('VC folder not found for version %s', repr(msvc_version)) return rval @@ -1706,7 +2388,6 @@ def msvc_query_version_toolset(version=None, prefer_newest: bool=True): """ debug('version=%s, prefer_newest=%s', repr(version), repr(prefer_newest)) - env = None msvc_version = None msvc_toolset_version = None @@ -1771,7 +2452,7 @@ def msvc_query_version_toolset(version=None, prefer_newest: bool=True): continue seen_msvc_version.add(query_msvc_version) - vc_dir = find_vc_pdir(env, query_msvc_version) + vc_dir = find_vc_pdir(query_msvc_version) if not vc_dir: continue diff --git a/SCons/Tool/MSCommon/vcTests.py b/SCons/Tool/MSCommon/vcTests.py index 9c2bd0ac8c..8c22ba37e9 100644 --- a/SCons/Tool/MSCommon/vcTests.py +++ b/SCons/Tool/MSCommon/vcTests.py @@ -75,7 +75,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 +94,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 +105,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 +123,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 +194,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 +205,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,7 +213,6 @@ 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 diff --git a/SCons/Tool/MSCommon/vs.py b/SCons/Tool/MSCommon/vs.py index af0fd26e5a..ef4f13cdfb 100644 --- a/SCons/Tool/MSCommon/vs.py +++ b/SCons/Tool/MSCommon/vs.py @@ -66,7 +66,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 = SCons.Tool.MSCommon.vc.find_vc_pdir(self.vc_version, env) if not dir: debug('no installed VC %s', self.vc_version) return None diff --git a/SCons/Tool/midl.py b/SCons/Tool/midl.py index 0c640f5092..2ae3f73d06 100644 --- a/SCons/Tool/midl.py +++ b/SCons/Tool/midl.py @@ -37,7 +37,7 @@ import SCons.Scanner.IDL import SCons.Util -from .MSCommon import msvc_setup_env_tool +from SCons.Tool.MSCommon import msvc_setup_env_tool tool_name = 'midl' diff --git a/SCons/Tool/mslib.py b/SCons/Tool/mslib.py index 6e15a808fa..bdce135f81 100644 --- a/SCons/Tool/mslib.py +++ b/SCons/Tool/mslib.py @@ -41,7 +41,10 @@ import SCons.Tool.msvc import SCons.Util -from .MSCommon import msvc_setup_env_tool, msvc_setup_env_once +from SCons.Tool.MSCommon import ( + msvc_setup_env_tool, + msvc_setup_env_once, +) tool_name = 'mslib' diff --git a/SCons/Tool/mslink.py b/SCons/Tool/mslink.py index 1e5b71ae10..74ceaa8572 100644 --- a/SCons/Tool/mslink.py +++ b/SCons/Tool/mslink.py @@ -43,8 +43,11 @@ import SCons.Tool.msvs import SCons.Util -from .MSCommon import msvc_setup_env_once, msvc_setup_env_tool -from .MSCommon.common import get_pch_node +from SCons.Tool.MSCommon import ( + msvc_setup_env_once, + msvc_setup_env_tool, +) +from SCons.Tool.MSCommon.common import get_pch_node tool_name = 'mslink' diff --git a/SCons/Tool/mssdk.py b/SCons/Tool/mssdk.py index 0151eff2b8..ef272c033d 100644 --- a/SCons/Tool/mssdk.py +++ b/SCons/Tool/mssdk.py @@ -33,8 +33,10 @@ selection method. """ -from .MSCommon import mssdk_exists, \ - mssdk_setup_env +from SCons.Tool.MSCommon import ( + mssdk_exists, + mssdk_setup_env, +) def generate(env) -> None: """Add construction variables for an MS SDK to an Environment.""" diff --git a/SCons/Tool/msvc.py b/SCons/Tool/msvc.py index 33a67d0f4f..6afa171c97 100644 --- a/SCons/Tool/msvc.py +++ b/SCons/Tool/msvc.py @@ -44,8 +44,13 @@ import SCons.Warnings import SCons.Scanner.RC -from .MSCommon import msvc_setup_env_tool, msvc_setup_env_once, msvc_version_to_maj_min, msvc_find_vswhere -from .MSCommon.common import get_pch_node +from SCons.Tool.MSCommon import ( + msvc_setup_env_tool, + msvc_setup_env_once, + msvc_version_to_maj_min, + msvc_find_vswhere, +) +from SCons.Tool.MSCommon.common import get_pch_node tool_name = 'msvc' diff --git a/SCons/Tool/msvs.py b/SCons/Tool/msvs.py index 153b84ecd6..16e422dace 100644 --- a/SCons/Tool/msvs.py +++ b/SCons/Tool/msvs.py @@ -45,7 +45,10 @@ import SCons.Warnings from SCons.Defaults import processDefines from SCons.compat import PICKLE_PROTOCOL -from .MSCommon import msvc_setup_env_tool, msvc_setup_env_once +from SCons.Tool.MSCommon import ( + msvc_setup_env_tool, + msvc_setup_env_once, +) tool_name = 'msvs' diff --git a/SCons/Tool/msvsTests.py b/SCons/Tool/msvsTests.py index dd708d0a7e..1266055494 100644 --- a/SCons/Tool/msvsTests.py +++ b/SCons/Tool/msvsTests.py @@ -880,9 +880,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' : {}, diff --git a/test/MSVC/MSVC_SDK_VERSION.py b/test/MSVC/MSVC_SDK_VERSION.py index e5eae6762c..f3f9913b52 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) @@ -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..7c93938019 100644 --- a/test/MSVC/MSVC_TOOLSET_VERSION.py +++ b/test/MSVC/MSVC_TOOLSET_VERSION.py @@ -38,6 +38,7 @@ 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] diff --git a/test/MSVC/MSVC_USE_SETTINGS.py b/test/MSVC/MSVC_USE_SETTINGS.py index 7c58c7b9a5..fd6f85ceb5 100644 --- a/test/MSVC/MSVC_USE_SETTINGS.py +++ b/test/MSVC/MSVC_USE_SETTINGS.py @@ -56,7 +56,7 @@ """ % locals()) test.run(arguments="--warn=visual-c-missing .", status=0, stderr=None) -test.must_contain_all(test.stderr(), "Could not find MSVC compiler 'cl'") +test.must_contain_all(test.stderr(), "Could not find requested MSVC compiler 'cl'") test.write('SConstruct', """ env = Environment(MSVC_USE_SETTINGS='dict or None') 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.py b/test/MSVC/VSWHERE.py index 8212415f37..e50e42a26e 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=".") 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.]+$' From caf8b0fbc7cf21bc5c89791d183dc852a4bdaf18 Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Fri, 8 Sep 2023 00:13:19 -0400 Subject: [PATCH 02/24] Temporarily disable command-line argument and keep original case of vc path from json output. Test failures when the vc path from json is normalized. Test failure for AddOption help output. --- SCons/Tool/MSCommon/vc.py | 44 +++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/SCons/Tool/MSCommon/vc.py b/SCons/Tool/MSCommon/vc.py index 9891ac3506..a9e12a88a9 100644 --- a/SCons/Tool/MSCommon/vc.py +++ b/SCons/Tool/MSCommon/vc.py @@ -55,7 +55,8 @@ import SCons.Util import SCons.Warnings from SCons.Tool import find_program_path -import SCons.Script + +# import SCons.Script from . import common from .common import CONFIG_CACHE, debug @@ -73,19 +74,19 @@ # user vswhere.exe location as command-line option -_vswhere_cmdline_arg = '--vswhere-path' -_vswhere_cmdline_var = 'vswhere_path' - -SCons.Script.AddOption( - _vswhere_cmdline_arg, - dest=_vswhere_cmdline_var, - type="string", - nargs=1, - action="store", - metavar='PATH', - default=None, - help='Fully qualified path to vswhere.exe.', -) +# _vswhere_cmdline_arg = '--vswhere-path' +# _vswhere_cmdline_var = 'vswhere_path' +# +# SCons.Script.AddOption( +# _vswhere_cmdline_arg, +# dest=_vswhere_cmdline_var, +# type="string", +# nargs=1, +# action="store", +# metavar='PATH', +# default=None, +# help='Fully qualified path to vswhere.exe.', +# ) # external exceptions @@ -982,10 +983,10 @@ def _msvc_cmdline_vswhere(): if _vswhere_path_cmdline == UNDEFINED: vswhere_path = None - vswhere_user = SCons.Script.GetOption(_vswhere_cmdline_var) + # vswhere_user = SCons.Script.GetOption(_vswhere_cmdline_var) - if vswhere_user: - vswhere_path = _vswhere_user_path(vswhere_user) + # if vswhere_user: + # vswhere_path = _vswhere_user_path(vswhere_user) _vswhere_path_cmdline = vswhere_path debug('vswhere_path=%s', vswhere_path) @@ -1184,14 +1185,13 @@ def _update_vswhere_msvc_map(env): if not installation_path or not os.path.exists(installation_path): continue - vc_root = os.path.join(installation_path, 'VC') - if not os.path.exists(vc_root): + vc_path = os.path.join(installation_path, 'VC') + if not os.path.exists(vc_path): continue - vc_root = MSVC.Util.process_path(vc_root) + vc_root = MSVC.Util.process_path(vc_path) if vc_root in _VSWhere.seen_root: continue - _VSWhere.seen_root.add(vc_root) installation_version = instance.get('installationVersion') @@ -1229,7 +1229,7 @@ def _update_vswhere_msvc_map(env): is_release = False if is_prerelease else True msvc_instance = MSVC_INSTANCE( - vc_path = vc_root, + vc_path = vc_path, vc_version = vc_version, vc_version_numeric = float(vc_version), vc_version_scons = vc_version_scons, From 1867d37a2269502a520b1fe75b92d869e92fd77f Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Fri, 8 Sep 2023 08:28:35 -0400 Subject: [PATCH 03/24] Update verification of vswhere executable and temporary removal of vswhere command-line argument --- SCons/Tool/MSCommon/vc.py | 60 +++++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/SCons/Tool/MSCommon/vc.py b/SCons/Tool/MSCommon/vc.py index a9e12a88a9..7b4820e650 100644 --- a/SCons/Tool/MSCommon/vc.py +++ b/SCons/Tool/MSCommon/vc.py @@ -73,10 +73,12 @@ ) # user vswhere.exe location as command-line option - +# +# TODO: INTENTIONALLY DISABLED +# # _vswhere_cmdline_arg = '--vswhere-path' # _vswhere_cmdline_var = 'vswhere_path' -# +# # SCons.Script.AddOption( # _vswhere_cmdline_arg, # dest=_vswhere_cmdline_var, @@ -554,9 +556,6 @@ def _make_target_host_map(all_hosts, host_all_targets_map): _VSWHERE_EXE = 'vswhere.exe' -# case-insensitive endswith vswhere.exe -_re_match_vswhere = re.compile('^.*' + re.escape(_VSWHERE_EXE) + '$', re.IGNORECASE) - 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' @@ -807,7 +806,7 @@ def _skip_sendtelemetry(env): # VS2015 and earlier: configure registry queries to probe for installed VC editions _VCVER_TO_PRODUCT_DIR = { '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'), # vs root (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\14.0\Setup\VC\ProductDir'), # not populated? @@ -954,20 +953,25 @@ def _vswhere_user_path(pval): if not os.path.exists(pval): - warn_msg = f'vswhere path not found: {pval!r}' + warn_msg = f'vswhere executable path not found: {pval!r}' SCons.Warnings.warn(MSVC.Warnings.VSWherePathWarning, warn_msg) debug(warn_msg) - elif not _re_match_vswhere.match(pval): + else: - warn_msg = f'vswhere.exe not found in vswhere path: {pval!r}' - SCons.Warnings.warn(MSVC.Warnings.VSWherePathWarning, warn_msg) - debug(warn_msg) + vswhere_norm = MSVC.Util.process_path(pval) - else: + tail = os.path.split(vswhere_norm)[-1] + if tail != _VSWHERE_EXE: + + warn_msg = f'unsupported vswhere executable (expected {_VSWHERE_EXE!r}, found {tail!r}): {pval!r}' + SCons.Warnings.warn(MSVC.Warnings.VSWherePathWarning, warn_msg) + debug(warn_msg) + + else: - vswhere_path = MSVC.Util.process_path(pval) - debug('vswhere_path=%s', vswhere_path) + vswhere_path = vswhere_norm + debug('vswhere_path=%s', vswhere_path) _cache_user_vswhere_paths[pval] = vswhere_path @@ -983,10 +987,12 @@ def _msvc_cmdline_vswhere(): if _vswhere_path_cmdline == UNDEFINED: vswhere_path = None + # TODO: INTENTIONALLY DISABLED # vswhere_user = SCons.Script.GetOption(_vswhere_cmdline_var) + vswhere_user = None - # if vswhere_user: - # vswhere_path = _vswhere_user_path(vswhere_user) + if vswhere_user: + vswhere_path = _vswhere_user_path(vswhere_user) _vswhere_path_cmdline = vswhere_path debug('vswhere_path=%s', vswhere_path) @@ -1027,11 +1033,14 @@ def msvc_find_vswhere(): # NB: this gets called from testsuite on non-Windows platforms. # Whether that makes sense or not, don't break it for those. vswhere_path = _msvc_cmdline_vswhere() - if not vswhere_path: - for pf in VSWHERE_PATHS: - if os.path.exists(pf): - vswhere_path = pf - break + if vswhere_path: + return + + vswhere_path = None + for pf in VSWHERE_PATHS: + if os.path.exists(pf): + vswhere_path = pf + break return vswhere_path @@ -1083,11 +1092,12 @@ def _filter_vswhere_paths(env): if vswhere_environ and vswhere_environ not in _VSWhere.seen_vswhere: vswhere_paths.append(vswhere_environ) - vswhere_cmdline = _msvc_cmdline_vswhere() - if vswhere_cmdline and vswhere_cmdline not in _VSWhere.seen_vswhere: - vswhere_paths.append(vswhere_cmdline) + # TODO: INTENTIONALLY DISABLED + # vswhere_cmdline = _msvc_cmdline_vswhere() + # if vswhere_cmdline and vswhere_cmdline not in _VSWhere.seen_vswhere: + # vswhere_paths.append(vswhere_cmdline) - vswhere_default = _msvc_default_vswhere() + vswhere_default = _msvc_default_vswhere() if vswhere_default and vswhere_default not in _VSWhere.seen_vswhere: vswhere_paths.append(vswhere_default) From 7a9c3361fb2057aa1cae2112095522afff2cbd3a Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Sat, 9 Sep 2023 20:08:34 -0400 Subject: [PATCH 04/24] Remove vswhere command-line option and replace retrieval with stub returning None. Change processing of user-specified vswhere path. TODO: * revisit MSVC.Util.process_path due to realpath behavior for some windows specifications (drive specifications, etc). --- SCons/Tool/MSCommon/vc.py | 33 +++++++-------------------------- 1 file changed, 7 insertions(+), 26 deletions(-) diff --git a/SCons/Tool/MSCommon/vc.py b/SCons/Tool/MSCommon/vc.py index 7b4820e650..a92ceb7cd8 100644 --- a/SCons/Tool/MSCommon/vc.py +++ b/SCons/Tool/MSCommon/vc.py @@ -72,24 +72,6 @@ MSVCToolsetVersionNotFound, ) -# user vswhere.exe location as command-line option -# -# TODO: INTENTIONALLY DISABLED -# -# _vswhere_cmdline_arg = '--vswhere-path' -# _vswhere_cmdline_var = 'vswhere_path' -# -# SCons.Script.AddOption( -# _vswhere_cmdline_arg, -# dest=_vswhere_cmdline_var, -# type="string", -# nargs=1, -# action="store", -# metavar='PATH', -# default=None, -# help='Fully qualified path to vswhere.exe.', -# ) - # external exceptions class MSVCUnsupportedHostArch(VisualCException): @@ -959,7 +941,8 @@ def _vswhere_user_path(pval): else: - vswhere_norm = MSVC.Util.process_path(pval) + # vswhere_norm = MSVC.Util.process_path(pval) + vswhere_norm = os.path.normcase(os.path.normpath(pval)) tail = os.path.split(vswhere_norm)[-1] if tail != _VSWHERE_EXE: @@ -979,7 +962,8 @@ def _vswhere_user_path(pval): # normalized user-specified command-line vswhere path -_vswhere_path_cmdline = UNDEFINED +# TODO: stub for command-line specification of vswhere +_vswhere_path_cmdline = None def _msvc_cmdline_vswhere(): global _vswhere_path_cmdline @@ -987,8 +971,6 @@ def _msvc_cmdline_vswhere(): if _vswhere_path_cmdline == UNDEFINED: vswhere_path = None - # TODO: INTENTIONALLY DISABLED - # vswhere_user = SCons.Script.GetOption(_vswhere_cmdline_var) vswhere_user = None if vswhere_user: @@ -1092,10 +1074,9 @@ def _filter_vswhere_paths(env): if vswhere_environ and vswhere_environ not in _VSWhere.seen_vswhere: vswhere_paths.append(vswhere_environ) - # TODO: INTENTIONALLY DISABLED - # vswhere_cmdline = _msvc_cmdline_vswhere() - # if vswhere_cmdline and vswhere_cmdline not in _VSWhere.seen_vswhere: - # vswhere_paths.append(vswhere_cmdline) + vswhere_cmdline = _msvc_cmdline_vswhere() + if vswhere_cmdline and vswhere_cmdline not in _VSWhere.seen_vswhere: + vswhere_paths.append(vswhere_cmdline) vswhere_default = _msvc_default_vswhere() if vswhere_default and vswhere_default not in _VSWhere.seen_vswhere: From 8b4fcdeec0dec4d8cb805db76e3626907290c333 Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Sun, 10 Sep 2023 09:31:45 -0400 Subject: [PATCH 05/24] Update MSCommon/README.rst documentation [ci skip] Changes: * Added MSVC detection priority. * Added VS2015 edition limitations. * Updated SDK batch file known issues. * Added footnotes for MSVC batch file argument limitations. * Added footnote for Windows SDK version numbers (Windows 10 and Windows 11) --- SCons/Tool/MSCommon/README.rst | 198 +++++++++++++++++++++++++++++---- 1 file changed, 178 insertions(+), 20 deletions(-) diff --git a/SCons/Tool/MSCommon/README.rst b/SCons/Tool/MSCommon/README.rst index 5ab07ad631..bc57ca43b0 100644 --- a/SCons/Tool/MSCommon/README.rst +++ b/SCons/Tool/MSCommon/README.rst @@ -27,16 +27,166 @@ 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 VCForPython, Develop, 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``. @@ -188,17 +338,21 @@ 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]_ +======= ======= ====== ======= ======= + +.. [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: @@ -315,13 +469,17 @@ Visual Studio Version Notes SDK Versions ------------ -==== ============ +==== ================= SDK Format -==== ============ -10.0 10.0.XXXXX.Y ----- ------------ +==== ================= +10.0 10.0.XXXXX.Y [*]_ +---- ----------------- 8.1 8.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. BuildTools Versions ------------------- From 3fbef22bf24568e27ad71a3a6b8853e8f99246d9 Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Sun, 10 Sep 2023 13:21:37 -0400 Subject: [PATCH 06/24] Initial additions for CHANGES.txt. [ci skip] --- CHANGES.txt | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 9336e5d2f1..4467bee773 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -38,6 +38,65 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER module was refactored. - Add arm64 to the MSVS supported architectures list for VS2017 and later to be consistent with the current documentation of MSVS_ARCH. + - For msvc version specifications without an 'Exp' suffix, an express installation + is used when no other edition is detected for the msvc version. + - VS2015 Express (14.1Exp) may not have been detected. The registry keys written + during installation appear to be different than for earlier express versions. + VS2015 Express should be correctly detected now. + - VS2015 Express (14.1Exp) 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. + - The MSCommon module import was changed from a relative import to a top-level + absolute import in the following Microsoft tools: midl, mslib, mslink, mssdk, msvc, + msvs. Moving any of these tools that used relative imports to the scons site tools + folder would fail on import (i.e., the relative import paths become invalid when + moved). + - 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. + - 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 detection of VS2017 (14.1), and later, is now cached at runtime and is + only evaluated once for each vswhere executable. The detected msvc instances for + all vswhere executables are merged (i.e., detected msvc instances are the union + of the results from all vswhere executables). There is a single invocation for + each vswhere executable that processes the output for all installations. Prior + implementations called the vswhere executable for each supported msvc version. + - The detection of the msvc compiler executable (cl.exe) has been modified. The + compiler detection no longer considers the host os environment path. In addition, + existence of the msvc compiler executable is checked in the detection dictionary + and the scons ENV path before the detection dictionary is merged into the scons + ENV. Different warnings are produced when the msvc compiler is not detected in the + detection dictionary based on whether or not an msvc compiler was detected in the + scons ENV path (i.e., already exists in the user's ENV path prior to detection). + - Previously, the installed vcs list was constructed once and cached at runtime. If + a vswhere executable was specified via the construction variable VSWHERE and found + additional msvc installations, the new installations were not reflected in the + installed vcs list. During runtime, if a user-specified vswhere executable finds + new msvc installations, internal runtime caches are cleared and the installed vcs + list is reconstructed. From Vitaly Cheptsov: - Fix race condition in `Mkdir` which can happen when two `SConscript` From 0fc09ba19f4c00fb40f7909c3b111a6a037b5bf1 Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Sun, 10 Sep 2023 17:29:10 -0400 Subject: [PATCH 07/24] Initial additions for RELEASE.txt. [ci skip] --- RELEASE.txt | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/RELEASE.txt b/RELEASE.txt index b1e49ff12d..37f1a44d13 100644 --- a/RELEASE.txt +++ b/RELEASE.txt @@ -59,6 +59,14 @@ CHANGED/ENHANCED EXISTING FUNCTIONALITY batch file exists but an individual msvc toolset may not support the host/target architecture combination. For example, when using VS2022 on arm64, the arm64 native tools are only installed for the 14.3x toolsets. +- 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 VS2008 (9.0) through VS2015 (14.0). This behavior + was extended to VS2017 (14.1) and VS2008 (8.0). +- MSVC: When the msvc compiler executable is not found during setup of the msvc + environment, the warning message issued takes into account whether or not a + possibly erroneous compiler executable was already present in the scons environment + path. - Extend range of recognized Java versions to 20. - Builder calls (like Program()) now accept pathlib objects in source lists. - The Help() function now takes an additional keyword argument keep_local: @@ -95,6 +103,26 @@ FIXES - MSVC: Erroneous construction of the installed msvc list (as described above) caused an index error in the msvc support code. An explicit check was added to prevent indexing into an empty list. Fixes #4312. +- MSVC: VS2015 Express (14.1Exp) may not have been detected. VS2015 Express should + be correctly detected. +- MSVC: VS2010 (10.0) could be inadvertently detected due to an sdk-only install + of Windows SDK 7.1. An sdk-only install of VS2010 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 VS2008 (9.0). +- MSVC: For VS2005 (8.0) to VS2015 (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 exists resulting in a build failure. +- MSVC: VS2008 (9.0) Visual C++ For Python was not detected when installed using the + ALLUSERS=1 option (i.e., msiexec /i VCForPython27.msi ALLUSERS=1). When installed + for all users, VS2008 (9.0) Visual C++ For Python is now correctly detected. +- MSVC: The search for the msvc compiler executable (cl.exe) no longer inspects the + OS system path in certain situations when setting up the msvc environment. +- MSVC: The installed vcs list was constructed and cached during the initial + invocation. If a vswhere executable was specified via the construction variable + VSWHERE and found additional msvc installations, the new installations were not + reflected in the installed vcs list. Now, when a user-specified vswhere executable + finds new msvc installations, the installed vcs list is reconstructed. - MSCommon: Test SConfTests.py would fail when mscommon debugging was enabled via the MSVC_MSCOMMON_DEBUG environment variable. The mscommon logging filter class registered with the python logging module was refactored to prevent test failure. @@ -114,6 +142,17 @@ IMPROVEMENTS ------------ - Now tries to find mingw if it comes from Chocolatey install of msys2. +- MSVC: VS2015 Express (14.1Exp) 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. +- MSVC: 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. +- MSVC: Module imports were changed from a relative import to a top-level + absolute import in the following Microsoft tools: midl, mslib, mslink, mssdk, msvc, + msvs. Moving any of these tools that used relative imports to the scons site tools + folder would fail on import (i.e., the relative import paths become invalid when + moved). PACKAGING --------- From 0d37f067a4ec411ab7bf3464a693b3716f099dec Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Sun, 10 Sep 2023 17:59:21 -0400 Subject: [PATCH 08/24] Initial update for SCons/Tool/msvc.xml. [ci skip] --- SCons/Tool/msvc.xml | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/SCons/Tool/msvc.xml b/SCons/Tool/msvc.xml index bf2e267347..84ca2bd800 100644 --- a/SCons/Tool/msvc.xml +++ b/SCons/Tool/msvc.xml @@ -371,11 +371,17 @@ loaded into the environment. 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 distict entries because they use a different -filesystem layout and have some feature limitations compared to -the full version. +of the compiler. Versions ending in Exp +refer to "Express" or "Express for Desktop" Visual Studio editions. +An express version may have some feature limitations as compared to +the full version. It is necessary to specify the Exp +suffix when both express and non-express editions are installed +simulaneously and the express version is desired. An express edition +is used for the msvc version without the 'Exp' suffix when there is one +edition installed and it is the express edition. + + + The following table shows correspondence of the selector string to various version indicators ('x' is used as a placeholder for From 518fd13dcb964c4740a5275167aad2b346d47282 Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Mon, 11 Sep 2023 13:04:18 -0400 Subject: [PATCH 09/24] Second update for SCons/Tool/msvc.xml. [ci skip] --- SCons/Tool/msvc.xml | 51 ++++++++++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/SCons/Tool/msvc.xml b/SCons/Tool/msvc.xml index 84ca2bd800..f02c5f405f 100644 --- a/SCons/Tool/msvc.xml +++ b/SCons/Tool/msvc.xml @@ -370,28 +370,10 @@ loaded into the environment. -The valid values for &cv-MSVC_VERSION; represent major versions -of the compiler. Versions ending in Exp -refer to "Express" or "Express for Desktop" Visual Studio editions. -An express version may have some feature limitations as compared to -the full version. It is necessary to specify the Exp -suffix when both express and non-express editions are installed -simulaneously and the express version is desired. An express edition -is used for the msvc version without the 'Exp' suffix when there is one -edition installed and it is the express edition. - - - -The following table shows correspondence -of the selector string 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 avaialable and will -correspond to the indicated versions. +The valid values for &cv-MSVC_VERSION; represent major versions of the +compiler suite. The following table shows the correspondence of +&cv-MSVC_VERSION; version specifications to various Visual Studio version +numbers. 'x' is used as a placeholder for a single digit that may vary. @@ -548,6 +530,31 @@ correspond to the indicated versions. + + + + + 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 From 6424fe3661135ac02f97b1da92b2528bdb56f68e Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Mon, 11 Sep 2023 16:13:12 -0400 Subject: [PATCH 10/24] Third update for SCons/Tool/msvc.xml (remove hard tabs). [ci skip] --- SCons/Tool/msvc.xml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/SCons/Tool/msvc.xml b/SCons/Tool/msvc.xml index f02c5f405f..37dccdd9f9 100644 --- a/SCons/Tool/msvc.xml +++ b/SCons/Tool/msvc.xml @@ -534,12 +534,12 @@ numbers. 'x' is used as a placeholder for a single digit that may vary. - 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. + 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. @@ -549,8 +549,8 @@ numbers. 'x' is used as a placeholder for a single digit that may vary. 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. + simulaneously. The Exp suffix is unnecessary, + but accepted, when only the express edition is installed. From 0d1aaa76358027ae94992e8220c4d475d600b3a0 Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Mon, 11 Sep 2023 16:19:17 -0400 Subject: [PATCH 11/24] Update for SCons/Tool/msvc.xml (remove hard tabs). [ci skip] --- SCons/Tool/msvc.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SCons/Tool/msvc.xml b/SCons/Tool/msvc.xml index 37dccdd9f9..8beebf9a2d 100644 --- a/SCons/Tool/msvc.xml +++ b/SCons/Tool/msvc.xml @@ -544,7 +544,7 @@ numbers. 'x' is used as a placeholder for a single digit that may vary. Versions ending in Exp refer to historical - "Express" or "Express for Desktop" Visual Studio editions, + "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 From 813b8d22f8cdbbfc96b23d37d210161d9f614bd7 Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Thu, 5 Oct 2023 17:05:13 -0400 Subject: [PATCH 12/24] Internal changes to support channel (preview/release) and component queries in the future. Internal changes: * Rename VISUALSTUDIO_ to MSVS_PRODUCT and vs_def to vs_product_def in all files. * Replace process_path with normalize_path and additional arguments in MSCommon.MSVC.Util. * Add msvs channel, msvs component id, and msvs component definitions. * Add Validate module to MSCommon.MSVC. * Add AutoInitialize class to MSCommon. * Add custom formatter to MSCommon debug logging to prepend the class name to the function name in the log record based on passed arguments. * Rework MSCommon.Kind to return the msvs component, vs directory, vs executable, and vc feature map. * Combine _VCVER_TO_PRODUCT_DIR vc version keys into vs product keys and use MSCommon.MSVC.Kind to determine the vs component type. * Add file exists and normalized path caches to MSCommon.vc. * Rework vswhere executable management and queries. * Add an object hierarchy for vs/vc detection. Include preliminary data structure construction for msvs channels (preview, release, any) and component types (Enterprise, Professional, Community, BuildTools, Express, etc). * Select an msvc instance rather than a vc dir and pass the instance to MSCommon.MSVC.ScriptArgument methods. * Rework msvc action selection (use script, select, use settings, bypass). --- CHANGES.txt | 2 +- SCons/Tool/MSCommon/MSVC/Config.py | 307 ++- SCons/Tool/MSCommon/MSVC/Exceptions.py | 3 + SCons/Tool/MSCommon/MSVC/Kind.py | 491 +--- SCons/Tool/MSCommon/MSVC/Registry.py | 4 +- SCons/Tool/MSCommon/MSVC/RegistryTests.py | 6 +- SCons/Tool/MSCommon/MSVC/ScriptArguments.py | 291 +- .../MSCommon/MSVC/ScriptArgumentsTests.py | 98 +- SCons/Tool/MSCommon/MSVC/Util.py | 100 +- SCons/Tool/MSCommon/MSVC/UtilTests.py | 4 +- SCons/Tool/MSCommon/MSVC/Validate.py | 113 + SCons/Tool/MSCommon/MSVC/WinSDK.py | 16 +- SCons/Tool/MSCommon/MSVC/WinSDKTests.py | 6 +- SCons/Tool/MSCommon/MSVC/__init__.py | 1 + SCons/Tool/MSCommon/__init__.py | 3 + SCons/Tool/MSCommon/common.py | 77 +- SCons/Tool/MSCommon/vc.py | 2408 ++++++++++++----- SCons/Tool/MSCommon/vcTests.py | 114 +- SCons/Tool/MSCommon/vs.py | 8 +- SCons/Tool/msvsTests.py | 8 +- test/MSVC/MSVC_SCRIPTERROR_POLICY.py | 11 +- test/MSVC/MSVC_SDK_VERSION.py | 73 +- test/MSVC/MSVC_SPECTRE_LIBS.py | 25 +- test/MSVC/MSVC_TOOLSET_VERSION.py | 33 +- test/MSVC/MSVC_UWP_APP.py | 69 +- test/MSVC/VSWHERE.py | 8 +- test/MSVC/msvc_cache_force_defaults.py | 25 +- test/MSVC/no_msvc.py | 2 +- test/fixture/no_msvc/no_msvcs_sconstruct.py | 14 +- ...s_sconstruct_msvc_query_toolset_version.py | 14 +- .../no_msvcs_sconstruct_msvc_sdk_versions.py | 14 +- ..._msvcs_sconstruct_msvc_toolset_versions.py | 14 +- .../no_msvc/no_msvcs_sconstruct_tools.py | 14 +- .../no_msvc/no_msvcs_sconstruct_version.py | 14 +- test/fixture/no_msvc/no_regs_sconstruct.py | 6 +- 35 files changed, 2807 insertions(+), 1589 deletions(-) create mode 100644 SCons/Tool/MSCommon/MSVC/Validate.py diff --git a/CHANGES.txt b/CHANGES.txt index 270861bf6e..349f9e8a26 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -79,7 +79,7 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER - 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. + Visual C++ For Python keys in HKLM following the HKCU query. - 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 detection of VS2017 (14.1), and later, is now cached at runtime and is diff --git a/SCons/Tool/MSCommon/MSVC/Config.py b/SCons/Tool/MSCommon/MSVC/Config.py index 29d6f246f2..78459cf76a 100644 --- a/SCons/Tool/MSCommon/MSVC/Config.py +++ b/SCons/Tool/MSCommon/MSVC/Config.py @@ -38,6 +38,10 @@ from . import Dispatcher Dispatcher.register_modulename(__name__) +# MSVC 9.0 preferred query order: +# True: VCForPython, VisualStudio +# False: VisualStudio, VCForPython +_VC90_Prefer_VCForPython = True UNDEFINED = object() @@ -86,7 +90,6 @@ 'vc_runtime', 'vc_runtime_numeric', 'vc_runtime_alias_list', - 'vc_runtime_vsdef_list', ]) MSVC_RUNTIME_DEFINITION_LIST = [] @@ -94,6 +97,8 @@ MSVC_RUNTIME_INTERNAL = {} MSVC_RUNTIME_EXTERNAL = {} +MSVC_RUNTIME_VSPRODUCTDEF_LIST_MAP = {} + for vc_runtime, vc_runtime_numeric, vc_runtime_alias_list in [ ('140', 140, ['ucrt']), ('120', 120, ['msvcr120']), @@ -108,8 +113,7 @@ vc_runtime_def = MSVC_RUNTIME_DEFINITION( vc_runtime = vc_runtime, vc_runtime_numeric = vc_runtime_numeric, - vc_runtime_alias_list = vc_runtime_alias_list, - vc_runtime_vsdef_list = [], + vc_runtime_alias_list = tuple(vc_runtime_alias_list), ) MSVC_RUNTIME_DEFINITION_LIST.append(vc_runtime_def) @@ -120,7 +124,9 @@ for vc_runtime_alias in vc_runtime_alias_list: MSVC_RUNTIME_EXTERNAL[vc_runtime_alias] = vc_runtime_def -MSVC_BUILDTOOLS_DEFINITION = namedtuple('MSVCBuildtools', [ + MSVC_RUNTIME_VSPRODUCTDEF_LIST_MAP[vc_runtime_def] = [] + +MSVC_BUILDTOOLS_DEFINITION = namedtuple('MSVCBuildTools', [ 'vc_buildtools', 'vc_buildtools_numeric', 'vc_version', @@ -187,8 +193,17 @@ MSVC_SDK_VERSIONS = set() -VISUALSTUDIO_DEFINITION = namedtuple('VisualStudioDefinition', [ +# vswhere: +# 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() + +REGISTRY_SUPPORTED_VCVER = set() + +MSVS_PRODUCT_DEFINITION = namedtuple('MSVSProductDefinition', [ 'vs_product', + 'vs_product_numeric', 'vs_product_alias_list', 'vs_version', 'vs_version_major', @@ -202,14 +217,14 @@ 'vc_buildtools_all', ]) -VISUALSTUDIO_DEFINITION_LIST = [] +MSVS_PRODUCT_DEFINITION_LIST = [] VS_PRODUCT_ALIAS = { '1998': ['6'] } # vs_envvar: VisualStudioVersion defined in environment for MSVS 2012 and later -# MSVS 2010 and earlier cl_version -> vs_def is a 1:1 mapping +# MSVS 2010 and earlier cl_version -> vs_product_def is a 1:1 mapping # SDK attached to product or buildtools? for vs_product, vs_version, vs_envvar, vs_express, vs_lookup, vc_sdk, vc_ucrt, vc_uwp, vc_buildtools_all in [ ('2022', '17.0', True, False, 'vswhere' , ['10.0', '8.1'], ['10'], 'uwp', ['v143', 'v142', 'v141', 'v140']), @@ -226,57 +241,80 @@ ('1998', '6.0', False, False, 'registry', None, None, None, ['v60']), ]: + vs_product_numeric = int(vs_product) + vs_version_major = vs_version.split('.')[0] vc_buildtools_def = MSVC_BUILDTOOLS_INTERNAL[vc_buildtools_all[0]] - vs_def = VISUALSTUDIO_DEFINITION( + if vs_product not in VS_PRODUCT_ALIAS: + vs_product_alias_list = [] + else: + vs_product_alias_list = [ + vs_product_alias + for vs_product_alias in VS_PRODUCT_ALIAS[vs_product] + if vs_product_alias + ] + + vs_product_def = MSVS_PRODUCT_DEFINITION( vs_product = vs_product, - vs_product_alias_list = [], + vs_product_numeric = vs_product_numeric, + vs_product_alias_list = tuple(vs_product_alias_list), vs_version = vs_version, vs_version_major = vs_version_major, vs_envvar = vs_envvar, vs_express = vs_express, vs_lookup = vs_lookup, - vc_sdk_versions = vc_sdk, - vc_ucrt_versions = vc_ucrt, + vc_sdk_versions = tuple(vc_sdk) if vc_sdk else vc_sdk, + vc_ucrt_versions = tuple(vc_ucrt) if vc_ucrt else vc_ucrt, vc_uwp = vc_uwp, vc_buildtools_def = vc_buildtools_def, - vc_buildtools_all = vc_buildtools_all, + vc_buildtools_all = tuple(vc_buildtools_all), ) - VISUALSTUDIO_DEFINITION_LIST.append(vs_def) + MSVS_PRODUCT_DEFINITION_LIST.append(vs_product_def) - vc_buildtools_def.vc_runtime_def.vc_runtime_vsdef_list.append(vs_def) + MSVC_RUNTIME_VSPRODUCTDEF_LIST_MAP[vc_buildtools_def.vc_runtime_def].append(vs_product_def) vc_version = vc_buildtools_def.vc_version - MSVS_VERSION_INTERNAL[vs_product] = vs_def - MSVS_VERSION_EXTERNAL[vs_product] = vs_def - MSVS_VERSION_EXTERNAL[vs_version] = vs_def + MSVS_VERSION_INTERNAL[vs_product] = vs_product_def + MSVS_VERSION_EXTERNAL[vs_product] = vs_product_def + MSVS_VERSION_EXTERNAL[vs_version] = vs_product_def - MSVC_VERSION_INTERNAL[vc_version] = vs_def - MSVC_VERSION_EXTERNAL[vs_product] = vs_def - MSVC_VERSION_EXTERNAL[vc_version] = vs_def - MSVC_VERSION_EXTERNAL[vc_buildtools_def.vc_buildtools] = vs_def + MSVC_VERSION_INTERNAL[vc_version] = vs_product_def + MSVC_VERSION_EXTERNAL[vs_product] = vs_product_def + MSVC_VERSION_EXTERNAL[vc_version] = vs_product_def + MSVC_VERSION_EXTERNAL[vc_buildtools_def.vc_buildtools] = vs_product_def - if vs_product in VS_PRODUCT_ALIAS: - for vs_product_alias in VS_PRODUCT_ALIAS[vs_product]: - vs_def.vs_product_alias_list.append(vs_product_alias) - MSVS_VERSION_EXTERNAL[vs_product_alias] = vs_def - MSVC_VERSION_EXTERNAL[vs_product_alias] = vs_def + for vs_product_alias in vs_product_alias_list: + MSVS_VERSION_EXTERNAL[vs_product_alias] = vs_product_def + MSVC_VERSION_EXTERNAL[vs_product_alias] = vs_product_def - MSVC_VERSION_SUFFIX[vc_version] = vs_def + MSVC_VERSION_SUFFIX[vc_version] = vs_product_def if vs_express: - MSVC_VERSION_SUFFIX[vc_version + 'Exp'] = vs_def + MSVC_VERSION_SUFFIX[vc_version + 'Exp'] = vs_product_def - MSVS_VERSION_MAJOR_MAP[vs_version_major] = vs_def + MSVS_VERSION_MAJOR_MAP[vs_version_major] = vs_product_def - CL_VERSION_MAP[vc_buildtools_def.cl_version] = vs_def + CL_VERSION_MAP[vc_buildtools_def.cl_version] = vs_product_def if vc_sdk: MSVC_SDK_VERSIONS.update(vc_sdk) + if vs_lookup == 'vswhere': + VSWHERE_VSMAJOR_TO_VCVERSION[vs_version_major] = vc_version + VSWHERE_SUPPORTED_VCVER.add(vc_version) + if vs_express: + VSWHERE_SUPPORTED_VCVER.add(vc_version + 'Exp') + elif vs_lookup == 'registry': + REGISTRY_SUPPORTED_VCVER.add(vc_version) + if vs_express: + REGISTRY_SUPPORTED_VCVER.add(vc_version + 'Exp') + +MSVS_VERSION_SYMBOLS = list(MSVC_VERSION_EXTERNAL.keys()) +MSVC_VERSIONS = list(MSVC_VERSION_SUFFIX.keys()) + # EXPERIMENTAL: msvc version/toolset search lists # # VS2017 example: @@ -291,32 +329,223 @@ MSVC_VERSION_TOOLSET_SEARCH_MAP = {} # Pass 1: Build defaults lists and setup express versions -for vs_def in VISUALSTUDIO_DEFINITION_LIST: - if not vs_def.vc_buildtools_def.vc_istoolset: +for vs_product_def in MSVS_PRODUCT_DEFINITION_LIST: + if not vs_product_def.vc_buildtools_def.vc_istoolset: continue - version_key = vs_def.vc_buildtools_def.vc_version + version_key = vs_product_def.vc_buildtools_def.vc_version MSVC_VERSION_TOOLSET_DEFAULTS_MAP[version_key] = [version_key] MSVC_VERSION_TOOLSET_SEARCH_MAP[version_key] = [] - if vs_def.vs_express: + if vs_product_def.vs_express: express_key = version_key + 'Exp' MSVC_VERSION_TOOLSET_DEFAULTS_MAP[version_key].append(express_key) MSVC_VERSION_TOOLSET_DEFAULTS_MAP[express_key] = [express_key] MSVC_VERSION_TOOLSET_SEARCH_MAP[express_key] = [express_key] # Pass 2: Extend search lists (decreasing version order) -for vs_def in VISUALSTUDIO_DEFINITION_LIST: - if not vs_def.vc_buildtools_def.vc_istoolset: +for vs_product_def in MSVS_PRODUCT_DEFINITION_LIST: + if not vs_product_def.vc_buildtools_def.vc_istoolset: continue - version_key = vs_def.vc_buildtools_def.vc_version - for vc_buildtools in vs_def.vc_buildtools_all: + version_key = vs_product_def.vc_buildtools_def.vc_version + for vc_buildtools in vs_product_def.vc_buildtools_all: toolset_buildtools_def = MSVC_BUILDTOOLS_INTERNAL[vc_buildtools] - toolset_vs_def = MSVC_VERSION_INTERNAL[toolset_buildtools_def.vc_version] + toolset_vs_product_def = MSVC_VERSION_INTERNAL[toolset_buildtools_def.vc_version] buildtools_key = toolset_buildtools_def.vc_version MSVC_VERSION_TOOLSET_SEARCH_MAP[buildtools_key].extend(MSVC_VERSION_TOOLSET_DEFAULTS_MAP[version_key]) # convert string version set to string version list ranked in descending order MSVC_SDK_VERSIONS = [str(f) for f in sorted([float(s) for s in MSVC_SDK_VERSIONS], reverse=True)] +# MSVS Channel: Release, Preview, Any + +MSVS_CHANNEL_DEFINITION = namedtuple('MSVSChannel', [ + 'vs_channel_id', + 'vs_channel_rank', + 'vs_channel_suffix', + 'vs_channel_symbols', +]) + +MSVS_CHANNEL_DEFINITION_LIST = [] + +MSVS_CHANNEL_INTERNAL = {} +MSVS_CHANNEL_EXTERNAL = {} +MSVS_CHANNEL_SYMBOLS = [] + +for vs_channel_rank, vs_channel_suffix, vs_channel_symbols in ( + (1, 'Rel', ['Release']), + (2, 'Pre', ['Preview']), + (3, 'Any', ['Any', '*']) +): + + vs_channel_id = vs_channel_symbols[0] + + if vs_channel_suffix not in vs_channel_symbols: + vs_channel_symbols = vs_channel_symbols + [vs_channel_suffix] + + vs_channel_def = MSVS_CHANNEL_DEFINITION( + vs_channel_id=vs_channel_id, + vs_channel_rank=vs_channel_rank, + vs_channel_suffix=vs_channel_suffix, + vs_channel_symbols=tuple(vs_channel_symbols), + ) + + MSVS_CHANNEL_DEFINITION_LIST.append(vs_channel_def) + + MSVS_CHANNEL_INTERNAL[vs_channel_id] = vs_channel_def + MSVS_CHANNEL_INTERNAL[vs_channel_rank] = vs_channel_def + MSVS_CHANNEL_INTERNAL[vs_channel_suffix] = vs_channel_def + + for symbol in vs_channel_symbols: + MSVS_CHANNEL_SYMBOLS.append(symbol) + MSVS_CHANNEL_EXTERNAL[symbol] = vs_channel_def + MSVS_CHANNEL_EXTERNAL[symbol.upper()] = vs_channel_def + MSVS_CHANNEL_EXTERNAL[symbol.lower()] = vs_channel_def + +MSVS_CHANNEL_RELEASE = MSVS_CHANNEL_INTERNAL['Release'] +MSVS_CHANNEL_PREVIEW = MSVS_CHANNEL_INTERNAL['Preview'] +MSVS_CHANNEL_ANY = MSVS_CHANNEL_INTERNAL['Any'] + +MSVS_CHANNEL_MEMBERLISTS = { + MSVS_CHANNEL_RELEASE: [MSVS_CHANNEL_RELEASE, MSVS_CHANNEL_ANY], + MSVS_CHANNEL_PREVIEW: [MSVS_CHANNEL_PREVIEW, MSVS_CHANNEL_ANY], +} + +# VS Component Id + +MSVS_COMPONENTID_DEFINITION = namedtuple('MSVSComponentId', [ + 'vs_component_id', + 'vs_component_suffix', + 'vs_component_isexpress', + 'vs_component_symbols', +]) + +MSVS_COMPONENTID_DEFINITION_LIST = [] + +MSVS_COMPONENTID_INTERNAL = {} +MSVS_COMPONENTID_EXTERNAL = {} +MSVS_COMPONENTID_SYMBOLS = [] + +for vs_component_isexpress, vs_component_suffix, vs_component_symbols in ( + (False, 'Ent', ['Enterprise']), + (False, 'Pro', ['Professional']), + (False, 'Com', ['Community']), + (False, 'Dev', ['Develop']), + (False, 'Py', ['Python', 'VCForPython']), + (False, 'Cmd', ['CmdLine', 'CommandLine']), + (False, 'BT', ['BuildTools']), + (True, 'Exp', ['Express', 'WDExpress']), +): + + vs_component_id = vs_component_symbols[0] + + if vs_component_suffix not in vs_component_symbols: + vs_component_symbols = vs_component_symbols + [vs_component_suffix] + + vs_componentid_def = MSVS_COMPONENTID_DEFINITION( + vs_component_id=vs_component_id, + vs_component_suffix=vs_component_suffix, + vs_component_isexpress=vs_component_isexpress, + vs_component_symbols=tuple(vs_component_symbols), + ) + + MSVS_COMPONENTID_DEFINITION_LIST.append(vs_componentid_def) + + MSVS_COMPONENTID_INTERNAL[vs_component_id] = vs_componentid_def + MSVS_COMPONENTID_EXTERNAL[vs_component_id] = vs_componentid_def + + for symbol in vs_component_symbols: + MSVS_COMPONENTID_EXTERNAL[symbol] = vs_componentid_def + MSVS_COMPONENTID_SYMBOLS.append(symbol) + +MSVS_COMPONENTID_ENTERPRISE = MSVS_COMPONENTID_INTERNAL['Enterprise'] +MSVS_COMPONENTID_PROFESSIONAL = MSVS_COMPONENTID_INTERNAL['Professional'] +MSVS_COMPONENTID_COMMUNITY = MSVS_COMPONENTID_INTERNAL['Community'] +MSVS_COMPONENTID_DEVELOP = MSVS_COMPONENTID_INTERNAL['Develop'] +MSVS_COMPONENTID_PYTHON = MSVS_COMPONENTID_INTERNAL['Python'] +MSVS_COMPONENTID_CMDLINE = MSVS_COMPONENTID_INTERNAL['CmdLine'] +MSVS_COMPONENTID_BUILDTOOLS = MSVS_COMPONENTID_INTERNAL['BuildTools'] +MSVS_COMPONENTID_EXPRESS = MSVS_COMPONENTID_INTERNAL['Express'] + +# VS Component Id + +MSVS_COMPONENT_DEFINITION = namedtuple('MSVSComponent', [ + 'vs_componentid_def', + 'vs_lookup', + 'vs_component_rank', +]) + +MSVS_COMPONENT_DEFINITION_LIST = [] + +MSVS_COMPONENT_INTERNAL = {} + +VSWHERE_COMPONENT_INTERNAL = {} +VSWHERE_COMPONENT_SYMBOLS = [] + +REGISTRY_COMPONENT_INTERNAL = {} +REGISTRY_COMPONENT_SYMBOLS = [] + +if _VC90_Prefer_VCForPython: + _REG_VPY = 140 + _REG_DEV = 130 +else: + _REG_VPY = 130 + _REG_DEV = 140 + +for vs_lookup, vs_component_rank, vs_componentid in ( + + ('vswhere', 240, 'Enterprise'), + ('vswhere', 230, 'Professional'), + ('vswhere', 220, 'Community'), + ('vswhere', 210, 'BuildTools'), + ('vswhere', 200, 'Express'), + + ('registry', 170, 'Enterprise'), + ('registry', 160, 'Professional'), + ('registry', 150, 'Community'), + ('registry', _REG_DEV, 'Develop'), + ('registry', _REG_VPY, 'Python'), + ('registry', 120, 'CmdLine'), + ('registry', 110, 'BuildTools'), + ('registry', 100, 'Express'), + +): + + vs_componentid_def = MSVS_COMPONENTID_INTERNAL[vs_componentid] + + vs_component_def = MSVS_COMPONENT_DEFINITION( + vs_componentid_def=vs_componentid_def, + vs_lookup=vs_lookup, + vs_component_rank=vs_component_rank, + ) + + MSVS_COMPONENT_DEFINITION_LIST.append(vs_component_def) + + if vs_lookup == 'vswhere': + for symbol in vs_componentid_def.vs_component_symbols: + MSVS_COMPONENT_INTERNAL[(vs_lookup, symbol)] = vs_component_def + VSWHERE_COMPONENT_INTERNAL[symbol] = vs_component_def + VSWHERE_COMPONENT_SYMBOLS.append(symbol) + elif vs_lookup == 'registry': + for symbol in vs_componentid_def.vs_component_symbols: + MSVS_COMPONENT_INTERNAL[(vs_lookup, symbol)] = vs_component_def + REGISTRY_COMPONENT_INTERNAL[symbol] = vs_component_def + REGISTRY_COMPONENT_SYMBOLS.append(symbol) + +VSWHERE_COMPONENT_ENTERPRISE = VSWHERE_COMPONENT_INTERNAL['Ent'] +VSWHERE_COMPONENT_PROFESSIONAL = VSWHERE_COMPONENT_INTERNAL['Pro'] +VSWHERE_COMPONENT_COMMUNITY = VSWHERE_COMPONENT_INTERNAL['Com'] +VSWHERE_COMPONENT_BUILDTOOLS = VSWHERE_COMPONENT_INTERNAL['BT'] +VSWHERE_COMPONENT_EXPRESS = VSWHERE_COMPONENT_INTERNAL['Exp'] + +REGISTRY_COMPONENT_ENTERPRISE = REGISTRY_COMPONENT_INTERNAL['Ent'] +REGISTRY_COMPONENT_PROFESSIONAL = REGISTRY_COMPONENT_INTERNAL['Pro'] +REGISTRY_COMPONENT_COMMUNITY = REGISTRY_COMPONENT_INTERNAL['Com'] +REGISTRY_COMPONENT_DEVELOP = REGISTRY_COMPONENT_INTERNAL['Dev'] +REGISTRY_COMPONENT_PYTHON = REGISTRY_COMPONENT_INTERNAL['Py'] +REGISTRY_COMPONENT_CMDLINE = REGISTRY_COMPONENT_INTERNAL['Cmd'] +REGISTRY_COMPONENT_BUILDTOOLS = REGISTRY_COMPONENT_INTERNAL['BT'] +REGISTRY_COMPONENT_EXPRESS = REGISTRY_COMPONENT_INTERNAL['Exp'] + +# internal consistency check (should be last) def verify(): from .. import vc diff --git a/SCons/Tool/MSCommon/MSVC/Exceptions.py b/SCons/Tool/MSCommon/MSVC/Exceptions.py index 7b24a2b4b4..4f24ed17e1 100644 --- a/SCons/Tool/MSCommon/MSVC/Exceptions.py +++ b/SCons/Tool/MSCommon/MSVC/Exceptions.py @@ -39,6 +39,9 @@ class MSVCUserError(VisualCException): class MSVCScriptExecutionError(VisualCException): pass +class MSVCVersionUnsupported(MSVCUserError): + pass + class MSVCVersionNotFound(MSVCUserError): pass diff --git a/SCons/Tool/MSCommon/MSVC/Kind.py b/SCons/Tool/MSCommon/MSVC/Kind.py index 8b254a23b4..9dbb371d6d 100644 --- a/SCons/Tool/MSCommon/MSVC/Kind.py +++ b/SCons/Tool/MSCommon/MSVC/Kind.py @@ -36,6 +36,7 @@ debug, ) +from . import Config from . import Registry from . import Util @@ -43,211 +44,75 @@ 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_KIND_VSEXECUTABLE = (BITFIELD_KIND_DEVELOP, BITFIELD_KIND_EXPRESS) + +VCBinary = namedtuple("VCBinary", [ + 'program', 'bitfield', ]) -# +# IDE binaries -IDE_PROGRAM_DEVENV_COM = VCVER_KIND_PROGRAM( - kind=VCVER_KIND_DEVELOP, +IDE_PROGRAM_DEVENV_COM = VCBinary( program='devenv.com', bitfield=BITFIELD_KIND_DEVELOP, ) -IDE_PROGRAM_MSDEV_COM = VCVER_KIND_PROGRAM( - kind=VCVER_KIND_DEVELOP, +IDE_PROGRAM_MSDEV_COM = VCBinary( program='msdev.com', bitfield=BITFIELD_KIND_DEVELOP, ) -IDE_PROGRAM_WDEXPRESS_EXE = VCVER_KIND_PROGRAM( - kind=VCVER_KIND_EXPRESS, +IDE_PROGRAM_WDEXPRESS_EXE = VCBinary( program='WDExpress.exe', bitfield=BITFIELD_KIND_EXPRESS, ) -IDE_PROGRAM_VCEXPRESS_EXE = VCVER_KIND_PROGRAM( - kind=VCVER_KIND_EXPRESS, +IDE_PROGRAM_VCEXPRESS_EXE = VCBinary( program='VCExpress.exe', bitfield=BITFIELD_KIND_EXPRESS, ) -IDE_PROGRAM_VSWINEXPRESS_EXE = VCVER_KIND_PROGRAM( - kind=VCVER_KIND_EXPRESS_WIN, +IDE_PROGRAM_VSWINEXPRESS_EXE = VCBinary( program='VSWinExpress.exe', bitfield=BITFIELD_KIND_EXPRESS_WIN, ) -IDE_PROGRAM_VWDEXPRESS_EXE = VCVER_KIND_PROGRAM( - kind=VCVER_KIND_EXPRESS_WEB, +IDE_PROGRAM_VWDEXPRESS_EXE = VCBinary( program='VWDExpress.exe', bitfield=BITFIELD_KIND_EXPRESS_WEB, ) # detection configuration -VCVER_KIND_DETECT = namedtuple("VCVerKindDetect", [ +VCDetectConfig = namedtuple("VCDetectConfig", [ 'root', # relpath from pdir to vsroot 'path', # vsroot to ide dir 'programs', # ide binaries ]) -# detected binaries +# detected IDE binaries -VCVER_DETECT_BINARIES = namedtuple("VCVerDetectBinaries", [ +VCDetectedBinaries = namedtuple("VCDetectedBinaries", [ '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 + 'vs_exec', # vs binary ]) +def _msvc_dir_detect_binaries(vc_dir, detect): -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 - # print('register_kind: msvc_version=%s, kind=%s' % (repr(msvc_version), repr(VCVER_KIND_STR[kind_t.kind]))) - 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_exec = None - vs_root = os.path.join(pdir, detect.root) - ide_path = os.path.join(vs_root, detect.path) + vs_dir = os.path.join(vc_dir, detect.root) + ide_path = os.path.join(vs_dir, detect.path) bitfields = 0b_0000 for ide_program in detect.programs: @@ -255,82 +120,42 @@ def _pdir_detect_binaries(pdir, detect): if not os.path.exists(prog): continue bitfields |= ide_program.bitfield + if vs_exec: + continue + if ide_program.bitfield not in _BITFIELD_KIND_VSEXECUTABLE: + continue + vs_exec = prog 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( + binaries_t = VCDetectedBinaries( bitfields=bitfields, have_dev=have_dev, have_exp=have_exp, have_exp_win=have_exp_win, have_exp_web=have_exp_web, + vs_exec=vs_exec, ) debug( - 'vs_root=%s, dev=%s, exp=%s, exp_win=%s, exp_web=%s, pdir=%s', - repr(vs_root), + 'vs_dir=%s, dev=%s, exp=%s, exp_win=%s, exp_web=%s, vs_exec=%s, vc_dir=%s', + repr(vs_dir), binaries_t.have_dev, binaries_t.have_exp, binaries_t.have_exp_win, binaries_t.have_exp_web, - repr(pdir) + repr(binaries_t.vs_exec), + repr(vc_dir) ) - 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) + return vs_dir, binaries_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) - ) +def msvc_dir_vswhere(vc_dir, detect_t): - _cache_pdir_vswhere_kind[cache_key] = kind_t + _, binaries_t = _msvc_dir_detect_binaries(vc_dir, detect_t) - return kind_t + return binaries_t.vs_exec # VS2015 buildtools batch file call detection # vs2015 buildtools do not support sdk_version or UWP arguments @@ -357,15 +182,15 @@ def _vs_buildtools_2015_vcvars(vcvars_file): break return have_buildtools_vcvars -def _vs_buildtools_2015(vs_root, vc_dir): +def _vs_buildtools_2015(vs_dir, vc_dir): - is_btdispatch = False + is_buildtools = False do_once = True while do_once: do_once = False - buildtools_file = os.path.join(vs_root, _VS2015BT_PATH) + buildtools_file = os.path.join(vs_dir, _VS2015BT_PATH) have_buildtools = os.path.exists(buildtools_file) debug('have_buildtools=%s', have_buildtools) if not have_buildtools: @@ -382,10 +207,10 @@ def _vs_buildtools_2015(vs_root, vc_dir): if not have_buildtools_vcvars: break - is_btdispatch = True + is_buildtools = True - debug('is_btdispatch=%s', is_btdispatch) - return is_btdispatch + debug('is_buildtools=%s', is_buildtools) + return is_buildtools _VS2015EXP_VCVARS_LIBPATH = re.compile( ''.join([ @@ -408,21 +233,21 @@ def _vs_express_2015_vcvars(vcvars_file): have_uwp_fix = n_libpath >= 2 return have_uwp_fix -def _vs_express_2015(pdir): +def _vs_express_2015(vc_dir): have_uwp_amd64 = False have_uwp_arm = False - vcvars_file = os.path.join(pdir, r'vcvarsall.bat') + vcvars_file = os.path.join(vc_dir, r'vcvarsall.bat') if os.path.exists(vcvars_file): - vcvars_file = os.path.join(pdir, r'bin\x86_amd64\vcvarsx86_amd64.bat') + vcvars_file = os.path.join(vc_dir, 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') + vcvars_file = os.path.join(vc_dir, 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: @@ -435,10 +260,7 @@ def _vs_express_2015(pdir): _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 +def _msvc_dir_is_winsdk_only(vc_dir, msvc_version): # detect winsdk-only installations # @@ -452,15 +274,6 @@ def _msvc_version_pdir_registry_winsdk(msvc_version, pdir): # - 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: @@ -470,202 +283,102 @@ def _msvc_version_pdir_registry_winsdk(msvc_version, pdir): 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 + vc_regdir = vc_qresults[0] if vc_qresults else None - if vc_dir != vc_root: - # registry vc path is not the current pdir + if vc_regdir != vc_dir: + # registry vc path is not the current vc dir 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) + 'is_sdk=%s, msvc_version=%s, vc_dir=%s, vc_regdir=%s', + is_sdk, repr(msvc_version), repr(vc_dir), repr(vc_regdir) ) else: - # registry vc path is the current pdir + # registry vc dir is the current vc root 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 + vs_dir = vs_qresults[0] if vs_qresults else None - is_sdk = bool(not vs_root and vc_root) + is_sdk = bool(not vs_dir and vc_dir) debug( - 'is_sdk=%s, msvc_version=%s, vs_root=%s, vc_root=%s', - is_sdk, repr(msvc_version), repr(vs_root), repr(vc_root) + 'is_sdk=%s, msvc_version=%s, vc_dir=%s, vs_dir=%s', + is_sdk, repr(msvc_version), repr(vc_dir), repr(vs_dir) ) - _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) +def msvc_dir_registry(vc_dir, detect_t, msvc_version, is_vcforpython, vs_version): - rval = _cache_pdir_registry_kind.get(cache_key) - if rval is not None: - debug('cache=%s', repr(rval)) - return rval + vc_feature_map = {} - extended = {} - - prefix, suffix = Util.get_msvc_version_prefix_suffix(msvc_version) - - vs_root, binaries_t = _pdir_detect_binaries(pdir, detect_t) + vs_dir, binaries_t = _msvc_dir_detect_binaries(vc_dir, detect_t) if binaries_t.have_dev: - kind = VCVER_KIND_DEVELOP + vs_component_def = Config.REGISTRY_COMPONENT_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 + vs_component_def = Config.REGISTRY_COMPONENT_EXPRESS elif msvc_version == '9.0' and is_vcforpython: - kind = VCVER_KIND_VCFORPYTHON + vs_component_def = Config.REGISTRY_COMPONENT_PYTHON elif binaries_t.have_exp_win: - kind = VCVER_KIND_EXPRESS_WIN + vs_component_def = None elif binaries_t.have_exp_web: - kind = VCVER_KIND_EXPRESS_WEB - elif _msvc_version_pdir_registry_winsdk(msvc_version, pdir): - kind = VCVER_KIND_SDK + vs_component_def = None + elif _msvc_dir_is_winsdk_only(vc_dir, msvc_version): + vs_component_def = None 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) + vs_component_def = Config.REGISTRY_COMPONENT_CMDLINE - kind_t = _msvc_version_kind_lookup(msvc_version, env) + if vs_component_def and msvc_version == '14.0': - 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 + # remap DEVELOP => ENTERPRISE, PROFESSIONAL, COMMUNITY + # remap CMDLINE => BUILDTOOLS [conditional] + # process EXPRESS + + if vs_component_def == Config.REGISTRY_COMPONENT_DEVELOP: + + for reg_component, reg_component_def in [ + ('community', Config.REGISTRY_COMPONENT_COMMUNITY), + ('professional', Config.REGISTRY_COMPONENT_PROFESSIONAL), + ('enterprise', Config.REGISTRY_COMPONENT_ENTERPRISE), + ]: + suffix = Registry.devdiv_vs_servicing_component(vs_version, reg_component) + qresults = Registry.microsoft_query_keys(suffix, usrval=reg_component_def) + if not qresults: + continue + vs_component_def = qresults[0][-1] + break - debug( - 'is_supported=%s, msvc_version=%s, kind=%s', - is_supported, repr(msvc_version), repr(VCVER_KIND_STR[kind_t.kind]) - ) - return is_supported + elif vs_component_def == Config.REGISTRY_COMPONENT_CMDLINE: -def msvc_version_uwp_is_supported(msvc_version, target_arch=None, env=None): + if _vs_buildtools_2015(vs_dir, vc_dir): + vs_component_def = Config.REGISTRY_COMPONENT_BUILDTOOLS - vernum = float(Util.get_msvc_version_prefix(msvc_version)) - vernum_int = int(vernum * 10) + elif vs_component_def == Config.REGISTRY_COMPONENT_EXPRESS: - kind_t = _msvc_version_kind_lookup(msvc_version, env) + have_uwp_amd64, have_uwp_arm = _vs_express_2015(vc_dir) - is_target = False + uwp_target_is_supported = { + 'x86': True, + 'amd64': have_uwp_amd64, + 'arm': have_uwp_arm, + } - 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 + vc_feature_map['uwp_target_is_supported'] = uwp_target_is_supported 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) + 'msvc_version=%s, vs_component=%s, vc_dir=%s, vc_feature_map=%s', + repr(msvc_version), + repr(vs_component_def.vs_componentid_def.vs_component_id) if vs_component_def else None, + repr(vc_dir), + repr(vc_feature_map), ) - return is_supported, is_target - -# reset cache - -def reset() -> None: - global _cache_installed_vcs - global _cache_vcver_kind_map - global _cache_pdir_vswhere_kind - global _cache_pdir_registry_kind - global _cache_pdir_registry_winsdk - - debug('') + return vs_component_def, vs_dir, binaries_t.vs_exec, vc_feature_map - _cache_installed_vcs = None - _cache_vcver_kind_map = {} - _cache_pdir_vswhere_kind = {} - _cache_pdir_registry_kind = {} - _cache_pdir_registry_winsdk = {} diff --git a/SCons/Tool/MSCommon/MSVC/Registry.py b/SCons/Tool/MSCommon/MSVC/Registry.py index 970b4d4412..b5b72c42fc 100644 --- a/SCons/Tool/MSCommon/MSVC/Registry.py +++ b/SCons/Tool/MSCommon/MSVC/Registry.py @@ -62,7 +62,7 @@ def registry_query_path(key, val, suffix, expand: bool=True): extval = val + '\\' + suffix if suffix else val qpath = read_value(key, extval, expand=expand) if qpath and os.path.exists(qpath): - qpath = Util.process_path(qpath) + qpath = Util.normalize_path(qpath) else: qpath = None return (qpath, key, val, extval) @@ -81,7 +81,7 @@ def microsoft_query_paths(suffix, usrval=None, expand: bool=True): extval = val + '\\' + suffix if suffix else val qpath = read_value(key, extval, expand=expand) if qpath and os.path.exists(qpath): - qpath = Util.process_path(qpath) + qpath = Util.normalize_path(qpath) if qpath not in paths: paths.append(qpath) records.append((qpath, key, val, extval, usrval)) diff --git a/SCons/Tool/MSCommon/MSVC/RegistryTests.py b/SCons/Tool/MSCommon/MSVC/RegistryTests.py index 9c9ef89a0e..859bb69ae4 100644 --- a/SCons/Tool/MSCommon/MSVC/RegistryTests.py +++ b/SCons/Tool/MSCommon/MSVC/RegistryTests.py @@ -40,10 +40,10 @@ class RegistryTests(unittest.TestCase): def setUpClass(cls) -> None: cls._sdk_versions = [] sdk_seen = set() - for vs_def in Config.VISUALSTUDIO_DEFINITION_LIST: - if not vs_def.vc_sdk_versions: + for vs_product_def in Config.MSVS_PRODUCT_DEFINITION_LIST: + if not vs_product_def.vc_sdk_versions: continue - for sdk_version in vs_def.vc_sdk_versions: + for sdk_version in vs_product_def.vc_sdk_versions: if sdk_version in sdk_seen: continue sdk_seen.add(sdk_version) diff --git a/SCons/Tool/MSCommon/MSVC/ScriptArguments.py b/SCons/Tool/MSCommon/MSVC/ScriptArguments.py index c689d7a0b5..ebef9d693b 100644 --- a/SCons/Tool/MSCommon/MSVC/ScriptArguments.py +++ b/SCons/Tool/MSCommon/MSVC/ScriptArguments.py @@ -42,7 +42,6 @@ from . import Config from . import Registry from . import WinSDK -from . import Kind from .Exceptions import ( MSVCInternalError, @@ -83,6 +82,7 @@ def _verify_re_sdk_dispatch_map(): _msvc_sxs_bugfix_folder = {} _msvc_sxs_bugfix_version = {} +# TODO(JCB): verstr or version? for msvc_version, sxs_version, sxs_bugfix in [ # VS2019\Common7\Tools\vsdevcmd\ext\vcvars.bat AzDO Bug#1293526 # special handling of the 16.8 SxS toolset, use VC\Auxiliary\Build\14.28 directory and SxS files @@ -168,39 +168,27 @@ class SortOrder(enum.IntEnum): VS2017 = Config.MSVS_VERSION_INTERNAL['2017'] VS2015 = Config.MSVS_VERSION_INTERNAL['2015'] -MSVC_VERSION_ARGS_DEFINITION = namedtuple('MSVCVersionArgsDefinition', [ - 'version', # full version (e.g., '14.1Exp', '14.32.31326') - 'vs_def', +MSVC_TOOLSET_VERSION_ARGS_DEFINITION = namedtuple('MSVCToolsetVersionArgsDefinition', [ + 'version', # full version (e.g., '14.32.31326') + 'vs_product_def', ]) -def _msvc_version(version): - - verstr = Util.get_msvc_version_prefix(version) - vs_def = Config.MSVC_VERSION_INTERNAL[verstr] - - version_args = MSVC_VERSION_ARGS_DEFINITION( - version = version, - vs_def = vs_def, - ) - - return version_args - def _toolset_version(version): verstr = Util.get_msvc_version_prefix(version) - vs_def = Config.MSVC_VERSION_INTERNAL[verstr] + vs_product_def = Config.MSVC_VERSION_INTERNAL[verstr] - version_args = MSVC_VERSION_ARGS_DEFINITION( + version_args = MSVC_TOOLSET_VERSION_ARGS_DEFINITION( version = version, - vs_def = vs_def, + vs_product_def = vs_product_def, ) return version_args -def _msvc_script_argument_uwp(env, msvc, arglist, target_arch): +def _msvc_script_argument_uwp(env, msvc_instance, arglist, target_arch): uwp_app = env['MSVC_UWP_APP'] - debug('MSVC_VERSION=%s, MSVC_UWP_APP=%s', repr(msvc.version), repr(uwp_app)) + debug('MSVC_VERSION=%s, MSVC_UWP_APP=%s', repr(msvc_instance.msvc_version), repr(uwp_app)) if not uwp_app: return None @@ -208,36 +196,36 @@ def _msvc_script_argument_uwp(env, msvc, arglist, target_arch): 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_instance.vs_product_def.vc_buildtools_def.vc_version_numeric < VS2015.vc_buildtools_def.vc_version_numeric: debug( 'invalid: msvc version constraint: %s < %s VS2015', - repr(msvc.vs_def.vc_buildtools_def.vc_version_numeric), + repr(msvc_instance.vs_product_def.vc_buildtools_def.vc_version_numeric), repr(VS2015.vc_buildtools_def.vc_version_numeric) ) err_msg = "MSVC_UWP_APP ({}) constraint violation: MSVC_VERSION {} < {} VS2015".format( - repr(uwp_app), repr(msvc.version), repr(VS2015.vc_buildtools_def.vc_version) + repr(uwp_app), repr(msvc_instance.msvc_version), repr(VS2015.vc_buildtools_def.vc_version) ) raise MSVCArgumentError(err_msg) - is_supported, is_target = Kind.msvc_version_uwp_is_supported(msvc.version, target_arch, env) + is_supported, is_target = msvc_instance.is_uwp_target_supported(target_arch) if not is_supported: - _, kind_str = Kind.get_msvc_version_kind(msvc.version) + component_str = msvc_instance.vs_component_id debug( 'invalid: msvc_version constraint: %s %s %s', - repr(msvc.version), repr(kind_str), repr(target_arch) + repr(msvc_instance.msvc_version), repr(component_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) + repr(uwp_app), repr(target_arch), repr(msvc_instance.msvc_version), repr(component_str) ) else: err_msg = "MSVC_UWP_APP ({}) is not supported for MSVC_VERSION {} ({})".format( - repr(uwp_app), repr(msvc.version), repr(kind_str) + repr(uwp_app), repr(msvc_instance.msvc_version), repr(component_str) ) raise MSVCArgumentError(err_msg) # VS2017+ rewrites uwp => store for 14.0 toolset - uwp_arg = msvc.vs_def.vc_uwp + uwp_arg = msvc_instance.vs_product_def.vc_uwp # store/uwp may not be fully installed argpair = (SortOrder.UWP, uwp_arg) @@ -268,28 +256,28 @@ def _user_script_argument_uwp(env, uwp, user_argstr) -> bool: raise MSVCArgumentError(err_msg) -def _msvc_script_argument_sdk_constraints(msvc, sdk_version, env): +def _msvc_script_argument_sdk_constraints(msvc_instance, sdk_version): - if msvc.vs_def.vc_buildtools_def.vc_version_numeric < VS2015.vc_buildtools_def.vc_version_numeric: + if msvc_instance.vs_product_def.vc_buildtools_def.vc_version_numeric < VS2015.vc_buildtools_def.vc_version_numeric: debug( 'invalid: msvc_version constraint: %s < %s VS2015', - repr(msvc.vs_def.vc_buildtools_def.vc_version_numeric), + repr(msvc_instance.vs_product_def.vc_buildtools_def.vc_version_numeric), repr(VS2015.vc_buildtools_def.vc_version_numeric) ) err_msg = "MSVC_SDK_VERSION ({}) constraint violation: MSVC_VERSION {} < {} VS2015".format( - repr(sdk_version), repr(msvc.version), repr(VS2015.vc_buildtools_def.vc_version) + repr(sdk_version), repr(msvc_instance.msvc_version), repr(VS2015.vc_buildtools_def.vc_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)) + if not msvc_instance.is_sdkversion_supported: + component_str = msvc_instance.vs_component_id + debug('invalid: msvc_version constraint: %s %s', repr(msvc_instance.msvc_version), repr(component_str)) err_msg = "MSVC_SDK_VERSION ({}) is not supported for MSVC_VERSION {} ({})".format( - repr(sdk_version), repr(msvc.version), repr(kind_str) + repr(sdk_version), repr(msvc_instance.msvc_version), repr(component_str) ) return err_msg - for msvc_sdk_version in msvc.vs_def.vc_sdk_versions: + for msvc_sdk_version in msvc_instance.vs_product_def.vc_sdk_versions: re_sdk_version = re_sdk_dispatch_map[msvc_sdk_version] if re_sdk_version.match(sdk_version): debug('valid: sdk_version=%s', repr(sdk_version)) @@ -299,19 +287,19 @@ def _msvc_script_argument_sdk_constraints(msvc, sdk_version, env): err_msg = f"MSVC_SDK_VERSION ({sdk_version!r}) is not supported" return err_msg -def _msvc_script_argument_sdk_platform_constraints(msvc, toolset, sdk_version, platform_def): +def _msvc_script_argument_sdk_platform_constraints(msvc_instance, toolset, sdk_version, platform_def): if sdk_version == '8.1' and platform_def.is_uwp: - vs_def = toolset.vs_def if toolset else msvc.vs_def + vs_product_def = toolset.vs_product_def if toolset else msvc_instance.vs_product_def - if vs_def.vc_buildtools_def.vc_version_numeric > VS2015.vc_buildtools_def.vc_version_numeric: + if vs_product_def.vc_buildtools_def.vc_version_numeric > VS2015.vc_buildtools_def.vc_version_numeric: debug( 'invalid: uwp/store SDK 8.1 msvc_version constraint: %s > %s VS2015', - repr(vs_def.vc_buildtools_def.vc_version_numeric), + repr(vs_product_def.vc_buildtools_def.vc_version_numeric), repr(VS2015.vc_buildtools_def.vc_version_numeric) ) - if toolset and toolset.vs_def != msvc.vs_def: + if toolset and toolset.vs_product_def != msvc_instance.vs_product_def: err_msg = "MSVC_SDK_VERSION ({}) and platform type ({}) constraint violation: toolset version {} > {} VS2015".format( repr(sdk_version), repr(platform_def.vc_platform), repr(toolset.version), repr(VS2015.vc_buildtools_def.vc_version) @@ -319,28 +307,28 @@ def _msvc_script_argument_sdk_platform_constraints(msvc, toolset, sdk_version, p 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_instance.msvc_version), repr(VS2015.vc_buildtools_def.vc_version) ) return err_msg return None -def _msvc_script_argument_sdk(env, msvc, toolset, platform_def, arglist): +def _msvc_script_argument_sdk(env, msvc_instance, toolset, platform_def, arglist): sdk_version = env['MSVC_SDK_VERSION'] debug( 'MSVC_VERSION=%s, MSVC_SDK_VERSION=%s, platform_type=%s', - repr(msvc.version), repr(sdk_version), repr(platform_def.vc_platform) + repr(msvc_instance.msvc_version), repr(sdk_version), repr(platform_def.vc_platform) ) if not sdk_version: return None - err_msg = _msvc_script_argument_sdk_constraints(msvc, sdk_version, env) + err_msg = _msvc_script_argument_sdk_constraints(msvc_instance, sdk_version) if err_msg: raise MSVCArgumentError(err_msg) - sdk_list = WinSDK.get_sdk_version_list(msvc.vs_def, platform_def) + sdk_list = WinSDK.get_sdk_version_list(msvc_instance.vs_product_def, platform_def) if sdk_version not in sdk_list: err_msg = "MSVC_SDK_VERSION {} not found for platform type {}".format( @@ -348,7 +336,7 @@ def _msvc_script_argument_sdk(env, msvc, toolset, platform_def, arglist): ) raise MSVCSDKVersionNotFound(err_msg) - err_msg = _msvc_script_argument_sdk_platform_constraints(msvc, toolset, sdk_version, platform_def) + err_msg = _msvc_script_argument_sdk_platform_constraints(msvc_instance, toolset, sdk_version, platform_def) if err_msg: raise MSVCArgumentError(err_msg) @@ -357,15 +345,15 @@ def _msvc_script_argument_sdk(env, msvc, toolset, platform_def, arglist): return sdk_version -def _msvc_script_default_sdk(env, msvc, platform_def, arglist, force_sdk: bool=False): +def _msvc_script_default_sdk(env, msvc_instance, 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_instance.vs_product_def.vc_buildtools_def.vc_version_numeric < VS2015.vc_buildtools_def.vc_version_numeric: return None - if not Kind.msvc_version_sdk_version_is_supported(msvc.version, env): + if not msvc_instance.is_sdkversion_supported: return None - sdk_list = WinSDK.get_sdk_version_list(msvc.vs_def, platform_def) + sdk_list = WinSDK.get_sdk_version_list(msvc_instance.vs_product_def, platform_def) if not len(sdk_list): return None @@ -373,7 +361,7 @@ def _msvc_script_default_sdk(env, msvc, platform_def, arglist, force_sdk: bool=F debug( 'MSVC_VERSION=%s, sdk_default=%s, platform_type=%s', - repr(msvc.version), repr(sdk_default), repr(platform_def.vc_platform) + repr(msvc_instance.msvc_version), repr(sdk_default), repr(platform_def.vc_platform) ) if force_sdk: @@ -424,27 +412,27 @@ def _reset_have140_cache() -> None: debug('reset: cache') _toolset_have140_cache = None -def _msvc_read_toolset_file(msvc, filename): +def _msvc_read_toolset_file(msvc_instance, filename): toolset_version = None try: with open(filename) as f: toolset_version = f.readlines()[0].strip() debug( 'msvc_version=%s, filename=%s, toolset_version=%s', - repr(msvc.version), repr(filename), repr(toolset_version) + repr(msvc_instance.msvc_version), repr(filename), repr(toolset_version) ) except OSError: - debug('OSError: msvc_version=%s, filename=%s', repr(msvc.version), repr(filename)) + debug('OSError: msvc_version=%s, filename=%s', repr(msvc_instance.msvc_version), repr(filename)) except IndexError: - debug('IndexError: msvc_version=%s, filename=%s', repr(msvc.version), repr(filename)) + debug('IndexError: msvc_version=%s, filename=%s', repr(msvc_instance.msvc_version), repr(filename)) return toolset_version -def _msvc_sxs_toolset_folder(msvc, sxs_folder): +def _msvc_sxs_toolset_folder(msvc_instance, 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) + key = (msvc_instance.vs_product_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 @@ -452,31 +440,31 @@ def _msvc_sxs_toolset_folder(msvc, sxs_folder): debug('sxs folder: ignore version=%s', repr(sxs_folder)) return None, None -def _msvc_read_toolset_folders(msvc, vc_dir): +def _msvc_read_toolset_folders(msvc_instance): toolsets_sxs = {} toolsets_full = [] - build_dir = os.path.join(vc_dir, "Auxiliary", "Build") + build_dir = os.path.join(msvc_instance.vc_dir, "Auxiliary", "Build") if os.path.exists(build_dir): for sxs_folder, sxs_path in Util.listdir_dirs(build_dir): - sxs_folder, sxs_version = _msvc_sxs_toolset_folder(msvc, sxs_folder) + sxs_folder, sxs_version = _msvc_sxs_toolset_folder(msvc_instance, sxs_folder) if not sxs_version: continue filename = f'Microsoft.VCToolsVersion.{sxs_folder}.txt' filepath = os.path.join(sxs_path, filename) debug('sxs toolset: check file=%s', repr(filepath)) if os.path.exists(filepath): - toolset_version = _msvc_read_toolset_file(msvc, filepath) + toolset_version = _msvc_read_toolset_file(msvc_instance, filepath) if not toolset_version: continue toolsets_sxs[sxs_version] = toolset_version debug( 'sxs toolset: msvc_version=%s, sxs_version=%s, toolset_version=%s', - repr(msvc.version), repr(sxs_version), repr(toolset_version) + repr(msvc_instance.msvc_version), repr(sxs_version), repr(toolset_version) ) - toolset_dir = os.path.join(vc_dir, "Tools", "MSVC") + toolset_dir = os.path.join(msvc_instance.vc_dir, "Tools", "MSVC") if os.path.exists(toolset_dir): for toolset_version, toolset_path in Util.listdir_dirs(toolset_dir): binpath = os.path.join(toolset_path, "bin") @@ -485,23 +473,23 @@ def _msvc_read_toolset_folders(msvc, vc_dir): toolsets_full.append(toolset_version) debug( 'toolset: msvc_version=%s, toolset_version=%s', - repr(msvc.version), repr(toolset_version) + repr(msvc_instance.msvc_version), repr(toolset_version) ) - vcvars140 = os.path.join(vc_dir, "..", "Common7", "Tools", "vsdevcmd", "ext", "vcvars", "vcvars140.bat") + vcvars140 = os.path.join(msvc_instance.vc_dir, "..", "Common7", "Tools", "vsdevcmd", "ext", "vcvars", "vcvars140.bat") if os.path.exists(vcvars140) and _msvc_have140_toolset(): toolset_version = '14.0' toolsets_full.append(toolset_version) debug( 'toolset: msvc_version=%s, toolset_version=%s', - repr(msvc.version), repr(toolset_version) + repr(msvc_instance.msvc_version), repr(toolset_version) ) toolsets_full.sort(reverse=True) # SxS bugfix fixup (if necessary) - if msvc.version in _msvc_sxs_bugfix_map: - for sxs_version, sxs_bugfix in _msvc_sxs_bugfix_map[msvc.version]: + if msvc_instance.msvc_version in _msvc_sxs_bugfix_map: + for sxs_version, sxs_bugfix in _msvc_sxs_bugfix_map[msvc_instance.msvc_version]: if sxs_version in toolsets_sxs: # have SxS version (folder/file mapping exists) continue @@ -516,21 +504,21 @@ def _msvc_read_toolset_folders(msvc, vc_dir): toolsets_sxs[sxs_version] = toolset_version break - debug('msvc_version=%s, toolsets=%s', repr(msvc.version), repr(toolsets_full)) + debug('msvc_version=%s, toolsets=%s', repr(msvc_instance.msvc_version), repr(toolsets_full)) return toolsets_sxs, toolsets_full -def _msvc_read_toolset_default(msvc, vc_dir): +def _msvc_read_toolset_default(msvc_instance): - build_dir = os.path.join(vc_dir, "Auxiliary", "Build") + build_dir = os.path.join(msvc_instance.vc_dir, "Auxiliary", "Build") # VS2019+ - filename = f"Microsoft.VCToolsVersion.{msvc.vs_def.vc_buildtools_def.vc_buildtools}.default.txt" + filename = f"Microsoft.VCToolsVersion.{msvc_instance.vs_product_def.vc_buildtools_def.vc_buildtools}.default.txt" filepath = os.path.join(build_dir, filename) debug('default toolset: check file=%s', repr(filepath)) if os.path.exists(filepath): - toolset_buildtools = _msvc_read_toolset_file(msvc, filepath) + toolset_buildtools = _msvc_read_toolset_file(msvc_instance, filepath) if toolset_buildtools: return toolset_buildtools @@ -540,7 +528,7 @@ def _msvc_read_toolset_default(msvc, vc_dir): debug('default toolset: check file=%s', repr(filepath)) if os.path.exists(filepath): - toolset_default = _msvc_read_toolset_file(msvc, filepath) + toolset_default = _msvc_read_toolset_file(msvc_instance, filepath) if toolset_default: return toolset_default @@ -556,29 +544,33 @@ def _reset_toolset_cache() -> None: _toolset_version_cache = {} _toolset_default_cache = {} -def _msvc_version_toolsets(msvc, vc_dir): +def _msvc_version_toolsets(msvc_instance): + + cache_key = msvc_instance.vs_edition_channel_component_seqnbr_key - if msvc.version in _toolset_version_cache: - toolsets_sxs, toolsets_full = _toolset_version_cache[msvc.version] + if cache_key in _toolset_version_cache: + toolsets_sxs, toolsets_full = _toolset_version_cache[cache_key] else: - toolsets_sxs, toolsets_full = _msvc_read_toolset_folders(msvc, vc_dir) - _toolset_version_cache[msvc.version] = toolsets_sxs, toolsets_full + toolsets_sxs, toolsets_full = _msvc_read_toolset_folders(msvc_instance) + _toolset_version_cache[cache_key] = toolsets_sxs, toolsets_full return toolsets_sxs, toolsets_full -def _msvc_default_toolset(msvc, vc_dir): +def _msvc_default_toolset(msvc_instance): - if msvc.version in _toolset_default_cache: - toolset_default = _toolset_default_cache[msvc.version] + cache_key = msvc_instance.vs_edition_channel_component_seqnbr_key + + if cache_key in _toolset_default_cache: + toolset_default = _toolset_default_cache[cache_key] else: - toolset_default = _msvc_read_toolset_default(msvc, vc_dir) - _toolset_default_cache[msvc.version] = toolset_default + toolset_default = _msvc_read_toolset_default(msvc_instance) + _toolset_default_cache[cache_key] = toolset_default return toolset_default -def _msvc_version_toolset_vcvars(msvc, vc_dir, toolset_version): +def _msvc_version_toolset_vcvars(msvc_instance, toolset_version): - toolsets_sxs, toolsets_full = _msvc_version_toolsets(msvc, vc_dir) + toolsets_sxs, toolsets_full = _msvc_version_toolsets(msvc_instance) if toolset_version in toolsets_full: # full toolset version provided @@ -601,16 +593,16 @@ def _msvc_version_toolset_vcvars(msvc, vc_dir, toolset_version): return None -def _msvc_script_argument_toolset_constraints(msvc, toolset_version): +def _msvc_script_argument_toolset_constraints(msvc_instance, toolset_version): - if msvc.vs_def.vc_buildtools_def.vc_version_numeric < VS2017.vc_buildtools_def.vc_version_numeric: + if msvc_instance.vs_product_def.vc_buildtools_def.vc_version_numeric < VS2017.vc_buildtools_def.vc_version_numeric: debug( 'invalid: msvc version constraint: %s < %s VS2017', - repr(msvc.vs_def.vc_buildtools_def.vc_version_numeric), + repr(msvc_instance.vs_product_def.vc_buildtools_def.vc_version_numeric), repr(VS2017.vc_buildtools_def.vc_version_numeric) ) err_msg = "MSVC_TOOLSET_VERSION ({}) constraint violation: MSVC_VERSION {} < {} VS2017".format( - repr(toolset_version), repr(msvc.version), repr(VS2017.vc_buildtools_def.vc_version) + repr(toolset_version), repr(msvc_instance.msvc_version), repr(VS2017.vc_buildtools_def.vc_version) ) return err_msg @@ -635,13 +627,13 @@ def _msvc_script_argument_toolset_constraints(msvc, toolset_version): ) return err_msg - if toolset_vernum > msvc.vs_def.vc_buildtools_def.vc_version_numeric: + if toolset_vernum > msvc_instance.vs_product_def.vc_buildtools_def.vc_version_numeric: debug( 'invalid: toolset version constraint: toolset %s > %s msvc', - repr(toolset_vernum), repr(msvc.vs_def.vc_buildtools_def.vc_version_numeric) + repr(toolset_vernum), repr(msvc_instance.vs_product_def.vc_buildtools_def.vc_version_numeric) ) err_msg = "MSVC_TOOLSET_VERSION ({}) constraint violation: toolset version {} > {} MSVC_VERSION".format( - repr(toolset_version), repr(toolset_verstr), repr(msvc.version) + repr(toolset_version), repr(toolset_verstr), repr(msvc_instance.msvc_version) ) return err_msg @@ -671,9 +663,9 @@ def _msvc_script_argument_toolset_constraints(msvc, toolset_version): err_msg = f"MSVC_TOOLSET_VERSION ({toolset_version!r}) format is not supported" return err_msg -def _msvc_script_argument_toolset_vcvars(msvc, toolset_version, vc_dir): +def _msvc_script_argument_toolset_vcvars(msvc_instance, toolset_version): - err_msg = _msvc_script_argument_toolset_constraints(msvc, toolset_version) + err_msg = _msvc_script_argument_toolset_constraints(msvc_instance, toolset_version) if err_msg: raise MSVCArgumentError(err_msg) @@ -685,7 +677,7 @@ def _msvc_script_argument_toolset_vcvars(msvc, toolset_version, vc_dir): ) toolset_version = new_toolset_version - toolset_vcvars = _msvc_version_toolset_vcvars(msvc, vc_dir, toolset_version) + toolset_vcvars = _msvc_version_toolset_vcvars(msvc_instance, toolset_version) debug( 'toolset: toolset_version=%s, toolset_vcvars=%s', repr(toolset_version), repr(toolset_vcvars) @@ -693,21 +685,21 @@ def _msvc_script_argument_toolset_vcvars(msvc, toolset_version, vc_dir): if not toolset_vcvars: err_msg = "MSVC_TOOLSET_VERSION {} not found for MSVC_VERSION {}".format( - repr(toolset_version), repr(msvc.version) + repr(toolset_version), repr(msvc_instance.msvc_version) ) raise MSVCToolsetVersionNotFound(err_msg) return toolset_vcvars -def _msvc_script_argument_toolset(env, msvc, vc_dir, arglist): +def _msvc_script_argument_toolset(env, msvc_instance, arglist): toolset_version = env['MSVC_TOOLSET_VERSION'] - debug('MSVC_VERSION=%s, MSVC_TOOLSET_VERSION=%s', repr(msvc.version), repr(toolset_version)) + debug('MSVC_VERSION=%s, MSVC_TOOLSET_VERSION=%s', repr(msvc_instance.msvc_version), repr(toolset_version)) if not toolset_version: return None - toolset_vcvars = _msvc_script_argument_toolset_vcvars(msvc, toolset_version, vc_dir) + toolset_vcvars = _msvc_script_argument_toolset_vcvars(msvc_instance, toolset_version) # toolset may not be installed for host/target argpair = (SortOrder.TOOLSET, f'-vcvars_ver={toolset_vcvars}') @@ -715,16 +707,16 @@ def _msvc_script_argument_toolset(env, msvc, vc_dir, arglist): return toolset_vcvars -def _msvc_script_default_toolset(env, msvc, vc_dir, arglist, force_toolset: bool=False): +def _msvc_script_default_toolset(env, msvc_instance, arglist, force_toolset: bool=False): - if msvc.vs_def.vc_buildtools_def.vc_version_numeric < VS2017.vc_buildtools_def.vc_version_numeric: + if msvc_instance.vs_product_def.vc_buildtools_def.vc_version_numeric < VS2017.vc_buildtools_def.vc_version_numeric: return None - toolset_default = _msvc_default_toolset(msvc, vc_dir) + toolset_default = _msvc_default_toolset(msvc_instance) if not toolset_default: return None - debug('MSVC_VERSION=%s, toolset_default=%s', repr(msvc.version), repr(toolset_default)) + debug('MSVC_VERSION=%s, toolset_default=%s', repr(msvc_instance.msvc_version), repr(toolset_default)) if force_toolset: argpair = (SortOrder.TOOLSET, f'-vcvars_ver={toolset_default}') @@ -756,24 +748,24 @@ def _user_script_argument_toolset(env, toolset_version, user_argstr): raise MSVCArgumentError(err_msg) -def _msvc_script_argument_spectre_constraints(msvc, toolset, spectre_libs, platform_def): +def _msvc_script_argument_spectre_constraints(msvc_instance, toolset, spectre_libs, platform_def): - if msvc.vs_def.vc_buildtools_def.vc_version_numeric < VS2017.vc_buildtools_def.vc_version_numeric: + if msvc_instance.vs_product_def.vc_buildtools_def.vc_version_numeric < VS2017.vc_buildtools_def.vc_version_numeric: debug( 'invalid: msvc version constraint: %s < %s VS2017', - repr(msvc.vs_def.vc_buildtools_def.vc_version_numeric), + repr(msvc_instance.vs_product_def.vc_buildtools_def.vc_version_numeric), repr(VS2017.vc_buildtools_def.vc_version_numeric) ) err_msg = "MSVC_SPECTRE_LIBS ({}) constraint violation: MSVC_VERSION {} < {} VS2017".format( - repr(spectre_libs), repr(msvc.version), repr(VS2017.vc_buildtools_def.vc_version) + repr(spectre_libs), repr(msvc_instance.msvc_version), repr(VS2017.vc_buildtools_def.vc_version) ) return err_msg if toolset: - if toolset.vs_def.vc_buildtools_def.vc_version_numeric < VS2017.vc_buildtools_def.vc_version_numeric: + if toolset.vs_product_def.vc_buildtools_def.vc_version_numeric < VS2017.vc_buildtools_def.vc_version_numeric: debug( 'invalid: toolset version constraint: %s < %s VS2017', - repr(toolset.vs_def.vc_buildtools_def.vc_version_numeric), + repr(toolset.vs_product_def.vc_buildtools_def.vc_version_numeric), repr(VS2017.vc_buildtools_def.vc_version_numeric) ) err_msg = "MSVC_SPECTRE_LIBS ({}) constraint violation: toolset version {} < {} VS2017".format( @@ -781,7 +773,6 @@ def _msvc_script_argument_spectre_constraints(msvc, toolset, spectre_libs, platf ) return err_msg - if platform_def.is_uwp: debug( 'invalid: spectre_libs=%s and platform_type=%s', @@ -794,14 +785,14 @@ def _msvc_script_argument_spectre_constraints(msvc, toolset, spectre_libs, platf return None -def _msvc_toolset_version_spectre_path(vc_dir, toolset_version): - spectre_dir = os.path.join(vc_dir, "Tools", "MSVC", toolset_version, "lib", "spectre") +def _msvc_toolset_version_spectre_path(msvc_instance, toolset_version): + spectre_dir = os.path.join(msvc_instance.vc_dir, "Tools", "MSVC", toolset_version, "lib", "spectre") return spectre_dir -def _msvc_script_argument_spectre(env, msvc, vc_dir, toolset, platform_def, arglist): +def _msvc_script_argument_spectre(env, msvc_instance, toolset, platform_def, arglist): spectre_libs = env['MSVC_SPECTRE_LIBS'] - debug('MSVC_VERSION=%s, MSVC_SPECTRE_LIBS=%s', repr(msvc.version), repr(spectre_libs)) + debug('MSVC_VERSION=%s, MSVC_SPECTRE_LIBS=%s', repr(msvc_instance.msvc_version), repr(spectre_libs)) if not spectre_libs: return None @@ -809,19 +800,19 @@ def _msvc_script_argument_spectre(env, msvc, vc_dir, toolset, platform_def, argl if spectre_libs not in _ARGUMENT_BOOLEAN_TRUE: return None - err_msg = _msvc_script_argument_spectre_constraints(msvc, toolset, spectre_libs, platform_def) + err_msg = _msvc_script_argument_spectre_constraints(msvc_instance, toolset, spectre_libs, platform_def) if err_msg: raise MSVCArgumentError(err_msg) if toolset: - spectre_dir = _msvc_toolset_version_spectre_path(vc_dir, toolset.version) + spectre_dir = _msvc_toolset_version_spectre_path(msvc_instance, toolset.version) if not os.path.exists(spectre_dir): debug( 'spectre libs: msvc_version=%s, toolset_version=%s, spectre_dir=%s', - repr(msvc.version), repr(toolset.version), repr(spectre_dir) + repr(msvc_instance.msvc_version), repr(toolset.version), repr(spectre_dir) ) err_msg = "Spectre libraries not found for MSVC_VERSION {} toolset version {}".format( - repr(msvc.version), repr(toolset.version) + repr(msvc_instance.msvc_version), repr(toolset.version) ) raise MSVCSpectreLibsNotFound(err_msg) @@ -856,23 +847,23 @@ def _user_script_argument_spectre(env, spectre, user_argstr): raise MSVCArgumentError(err_msg) -def _msvc_script_argument_user(env, msvc, arglist): +def _msvc_script_argument_user(env, msvc_instance, arglist): # subst None -> empty string script_args = env.subst('$MSVC_SCRIPT_ARGS') - debug('MSVC_VERSION=%s, MSVC_SCRIPT_ARGS=%s', repr(msvc.version), repr(script_args)) + debug('MSVC_VERSION=%s, MSVC_SCRIPT_ARGS=%s', repr(msvc_instance.msvc_version), repr(script_args)) if not script_args: return None - if msvc.vs_def.vc_buildtools_def.vc_version_numeric < VS2015.vc_buildtools_def.vc_version_numeric: + if msvc_instance.vs_product_def.vc_buildtools_def.vc_version_numeric < VS2015.vc_buildtools_def.vc_version_numeric: debug( 'invalid: msvc version constraint: %s < %s VS2015', - repr(msvc.vs_def.vc_buildtools_def.vc_version_numeric), + repr(msvc_instance.vs_product_def.vc_buildtools_def.vc_version_numeric), repr(VS2015.vc_buildtools_def.vc_version_numeric) ) err_msg = "MSVC_SCRIPT_ARGS ({}) constraint violation: MSVC_VERSION {} < {} VS2015".format( - repr(script_args), repr(msvc.version), repr(VS2015.vc_buildtools_def.vc_version) + repr(script_args), repr(msvc_instance.msvc_version), repr(VS2015.vc_buildtools_def.vc_version) ) raise MSVCArgumentError(err_msg) @@ -913,17 +904,15 @@ def msvc_script_arguments_has_uwp(env): debug('is_uwp=%s', is_uwp) return is_uwp -def msvc_script_arguments(env, version, vc_dir, arg=None): +def msvc_script_arguments(env, msvc_instance, arg=None): arguments = [arg] if arg else [] arglist = [] arglist_reverse = False - msvc = _msvc_version(version) - if 'MSVC_SCRIPT_ARGS' in env: - user_argstr = _msvc_script_argument_user(env, msvc, arglist) + user_argstr = _msvc_script_argument_user(env, msvc_instance, arglist) else: user_argstr = None @@ -934,7 +923,7 @@ def msvc_script_arguments(env, version, vc_dir, arg=None): # MSVC_UWP_APP if 'MSVC_UWP_APP' in env: - uwp = _msvc_script_argument_uwp(env, msvc, arglist, target_arch) + uwp = _msvc_script_argument_uwp(env, msvc_instance, arglist, target_arch) else: uwp = None @@ -949,7 +938,7 @@ def msvc_script_arguments(env, version, vc_dir, arg=None): # MSVC_TOOLSET_VERSION if 'MSVC_TOOLSET_VERSION' in env: - toolset_version = _msvc_script_argument_toolset(env, msvc, vc_dir, arglist) + toolset_version = _msvc_script_argument_toolset(env, msvc_instance, arglist) else: toolset_version = None @@ -959,7 +948,7 @@ def msvc_script_arguments(env, version, vc_dir, arg=None): user_toolset = None if not toolset_version and not user_toolset: - default_toolset = _msvc_script_default_toolset(env, msvc, vc_dir, arglist, _MSVC_FORCE_DEFAULT_TOOLSET) + default_toolset = _msvc_script_default_toolset(env, msvc_instance, arglist, _MSVC_FORCE_DEFAULT_TOOLSET) if _MSVC_FORCE_DEFAULT_TOOLSET: toolset_version = default_toolset else: @@ -977,7 +966,7 @@ def msvc_script_arguments(env, version, vc_dir, arg=None): # MSVC_SDK_VERSION if 'MSVC_SDK_VERSION' in env: - sdk_version = _msvc_script_argument_sdk(env, msvc, toolset, platform_def, arglist) + sdk_version = _msvc_script_argument_sdk(env, msvc_instance, toolset, platform_def, arglist) else: sdk_version = None @@ -988,19 +977,19 @@ def msvc_script_arguments(env, version, vc_dir, arg=None): if _MSVC_FORCE_DEFAULT_SDK: if not sdk_version and not user_sdk: - sdk_version = _msvc_script_default_sdk(env, msvc, platform_def, arglist, _MSVC_FORCE_DEFAULT_SDK) + sdk_version = _msvc_script_default_sdk(env, msvc_instance, platform_def, arglist, _MSVC_FORCE_DEFAULT_SDK) # MSVC_SPECTRE_LIBS if 'MSVC_SPECTRE_LIBS' in env: - spectre = _msvc_script_argument_spectre(env, msvc, vc_dir, toolset, platform_def, arglist) + spectre = _msvc_script_argument_spectre(env, msvc_instance, toolset, platform_def, arglist) else: spectre = None if user_argstr: _user_script_argument_spectre(env, spectre, user_argstr) - if msvc.vs_def.vc_buildtools_def.vc_version == '14.0': + if msvc_instance.vs_product_def.vc_buildtools_def.vc_version == '14.0': if user_uwp and sdk_version and len(arglist) == 2: # VS2015 toolset argument order issue: SDK store => store SDK arglist_reverse = True @@ -1016,24 +1005,20 @@ def msvc_script_arguments(env, version, vc_dir, arg=None): debug('arguments: %s', repr(argstr)) return argstr -def _msvc_toolset_internal(msvc_version, toolset_version, vc_dir): - - msvc = _msvc_version(msvc_version) +def _msvc_toolset_internal(msvc_instance, toolset_version): - toolset_vcvars = _msvc_script_argument_toolset_vcvars(msvc, toolset_version, vc_dir) + toolset_vcvars = _msvc_script_argument_toolset_vcvars(msvc_instance, toolset_version) return toolset_vcvars -def _msvc_toolset_versions_internal(msvc_version, vc_dir, full: bool=True, sxs: bool=False): +def _msvc_toolset_versions_internal(msvc_instance, full: bool=True, sxs: bool=False): - msvc = _msvc_version(msvc_version) - - if len(msvc.vs_def.vc_buildtools_all) <= 1: + if len(msvc_instance.vs_product_def.vc_buildtools_all) <= 1: return None toolset_versions = [] - toolsets_sxs, toolsets_full = _msvc_version_toolsets(msvc, vc_dir) + toolsets_sxs, toolsets_full = _msvc_version_toolsets(msvc_instance) if sxs: sxs_versions = list(toolsets_sxs.keys()) @@ -1045,19 +1030,17 @@ def _msvc_toolset_versions_internal(msvc_version, vc_dir, full: bool=True, sxs: return toolset_versions -def _msvc_toolset_versions_spectre_internal(msvc_version, vc_dir): - - msvc = _msvc_version(msvc_version) +def _msvc_toolset_versions_spectre_internal(msvc_instance): - if len(msvc.vs_def.vc_buildtools_all) <= 1: + if len(msvc_instance.vs_product_def.vc_buildtools_all) <= 1: return None - _, toolsets_full = _msvc_version_toolsets(msvc, vc_dir) + _, toolsets_full = _msvc_version_toolsets(msvc_instance) spectre_toolset_versions = [ toolset_version for toolset_version in toolsets_full - if os.path.exists(_msvc_toolset_version_spectre_path(vc_dir, toolset_version)) + if os.path.exists(_msvc_toolset_version_spectre_path(msvc_instance, toolset_version)) ] return spectre_toolset_versions diff --git a/SCons/Tool/MSCommon/MSVC/ScriptArgumentsTests.py b/SCons/Tool/MSCommon/MSVC/ScriptArgumentsTests.py index c79f044c55..507156b36b 100644 --- a/SCons/Tool/MSCommon/MSVC/ScriptArgumentsTests.py +++ b/SCons/Tool/MSCommon/MSVC/ScriptArgumentsTests.py @@ -36,10 +36,6 @@ 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, @@ -120,9 +116,6 @@ def _make_notfound_version(sdk_seen, sdk_def): class Data: - # all versions - ALL_VERSIONS_PAIRS = [] - # installed versions INSTALLED_VERSIONS_PAIRS = [] @@ -131,11 +124,11 @@ class Data: for vcver in Config.MSVC_VERSION_SUFFIX.keys(): version_def = Util.msvc_version_components(vcver) - vc_dir = vc.find_vc_pdir(vcver) - t = (version_def, vc_dir) - ALL_VERSIONS_PAIRS.append(t) - if vc_dir: - INSTALLED_VERSIONS_PAIRS.append(t) + msvc_instance = vc.find_msvc_instance(vcver) + if not msvc_instance: + continue + t = (version_def, msvc_instance) + INSTALLED_VERSIONS_PAIRS.append(t) HAVE_MSVC = True if len(INSTALLED_VERSIONS_PAIRS) else False @@ -222,22 +215,22 @@ def test_msvc_script_arguments_defaults(self) -> None: env = Environment() # disable forcing sdk and toolset versions as arguments force = ScriptArguments.msvc_force_default_arguments(force=False) - for version_def, vc_dir in Data.INSTALLED_VERSIONS_PAIRS: + for version_def, msvc_instance in Data.INSTALLED_VERSIONS_PAIRS: for arg in ('', 'arch'): - scriptargs = func(env, version_def.msvc_version, vc_dir, arg) + scriptargs = func(env, msvc_instance, arg) self.assertTrue(scriptargs == arg, "{}({},{}) != {} [force=False]".format( func.__name__, repr(version_def.msvc_version), repr(arg), repr(scriptargs) )) # enable forcing sdk and toolset versions as arguments ScriptArguments.msvc_force_default_arguments(force=True) - for version_def, vc_dir in Data.INSTALLED_VERSIONS_PAIRS: + for version_def, msvc_instance in Data.INSTALLED_VERSIONS_PAIRS: for arg in ('', 'arch'): - scriptargs = func(env, version_def.msvc_version, vc_dir, arg) + scriptargs = func(env, msvc_instance, arg) sdk_supported = True if version_def.msvc_verstr == '14.0': - if msvc_version_is_express(version_def.msvc_version): + if msvc_instance.is_express: sdk_supported = False - elif msvc_version_is_btdispatch(version_def.msvc_version): + elif msvc_instance.is_buildtools: sdk_supported = False if version_def.msvc_vernum >= 14.0 and sdk_supported: if arg and scriptargs.startswith(arg): @@ -256,10 +249,11 @@ def test_msvc_script_arguments_defaults(self) -> None: def test_msvc_toolset_versions_internal(self) -> None: func = ScriptArguments._msvc_toolset_versions_internal - for version_def, vc_dir in Data.INSTALLED_VERSIONS_PAIRS: + for version_def, msvc_instance in Data.INSTALLED_VERSIONS_PAIRS: + vc_dir = msvc_instance.vc_dir for full in (True, False): for sxs in (True, False): - toolset_versions = func(version_def.msvc_version, vc_dir, full=full, sxs=sxs) + toolset_versions = func(msvc_instance, full=full, sxs=sxs) if version_def.msvc_vernum < 14.1: self.assertTrue(toolset_versions is None, "{}({},{},full={},sxs={}) is not None ({})".format( func.__name__, repr(version_def.msvc_version), repr(vc_dir), repr(full), repr(sxs), @@ -281,16 +275,17 @@ def test_msvc_toolset_internal(self) -> None: if not Data.HAVE_MSVC: return func = ScriptArguments._msvc_toolset_internal - for version_def, vc_dir in Data.INSTALLED_VERSIONS_PAIRS: - toolset_versions = ScriptArguments._msvc_toolset_versions_internal(version_def.msvc_version, vc_dir, full=True, sxs=True) + for version_def, msvc_instance in Data.INSTALLED_VERSIONS_PAIRS: + vc_dir = msvc_instance.vc_dir + toolset_versions = ScriptArguments._msvc_toolset_versions_internal(msvc_instance, full=True, sxs=True) if not toolset_versions: continue for toolset_version in toolset_versions: - _ = func(version_def.msvc_version, toolset_version, vc_dir) + _ = func(msvc_instance, toolset_version) def run_msvc_script_args_none(self) -> None: func = ScriptArguments.msvc_script_arguments - for version_def, vc_dir in Data.INSTALLED_VERSIONS_PAIRS: + for version_def, msvc_instance in Data.INSTALLED_VERSIONS_PAIRS: for kwargs in [ {'MSVC_SCRIPT_ARGS': None}, {'MSVC_SCRIPT_ARGS': None, 'MSVC_UWP_APP': None}, @@ -299,11 +294,12 @@ 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, msvc_instance) def run_msvc_script_args(self) -> None: func = ScriptArguments.msvc_script_arguments - for version_def, vc_dir in Data.INSTALLED_VERSIONS_PAIRS: + for version_def, msvc_instance in Data.INSTALLED_VERSIONS_PAIRS: + vc_dir = msvc_instance.vc_dir if version_def.msvc_vernum >= 14.1: # VS2017 and later @@ -311,7 +307,7 @@ def run_msvc_script_args(self) -> None: Util.msvc_extended_version_components(toolset_version) for toolset_version in ScriptArguments._msvc_toolset_versions_internal( - version_def.msvc_version, vc_dir, full=True, sxs=False + msvc_instance, full=True, sxs=False ) ] @@ -322,7 +318,7 @@ def run_msvc_script_args(self) -> None: # should not raise exception (argument not validated) env = Environment(MSVC_SCRIPT_ARGS='undefinedsymbol') - _ = func(env, version_def.msvc_version, vc_dir) + _ = func(env, msvc_instance) for kwargs in [ {'MSVC_UWP_APP': False, 'MSVC_SCRIPT_ARGS': None}, @@ -333,7 +329,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, msvc_instance) for msvc_uwp_app in (True, False): @@ -378,9 +374,9 @@ def run_msvc_script_args(self) -> None: env = Environment(**kwargs) if exc: with self.assertRaises(MSVCArgumentError): - _ = func(env, version_def.msvc_version, vc_dir) + _ = func(env, msvc_instance) else: - _ = func(env, version_def.msvc_version, vc_dir) + _ = func(env, msvc_instance) else: @@ -389,14 +385,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, msvc_instance) for kwargs in [ {'MSVC_SCRIPT_ARGS': '-vcvars_ver={}'.format(version_def.msvc_verstr)}, {'MSVC_TOOLSET_VERSION': version_def.msvc_verstr}, ]: env = Environment(**kwargs) - _ = func(env, version_def.msvc_version, vc_dir) + _ = func(env, msvc_instance) msvc_toolset_notfound_version = Data.msvc_toolset_notfound_version(version_def.msvc_version) @@ -408,7 +404,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, msvc_instance) msvc_sdk_notfound_version = Data.msvc_sdk_notfound_version(version_def.msvc_version) @@ -417,15 +413,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, msvc_instance) 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, msvc_instance) else: - _ = func(env, version_def.msvc_version, vc_dir) + _ = func(env, msvc_instance) msvc_sdk_version = Data.msvc_sdk_version(version_def.msvc_version) @@ -529,14 +525,14 @@ 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, msvc_instance) elif version_def.msvc_verstr == '14.0': - if msvc_version_is_express(version_def.msvc_version): + if msvc_instance.is_express: sdk_supported = False uwp_supported = True # based on target arch - elif msvc_version_is_btdispatch(version_def.msvc_version): + elif msvc_instance.is_buildtools: sdk_supported = False uwp_supported = False else: @@ -544,7 +540,7 @@ def run_msvc_script_args(self) -> None: uwp_supported = True env = Environment(MSVC_SCRIPT_ARGS='undefinedsymbol') - _ = func(env, version_def.msvc_version, vc_dir) + _ = func(env, msvc_instance) if sdk_supported: # VS2015: MSVC_SDK_VERSION @@ -562,7 +558,7 @@ def run_msvc_script_args(self) -> None: {'MSVC_SDK_VERSION': sdk_version, 'MSVC_UWP_APP': msvc_uwp_app}, ]: env = Environment(**kwargs) - _ = func(env, version_def.msvc_version, vc_dir) + _ = func(env, msvc_instance) else: # VS2015: MSVC_UWP_APP error @@ -578,7 +574,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, msvc_instance) else: # VS2015: MSVC_SDK_VERSION error @@ -588,18 +584,18 @@ def run_msvc_script_args(self) -> None: env = Environment(MSVC_SDK_VERSION=sdk_version) with self.assertRaises(MSVCArgumentError): - _ = func(env, version_def.msvc_version, vc_dir) + _ = func(env, msvc_instance) # MSVC_SCRIPT_ARGS sdk_version not validated env = Environment(MSVC_SCRIPT_ARGS=sdk_version) - _ = func(env, version_def.msvc_version, vc_dir) + _ = func(env, msvc_instance) 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) + _ = func(env, msvc_instance) else: # VS2015: MSVC_UWP_APP error @@ -608,11 +604,11 @@ def run_msvc_script_args(self) -> None: env = Environment(MSVC_UWP_APP=msvc_uwp_app) with self.assertRaises(MSVCArgumentError): - _ = func(env, version_def.msvc_version, vc_dir) + _ = func(env, msvc_instance) # MSVC_SCRIPT_ARGS store not validated env = Environment(MSVC_SCRIPT_ARGS='store') - _ = func(env, version_def.msvc_version, vc_dir) + _ = func(env, msvc_instance) for kwargs in [ {'MSVC_SPECTRE_LIBS': True, 'MSVC_SCRIPT_ARGS': None}, @@ -620,14 +616,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, msvc_instance) 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, msvc_instance) for kwargs in [ {'MSVC_UWP_APP': True, 'MSVC_SCRIPT_ARGS': None}, @@ -638,7 +634,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, msvc_instance) 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 d41ff7d9f6..345922c334 100644 --- a/SCons/Tool/MSCommon/MSVC/Util.py +++ b/SCons/Tool/MSCommon/MSVC/Util.py @@ -26,6 +26,7 @@ """ import os +import pathlib import re from collections import ( @@ -36,6 +37,12 @@ # path utilities +# windows drive specification (e.g., 'C:') +_RE_DRIVESPEC = re.compile(r'^[A-Za-z][:]$', re.IGNORECASE) + +# windows path separators +_OS_PATH_SEPS = ('\\', '/') + def listdir_dirs(p): """ Return a list of tuples for each subdirectory of the given directory path. @@ -57,22 +64,90 @@ def listdir_dirs(p): dirs.append((dir_name, dir_path)) return dirs -def process_path(p): +def resolve_path(p, ignore_drivespec=True): + """ + Make path absolute resolving any symlinks + + Args: + p: str + system path + ignore_drivespec: bool + ignore drive specifications when True + + Returns: + str: absolute path with symlinks resolved + + """ + + if p: + + if ignore_drivespec and _RE_DRIVESPEC.match(p): + # don't attempt to resolve drive specification (e.g., C:) + pass + else: + # both abspath and resolve necessary to produce identical path + # when (unqualified) file name is on a mapped network drive for + # python 3.6 and 3.11 + p = os.path.abspath(p) + try: + p = str(pathlib.Path(p).resolve()) + except OSError: + # TODO(JCB): log error + pass + + return p + +def normalize_path( + p, + strip=True, + preserve_trailing=False, + expand=False, + realpath=True, + ignore_drivespec=True, +): """ - Normalize a system path + Normalize path Args: p: str system path + strip: bool + remove leading and trailing whitespace when True + preserve_trailing: bool + preserve trailing path separator when True + expand: bool + apply expanduser and expandvars when True + realpath: bool + make the path absolute resolving any symlinks when True + ignore_drivespec: bool + ignore drive specifications for realpath when True Returns: - str: normalized system path + str: normalized path """ + + if p and strip: + p = p.strip() + if p: + + trailing = bool(preserve_trailing and p.endswith(_OS_PATH_SEPS)) + + if expand: + p = os.path.expanduser(p) + p = os.path.expandvars(p) + p = os.path.normpath(p) - p = os.path.realpath(p) + + if realpath: + p = resolve_path(p, ignore_drivespec=ignore_drivespec) + p = os.path.normcase(p) + + if trailing: + p += os.path.sep + return p # msvc version and msvc toolset version regexes @@ -175,6 +250,15 @@ def get_msvc_version_prefix_suffix(version): suffix = m.group('suffix') if m.group('suffix') else '' return prefix, suffix +# msvc version query utilities + +def is_version_valid(version) -> bool: + rval = False + if version: + if re_msvc_version.match(version): + rval = True + return rval + # toolset version query utilities def is_toolset_full(toolset_version) -> bool: @@ -238,8 +322,8 @@ def msvc_version_components(vcver): if not m: return None - vs_def = Config.MSVC_VERSION_SUFFIX.get(vcver) - if not vs_def: + vs_product_def = Config.MSVC_VERSION_SUFFIX.get(vcver) + if not vs_product_def: return None msvc_version = vcver @@ -304,8 +388,8 @@ def msvc_extended_version_components(version): msvc_suffix = m.group('suffix') if m.group('suffix') else '' msvc_version = msvc_verstr + msvc_suffix - vs_def = Config.MSVC_VERSION_SUFFIX.get(msvc_version) - if not vs_def: + vs_product_def = Config.MSVC_VERSION_SUFFIX.get(msvc_version) + if not vs_product_def: return None msvc_vernum = float(msvc_verstr) diff --git a/SCons/Tool/MSCommon/MSVC/UtilTests.py b/SCons/Tool/MSCommon/MSVC/UtilTests.py index 36e08f5eb1..8e17326ff5 100644 --- a/SCons/Tool/MSCommon/MSVC/UtilTests.py +++ b/SCons/Tool/MSCommon/MSVC/UtilTests.py @@ -50,8 +50,8 @@ def test_listdir_dirs(self) -> None: func.__name__, repr(dirname), 'list is empty' if expect else 'list is not empty' )) - def test_process_path(self) -> None: - func = Util.process_path + def test_normalize_path(self) -> None: + func = Util.normalize_path for p, expect in [ (None, True), ('', True), ('doesnotexist.xyz.abc', False), (Data.UTIL_PARENT_DIR, False), diff --git a/SCons/Tool/MSCommon/MSVC/Validate.py b/SCons/Tool/MSCommon/MSVC/Validate.py new file mode 100644 index 0000000000..380bef2701 --- /dev/null +++ b/SCons/Tool/MSCommon/MSVC/Validate.py @@ -0,0 +1,113 @@ +# 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 validation for Microsoft Visual C/C++. +""" + +from ..common import debug + +from .Exceptions import ( + MSVCVersionUnsupported, + MSVCArgumentError, +) + +from . import Config +from . import Util + + +from . import Dispatcher +Dispatcher.register_modulename(__name__) + + +def validate_msvc_version(vc_version, label): + + vc_version_def = None + + if not vc_version: + return vc_version_def + + vc_version_def = Util.msvc_version_components(vc_version) + if not vc_version_def: + + if not Util.is_version_valid(vc_version): + err_msg = f'Unsupported {label} format: {vc_version!r}' + debug(err_msg) + raise MSVCArgumentError(err_msg) + + if vc_version not in Config.MSVC_VERSIONS: + symbols = Config.MSVC_VERSIONS + err_msg = f'Unrecognized {label} {vc_version!r}\n Valid msvc versions are: {symbols}' + debug(err_msg) + raise MSVCVersionUnsupported(err_msg) + + return vc_version_def + +def validate_msvs_product(vs_product, label): + + vs_product_def = None + + if not vs_product: + return vs_product_def + + vs_product_def = Config.MSVS_VERSION_EXTERNAL.get(vs_product) + if not vs_product_def: + symbols = ', '.join(Config.MSVS_VERSION_SYMBOLS) + err_msg = f'Unrecognized {label} {vs_product!r}\n Valid msvs products are: {symbols}' + debug(err_msg) + raise MSVCArgumentError(err_msg) + + return vs_product_def + +def validate_msvs_component(vs_component, label): + + vs_componentid_def = None + + if not vs_component: + return vs_componentid_def + + vs_componentid_def = Config.MSVS_COMPONENTID_EXTERNAL.get(vs_componentid_def) + if not vs_componentid_def: + symbols = ', '.join(Config.MSVS_COMPONENTID_SYMBOLS) + err_msg = f'Unrecognized {label} {vs_component!r}\n Valid msvs components are: {symbols}' + debug(err_msg) + raise MSVCArgumentError(err_msg) + + return vs_componentid_def + +def validate_msvs_channel(vs_channel, label): + + vs_channel_def = None + + if not vs_channel: + return vs_channel_def + + vs_channel_def = Config.MSVS_CHANNEL_EXTERNAL.get(vs_channel) + if not vs_channel_def: + symbols = ', '.join(Config.MSVS_CHANNEL_SYMBOLS) + err_msg = f'Unrecognized {label} {vs_channel!r}\n Valid msvs channels are: {symbols}' + debug(err_msg) + raise MSVCArgumentError(err_msg) + + return vs_channel_def + diff --git a/SCons/Tool/MSCommon/MSVC/WinSDK.py b/SCons/Tool/MSCommon/MSVC/WinSDK.py index 39617b16cc..965d9aa98b 100644 --- a/SCons/Tool/MSCommon/MSVC/WinSDK.py +++ b/SCons/Tool/MSCommon/MSVC/WinSDK.py @@ -83,7 +83,7 @@ def _sdk_10_layout(version): if not version_nbr.startswith(folder_prefix): continue - sdk_inc_path = Util.process_path(os.path.join(version_nbr_path, 'um')) + sdk_inc_path = Util.normalize_path(os.path.join(version_nbr_path, 'um')) if not os.path.exists(sdk_inc_path): continue @@ -127,7 +127,7 @@ def _sdk_81_layout(version): # msvc does not check for existence of root or other files - sdk_inc_path = Util.process_path(os.path.join(sdk_root, r'include\um')) + sdk_inc_path = Util.normalize_path(os.path.join(sdk_root, r'include\um')) if not os.path.exists(sdk_inc_path): continue @@ -224,8 +224,8 @@ def get_msvc_platform(is_uwp: bool=False): platform_def = _UWP if is_uwp else _DESKTOP return platform_def -def get_sdk_version_list(vs_def, platform_def): - version_list = vs_def.vc_sdk_versions if vs_def.vc_sdk_versions is not None else [] +def get_sdk_version_list(vs_product_def, platform_def): + version_list = vs_product_def.vc_sdk_versions if vs_product_def.vc_sdk_versions is not None else [] sdk_map = _sdk_map(version_list) sdk_list = sdk_map.get(platform_def.vc_platform, []) return sdk_list @@ -240,14 +240,14 @@ def get_msvc_sdk_version_list(msvc_version, msvc_uwp_app: bool=False): debug('msvc_version is not defined') return sdk_versions - vs_def = Config.MSVC_VERSION_EXTERNAL.get(verstr, None) - if not vs_def: - debug('vs_def is not defined') + vs_product_def = Config.MSVC_VERSION_EXTERNAL.get(verstr, None) + if not vs_product_def: + debug('vs_product_def is not defined') return sdk_versions is_uwp = True if msvc_uwp_app in Config.BOOLEAN_SYMBOLS[True] else False platform_def = get_msvc_platform(is_uwp) - sdk_list = get_sdk_version_list(vs_def, platform_def) + sdk_list = get_sdk_version_list(vs_product_def, platform_def) sdk_versions.extend(sdk_list) debug('sdk_versions=%s', repr(sdk_versions)) diff --git a/SCons/Tool/MSCommon/MSVC/WinSDKTests.py b/SCons/Tool/MSCommon/MSVC/WinSDKTests.py index 10e68f3d6e..d3e54072cb 100644 --- a/SCons/Tool/MSCommon/MSVC/WinSDKTests.py +++ b/SCons/Tool/MSCommon/MSVC/WinSDKTests.py @@ -101,10 +101,10 @@ def _run_get_msvc_sdk_version_list(self) -> None: def _run_version_list_sdk_map(self) -> None: for vcver in Config.MSVC_VERSION_SUFFIX.keys(): - vs_def = Config.MSVC_VERSION_SUFFIX.get(vcver) - if not vs_def.vc_sdk_versions: + vs_product_def = Config.MSVC_VERSION_SUFFIX.get(vcver) + if not vs_product_def.vc_sdk_versions: continue - _ = WinSDK._version_list_sdk_map(vs_def.vc_sdk_versions) + _ = WinSDK._version_list_sdk_map(vs_product_def.vc_sdk_versions) def test_version_list_sdk_map(self) -> None: self._run_version_list_sdk_map() diff --git a/SCons/Tool/MSCommon/MSVC/__init__.py b/SCons/Tool/MSCommon/MSVC/__init__.py index f87b0f1d8c..cdf17706ea 100644 --- a/SCons/Tool/MSCommon/MSVC/__init__.py +++ b/SCons/Tool/MSCommon/MSVC/__init__.py @@ -44,6 +44,7 @@ from . import SetupEnvDefault # noqa: F401 from . import Policy # noqa: F401 from . import WinSDK # noqa: F401 +from . import Validate # noqa: F401 from . import ScriptArguments # noqa: F401 from . import Dispatcher as _Dispatcher diff --git a/SCons/Tool/MSCommon/__init__.py b/SCons/Tool/MSCommon/__init__.py index c3078ac630..29ab4163a1 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_push_location, + msvs_set_channel_default, + msvs_get_channel_default, ) from SCons.Tool.MSCommon.vs import ( # noqa: F401 diff --git a/SCons/Tool/MSCommon/common.py b/SCons/Tool/MSCommon/common.py index f8816c4f0c..fe2e0a6185 100644 --- a/SCons/Tool/MSCommon/common.py +++ b/SCons/Tool/MSCommon/common.py @@ -40,6 +40,13 @@ class MSVCCacheInvalidWarning(SCons.Warnings.WarningOnByDefault): pass +class AutoInitialize: + # Automatically call _initialize classmethod upon class definition completion. + def __init_subclass__(cls, **kwargs): + super().__init_subclass__(**kwargs) + if hasattr(cls, '_initialize') and callable(getattr(cls, '_initialize', None)): + cls._initialize() + # SCONS_MSCOMMON_DEBUG is internal-use so undocumented: # set to '-' to print to console, else set to filename to log to LOGFILE = os.environ.get('SCONS_MSCOMMON_DEBUG') @@ -75,32 +82,78 @@ def filter(self, record) -> bool: record.relfilename = relfilename return True - # Log format looks like: - # 00109ms:MSCommon/vc.py:find_vc_pdir#447: VC found '14.3' [file] - # debug: 00109ms:MSCommon/vc.py:find_vc_pdir#447: VC found '14.3' [stdout] - log_format=( - '%(relativeCreated)05dms' - ':%(relfilename)s' - ':%(funcName)s' - '#%(lineno)s' - ': %(message)s' - ) + class _CustomFormatter(logging.Formatter): + + # Log format looks like: + # 00109ms:MSCommon/vc.py:find_vc_pdir#447: VC found '14.3' [file] + # debug: 00109ms:MSCommon/vc.py:find_vc_pdir#447: VC found '14.3' [stdout] + + log_format=( + '%(relativeCreated)05dms' + ':%(relfilename)s' + ':%(funcName)s' + '#%(lineno)s' + ': %(message)s' + ) + + log_format_classname=( + '%(relativeCreated)05dms' + ':%(relfilename)s' + ':%(classname)s' + '.%(funcName)s' + '#%(lineno)s' + ': %(message)s' + ) + + def __init__(self): + super().__init__() + log_record = logging.LogRecord( + '', # name (str) + 0, # level (int) + '', # pathname (str) + 0, # lineno (int) + None, # msg (Any) + {}, # args (tuple | dict[str, Any]) + None # exc_info (tuple[type[BaseException], BaseException, types.TracebackType] | None) + ) + self.default_attrs = set(log_record.__dict__.keys()) + self.default_attrs.add('relfilename') + + def format(self, record): + extras = set(record.__dict__.keys()) - self.default_attrs + if 'classname' in extras: + log_format = self.log_format_classname + else: + log_format = self.log_format + formatter = logging.Formatter(log_format) + return formatter.format(record) + if LOGFILE == '-': log_format = 'debug: ' + log_format log_handler = logging.StreamHandler(sys.stdout) else: log_handler = logging.FileHandler(filename=LOGFILE) - log_formatter = logging.Formatter(log_format) + log_formatter = _CustomFormatter() log_handler.setFormatter(log_formatter) logger = logging.getLogger(name=__name__) logger.setLevel(level=logging.DEBUG) logger.addHandler(log_handler) logger.addFilter(_Debug_Filter()) debug = logger.debug + + def debug_extra(cls=None): + if cls: + extra = {'classname': cls.__qualname__} + else: + extra = None + return extra + else: - def debug(x, *args): + def debug(x, *args, **kwargs): return None + def debug_extra(*args, **kwargs): + return None # SCONS_CACHE_MSVC_CONFIG is public, and is documented. CONFIG_CACHE = os.environ.get('SCONS_CACHE_MSVC_CONFIG', '') diff --git a/SCons/Tool/MSCommon/vc.py b/SCons/Tool/MSCommon/vc.py index a92ceb7cd8..4dca6c52f3 100644 --- a/SCons/Tool/MSCommon/vc.py +++ b/SCons/Tool/MSCommon/vc.py @@ -35,15 +35,12 @@ # * Assembly """ -import SCons.compat - import subprocess import os import platform import sysconfig from pathlib import Path from string import digits as string_digits -from subprocess import PIPE import re from collections import ( namedtuple, @@ -56,11 +53,15 @@ import SCons.Warnings from SCons.Tool import find_program_path -# import SCons.Script +import SCons.Script from . import common -from .common import CONFIG_CACHE, debug -from .sdk import get_installed_sdks +from .common import ( + CONFIG_CACHE, + debug, + debug_extra, + AutoInitialize, +) from . import MSVC @@ -88,9 +89,6 @@ class MSVCUseSettingsError(MSVCUserError): # internal exceptions -class UnsupportedVersion(VisualCException): - pass - class BatchFileExecutionError(VisualCException): pass @@ -102,11 +100,6 @@ class BatchFileExecutionError(VisualCException): # False: do nothing _ARM32_ON_ARM64_SKIP_SENDTELEMETRY = True -# MSVC 9.0 preferred query order: -# True: VCForPython, VisualStudio -# False: VisualStudio, VCForPython -_VC90_Prefer_VCForPython = True - # Dict to 'canonalize' the arch _ARCH_TO_CANONICAL = { "amd64" : "amd64", @@ -742,102 +735,44 @@ def _skip_sendtelemetry(env): "6.0"] # 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.0': [ - (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'), # vs root - (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\14.0\Setup\VC\ProductDir'), # not populated? - (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\14.0\Setup\VC\ProductDir')], # kind detection - '12.0': [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\12.0\Setup\VC\ProductDir'), +_VSPRODUCT_REGISTRY_VCDIR = { + # vs_product: [(is_vsroot, is_vcforpython, hkroot, key), ...] + '2015': [ + (False, False, SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\14.0\Setup\VC\ProductDir'), + (True, False, SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\WDExpress\14.0\Setup\VS\ProductDir'), # vs root + (False, False, SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\14.0\Setup\VC\ProductDir'), # not populated? ], - '12.0Exp': [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\12.0\Setup\VC\ProductDir'), + '2013': [ + (False, False, SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\12.0\Setup\VC\ProductDir'), + (False, False, 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'), + '2012': [ + (False, False, SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\11.0\Setup\VC\ProductDir'), + (False, False, SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\11.0\Setup\VC\ProductDir'), ], - '11.0Exp': [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\11.0\Setup\VC\ProductDir'), + '2010': [ + (False, False, SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\10.0\Setup\VC\ProductDir'), + (False, False, SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\10.0\Setup\VC\ProductDir'), ], - '10.0': [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\10.0\Setup\VC\ProductDir'), + '2008': [ + (True, True, SCons.Util.HKEY_CURRENT_USER, r'Microsoft\DevDiv\VCForPython\9.0\InstallDir',), # vs root + (True, True, SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\DevDiv\VCForPython\9.0\InstallDir',), # vs root + (False, False, SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\9.0\Setup\VC\ProductDir',), + (False, False, SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\9.0\Setup\VC\ProductDir'), ], - '10.0Exp': [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\10.0\Setup\VC\ProductDir'), + '2005': [ + (False, False, SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\8.0\Setup\VC\ProductDir'), + (False, False, SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\8.0\Setup\VC\ProductDir'), ], - '9.0': [ - (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',), # vs root - (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\DevDiv\VCForPython\9.0\installdir',), # vs root + '2003': [ + (False, False, SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\7.1\Setup\VC\ProductDir'), ], - '9.0Exp': [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\9.0\Setup\VC\ProductDir'), + '2002': [ + (False, False, SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\7.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'), - ], - '7.1': [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\7.1\Setup\VC\ProductDir'), - ], - '7.0': [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\7.0\Setup\VC\ProductDir'), - ], - '6.0': [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\6.0\Setup\Microsoft Visual C++\ProductDir'), + '1998': [ + (False, False, SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\6.0\Setup\Microsoft Visual C++\ProductDir'), ] } @@ -867,35 +802,29 @@ def _skip_sendtelemetry(env): # detect productdir kind -_DETECT = MSVC.Kind.VCVER_KIND_DETECT +_VCDetectConfig = MSVC.Kind.VCDetectConfig -_VCVER_KIND_DETECT = { +_VSPRODUCT_KIND_DETECT = { - # 'VCVer': (relpath from pdir to vsroot, path from vsroot to ide binaries, ide binaries) + # 'product': (relpath from vcroot 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 + '2022': _VCDetectConfig(root=os.pardir, path=r'Common7\IDE', programs=VS2022_VS2002_DEV), # 14.3 + '2019': _VCDetectConfig(root=os.pardir, path=r'Common7\IDE', programs=VS2022_VS2002_DEV), # 14.2 - '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 + '2017': _VCDetectConfig(root=os.pardir, path=r'Common7\IDE', programs=VS2022_VS2002_DEV + VS2017_EXP), # 14.1 - '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 + '2015': _VCDetectConfig(root=os.pardir, path=r'Common7\IDE', programs=VS2022_VS2002_DEV + VS2015_VS2012_EXP), # 14.0 + '2013': _VCDetectConfig(root=os.pardir, path=r'Common7\IDE', programs=VS2022_VS2002_DEV + VS2015_VS2012_EXP), # 12.0 + '2012': _VCDetectConfig(root=os.pardir, path=r'Common7\IDE', programs=VS2022_VS2002_DEV + VS2015_VS2012_EXP), # 11.0 - '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 + '2010': _VCDetectConfig(root=os.pardir, path=r'Common7\IDE', programs=VS2022_VS2002_DEV + VS2010_VS2005_EXP), # 10.0 + '2008': _VCDetectConfig(root=os.pardir, path=r'Common7\IDE', programs=VS2022_VS2002_DEV + VS2010_VS2005_EXP), # 9.0 + '2005': _VCDetectConfig(root=os.pardir, path=r'Common7\IDE', programs=VS2022_VS2002_DEV + VS2010_VS2005_EXP), # 8.0 - '6.0': _DETECT(root='..', path=r'Common\MSDev98\Bin', programs=VS1998_DEV), # 1998 + '2003': _VCDetectConfig(root=os.pardir, path=r'Common7\IDE', programs=VS2022_VS2002_DEV), # 7.1 + '2002': _VCDetectConfig(root=os.pardir, path=r'Common7\IDE', programs=VS2022_VS2002_DEV), # 7.0 + + '1998': _VCDetectConfig(root=os.pardir, path=r'Common\MSDev98\Bin', programs=VS1998_DEV), # 6.0 } def msvc_version_to_maj_min(msvc_version): @@ -911,604 +840,1631 @@ def msvc_version_to_maj_min(msvc_version): except ValueError: raise ValueError("Unrecognized version %s (%s)" % (msvc_version,msvc_version_numeric)) from None +# normalized paths + +_normalize_path = {} + +def normalize_path(pval): + global _normalize_path + rval = _normalize_path.get(pval, UNDEFINED) + if rval == UNDEFINED: + rval = MSVC.Util.normalize_path(pval) + _normalize_path[pval] = rval + # debug('pval=%s, orig=%s', repr(rval), repr(pval)) + return rval + +# path existence + +_path_exists = {} + +def path_exists(pval): + global _path_exists + rval = _path_exists.get(pval, UNDEFINED) + if rval == UNDEFINED: + rval = os.path.exists(pval) + _path_exists[pval] = rval + # debug('exists=%s, pval=%s', rval, repr(pval)) + return rval + +# initialize vswhere paths + +VSWhereExecutable = namedtuple('VSWhereExecutable', [ + 'path', + 'norm', +]) + +# 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. + +VSWHERE_PATHS = [ + os.path.join(p,'vswhere.exe') + for p in [ + os.path.expandvars(r"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer"), + os.path.expandvars(r"%ProgramFiles%\Microsoft Visual Studio\Installer"), + os.path.expandvars(r"%ChocolateyInstall%\bin"), + os.path.expandvars(r"%LOCALAPPDATA%\Microsoft\WinGet\Links"), + os.path.expanduser(r"~\scoop\shims"), + os.path.expandvars(r"%SCOOP%\shims"), + ] + if not p.startswith('%') +] + +VSWHERE_EXECS_EXIST = [ + VSWhereExecutable(path=p, norm=normalize_path(p)) + for p in VSWHERE_PATHS + if path_exists(p) +] -VSWHERE_PATHS = [os.path.join(p,'vswhere.exe') for p in [ - os.path.expandvars(r"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer"), - os.path.expandvars(r"%ProgramFiles%\Microsoft Visual Studio\Installer"), - os.path.expandvars(r"%ChocolateyInstall%\bin"), -]] +debug('VSWHERE_EXECS_EXIST=%s', VSWHERE_EXECS_EXIST) -# normalize user-specified vswhere paths +# user-specified vswhere executables _cache_user_vswhere_paths = {} -def _vswhere_user_path(pval): - global _cache_user_vswhere_path +def _vswhere_user_path(pval, source): + global _cache_user_vswhere_paths rval = _cache_user_vswhere_paths.get(pval, UNDEFINED) if rval != UNDEFINED: - debug('vswhere_path=%s', rval) + debug('vswhere_exec=%s', repr(rval)) return rval - vswhere_path = None + vswhere_exec = None if pval: - if not os.path.exists(pval): + if not path_exists(pval): - warn_msg = f'vswhere executable path not found: {pval!r}' + warn_msg = f'vswhere executable path not found: {source}={pval!r}' SCons.Warnings.warn(MSVC.Warnings.VSWherePathWarning, warn_msg) debug(warn_msg) else: - # vswhere_norm = MSVC.Util.process_path(pval) - vswhere_norm = os.path.normcase(os.path.normpath(pval)) - - tail = os.path.split(vswhere_norm)[-1] + norm = normalize_path(pval) + tail = os.path.split(norm)[-1] if tail != _VSWHERE_EXE: - warn_msg = f'unsupported vswhere executable (expected {_VSWHERE_EXE!r}, found {tail!r}): {pval!r}' + warn_msg = f'unsupported vswhere executable (expected {_VSWHERE_EXE!r}, found {tail!r}): {source}={pval!r}' SCons.Warnings.warn(MSVC.Warnings.VSWherePathWarning, warn_msg) debug(warn_msg) else: - vswhere_path = vswhere_norm - debug('vswhere_path=%s', vswhere_path) + vswhere_exec = VSWhereExecutable(path=pval, norm=norm) + debug('vswhere_exec=%s', repr(vswhere_exec)) - _cache_user_vswhere_paths[pval] = vswhere_path + _cache_user_vswhere_paths[pval] = vswhere_exec - return vswhere_path + return vswhere_exec -# normalized user-specified command-line vswhere path +# register user vswhere executable location(s) -# TODO: stub for command-line specification of vswhere -_vswhere_path_cmdline = None +def vswhere_push_location(string_or_list, front=False) -> None: + global VSWHERE_EXECS_EXIST -def _msvc_cmdline_vswhere(): - global _vswhere_path_cmdline + path_list = SCons.Util.flatten(string_or_list) + if path_list: - if _vswhere_path_cmdline == UNDEFINED: + user_execs = [] + for pval in path_list: + vswhere_exec = _vswhere_user_path(pval, 'vswhere_push_location') + if vswhere_exec: + user_execs.append(vswhere_exec) - vswhere_path = None - vswhere_user = None + if user_execs: - if vswhere_user: - vswhere_path = _vswhere_user_path(vswhere_user) + if front: + all_execs = user_execs + VSWHERE_EXECS_EXIST + else: + all_execs = VSWHERE_EXECS_EXIST + user_execs - _vswhere_path_cmdline = vswhere_path - debug('vswhere_path=%s', vswhere_path) + seen = set() + unique_execs = [] + for vswhere_exec in all_execs: + if vswhere_exec.norm in seen: + continue + seen.add(vswhere_exec.norm) + unique_execs.append(vswhere_exec) - return _vswhere_path_cmdline + VSWHERE_EXECS_EXIST = unique_execs + debug('VSWHERE_EXECS_EXIST=%s', VSWHERE_EXECS_EXIST) -# normalized default vswhere path +# all vswhere executables -_vswhere_paths_processed = [ - MSVC.Util.process_path(pval) - for pval in VSWHERE_PATHS - if os.path.exists(pval) -] +# TODO(JCB): command-line argument: --vswhere=EXEPATH -_vswhere_path_default = UNDEFINED +def _find_vswhere_executables(vswhere_env=None): -def _msvc_default_vswhere(): - global _vswhere_paths_processed - global _vswhere_path_default + vswhere_execs = [] - if _vswhere_path_default == UNDEFINED: + # env['VSWHERE'] path + if vswhere_env: + vswhere_exec = _vswhere_user_path(vswhere_env, "env['VSWHERE']") + if vswhere_exec: + vswhere_execs.append(vswhere_exec) - if _vswhere_paths_processed: - vswhere_path = _vswhere_paths_processed[0] - else: - vswhere_path = None + # default paths and user paths (vswhere_push_location) + if VSWHERE_EXECS_EXIST: + vswhere_execs.extend(VSWHERE_EXECS_EXIST) - _vswhere_path_default = vswhere_path - debug('vswhere_path=%s', vswhere_path) + return vswhere_execs - return _vswhere_path_default +def find_vswhere_executables(env=None): + vswhere_env = env.subst('$VSWHERE') if env and 'VSWHERE' in env else None + vswhere_execs = _find_vswhere_executables(vswhere_env) + return vswhere_execs -def msvc_find_vswhere(): +def msvc_find_vswhere(env=None): """ 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 = _msvc_cmdline_vswhere() - if vswhere_path: - return - - vswhere_path = None - for pf in VSWHERE_PATHS: - if os.path.exists(pf): - vswhere_path = pf - break - + vswhere_execs = find_vswhere_executables(env) + if vswhere_execs: + vswhere_path = vswhere_execs[0].path + else: + vswhere_path = None + debug('vswhere_path=%s', vswhere_path) return vswhere_path -class _VSWhere: +# TODO(JCB): command-line argument: --msvs-channel=Release|Preview|Any +_default_msvs_channel_def = MSVC.Config.MSVS_CHANNEL_RELEASE +debug('default msvs_channel=%s', _default_msvs_channel_def.vs_channel_id) - reset_funcs = [] +_default_msvs_channel_retrieved = False + +def _msvs_channel_default(): + global _default_msvs_channel_def + global _default_msvs_channel_retrieved + _default_msvs_channel_retrieved = True + return _default_msvs_channel_def + +def msvs_set_channel_default(msvs_channel): + """Set the default msvs channel. + + Args: + msvs_channel: str + string representing the msvs channel + + Case-insensitive values are: + Release, Rel, Preview, Pre, Any, * + + Returns: + bool: True if the default msvs channel was accepted. + False if the default msvs channel was not accepted. + + """ + global _default_msvs_channel_retrieved + global _default_msvs_channel_def + rval = False + if _default_msvs_channel_retrieved: + # TODO(JCB): warn msvs_channel ignored (must be set earlier) + pass + else: + vs_channel_def = MSVC.Validate.validate_msvs_channel( + msvs_channel, 'msvs_channel_set_default' + ) + if vs_channel_def: + _default_msvs_channel_def = vs_channel_def + debug('default msvs_channel=%s', _default_msvs_channel_def.vs_channel_id) + rval = True + return rval + +def msvs_get_channel_default(): + """Get the default msvs channel. + + Returns: + str: default msvs channel + + """ + global _default_msvs_channel_def + return _default_msvs_channel_def.vs_channel_id + +class _VSKeys: + + # edition key: (product, channel, component/None, seqnbr/None) + + _MSVSEditionKey = namedtuple('_MSVSEditionKey', [ + 'vs_product_def', + 'vs_channel_def', + 'vs_componentid_def', + 'vs_sequence_nbr', + ]) + + class MSVSEditionKey(_MSVSEditionKey): + + def serialize(self): + values = [ + self.vs_product_def.vs_product, + self.vs_channel_def.vs_channel_suffix, + ] + if self.vs_componentid_def: + values.append(self.vs_componentid_def.vs_component_suffix) + if self.vs_sequence_nbr: + values.append(str(self.vs_sequence_nbr)) + rval = '-'.join(values) + return rval + + @classmethod + def factory( + cls, *, + vs_product_def, + vs_channel_def, + vs_componentid_def, + vs_sequence_nbr, + ): + + vs_edition_key = cls( + vs_product_def=vs_product_def, + vs_channel_def=vs_channel_def, + vs_componentid_def=vs_componentid_def, + vs_sequence_nbr=vs_sequence_nbr + ) + + return vs_edition_key @classmethod - def reset(cls): + def msvs_edition_key( + cls, *, + vs_product_def=None, + vs_channel_def=None, + vs_componentid_def=None, + vs_sequence_nbr=None, + ): + + if not vs_product_def: + errmsg = 'vs_product_def is undefined' + debug('MSVCInternalError: %s', errmsg) + raise MSVCInternalError(errmsg) + + if not vs_channel_def: + errmsg = 'vs_channel_def is undefined' + debug('MSVCInternalError: %s', errmsg) + raise MSVCInternalError(errmsg) + + vs_edition_key = cls.MSVSEditionKey.factory( + vs_product_def=vs_product_def, + vs_channel_def=vs_channel_def, + vs_componentid_def=vs_componentid_def, + vs_sequence_nbr=vs_sequence_nbr + ) - cls.seen_vswhere = set() - cls.seen_root = set() + return vs_edition_key - cls.vswhere_executables = [] + # channel key: (channel, component/None) - cls.msvc_instances = [] - cls.msvc_map = {} + _MSVSChannelKey = namedtuple('_MSVSChannelKey', [ + 'vs_channel_def', + 'vs_componentid_def', + ]) + + class MSVSChannelKey(_MSVSChannelKey): + + def serialize(self): + values = [ + self.vs_channel_def.vs_channel_suffix, + ] + if self.vs_componentid_def: + values.append(self.vs_componentid_def.vs_component_suffix) + rval = '-'.join(values) + return rval + + @classmethod + def factory( + cls, *, + vs_channel_def, + vs_componentid_def, + ): + + vs_channel_key = cls( + vs_channel_def=vs_channel_def, + vs_componentid_def=vs_componentid_def, + ) + + return vs_channel_key + + @classmethod + def msvs_channel_key( + cls, *, + vs_channel_def=None, + vs_componentid_def=None, + ): + + if not vs_channel_def: + errmsg = 'vs_channel_def is undefined' + debug('MSVCInternalError: %s', errmsg) + raise MSVCInternalError(errmsg) + + vs_channel_key = cls.MSVSChannelKey.factory( + vs_channel_def=vs_channel_def, + vs_componentid_def=vs_componentid_def, + ) + + return vs_channel_key + +_MSVSBase = namedtuple('_MSVSBase', [ + 'id_str', + 'id_comps', + 'vs_product_def', + 'vs_channel_def', + 'vs_component_def', + 'vs_sequence_nbr', + 'vs_dir', + 'vs_dir_norm', + 'vs_version', + 'is_express', + 'is_buildtools', + 'is_vcforpython', + 'vs_edition_channel_component_seqnbr_key', + 'vs_edition_channel_component_key', + 'vs_edition_channel_key', + 'vs_channel_component_key', + 'vs_channel_key', + 'instance_map', +]) + +class MSVSBase(_MSVSBase): + + def _is_express(vs_component_def): + vs_componentid_def = vs_component_def.vs_componentid_def + is_express = bool(vs_componentid_def == MSVC.Config.MSVS_COMPONENTID_EXPRESS) + return is_express @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 + def _is_buildtools(vs_component_def): + vs_componentid_def = vs_component_def.vs_componentid_def + is_buildtools = bool(vs_componentid_def == MSVC.Config.MSVS_COMPONENTID_BUILDTOOLS) + return is_buildtools + + @staticmethod + def _is_vcforpython(vs_component_def): + vs_componentid_def = vs_component_def.vs_componentid_def + is_vcforpython = bool(vs_componentid_def == MSVC.Config.MSVS_COMPONENTID_PYTHON) + return is_vcforpython + + @classmethod + def factory( + cls, *, + vs_product_def, + vs_channel_def, + vs_component_def, + vs_sequence_nbr, + vs_dir, + vs_version, + ): + + vs_componentid_def = vs_component_def.vs_componentid_def + + vs_edition_channel_component_seqnbr_key = _VSKeys.msvs_edition_key( + vs_product_def=vs_product_def, + vs_channel_def=vs_channel_def, + vs_componentid_def=vs_componentid_def, + vs_sequence_nbr=vs_sequence_nbr, + ) + + vs_edition_channel_component_key = _VSKeys.msvs_edition_key( + vs_product_def=vs_product_def, + vs_channel_def=vs_channel_def, + vs_componentid_def=vs_componentid_def, + ) + + vs_edition_channel_key = _VSKeys.msvs_edition_key( + vs_product_def=vs_product_def, + vs_channel_def=vs_channel_def, + ) + + vs_channel_component_key = _VSKeys.msvs_channel_key( + vs_channel_def=vs_channel_def, + vs_componentid_def=vs_componentid_def, + ) + + vs_channel_key = _VSKeys.msvs_channel_key( + vs_channel_def=vs_channel_def, + ) + + instance_map = { + 'msvs_instance': None, + 'msvc_instance': None, + } + + id_comps = ( + vs_product_def.vs_product, + vs_channel_def.vs_channel_suffix, + vs_component_def.vs_componentid_def.vs_component_suffix, + str(vs_sequence_nbr), + ) + + id_str = '{}({})'.format(cls.__name__, ', '.join(id_comps)) + + msvs_base = cls( + id_str=id_str, + id_comps=id_comps, + vs_product_def=vs_product_def, + vs_channel_def=vs_channel_def, + vs_component_def=vs_component_def, + vs_sequence_nbr=vs_sequence_nbr, + vs_dir=vs_dir, + vs_dir_norm=normalize_path(vs_dir), + vs_version=vs_version, + is_express=cls._is_express(vs_component_def), + is_buildtools=cls._is_buildtools(vs_component_def), + is_vcforpython=cls._is_vcforpython(vs_component_def), + vs_edition_channel_component_seqnbr_key=vs_edition_channel_component_seqnbr_key, + vs_edition_channel_component_key=vs_edition_channel_component_key, + vs_edition_channel_key=vs_edition_channel_key, + vs_channel_component_key=vs_channel_component_key, + vs_channel_key=vs_channel_key, + instance_map=instance_map, + ) + + return msvs_base + + @staticmethod + def default_order(a, b): + # vs product numeric: descending order + if a.vs_product_def.vs_product_numeric != b.vs_product_def.vs_product_numeric: + return 1 if a.vs_product_def.vs_product_numeric < b.vs_product_def.vs_product_numeric else -1 + # vs channel: ascending order (release, preview) + if a.vs_channel_def.vs_channel_rank != b.vs_channel_def.vs_channel_rank: + return 1 if a.vs_channel_def.vs_channel_rank > b.vs_channel_def.vs_channel_rank 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 + if a.vs_component_def.vs_component_rank != b.vs_component_def.vs_component_rank: + return 1 if a.vs_component_def.vs_component_rank < b.vs_component_def.vs_component_rank else -1 + # sequence number: ascending order + if a.vs_sequence_nbr != b.vs_sequence_nbr: + return 1 if a.vs_sequence_nbr > b.vs_sequence_nbr else -1 return 0 + def register_msvs_instance(self, msvs_instance): + self.instance_map['msvs_instance'] = msvs_instance + + def register_msvc_instance(self, msvc_instance): + self.instance_map['msvc_instance'] = msvc_instance + + @property + def msvs_instance(self): + return self.instance_map.get('msvs_instance') + + @property + def msvc_instance(self): + return self.instance_map.get('msvc_instance') + + @property + def vs_component_suffix(self): + return self.vs_component_def.vs_componentid_def.vs_component_suffix + +_MSVSInstance = namedtuple('_MSVSInstance', [ + 'id_str', + 'msvs_base', + 'vs_executable', + 'vs_executable_norm', + 'vc_version_def', +]) + +class MSVSInstance(_MSVSInstance): + @classmethod - def register_reset_func(cls, func): - cls.reset_funcs.append(func) + def factory( + cls, *, + msvs_base, + vs_executable, + vc_version_def, + ): + + id_str = '{}({})'.format(cls.__name__, ', '.join(msvs_base.id_comps)) + + msvs_instance = cls( + id_str=id_str, + msvs_base=msvs_base, + vs_executable=vs_executable, + vs_executable_norm=normalize_path(vs_executable), + vc_version_def=vc_version_def, + ) + + return msvs_instance + + @staticmethod + def default_order(a, b): + return MSVSBase.default_order(a.msvs_base, b.msvs_base) + + @property + def id_comps(self): + return self.msvs_base.id_comps + + @property + def msvc_instance(self): + return self.msvs_base.msvc_instance + + @property + def vs_dir(self): + return self.msvs_base.vs_dir + + @property + def vs_version(self): + return self.msvs_base.vs_version + + @property + def vs_edition_channel_component_seqnbr_key(self): + return self.msvs_base.vs_edition_channel_component_seqnbr_key + + @property + def vs_edition_channel_component_key(self): + return self.msvs_base.vs_edition_channel_component_key + + @property + def vs_edition_channel_key(self): + return self.msvs_base.vs_edition_channel_key + + @property + def vs_channel_component_key(self): + return self.msvs_base.vs_channel_component_key + + @property + def vs_channel_key(self): + return self.msvs_base.vs_channel_key + + @property + def msvc_version(self): + return self.vc_version_def.msvc_version + + @property + def msvc_verstr(self): + return self.vc_version_def.msvc_verstr + + @property + def vs_product_def(self): + return self.msvs_base.vs_product_def + + @property + def vs_channel_def(self): + return self.msvs_base.vs_channel_def + + @property + def vs_componentid_def(self): + return self.msvs_base.vs_component_def.vs_componentid_def + + @property + def vs_product(self): + return self.msvs_base.vs_product_def.vs_product + + @property + def vs_channel_id(self): + return self.msvs_base.vs_channel_def.vs_channel_id + + @property + def vs_component_id(self): + return self.msvs_base.vs_component_def.vs_componentid_def.vs_component_id + + @property + def vs_sequence_nbr(self): + return self.msvs_base.vs_sequence_nbr + +_MSVSInstalled = namedtuple('_MSVSInstalled', [ + 'msvs_instances', + 'msvs_edition_instances_map', + 'msvs_channel_instances_map', + 'msvs_channel_map', # TODO(JCB): remove? +]) + +class MSVSInstalled(_MSVSInstalled, AutoInitialize): + + debug_extra = None @classmethod - def call_reset_funcs(cls): - for func in cls.reset_funcs: - func() + def _initialize(cls): + cls.debug_extra = debug_extra(cls) -_VSWhere.reset() + @classmethod + def factory( + cls, *, + msvs_instances, + ): -def _filter_vswhere_paths(env): + msvs_instances = sorted( + msvs_instances, key=cmp_to_key(MSVSInstance.default_order) + ) - vswhere_paths = [] + msvs_channel_map = { + vs_channel_def: {} + for vs_channel_def in MSVC.Config.MSVS_CHANNEL_DEFINITION_LIST + } - if env and 'VSWHERE' in env: - vswhere_environ = _vswhere_user_path(env.subst('$VSWHERE')) - if vswhere_environ and vswhere_environ not in _VSWhere.seen_vswhere: - vswhere_paths.append(vswhere_environ) + vs_edition_instances = {} + vs_channel_instances = {} - vswhere_cmdline = _msvc_cmdline_vswhere() - if vswhere_cmdline and vswhere_cmdline not in _VSWhere.seen_vswhere: - vswhere_paths.append(vswhere_cmdline) + # channel key: (ANY, None) + vs_anychannel_key = _VSKeys.msvs_channel_key( + vs_channel_def=MSVC.Config.MSVS_CHANNEL_ANY, + ) - vswhere_default = _msvc_default_vswhere() - if vswhere_default and vswhere_default not in _VSWhere.seen_vswhere: - vswhere_paths.append(vswhere_default) + for msvs_instance in msvs_instances: - debug('vswhere_paths=%s', vswhere_paths) - return vswhere_paths + debug( + 'msvs instance: id_str=%s, msvc_version=%s, vs_dir=%s, vs_exec=%s', + repr(msvs_instance.id_str), + repr(msvs_instance.msvc_version), + repr(msvs_instance.vs_dir), + repr(msvs_instance.vs_executable_norm), + extra=cls.debug_extra, + ) -def _vswhere_query_json_output(vswhere_exe, vswhere_args): + # edition key: (product, ANY, None, None) + vs_edition_any_key = _VSKeys.msvs_edition_key( + vs_product_def=msvs_instance.vs_product_def, + vs_channel_def=MSVC.Config.MSVS_CHANNEL_ANY, + ) - vswhere_json = None + # edition key: (product, ANY, component, None) + vs_edition_anychannel_component_key = _VSKeys.msvs_edition_key( + vs_product_def=msvs_instance.vs_product_def, + vs_channel_def=MSVC.Config.MSVS_CHANNEL_ANY, + vs_componentid_def=msvs_instance.vs_componentid_def, + ) - once = True - while once: - once = False - # using break for single exit (unless exception) + # all editions keys + vs_edition_keys = ( + vs_edition_any_key, + msvs_instance.vs_edition_channel_key, + vs_edition_anychannel_component_key, + msvs_instance.vs_edition_channel_component_key, + msvs_instance.vs_edition_channel_component_seqnbr_key, + ) - vswhere_cmd = [vswhere_exe] + vswhere_args + ['-format', 'json', '-utf8'] - debug("running: %s", vswhere_cmd) + # channel key: (ANY, component) + vs_anychannel_component_key = _VSKeys.msvs_channel_key( + vs_channel_def=MSVC.Config.MSVS_CHANNEL_ANY, + vs_componentid_def=msvs_instance.vs_componentid_def, + ) - 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) - break - except Exception as e: - errmsg = str(e) - debug("%s: %s", type(e).__name__, errmsg) - raise + # all channel keys + vs_channel_keys = ( + vs_anychannel_key, + msvs_instance.vs_channel_key, + vs_anychannel_component_key, + msvs_instance.vs_channel_component_key, + ) - if not cp.stdout: - debug("no vswhere information returned") - break + for vs_edition_key in vs_edition_keys: + vs_edition_instances.setdefault(vs_edition_key, []).append(msvs_instance) - vswhere_output = cp.stdout.decode('utf8', errors='replace') - if not vswhere_output: - debug("no vswhere information output") - break + for vs_channel_key in vs_channel_keys: + vs_channel_instances.setdefault(vs_channel_key, []).append(msvs_instance) - try: - vswhere_output_json = json.loads(vswhere_output) - except json.decoder.JSONDecodeError: - debug("json decode exception loading vswhere output") - break + # msvc_version support + + if msvs_instance.msvc_version != msvs_instance.msvc_verstr: + versions = [msvs_instance.msvc_verstr, msvs_instance.msvc_version] + else: + versions = [msvs_instance.msvc_verstr] + + vs_channel_defs = MSVC.Config.MSVS_CHANNEL_MEMBERLISTS[msvs_instance.vs_channel_def] + + for vcver in versions: + for vs_channel_def in vs_channel_defs: + + msvs_version_map = msvs_channel_map[vs_channel_def] + + msvs_instance_list = msvs_version_map.setdefault(vcver, []) + slot = len(msvs_instance_list) + msvs_instance_list.append(msvs_instance) + debug( + 'msvs_version_map[%s][%s][%d]=%s', + repr(vs_channel_def.vs_channel_suffix), + repr(vcver), + slot, + repr(msvs_instance.id_str), + extra=cls.debug_extra, + ) + + msvs_installed = cls( + msvs_instances=msvs_instances, + msvs_edition_instances_map=vs_edition_instances, + msvs_channel_instances_map=vs_channel_instances, + msvs_channel_map=msvs_channel_map, + ) + + return msvs_installed + + def query_msvs_instances( + self, *, + vs_product_def=None, + vs_channel_def=None, + vs_componentid_def=None, + vs_sequence_nbr=None, + ): + + # TODO(JCB): error checking combinations (ignored, etc) - vswhere_json = vswhere_output_json - break + if not vs_channel_def: + vs_channel_def = _msvs_channel_default() - debug('vswhere_json=%s, vswhere_exe=%s', bool(vswhere_json), repr(vswhere_exe)) + if vs_product_def: + + query_key = _VSKeys.msvs_edition_key( + vs_product_def=vs_product_def, + vs_channel_def=vs_channel_def, + vs_componentid_def=vs_componentid_def, + vs_sequence_nbr=vs_sequence_nbr + ) + + msvs_instances = self.msvs_edition_instances_map.get(query_key, []) + + else: + + query_key = _VSKeys.msvs_channel_key( + vs_channel_def=vs_channel_def, + vs_componentid_def=vs_componentid_def, + ) - return vswhere_json + msvs_instances = self.msvs_channel_instances_map.get(query_key, []) -MSVC_INSTANCE = namedtuple('MSVCInstance', [ - 'vc_path', - 'vc_version', - 'vc_version_numeric', - 'vc_version_scons', - 'vc_release', - 'vc_component_id', - 'vc_component_rank', - 'vc_component_suffix', + debug( + 'query_key=%s, n_msvs_instances=%s', + repr(query_key.serialize()), + repr(len(msvs_instances)), + extra=self.debug_extra, + ) + + return query_key, msvs_instances + +_MSVCInstance = namedtuple('_MSVCInstance', [ + 'id_str', + 'msvs_base', + 'vc_version_def', + 'vc_feature_map', + 'vc_dir', + 'vc_dir_norm', + 'is_sdkversion_supported', ]) -def _update_vswhere_msvc_map(env): +class MSVCInstance(_MSVCInstance, AutoInitialize): - vswhere_paths = _filter_vswhere_paths(env) - if not vswhere_paths: - debug('new_roots=False, msvc_instances=%s', len(_VSWhere.msvc_instances)) - return _VSWhere.msvc_map + debug_extra = None - n_instances = len(_VSWhere.msvc_instances) + @classmethod + def _initialize(cls): + cls.debug_extra = debug_extra(cls) - for vswhere_exe in vswhere_paths: + @classmethod + def factory( + cls, *, + msvs_base, + vc_version_def, + vc_feature_map, + vc_dir, + ): + + id_str = '{}({})'.format(cls.__name__, ', '.join(msvs_base.id_comps)) + + if vc_feature_map is None: + vc_feature_map = {} + + msvc_instance = cls( + id_str=id_str, + msvs_base=msvs_base, + vc_version_def=vc_version_def, + vc_feature_map=vc_feature_map, + vc_dir=vc_dir, + vc_dir_norm=normalize_path(vc_dir), + is_sdkversion_supported=cls._is_sdkversion_supported(msvs_base), + ) - if vswhere_exe in _VSWhere.seen_vswhere: - continue + return msvc_instance + + @staticmethod + def default_order(a, b): + return MSVSBase.default_order(a.msvs_base, b.msvs_base) - _VSWhere.seen_vswhere.add(vswhere_exe) - _VSWhere.vswhere_executables.append(vswhere_exe) + @property + def id_comps(self): + return self.msvs_base.id_comps - debug('vswhere_exe=%s', repr(vswhere_exe)) + @property + def msvs_instance(self): + return self.msvs_base.msvs_instance - vswhere_json = _vswhere_query_json_output( - vswhere_exe, - ['-all', '-products', '*'] + @staticmethod + def _is_sdkversion_supported(msvs_base): + + vs_componentid_def = msvs_base.vs_component_def.vs_componentid_def + vs_product_numeric = msvs_base.vs_product_def.vs_product_numeric + + if vs_product_numeric >= 2017: + # VS2017 and later + is_supported = True + elif vs_product_numeric == 2015: + # VS2015: + # False: Express, BuildTools + # True: Develop, CmdLine + if vs_componentid_def == MSVC.Config.MSVS_COMPONENTID_EXPRESS: + is_supported = False + elif vs_componentid_def == MSVC.Config.MSVS_COMPONENTID_BUILDTOOLS: + is_supported = False + else: + is_supported = True + else: + # VS2013 and earlier + is_supported = False + + return is_supported + + def skip_uwp_target(self, env): + skip = False + if self.vc_feature_map: + target_arch = env.get('TARGET_ARCH') + uwp_target_is_supported = self.vc_feature_map.get('uwp_target_is_supported', {}) + is_supported = uwp_target_is_supported.get(target_arch, True) + skip = bool(not is_supported) + debug( + 'skip=%s, msvc_version=%s', + repr(skip), repr(self.vc_version_def.msvc_version), extra=self.debug_extra ) + return skip + + def is_uwp_target_supported(self, target_arch=None): + + vs_componentid_def = self.msvs_base.vs_component_def.vs_componentid_def + vs_product_numeric = self.msvs_base.vs_product_def.vs_product_numeric + + is_target = False + if vs_product_numeric >= 2017: + # VS2017 and later + is_supported = True + elif vs_product_numeric == 2015: + # VS2015: + # Maybe: Express + # False: BuildTools + # True: Develop, CmdLine + if vs_componentid_def == MSVC.Config.MSVS_COMPONENTID_EXPRESS: + uwp_target_is_supported = self.vc_feature_map.get('uwp_target_is_supported', {}) + is_supported = uwp_target_is_supported.get(target_arch, True) + is_target = True + elif vs_componentid_def == MSVC.Config.MSVS_COMPONENTID_BUILDTOOLS: + is_supported = False + else: + is_supported = True + else: + # VS2013 and earlier + is_supported = False - if not vswhere_json: - continue + debug( + 'is_supported=%s, is_target=%s, msvc_version=%s, msvs_component=%s, target_arch=%s', + is_supported, is_target, + repr(self.vc_version_def.msvc_version), + repr(vs_componentid_def.vs_component_id), + repr(target_arch), + extra=self.debug_extra, + ) - for instance in vswhere_json: + return is_supported, is_target - #print(json.dumps(instance, indent=4, sort_keys=True)) + # convenience properties: reduce member lookup chain for readability - installation_path = instance.get('installationPath') - if not installation_path or not os.path.exists(installation_path): - continue + @property + def is_express(self): + return self.msvs_base.is_express - vc_path = os.path.join(installation_path, 'VC') - if not os.path.exists(vc_path): - continue + @property + def is_buildtools(self): + return self.msvs_base.is_buildtools - vc_root = MSVC.Util.process_path(vc_path) - if vc_root in _VSWhere.seen_root: - continue - _VSWhere.seen_root.add(vc_root) + @property + def is_vcforpython(self): + return self.msvs_base.is_vcforpython - installation_version = instance.get('installationVersion') - if not installation_version: - continue + @property + def vs_edition_channel_component_seqnbr_key(self): + return self.msvs_base.vs_edition_channel_component_seqnbr_key - vs_major = installation_version.split('.')[0] - if not vs_major in _VSWHERE_VSMAJOR_TO_VCVERSION: - debug('ignore vs_major: %s', vs_major) - continue + @property + def vs_edition_channel_component_key(self): + return self.msvs_base.vs_edition_channel_component_key - vc_version = _VSWHERE_VSMAJOR_TO_VCVERSION[vs_major] + @property + def vs_edition_channel_key(self): + return self.msvs_base.vs_edition_channel_key - product_id = instance.get('productId') - if not product_id: - continue + @property + def vs_channel_component_key(self): + return self.msvs_base.vs_channel_component_key - component_id = product_id.split('.')[-1] - if component_id not in _VSWHERE_COMPONENTID_CANDIDATES: - debug('ignore component_id: %s', component_id) - continue + @property + def vs_channel_key(self): + return self.msvs_base.vs_channel_key - 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}') + @property + def msvc_version(self): + return self.vc_version_def.msvc_version - scons_suffix = _VSWHERE_COMPONENTID_SCONS_SUFFIX[component_id] + @property + def msvc_vernum(self): + return self.vc_version_def.msvc_vernum - 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 = MSVC_INSTANCE( - 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, - ) + @property + def msvc_verstr(self): + return self.vc_version_def.msvc_verstr + + @property + def vs_product_def(self): + return self.msvs_base.vs_product_def + + @property + def vs_channel_def(self): + return self.msvs_base.vs_channel_def + + @property + def vs_componentid_def(self): + return self.msvs_base.vs_component_def.vs_componentid_def + + @property + def vs_product(self): + return self.msvs_base.vs_product_def.vs_product + + @property + def vs_product_numeric(self): + return self.msvs_base.vs_product_def.vs_product_numeric + + @property + def vs_channel_id(self): + return self.msvs_base.vs_channel_def.vs_channel_id - _VSWhere.msvc_instances.append(msvc_instance) + @property + def vs_component_id(self): + return self.msvs_base.vs_component_def.vs_componentid_def.vs_component_id - new_roots = bool(len(_VSWhere.msvc_instances) > n_instances) - if new_roots: + @property + def vs_sequence_nbr(self): + return self.msvs_base.vs_sequence_nbr + +_MSVCInstalled = namedtuple('_MSVCInstalled', [ + 'msvc_instances', + 'msvs_edition_instances_map', + 'msvs_channel_instances_map', + 'msvs_channel_map', # TODO(JCB): remove? +]) + +class MSVCInstalled(_MSVCInstalled, AutoInitialize): + + debug_extra = None + + @classmethod + def _initialize(cls): + cls.debug_extra = debug_extra(cls) - _VSWhere.msvc_instances = sorted( - _VSWhere.msvc_instances, - key=cmp_to_key(_VSWhere.msvc_instances_default_order) + @classmethod + def factory( + cls, *, + msvc_instances, + ): + + msvc_instances = sorted( + msvc_instances, key=cmp_to_key(MSVCInstance.default_order) ) - _VSWhere.msvc_map = {} + msvs_channel_map = { + vs_channel_def: {} + for vs_channel_def in MSVC.Config.MSVS_CHANNEL_DEFINITION_LIST + } - for msvc_instance in _VSWhere.msvc_instances: + vs_edition_instances = {} + vs_channel_instances = {} + + # channel key: (ANY, None) + vs_anychannel_key = _VSKeys.msvs_channel_key( + vs_channel_def=MSVC.Config.MSVS_CHANNEL_ANY, + ) + + for msvc_instance in 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) + 'msvc_instance: id_str=%s, msvc_version=%s, vc_dir=%s', + repr(msvc_instance.id_str), + repr(msvc_instance.msvc_version), + repr(msvc_instance.vc_dir), + extra=cls.debug_extra, ) - key = (msvc_instance.vc_version_scons, msvc_instance.vc_release) - _VSWhere.msvc_map.setdefault(key,[]).append(msvc_instance) + # edition key: (product, ANY, None, None) + vs_edition_any_key = _VSKeys.msvs_edition_key( + vs_product_def=msvc_instance.vs_product_def, + vs_channel_def=MSVC.Config.MSVS_CHANNEL_ANY, + ) - if msvc_instance.vc_version_scons == msvc_instance.vc_version: - continue + # edition key: (product, ANY, component, None) + vs_edition_anychannel_component_key = _VSKeys.msvs_edition_key( + vs_product_def=msvc_instance.vs_product_def, + vs_channel_def=MSVC.Config.MSVS_CHANNEL_ANY, + vs_componentid_def=msvc_instance.vs_componentid_def, + ) - key = (msvc_instance.vc_version, msvc_instance.vc_release) - _VSWhere.msvc_map.setdefault(key,[]).append(msvc_instance) + # all editions keys + vs_edition_keys = ( + vs_edition_any_key, + msvc_instance.vs_edition_channel_key, + vs_edition_anychannel_component_key, + msvc_instance.vs_edition_channel_component_key, + msvc_instance.vs_edition_channel_component_seqnbr_key, + ) - _VSWhere.call_reset_funcs() + # channel key: (ANY, component) + vs_anychannel_component_key = _VSKeys.msvs_channel_key( + vs_channel_def=MSVC.Config.MSVS_CHANNEL_ANY, + vs_componentid_def=msvc_instance.vs_componentid_def, + ) - debug('new_roots=%s, msvc_instances=%s', new_roots, len(_VSWhere.msvc_instances)) + # all channel keys + vs_channel_keys = ( + vs_anychannel_key, + msvc_instance.vs_channel_key, + vs_anychannel_component_key, + msvc_instance.vs_channel_component_key, + ) - return _VSWhere.msvc_map + for vs_edition_key in vs_edition_keys: + vs_edition_instances.setdefault(vs_edition_key, []).append(msvc_instance) -_cache_pdir_vswhere_queries = {} + for vs_channel_key in vs_channel_keys: + vs_channel_instances.setdefault(vs_channel_key, []).append(msvc_instance) -def _reset_pdir_vswhere_queries(): - global _cache_pdir_vswhere_queries - _cache_pdir_vswhere_queries = {} - debug('reset _cache_pdir_vswhere_queries') + # msvc_version support -# register pdir vswhere cache reset function with vswhere state manager -_VSWhere.register_reset_func(_reset_pdir_vswhere_queries) + if msvc_instance.msvc_version != msvc_instance.msvc_verstr: + versions = [msvc_instance.msvc_verstr, msvc_instance.msvc_version] + else: + versions = [msvc_instance.msvc_verstr] -def find_vc_pdir_vswhere(msvc_version, env=None): - """ Find the MSVC product directory using the vswhere program. + vs_channel_defs = MSVC.Config.MSVS_CHANNEL_MEMBERLISTS[msvc_instance.vs_channel_def] - Args: - msvc_version: MSVC version to search for - env: optional to look up VSWHERE variable + for vcver in versions: - Returns: - MSVC install dir or None + for vs_channel_def in vs_channel_defs: - Raises: - UnsupportedVersion: if the version is not known by this file + msvc_version_map = msvs_channel_map[vs_channel_def] - """ - global _cache_pdir_vswhere_queries + msvc_instance_list = msvc_version_map.setdefault(vcver, []) + slot = len(msvc_instance_list) + msvc_instance_list.append(msvc_instance) + debug( + 'msvc_version_map[%s][%s][%d]=%s', + repr(vs_channel_def.vs_channel_suffix), + repr(vcver), + slot, + repr(msvc_instance.id_str), + extra=cls.debug_extra, + ) - msvc_map = _update_vswhere_msvc_map(env) - if not msvc_map: - return None + msvc_installed = cls( + msvc_instances=msvc_instances, + msvs_edition_instances_map=vs_edition_instances, + msvs_channel_instances_map=vs_channel_instances, + msvs_channel_map=msvs_channel_map, + ) - 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 + return msvc_installed + + def query_msvc_instances( + self, *, + vs_product_def=None, + vs_channel_def=None, + vs_componentid_def=None, + vs_sequence_nbr=None, + ): + + # TODO(JCB): error checking combinations (ignored, etc) + + if not vs_channel_def: + vs_channel_def = _msvs_channel_default() + + if vs_product_def: + + query_key = _VSKeys.msvs_edition_key( + vs_product_def=vs_product_def, + vs_channel_def=vs_channel_def, + vs_componentid_def=vs_componentid_def, + vs_sequence_nbr=vs_sequence_nbr + ) - if msvc_version not in _VSWHERE_SUPPORTED_VCVER: - debug("Unknown version of MSVC: %s", msvc_version) - raise UnsupportedVersion("Unknown version %s" % msvc_version) + msvc_instances = self.msvs_edition_instances_map.get(query_key, []) - is_release = True - key = (msvc_version, is_release) + else: + + query_key = _VSKeys.msvs_channel_key( + vs_channel_def=vs_channel_def, + vs_componentid_def=vs_componentid_def, + ) + + msvc_instances = self.msvs_channel_instances_map.get(query_key, []) - 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) + 'query_key=%s, n_msvc_instances=%s', + repr(query_key.serialize()), + repr(len(msvc_instances)), + extra=self.debug_extra, ) - msvc_instances = [] - save_pdir_kind = [] + return query_key, msvc_instances - pdir = None - kind_t = None +_MSVSManager = namedtuple('_MSVSManager', [ + 'msvc_installed', + 'msvs_installed', +]) - for msvc_instance in msvc_instances: +class MSVSManager(_MSVSManager): - vcdir = msvc_instance.vc_path + @classmethod + def factory(cls, msvc_installed, msvs_installed): - 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 + msvs_manager = cls( + msvc_installed=msvc_installed, + msvs_installed=msvs_installed, + ) - pdir = vcdir - kind_t = vckind_t - break + return msvs_manager - if not pdir and not kind_t: - if save_pdir_kind: - pdir, kind_t = save_pdir_kind[0] +class _VSDetect(AutoInitialize): - MSVC.Kind.msvc_version_register_kind(msvc_version, kind_t) + debug_extra = None - debug('msvc_version=%s, pdir=%s', repr(msvc_version), repr(pdir)) - _cache_pdir_vswhere_queries[msvc_version] = pdir + @classmethod + def _initialize(cls): + cls.debug_extra = debug_extra(cls) + cls.reset() - return pdir + @classmethod + def reset(cls): -_cache_pdir_registry_queries = {} + cls.vswhere_executable_seen = set() + cls.vswhere_executables = [] -def find_vc_pdir_registry(msvc_version): - """ Find the MSVC product directory using the registry. + cls.registry_once = False - Args: - msvc_version: MSVC version to search for + cls.vs_dir_seen = set() # vswhere + cls.vc_dir_seen = set() # registry - Returns: - MSVC install dir or None + cls.msvs_sequence_nbr = {} - Raises: - UnsupportedVersion: if the version is not known by this file + cls.msvs_instances = [] + cls.msvc_instances = [] - """ - global _cache_pdir_registry_queries + # volatile: reconstructed when new instances detected + cls.msvs_manager = None - 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 + reset_funcs = [] - try: - 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 + @classmethod + def register_reset_func(cls, func): + cls.reset_funcs.append(func) + + @classmethod + def call_reset_funcs(cls): + for func in cls.reset_funcs: + func() + + @classmethod + def _msvc_dir_evaluate( + cls, *, + vs_product_def, + vs_component_def, + vs_channel_def, + vs_dir, + vs_version, + vs_exec, + vc_version, + vc_dir, + vc_feature_map=None, + ): + + vs_sequence_key = (vs_product_def, vs_channel_def, vs_component_def) + + cls.msvs_sequence_nbr.setdefault(vs_sequence_key, 0) + cls.msvs_sequence_nbr[vs_sequence_key] += 1 + + vs_sequence_nbr = cls.msvs_sequence_nbr[vs_sequence_key] + + msvs_base = MSVSBase.factory( + vs_product_def=vs_product_def, + vs_channel_def=vs_channel_def, + vs_component_def=vs_component_def, + vs_sequence_nbr=vs_sequence_nbr, + vs_dir=vs_dir, + vs_version=vs_version, + ) - save_pdir_kind = [] + if msvs_base.is_express: + vc_version += msvs_base.vs_component_suffix - is_win64 = common.is_win64() + vc_version_def = MSVC.Util.msvc_version_components(vc_version) - pdir = None - kind_t = None + msvc_instance = MSVCInstance.factory( + msvs_base=msvs_base, + vc_version_def=vc_version_def, + vc_feature_map=vc_feature_map, + vc_dir=vc_dir, + ) - root = 'Software\\' - for hkroot, key in regkeys: + # TODO(JCB): load toolsets/tools + if not _msvc_instance_check_files_exist(msvc_instance): + # no compilers detected don't register msvc_instance + return None - if not hkroot or not key: - continue + msvs_base.register_msvc_instance(msvc_instance) + cls.msvc_instances.append(msvc_instance) - if is_win64: - msregkeys = [root + 'Wow6432Node\\' + key, root + key] - else: - msregkeys = [root + key] + if vs_exec: + + msvs_instance = MSVSInstance.factory( + msvs_base=msvs_base, + vs_executable=vs_exec, + vc_version_def=vc_version_def, + ) + + msvs_base.register_msvs_instance(msvs_instance) + cls.msvs_instances.append(msvs_instance) + + return msvc_instance + + @classmethod + def _filter_vswhere_paths(cls, vswhere_env=None): + vswhere_paths = [ + vswhere_exec.norm + for vswhere_exec in _find_vswhere_executables(vswhere_env) + if vswhere_exec.norm not in cls.vswhere_executable_seen + ] + debug('vswhere_paths=%s', vswhere_paths, extra=cls.debug_extra) + return vswhere_paths + + @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) - 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: + cp = subprocess.run( + vswhere_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False + ) + 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 vcdir: - debug('no VC registry key %s', repr(key)) - continue + if not cp.stdout: + debug("no vswhere information returned", extra=cls.debug_extra) + break - is_vcforpython = False + vswhere_output = cp.stdout.decode('utf8', errors='replace') + if not vswhere_output: + debug("no vswhere information output", extra=cls.debug_extra) + break - 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 == '14.0Exp' and key.lower().endswith('\\14.0\\setup\\vs\\productdir'): - # 2015Exp VS productdir (root) not VC productdir - is_vsroot = True + try: + vswhere_output_json = json.loads(vswhere_output) + except json.decoder.JSONDecodeError: + debug("json decode exception loading vswhere output", extra=cls.debug_extra) + break - if is_vsroot: - vcdir = os.path.join(vcdir, 'VC') - debug('convert vs root to vc dir: %s', repr(vcdir)) + vswhere_json = vswhere_output_json + break - if not os.path.exists(vcdir): - debug('reg says dir is %s, but it does not exist. (ignoring)', repr(vcdir)) - continue + debug( + 'vswhere_json=%s, vswhere_exe=%s', + bool(vswhere_json), repr(vswhere_exe), extra=cls.debug_extra + ) - 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 + return vswhere_json - pdir = vcdir - kind_t = vckind_t - break + @classmethod + def _msvc_instances_vswhere(cls, vswhere_env=None): - if not pdir and not kind_t: - if save_pdir_kind: - pdir, kind_t = save_pdir_kind[0] + num_instances = len(cls.msvc_instances) + num_new_instances = 0 - MSVC.Kind.msvc_version_register_kind(msvc_version, kind_t) + vswhere_paths = cls._filter_vswhere_paths(vswhere_env) + if vswhere_paths: - debug('msvc_version=%s, pdir=%s', repr(msvc_version), repr(pdir)) - _cache_pdir_registry_queries[msvc_version] = pdir + num_beg_instances = num_instances - return pdir + for vswhere_exe in vswhere_paths: -def find_vc_pdir(msvc_version, env=None): - """Find the MSVC product directory for the given version. + if vswhere_exe in cls.vswhere_executable_seen: + continue - Use find_vc_pdir_vsvwhere for msvc versions 14.1 and later. - Use find_vc_pdir_registry for msvc versions 14.0 and earlier. + cls.vswhere_executable_seen.add(vswhere_exe) + cls.vswhere_executables.append(vswhere_exe) - Args: - msvc_version: str - msvc version (major.minor, e.g. 10.0) - env: - optional to look up VSWHERE variable + debug('vswhere_exe=%s', repr(vswhere_exe), extra=cls.debug_extra) - Returns: - str: Path found in registry, or None + vswhere_json = cls._vswhere_query_json_output( + vswhere_exe, + ['-all', '-products', '*', '-prerelease'] + ) - Raises: - UnsupportedVersion: if the version is not known by this file. + if not vswhere_json: + continue - UnsupportedVersion inherits from VisualCException. + for instance in vswhere_json: - """ + #print(json.dumps(instance, indent=4, sort_keys=True)) + + vs_dir = instance.get('installationPath') + if not vs_dir or not path_exists(vs_dir): + continue + + vs_norm = normalize_path(vs_dir) + if vs_norm in cls.vs_dir_seen: + continue + + vs_version = instance.get('installationVersion') + if not vs_version: + continue + + product_id = instance.get('productId') + if not product_id: + continue + + # consider msvs root evaluated at this point + cls.vs_dir_seen.add(vs_norm) + + vc_dir = os.path.join(vs_dir, 'VC') + if not path_exists(vc_dir): + continue + + vs_major = vs_version.split('.')[0] + if vs_major not in MSVC.Config.MSVS_VERSION_MAJOR_MAP: + debug('ignore vs_major: %s', vs_major, extra=cls.debug_extra) + continue + + vs_product_def = MSVC.Config.MSVS_VERSION_MAJOR_MAP[vs_major] + + component_id = product_id.split('.')[-1] + vs_component_def = MSVC.Config.VSWHERE_COMPONENT_INTERNAL.get(component_id) + if not vs_component_def: + debug('ignore component_id: %s', component_id, extra=cls.debug_extra) + continue + + is_prerelease = True if instance.get('isPrerelease', False) else False + if is_prerelease: + vs_channel_def = MSVC.Config.MSVS_CHANNEL_PREVIEW + else: + vs_channel_def = MSVC.Config.MSVS_CHANNEL_RELEASE + + vc_version = MSVC.Config.VSWHERE_VSMAJOR_TO_VCVERSION[vs_major] + + vs_exec = MSVC.Kind.msvc_dir_vswhere( + vc_dir, + _VSPRODUCT_KIND_DETECT[vs_product_def.vs_product], + ) + + cls._msvc_dir_evaluate( + vs_product_def=vs_product_def, + vs_component_def=vs_component_def, + vs_channel_def=vs_channel_def, + vs_dir=vs_dir, + vs_version=vs_version, + vs_exec=vs_exec, + vc_version=vc_version, + vc_dir=vc_dir, + ) + + num_instances = len(cls.msvc_instances) + num_new_instances = num_instances - num_beg_instances + + if num_new_instances > 0: + cls.call_reset_funcs() + debug('num_new_instances=%s, num_instances=%s', num_new_instances, num_instances, extra=cls.debug_extra) + + return num_new_instances + + @classmethod + def _msvc_instances_registry(cls): + + num_instances = len(cls.msvc_instances) + num_new_instances = 0 + + if not cls.registry_once: + cls.registry_once = True + + num_beg_instances = num_instances + + is_win64 = common.is_win64() + + for vs_product, regkeys in _VSPRODUCT_REGISTRY_VCDIR.items(): + + key_prefix = 'Software\\' + for is_vsroot, is_vcforpython, hkroot, key in regkeys: + + if not hkroot or not key: + continue + + if is_win64: + msregkeys = [key_prefix + 'Wow6432Node\\' + key, key_prefix + key] + else: + msregkeys = [key_prefix + key] + + vc_dir = None + for msregkey in msregkeys: + debug('trying VC registry key %s', repr(msregkey), extra=cls.debug_extra) + try: + vc_dir = common.read_reg(msregkey, hkroot) + except OSError: + continue + if vc_dir: + break + + if not vc_dir: + debug('no VC registry key %s', repr(key), extra=cls.debug_extra) + continue + + if vc_dir in cls.vc_dir_seen: + continue + cls.vc_dir_seen.add(vc_dir) + + if not os.path.exists(vc_dir): + debug( + 'reg says vc dir is %s, but it does not exist. (ignoring)', + repr(vc_dir), extra=cls.debug_extra + ) + continue + + if is_vsroot: + + vc_dir = os.path.join(vc_dir, 'VC') + debug('convert vs dir to vc dir: %s', repr(vc_dir), extra=cls.debug_extra) + + if vc_dir in cls.vc_dir_seen: + continue + cls.vc_dir_seen.add(vc_dir) + + if not os.path.exists(vc_dir): + debug('vc dir does not exist. (ignoring)', repr(vc_dir), extra=cls.debug_extra) + continue + + vc_norm = normalize_path(vc_dir) + if vc_norm in cls.vc_dir_seen: + continue + cls.vc_dir_seen.add(vc_norm) - if msvc_version in _VSWHERE_SUPPORTED_VCVER: + vs_product_def = MSVC.Config.MSVS_VERSION_INTERNAL[vs_product] + vc_version = vs_product_def.vc_buildtools_def.vc_version - pdir = find_vc_pdir_vswhere(msvc_version, env) - if pdir: - debug('VC found: %s, dir=%s', repr(msvc_version), repr(pdir)) - return pdir + vs_component_def, vs_dir, vs_exec, vc_feature_map = MSVC.Kind.msvc_dir_registry( + vc_norm, + _VSPRODUCT_KIND_DETECT[vs_product_def.vs_product], + vc_version, + is_vcforpython, + vs_product_def.vs_version + ) - elif msvc_version in _VCVER_TO_PRODUCT_DIR: + if not vs_component_def: + continue - pdir = find_vc_pdir_registry(msvc_version) - if pdir: - debug('VC found: %s, dir=%s', repr(msvc_version), repr(pdir)) - return pdir + vs_channel_def = MSVC.Config.MSVS_CHANNEL_RELEASE + cls._msvc_dir_evaluate( + vs_product_def=vs_product_def, + vs_component_def=vs_component_def, + vs_channel_def=vs_channel_def, + vs_dir=vs_dir, + vs_version=vs_product_def.vs_version, + vs_exec=vs_exec, + vc_version=vc_version, + vc_dir=vc_dir, + vc_feature_map=vc_feature_map, + ) + + num_instances = len(cls.msvc_instances) + num_new_instances = num_instances - num_beg_instances + + return num_new_instances + + @classmethod + def detect(cls, vswhere_env=None): + + num_new_instances = 0 + num_new_instances += cls._msvc_instances_vswhere(vswhere_env) + num_new_instances += cls._msvc_instances_registry() + + if num_new_instances > 0 or cls.msvs_manager is None: + + msvc_installed = MSVCInstalled.factory( + msvc_instances=cls.msvc_instances, + ) + + msvs_installed = MSVSInstalled.factory( + msvs_instances=cls.msvs_instances, + ) + + cls.msvs_manager = MSVSManager.factory( + msvc_installed=msvc_installed, + msvs_installed=msvs_installed, + ) + + return cls.msvs_manager + +def vs_detect(env=None): + vswhere_env = env.subst('$VSWHERE') if env and 'VSWHERE' in env else None + msvs_manager = _VSDetect.detect(vswhere_env) + return msvs_manager + +def _query_key_components(msvc_version, env=None): + + # TODO(JCB): temporary placeholder + + prefix, suffix = MSVC.Util.get_msvc_version_prefix_suffix(msvc_version) + + vs_product_def = MSVC.Config.MSVC_VERSION_INTERNAL[prefix] + + vs_channel_def = None + + if suffix == 'Exp': + vs_componentid_def = MSVC.Config.MSVS_COMPONENTID_EXPRESS else: + vs_componentid_def = None + + return vs_product_def, vs_componentid_def, vs_channel_def +def _find_msvc_instance(msvc_version, env=None): + + msvc_instance = None + query_key = None + + if msvc_version not in _VCVER: + # TODO(JCB): issue warning (add policy?) debug("Unknown version of MSVC: %s", repr(msvc_version)) - raise UnsupportedVersion("Unknown version %s" % repr(msvc_version)) from None + return msvc_instance, query_key - debug('no VC found for version %s', repr(msvc_version)) - return None + vs_manager = vs_detect(env) + + vs_product_def, vs_componentid_def, vs_channel_def = _query_key_components(msvc_version, env) + + query_key, msvc_instances = vs_manager.msvc_installed.query_msvc_instances( + vs_product_def=vs_product_def, + vs_componentid_def=vs_componentid_def, + vs_channel_def=vs_channel_def, + ) + + msvc_instance = msvc_instances[0] if msvc_instances else None + + debug( + 'msvc_version=%s, vc_dir=%s', + repr(msvc_version), + repr(msvc_instance.vc_dir if msvc_instance else None) + ) + + return msvc_instance, query_key + +def find_msvc_instance(msvc_version, env=None): + """Select an MSVC installation record. -# 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) + Args: + msvc_version: str + msvc version (major.minor, e.g. 10.0) + env: optional + env to look up construction variables -def _reset_vc_pdir(): - debug('reset pdir caches') + Returns: + str: MSVC installation record or None + """ + msvc_instance, _ = _find_msvc_instance(msvc_version, env) + return msvc_instance + +def _reset_installed_queries(): + debug('reset') global _cache_user_vswhere_path - global _cache_pdir_vswhere_queries - global _cache_pdir_registry_queries _cache_user_vswhere_paths = {} - _cache_pdir_vswhere_queries = {} - _cache_pdir_registry_queries = {} -def find_batch_file(msvc_version, host_arch, target_arch, pdir): +def find_batch_file(msvc_instance, host_arch, target_arch): """ Find the location of the batch script which should set up the compiler for any TARGET_ARCH whose compilers were installed by Visual Studio/VCExpress @@ -1518,51 +2474,44 @@ def find_batch_file(msvc_version, host_arch, target_arch, pdir): so use that and return an empty argument. """ - # filter out e.g. "Exp" from the version name - vernum = float(get_msvc_version_numeric(msvc_version)) - vernum_int = int(vernum * 10) - - sdk_pdir = pdir - arg = '' - vcdir = None + batfilename = None clexe = None depbat = None - if vernum_int >= 143: + pdir = msvc_instance.vc_dir + + if msvc_instance.vs_product_numeric >= 2022: # 14.3 (VS2022) and later batfiledir = os.path.join(pdir, "Auxiliary", "Build") batfile, _ = _GE2022_HOST_TARGET_BATCHFILE_CLPATHCOMPS[(host_arch, target_arch)] batfilename = os.path.join(batfiledir, batfile) - vcdir = pdir - elif 143 > vernum_int >= 141: + elif 2022 > msvc_instance.vs_product_numeric >= 2017: # 14.2 (VS2019) to 14.1 (VS2017) batfiledir = os.path.join(pdir, "Auxiliary", "Build") batfile, _ = _LE2019_HOST_TARGET_BATCHFILE_CLPATHCOMPS[(host_arch, target_arch)] batfilename = os.path.join(batfiledir, batfile) - vcdir = pdir - elif 141 > vernum_int >= 100: + elif 2017 > msvc_instance.vs_product_numeric >= 2010: # 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") 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: + elif 2010 > msvc_instance.vs_product_numeric >= 2005: # 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): + if msvc_instance.is_vcforpython: # 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 + else: # 2005 > vc_product_numeric >= 6.0 # 7.1 (VS2003) and earlier pdir = os.path.join(pdir, "Bin") batfilename = os.path.join(pdir, "vcvars32.bat") @@ -1580,13 +2529,15 @@ def find_batch_file(msvc_version, host_arch, target_arch, pdir): debug("dependency batch file not found: %s", depbat) batfilename = None - return batfilename, arg, vcdir, sdk_pdir + return batfilename, arg +# TODO(JCB): test and remove (orphaned) def find_batch_file_sdk(host_arch, target_arch, sdk_pdir): """ Find the location of the sdk batch script which should set up the compiler for any TARGET_ARCH whose compilers were installed by Visual Studio/VCExpress """ + from .sdk import get_installed_sdks installed_sdks = get_installed_sdks() for _sdk in installed_sdks: @@ -1601,37 +2552,31 @@ def find_batch_file_sdk(host_arch, target_arch, sdk_pdir): return None -__INSTALLED_VCS_RUN = None +_cache_installed_msvc_versions = None +_cache_installed_msvc_instances = None def _reset_installed_vcs(): - global __INSTALLED_VCS_RUN - debug('reset __INSTALLED_VCS_RUN') - __INSTALLED_VCS_RUN = None + global _cache_installed_msvc_versions + global _cache_installed_msvc_instances + _cache_installed_msvc_versions = None + _cache_installed_msvc_instances = None + debug('reset') -# register vcs cache reset function with vswhere state manager -_VSWhere.register_reset_func(_reset_installed_vcs) +# register vcs cache reset function with vsdetect state manager +_VSDetect.register_reset_func(_reset_installed_vcs) _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_files_exist_in_vc_dir(env, vc_dir, msvc_version) -> bool: - """Return status of finding batch file and cl.exe to use. +def _msvc_instance_check_files_exist(msvc_instance) -> bool: + """Return status of finding at least one batch file and cl.exe. - 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. + Locates at least one required vcvars batch file and cl.ex in the vc_dir. + The native platform is assumed for the host and all associated targets. Args: - env: Environment - a construction environment, usually if this is passed its - because there is a desired TARGET_ARCH to be used when searching - for a cl.exe - vc_dir: str - the path to the VC dir in the MSVC installation - msvc_version: str - msvc version (major.minor, e.g. 10.0) + msvc_instance: + MSVC installation record Returns: bool: @@ -1639,21 +2584,18 @@ def _check_files_exist_in_vc_dir(env, vc_dir, msvc_version) -> bool: """ # Find the host, target, and all candidate (host, target) platform combinations: - platforms = get_host_target(env, msvc_version, all_host_targets=True) - debug("host_platform %s, target_platform %s host_target_list %s", *platforms) + platforms = get_host_target(None, msvc_instance.msvc_version, all_host_targets=True) + debug("host_platform=%s, target_platform=%s, host_target_list=%s", *platforms) host_platform, target_platform, host_target_list = platforms - vernum = float(get_msvc_version_numeric(msvc_version)) - vernum_int = int(vernum * 10) - # make sure the cl.exe exists meaning the tool is installed - if vernum_int >= 141: + if msvc_instance.vs_product_numeric >= 2017: # 14.1 (VS2017) and later # 2017 and newer allowed multiple versions of the VC toolset to be # installed at the same time. This changes the layout. # Just get the default tool version for now # TODO: support setting a specific minor VC version - default_toolset_file = os.path.join(vc_dir, _VC_TOOLS_VERSION_FILE) + default_toolset_file = os.path.join(msvc_instance.vc_dir, _VC_TOOLS_VERSION_FILE) try: with open(default_toolset_file) as f: vc_specific_version = f.readlines()[0].strip() @@ -1664,7 +2606,7 @@ def _check_files_exist_in_vc_dir(env, vc_dir, msvc_version) -> bool: debug('failed to find MSVC version in %s', default_toolset_file) return False - if vernum_int >= 143: + if msvc_instance.vs_product_numeric >= 2022: # 14.3 (VS2022) and later host_target_batchfile_clpathcomps = _GE2022_HOST_TARGET_BATCHFILE_CLPATHCOMPS else: @@ -1673,7 +2615,10 @@ def _check_files_exist_in_vc_dir(env, vc_dir, msvc_version) -> bool: 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) + debug( + 'host_platform=%s, target_platform=%s, msvc_version=%s', + host_platform, target_platform, msvc_instance.msvc_version + ) batchfile_clpathcomps = host_target_batchfile_clpathcomps.get((host_platform, target_platform), None) if batchfile_clpathcomps is None: @@ -1682,12 +2627,14 @@ def _check_files_exist_in_vc_dir(env, vc_dir, msvc_version) -> bool: batfile, cl_path_comps = batchfile_clpathcomps - batfile_path = os.path.join(vc_dir, "Auxiliary", "Build", batfile) + batfile_path = os.path.join(msvc_instance.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) + cl_path = os.path.join( + msvc_instance.vc_dir, 'Tools', 'MSVC', vc_specific_version, *cl_path_comps, _CL_EXE_NAME + ) if not os.path.exists(cl_path): debug("%s not found: %s", _CL_EXE_NAME, cl_path) continue @@ -1695,24 +2642,24 @@ def _check_files_exist_in_vc_dir(env, vc_dir, msvc_version) -> bool: debug('%s found: %s', _CL_EXE_NAME, cl_path) return True - elif 141 > vernum_int >= 80: + elif 2017 > msvc_instance.vs_product_numeric >= 2005: # 14.0 (VS2015) to 8.0 (VS2005) - if vernum_int >= 100: + if msvc_instance.vs_product_numeric >= 2010: # 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): + if msvc_instance.is_vcforpython: # 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") + batfile_path = os.path.join(msvc_instance.vc_dir, os.pardir, "vcvarsall.bat") check_depbat = False else: - batfile_path = os.path.join(vc_dir, "vcvarsall.bat") + batfile_path = os.path.join(msvc_instance.vc_dir, "vcvarsall.bat") check_depbat = True if not os.path.exists(batfile_path): @@ -1721,7 +2668,10 @@ def _check_files_exist_in_vc_dir(env, vc_dir, msvc_version) -> bool: 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) + debug( + 'host_platform=%s, target_platform=%s, msvc_version=%s', + host_platform, target_platform, msvc_instance.msvc_version + ) batcharg_batchfile_clpathcomps = host_target_batcharg_batchfile_clpathcomps.get( (host_platform, target_platform), None @@ -1734,12 +2684,12 @@ def _check_files_exist_in_vc_dir(env, vc_dir, msvc_version) -> bool: _, batfile, cl_path_comps = batcharg_batchfile_clpathcomps if check_depbat: - batfile_path = os.path.join(vc_dir, *cl_path_comps, batfile) + batfile_path = os.path.join(msvc_instance.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) + cl_path = os.path.join(msvc_instance.vc_dir, *cl_path_comps, _CL_EXE_NAME) if not os.path.exists(cl_path): debug("%s not found: %s", _CL_EXE_NAME, cl_path) continue @@ -1747,82 +2697,87 @@ def _check_files_exist_in_vc_dir(env, vc_dir, msvc_version) -> bool: debug('%s found: %s', _CL_EXE_NAME, cl_path) return True - elif 80 > vernum_int >= 60: + elif 2005 > msvc_instance.vs_product_numeric >= 1998: # 7.1 (VS2003) to 6.0 (VS6) - # quick check for vc_dir/bin and vc_dir/ before walk - # need to check root as the walk only considers subdirectories - for cl_dir in ('bin', ''): - cl_path = os.path.join(vc_dir, cl_dir, _CL_EXE_NAME) - if os.path.exists(cl_path): - debug('%s found %s', _CL_EXE_NAME, cl_path) - return True - # not in bin or root: must be in a subdirectory - for cl_root, cl_dirs, _ in os.walk(vc_dir): - 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) - return True - return False + bin_dir = os.path.join(msvc_instance.vc_dir, "bin") + + batfile_path = os.path.join(bin_dir, "vcvars32.bat") + if not os.path.exists(batfile_path): + debug("batch file not found: %s", batfile_path) + return False + + cl_path = os.path.join(bin_dir, _CL_EXE_NAME) + if not os.path.exists(cl_path): + debug("%s not found: %s", _CL_EXE_NAME, cl_path) + return False + + debug('%s found: %s', _CL_EXE_NAME, cl_path) + return True else: # version not supported return false - debug('unsupported MSVC version: %s', str(vernum)) + debug('unsupported MSVC version: %s', str(msvc_instance.msvc_version)) return False +def get_installed_msvc_instances(env=None): + global _cache_installed_msvc_instances + + # the installed instance cache is cleared if new instances are discovered + vs_manager = vs_detect(env) + + if _cache_installed_msvc_instances is not None: + return _cache_installed_msvc_instances + + msvc_installed = vs_manager.msvc_installed + + # TODO(JCB): first-use default channel + vs_channel_def = _msvs_channel_default() + + msvs_channel_map = msvc_installed.msvs_channel_map + msvc_version_map = msvs_channel_map.get(vs_channel_def) + + installed_msvc_instances = [ + msvc_version_map[vc_version][0] + for vc_version in msvc_version_map.keys() + ] + + _cache_installed_msvc_instances = installed_msvc_instances + # debug("installed_msvc_instances=%s", _cache_installed_msvc_versions) + return _cache_installed_msvc_instances + def get_installed_vcs(env=None): - global __INSTALLED_VCS_RUN + global _cache_installed_msvc_versions - # the installed vcs cache is cleared - # if new vc roots are discovered - _update_vswhere_msvc_map(env) + # the installed version cache is cleared if new instances are discovered + vs_manager = vs_detect(env) - if __INSTALLED_VCS_RUN is not None: - return __INSTALLED_VCS_RUN + if _cache_installed_msvc_versions is not None: + return _cache_installed_msvc_versions - save_target_arch = env.get('TARGET_ARCH', UNDEFINED) if env else None - force_target = env and save_target_arch and save_target_arch != UNDEFINED + msvc_installed = vs_manager.msvc_installed - if force_target: - del env['TARGET_ARCH'] - debug("delete env['TARGET_ARCH']") + # TODO(JCB): first-use default channel + vs_channel_def = _msvs_channel_default() - installed_versions = [] + msvs_channel_map = msvc_installed.msvs_channel_map + msvc_version_map = msvs_channel_map.get(vs_channel_def) - for ver in _VCVER: - debug('trying to find VC %s', ver) - try: - VC_DIR = find_vc_pdir(ver, env) - if VC_DIR: - debug('found VC %s', 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 - # SCons to exit with an error code - raise - except VisualCException as e: - debug('did not find VC %s: caught exception %s', ver, str(e)) - - if force_target: - env['TARGET_ARCH'] = save_target_arch - debug("restore env['TARGET_ARCH']=%s", save_target_arch) - - __INSTALLED_VCS_RUN = installed_versions - debug("__INSTALLED_VCS_RUN=%s", __INSTALLED_VCS_RUN) - return __INSTALLED_VCS_RUN + installed_msvc_versions = [ + vc_version + for vc_version in msvc_version_map.keys() + ] + + _cache_installed_msvc_versions = installed_msvc_versions + debug("installed_msvc_versions=%s", _cache_installed_msvc_versions) + return _cache_installed_msvc_versions def reset_installed_vcs() -> None: """Make it try again to find VC. This is just for the tests.""" _reset_installed_vcs() - _reset_vc_pdir() - _VSWhere.reset() + _reset_installed_queries() + _VSDetect.reset() MSVC._reset() def msvc_default_version(env=None): @@ -1995,27 +2950,24 @@ def msvc_find_valid_batch_script(env, version): get it right. """ - # Find the product directory - pdir = None - try: - pdir = find_vc_pdir(version, env) - except UnsupportedVersion: - # Unsupported msvc version (raise MSVCArgumentError?) - pass - debug('product directory: version=%s, pdir=%s', version, pdir) + msvc_instance, msvc_query_key = _find_msvc_instance(version, env) - # Find the host, target, and all candidate (host, target) platform combinations: - platforms = get_host_target(env, version) - debug("host_platform %s, target_platform %s host_target_list %s", *platforms) - host_platform, target_platform, host_target_list = platforms + debug( + 'version=%s, query_key=%s, vc_dir=%s', + version, + repr(msvc_query_key.serialize()) if msvc_query_key else None, + msvc_instance.vc_dir if msvc_instance else None + ) d = None version_installed = False - if pdir: + if msvc_instance: - # Query all candidate sdk (host, target, sdk_pdir) after vc_script pass if necessary - sdk_queries = [] + # Find the host, target, and all candidate (host, target) platform combinations: + platforms = get_host_target(env, msvc_instance.msvc_version) + debug("host_platform %s, target_platform %s host_target_list %s", *platforms) + host_platform, target_platform, host_target_list = platforms for host_arch, target_arch, in host_target_list: # Set to current arch. @@ -2024,7 +2976,7 @@ def msvc_find_valid_batch_script(env, version): # Try to locate a batch file for this host/target platform combo try: - (vc_script, arg, vc_dir, sdk_pdir) = find_batch_file(version, host_arch, target_arch, pdir) + vc_script, arg = find_batch_file(msvc_instance, host_arch, target_arch) debug('vc_script:%s vc_script_arg:%s', vc_script, arg) version_installed = True except VisualCException as e: @@ -2033,25 +2985,18 @@ def msvc_find_valid_batch_script(env, version): version_installed = False continue - # Save (host, target, sdk_pdir) platform combo for sdk queries - if sdk_pdir: - sdk_query = (host_arch, target_arch, sdk_pdir) - if sdk_query not in sdk_queries: - debug('save sdk_query host=%s, target=%s, sdk_pdir=%s', host_arch, target_arch, sdk_pdir) - sdk_queries.append(sdk_query) - 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): + if msvc_instance.skip_uwp_target(env): # 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) + arg = MSVC.ScriptArguments.msvc_script_arguments(env, msvc_instance, arg) debug('trying vc_script:%s, vc_script_args:%s', repr(vc_script), arg) try: d = script_env(env, vc_script, args=arg) @@ -2068,36 +3013,12 @@ def msvc_find_valid_batch_script(env, version): debug("Found a working script/target: %s/%s", repr(vc_script), arg) break # We've found a working target_platform, so stop looking + # If we cannot find a viable installed compiler, reset the TARGET_ARCH + # To it's initial value if not d: - for host_arch, target_arch, sdk_pdir in sdk_queries: - # Set to current arch. - env['TARGET_ARCH'] = target_arch - - sdk_script = find_batch_file_sdk(host_arch, target_arch, sdk_pdir) - if not sdk_script: - continue - - # Try to use the sdk batch file for this (host, target, sdk_pdir) combo - debug('trying sdk_script:%s', repr(sdk_script)) - try: - d = script_env(env, sdk_script) - version_installed = True - except BatchFileExecutionError as e: - debug('failed sdk_script:%s, error=%s', repr(sdk_script), e) - continue - - have_cl, _ = _check_cl_exists_in_script_env(d) - if not have_cl: - debug('skip cl.exe not found sdk_script:%s', repr(sdk_script)) - continue - - debug("Found a working script/target: %s", repr(sdk_script)) - break # We've found a working script, so stop looking + env['TARGET_ARCH'] = target_platform - # If we cannot find a viable installed compiler, reset the TARGET_ARCH - # To it's initial value if not d: - env['TARGET_ARCH'] = target_platform if version_installed: msg = "MSVC version '{}' working host/target script was not found.\n" \ @@ -2120,30 +3041,54 @@ def msvc_find_valid_batch_script(env, version): return d -def get_use_script_use_settings(env): +MSVCAction = namedtuple("MSVCAction", [ + 'action', +]) - # use_script use_settings return values action - # value ignored (value, None) use script or bypass detection - # undefined value not None (False, value) use dictionary - # undefined undefined/None (True, None) msvc detection +MSVC_ACTION_SCRIPT = MSVCAction(action='SCRIPT') +MSVC_ACTION_SELECT = MSVCAction(action='SELECT') +MSVC_ACTION_SETTINGS = MSVCAction(action='SETTINGS') +MSVC_ACTION_BYPASS = MSVCAction(action='BYPASS') - # None (documentation) or evaluates False (code): bypass detection - # need to distinguish between undefined and None - use_script = env.get('MSVC_USE_SCRIPT', UNDEFINED) +def _msvc_action(env): - if use_script != UNDEFINED: - # use_script defined, use_settings ignored (not type checked) - return use_script, None + # use_script use_settings return description + # ---------- -------------- -------- ---------------- + # string ignored SCRIPT use script + # eval false ignored BYPASS bypass detection + # eval true ignored SELECT msvc selection + # undefined value not None SETTINGS use dictionary + # undefined undefined/None SELECT msvc selection + + msvc_action = None - # undefined or None: use_settings ignored + use_script = env.get('MSVC_USE_SCRIPT', UNDEFINED) use_settings = env.get('MSVC_USE_SETTINGS', None) - if use_settings is not None: + if use_script != UNDEFINED: + # use script defined + if SCons.Util.is_String(use_script): + # use_script is string, use_settings ignored + msvc_action = MSVC_ACTION_SCRIPT + elif not use_script: + # use_script eval false, use_settings ignored + msvc_action = MSVC_ACTION_BYPASS + else: + # use script eval true, use_settings ignored + msvc_action = MSVC_ACTION_SELECT + elif use_settings is not None: # use script undefined, use_settings defined and not None (type checked) - return False, use_settings + msvc_action = MSVC_ACTION_SETTINGS + else: + # use script undefined, use_settings undefined or None + msvc_action = MSVC_ACTION_SELECT + + if not msvc_action: + errmsg = 'msvc_action is undefined' + debug('MSVCInternalError: %s', errmsg) + raise MSVCInternalError(errmsg) - # use script undefined, use_settings undefined or None - return True, None + return msvc_action, use_script, use_settings def msvc_setup_env(env): debug('called') @@ -2159,31 +3104,38 @@ def msvc_setup_env(env): env['MSVS_VERSION'] = version env['MSVS'] = {} - use_script, use_settings = get_use_script_use_settings(env) - if SCons.Util.is_String(use_script): + msvc_action, use_script, use_settings = _msvc_action(env) + if msvc_action == MSVC_ACTION_SCRIPT: use_script = use_script.strip() if not os.path.exists(use_script): - raise MSVCScriptNotFound(f'Script specified by MSVC_USE_SCRIPT not found: "{use_script}"') + error_msg = f'Script specified by MSVC_USE_SCRIPT not found: {use_script!r}' + debug(error_msg) + raise MSVCScriptNotFound(error_msg) args = env.subst('$MSVC_USE_SCRIPT_ARGS') debug('use_script 1 %s %s', repr(use_script), repr(args)) d = script_env(env, use_script, args) - elif use_script: - d = msvc_find_valid_batch_script(env,version) + elif msvc_action == MSVC_ACTION_SELECT: + d = msvc_find_valid_batch_script(env, version) debug('use_script 2 %s', d) if not d: return d - elif use_settings is not None: + elif msvc_action == MSVC_ACTION_SETTINGS: if not SCons.Util.is_Dict(use_settings): error_msg = f'MSVC_USE_SETTINGS type error: expected a dictionary, found {type(use_settings).__name__}' + debug(error_msg) raise MSVCUseSettingsError(error_msg) d = use_settings debug('use_settings %s', d) - else: + elif msvc_action == MSVC_ACTION_BYPASS: debug('MSVC_USE_SCRIPT set to False') - warn_msg = "MSVC_USE_SCRIPT set to False, assuming environment " \ - "set correctly." + warn_msg = "MSVC_USE_SCRIPT set to False, assuming environment set correctly." SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, warn_msg) return None + else: + action = msvc_action.action if msvc_action else None + errmsg = f'unhandled msvc_action ({action!r})' + debug('MSVCInternalError: %s', errmsg) + raise MSVCInternalError(errmsg) found_cl_path = None found_cl_envpath = None @@ -2300,12 +3252,12 @@ 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(msvc_version) - if not vc_dir: - debug('VC folder not found for version %s', repr(msvc_version)) + msvc_instance = find_msvc_instance(msvc_version) + if not msvc_instance: + debug('MSVC instance not found for version %s', repr(msvc_version)) return rval - rval = MSVC.ScriptArguments._msvc_toolset_versions_internal(msvc_version, vc_dir, full=full, sxs=sxs) + rval = MSVC.ScriptArguments._msvc_toolset_versions_internal(msvc_instance, full=full, sxs=sxs) return rval def msvc_toolset_versions_spectre(msvc_version=None): @@ -2324,12 +3276,12 @@ def msvc_toolset_versions_spectre(msvc_version=None): msg = f'Unsupported msvc version {msvc_version!r}' raise MSVCArgumentError(msg) - vc_dir = find_vc_pdir(msvc_version) - if not vc_dir: - debug('VC folder not found for version %s', repr(msvc_version)) + msvc_instance = find_msvc_instance(msvc_version) + if not msvc_instance: + debug('MSVC instance not found for version %s', repr(msvc_version)) return rval - rval = MSVC.ScriptArguments._msvc_toolset_versions_spectre_internal(msvc_version, vc_dir) + rval = MSVC.ScriptArguments._msvc_toolset_versions_spectre_internal(msvc_instance) return rval def msvc_query_version_toolset(version=None, prefer_newest: bool=True): @@ -2443,8 +3395,8 @@ def msvc_query_version_toolset(version=None, prefer_newest: bool=True): continue seen_msvc_version.add(query_msvc_version) - vc_dir = find_vc_pdir(query_msvc_version) - if not vc_dir: + query_msvc_instance = find_msvc_instance(query_msvc_version) + if not query_msvc_instance: continue if query_msvc_version.startswith('14.0'): @@ -2457,7 +3409,7 @@ def msvc_query_version_toolset(version=None, prefer_newest: bool=True): return query_msvc_version, msvc_toolset_version try: - toolset_vcvars = MSVC.ScriptArguments._msvc_toolset_internal(query_msvc_version, query_msvc_toolset_version, vc_dir) + toolset_vcvars = MSVC.ScriptArguments._msvc_toolset_internal(query_msvc_instance, query_msvc_toolset_version) if toolset_vcvars: msvc_toolset_version = toolset_vcvars debug( diff --git a/SCons/Tool/MSCommon/vcTests.py b/SCons/Tool/MSCommon/vcTests.py index 8c22ba37e9..db2d46fe89 100644 --- a/SCons/Tool/MSCommon/vcTests.py +++ b/SCons/Tool/MSCommon/vcTests.py @@ -52,21 +52,61 @@ def _createVSWhere(path) -> None: with open(path, 'w') as f: f.write("Created:%s" % f) + _existing_path = None + + @classmethod + def _path_exists(cls, path): + return (path == cls._existing_path) + 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] + + restore_vswhere_execs_exist = MSCommon.vc.VSWHERE_EXECS_EXIST[:] + base_dir = test.workpath('fake_vswhere') - test_vswhere_dirs = [os.path.join(base_dir,d[1:]) for d in vswhere_dirs] + norm_dir = os.path.normcase(os.path.normpath(base_dir)) + # import pdb; pdb.set_trace() - SCons.Tool.MSCommon.vc.VSWHERE_PATHS = test_vswhere_dirs - for vsw in test_vswhere_dirs: - VswhereTestCase._createVSWhere(vsw) - find_path = SCons.Tool.MSCommon.vc.msvc_find_vswhere() - self.assertTrue(vsw == find_path, "Didn't find vswhere in %s found in %s" % (vsw, find_path)) - os.remove(vsw) + test_vswhere_dirs = [ + MSCommon.vc.VSWhereExecutable( + path=os.path.join(base_dir,t[0][1:]), + norm=os.path.join(norm_dir,t[1][1:]), + ) + for t in [ + (os.path.splitdrive(vswexec.path)[1], os.path.splitdrive(vswexec.norm)[1]) + for vswexec in MSCommon.vc.VSWHERE_EXECS_EXIST + ] + ] + + for vswexec in test_vswhere_dirs: + VswhereTestCase._createVSWhere(vswexec.path) + MSCommon.vc.VSWHERE_EXECS_EXIST = [vswexec] + find_path = MSCommon.vc.msvc_find_vswhere() + self.assertTrue(vswexec.path == find_path, "Didn't find vswhere in %s found in %s" % (vswexec.path, find_path)) + os.remove(vswexec.path) + + test_vswhere_dirs = [ + os.path.join(base_dir,d[1:]) + for d in [ + os.path.splitdrive(p)[1] + for p in MSCommon.vc.VSWHERE_PATHS + ] + ] + MSCommon.vc.path_exists = VswhereTestCase._path_exists + + for vswpath in test_vswhere_dirs: + VswhereTestCase._createVSWhere(vswpath) + VswhereTestCase._existing_path = vswpath + for front in (True, False): + MSCommon.vc.VSWHERE_EXECS_EXIST = [] + MSCommon.vc.vswhere_push_location(vswpath, front=front) + find_path = MSCommon.vc.msvc_find_vswhere() + self.assertTrue(vswpath == find_path, "Didn't find vswhere in %s found in %s" % (vswpath, find_path)) + os.remove(vswpath) + + MSCommon.vc.VSWHERE_EXECS_EXIST = restore_vswhere_execs_exist # def specifiedVswherePathTest(self): # "Verify that msvc.generate() respects VSWHERE Specified" @@ -74,6 +114,30 @@ def testDefaults(self) -> None: class MSVcTestCase(unittest.TestCase): + @staticmethod + def _createDummyMSVSBase(vc_version, vs_component, vs_dir): + vs_product_def = SCons.Tool.MSCommon.MSVC.Config.MSVC_VERSION_INTERNAL[vc_version] + vs_component_key = (vs_product_def.vs_lookup, vs_component) + msvs_base = SCons.Tool.MSCommon.vc.MSVSBase.factory( + vs_product_def=vs_product_def, + vs_channel_def=SCons.Tool.MSCommon.MSVC.Config.MSVS_CHANNEL_RELEASE, + vs_component_def=SCons.Tool.MSCommon.MSVC.Config.MSVS_COMPONENT_INTERNAL[vs_component_key], + vs_sequence_nbr=1, + vs_dir=vs_dir, + vs_version=vs_product_def.vs_version, + ) + return msvs_base + + @classmethod + def _createDummyMSVCInstance(cls, vc_version, vs_component, vc_dir): + msvc_instance = SCons.Tool.MSCommon.vc.MSVCInstance.factory( + msvs_base=cls._createDummyMSVSBase(vc_version, vs_component, vc_dir), + vc_version_def=SCons.Tool.MSCommon.MSVC.Util.msvc_version_components(vc_version), + vc_feature_map=None, + vc_dir=vc_dir, + ) + return msvc_instance + @staticmethod def _createDummyFile(path, filename, add_bin: bool=True) -> None: """ @@ -105,10 +169,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_files_exist_in_vc_dir() + by SCons.Tool.MSCommon.vc._msvc_instance_check_files_exist() """ - check = SCons.Tool.MSCommon.vc._check_files_exist_in_vc_dir + check = SCons.Tool.MSCommon.vc._msvc_instance_check_files_exist # Setup for 14.1 (VS2017) and later tests @@ -124,77 +188,80 @@ def runTest(self) -> None: print("Failed trying to write :%s :%s" % (tools_version_file, e)) # Test 14.3 (VS2022) and later + msvc_instance_VS2022 = MSVcTestCase._createDummyMSVCInstance('14.3', 'Community', '.') 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._createDummyFile(path, 'cl.exe', add_bin=False) - result=check(env, '.', '14.3') + result=check(msvc_instance_VS2022) # print("for:(%s, %s) got :%s"%(host, target, result)) self.assertTrue(result, "Checking host: %s target: %s" % (host, target)) # Test 14.2 (VS2019) to 14.1 (VS2017) versions + msvc_instance_VS2017 = MSVcTestCase._createDummyMSVCInstance('14.1', 'Community', '.') 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._createDummyFile(path, 'cl.exe', add_bin=False) - result=check(env, '.', '14.1') + result=check(msvc_instance_VS2017) # print("for:(%s, %s) got :%s"%(host, target, result)) self.assertTrue(result, "Checking host: %s target: %s" % (host, target)) # Test 14.0 (VS2015) to 10.0 (VS2010) versions + msvc_instance_VS2010 = MSVcTestCase._createDummyMSVCInstance('10.0', 'Develop', '.') vc_le2015_list = SCons.Tool.MSCommon.vc._LE2015_HOST_TARGET_CFG.all_pairs for host, target in vc_le2015_list: 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._createDummyFile(path, batfile, add_bin=False) MSVcTestCase._createDummyFile(path, 'cl.exe', add_bin=False) - result=check(env, '.', '10.0') + result=check(msvc_instance_VS2010) # print("for:(%s, %s) got :%s"%(host, target, result)) self.assertTrue(result, "Checking host: %s target: %s" % (host, target)) # Test 9.0 (VC2008) to 8.0 (VS2005) + msvc_instance_VS2005 = MSVcTestCase._createDummyMSVCInstance('8.0', 'Develop', '.') 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') + result=check(msvc_instance_VS2005) # 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 + msvc_instance_VS6 = MSVcTestCase._createDummyMSVCInstance('6.0', 'Develop', '.') 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._createDummyFile(path, 'cl.exe') - result=check(env, '.', '6.0') + result=check(msvc_instance_VS6) # print("for:(%s, %s) got :%s"%(host, target, result)) self.assertTrue(result, "Checking host: %s target: %s" % (host, target)) + check = SCons.Tool.MSCommon.vc.get_host_target + # Now test bogus value for HOST_ARCH env={'TARGET_ARCH':'x86', 'HOST_ARCH':'GARBAGE'} try: - result=check(env, '.', '14.3') + check(env, msvc_instance_VS2022.vc_version_def.msvc_version) + result = True # 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: @@ -205,7 +272,8 @@ def runTest(self) -> None: # Now test bogus value for TARGET_ARCH env={'TARGET_ARCH':'GARBAGE', 'HOST_ARCH':'x86'} try: - result=check(env, '.', '14.3') + check(env, msvc_instance_VS2022.vc_version_def.msvc_version) + result = True # 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: diff --git a/SCons/Tool/MSCommon/vs.py b/SCons/Tool/MSCommon/vs.py index ef4f13cdfb..316d809b1b 100644 --- a/SCons/Tool/MSCommon/vs.py +++ b/SCons/Tool/MSCommon/vs.py @@ -66,11 +66,11 @@ def find_batch_file(self): return batch_file def find_vs_dir_by_vc(self, env): - dir = SCons.Tool.MSCommon.vc.find_vc_pdir(self.vc_version, env) - if not dir: + msvc_instance = SCons.Tool.MSCommon.vc.find_msvc_instance(self.vc_version, env) + if not msvc_instance: debug('no installed VC %s', self.vc_version) return None - return os.path.abspath(os.path.join(dir, os.pardir)) + return os.path.abspath(os.path.join(msvc_instance.vc_dir, os.pardir)) def find_vs_dir_by_reg(self, env): root = 'Software\\' @@ -194,7 +194,7 @@ def reset(self) -> None: # good money for in preference to whatever Microsoft makes available # for free. # -# If you update this list, update _VCVER and _VCVER_TO_PRODUCT_DIR in +# If you update this list, update _VCVER and _VSPRODUCT_REGISTRY_VCDIR in # Tool/MSCommon/vc.py, and the MSVC_VERSION documentation in Tool/msvc.xml. SupportedVSList = [ diff --git a/SCons/Tool/msvsTests.py b/SCons/Tool/msvsTests.py index 1266055494..0e9a19fa6b 100644 --- a/SCons/Tool/msvsTests.py +++ b/SCons/Tool/msvsTests.py @@ -579,9 +579,9 @@ def DummyQueryValue(key, value): def DummyExists(path) -> bool: return True -def DummyVsWhere(msvc_version, env): - # not testing versions with vswhere, so return none - return None +def DummyVsWhereExecutables(env=None): + # not testing versions with vswhere, so return empty list + return [] class msvsTestCase(unittest.TestCase): """This test case is run several times with different defaults. @@ -947,7 +947,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_vswhere_executables = DummyVsWhereExecutables 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_SCRIPTERROR_POLICY.py b/test/MSVC/MSVC_SCRIPTERROR_POLICY.py index 5efa753155..81686daa1b 100644 --- a/test/MSVC/MSVC_SCRIPTERROR_POLICY.py +++ b/test/MSVC/MSVC_SCRIPTERROR_POLICY.py @@ -30,13 +30,16 @@ import TestSCons import textwrap -from SCons.Tool.MSCommon.vc import get_installed_vcs_components +from SCons.Tool.MSCommon.vc import get_installed_msvc_instances test = TestSCons.TestSCons() test.skip_if_not_msvc() -installed_versions = get_installed_vcs_components() -default_version = installed_versions[0] +installed_instances = get_installed_msvc_instances() +if not installed_instances: + test.skip_test("No MSVC instances, skipping.") + +default_instance = installed_instances[0] # Test construction variable with valid symbols test.write('SConstruct', textwrap.dedent( @@ -51,7 +54,7 @@ )) test.run(arguments='-Q -s', stdout='') -if default_version.msvc_vernum >= 14.1: +if default_instance.vs_product_numeric >= 2017: # Need VS2017 or later for MSVC_SCRIPT_ARGS # Test environment construction with construction variable (invalid) diff --git a/test/MSVC/MSVC_SDK_VERSION.py b/test/MSVC/MSVC_SDK_VERSION.py index f3f9913b52..3b54c5f1bb 100644 --- a/test/MSVC/MSVC_SDK_VERSION.py +++ b/test/MSVC/MSVC_SDK_VERSION.py @@ -28,44 +28,41 @@ """ import textwrap -from SCons.Tool.MSCommon.vc import get_installed_vcs_components +from SCons.Tool.MSCommon.vc import get_installed_msvc_instances 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() test.skip_if_not_msvc() -installed_versions = get_installed_vcs_components() -default_version = installed_versions[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')) +installed_instances = get_installed_msvc_instances() +if not installed_instances: + test.skip_test("No MSVC instances, skipping.") + +GE_VS2015_supported_instances = [] +GE_VS2015_unsupported_instances = [] +LT_VS2015_unsupported_instances = [] + +for msvc_instance in installed_instances: + if msvc_instance.vs_product_numeric > 2015: + GE_VS2015_supported_instances.append(msvc_instance) + elif msvc_instance.vs_product_numeric == 2015: + if msvc_instance.is_express: + GE_VS2015_unsupported_instances.append((msvc_instance, 'Express')) + elif msvc_instance.is_buildtools: + GE_VS2015_unsupported_instances.append((msvc_instance, 'BuildTools')) else: - GE_VS2015_supported_versions.append(v) + GE_VS2015_supported_instances.append(msvc_instance) else: - LT_VS2015_unsupported_versions.append(v) + LT_VS2015_unsupported_instances.append(msvc_instance) 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 installed_versions) +have_2015 = any(msvc_instance.vs_product_numeric == 2015 for v in installed_instances) -def version_major(version): +def sdk_version_major(version): components = version.split('.') if len(components) >= 2: return components[0] + '.' + components[1][0] @@ -73,27 +70,27 @@ def version_major(version): return components[0] + '.0' return version -def version_major_list(version_list): +def sdk_version_major_list(version_list): versions = [] seen_major = set() for version in version_list: - major = version_major(version) + major = sdk_version_major(version) if major in seen_major: continue versions.append(version) seen_major.add(major) return versions -if GE_VS2015_supported_versions: +if GE_VS2015_supported_instances: - for supported in GE_VS2015_supported_versions: + for supported in GE_VS2015_supported_instances: # 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) # find sdk version for each major SDK - sdk_versions = version_major_list(sdk_versions_def) + sdk_versions = sdk_version_major_list(sdk_versions_def) for sdk_version in sdk_versions: @@ -165,13 +162,13 @@ def version_major_list(version_list): # platform contraints: 8.1 and UWP if '8.1' in sdk_versions: - if supported.msvc_vernum > 14.0: + if supported.vs_product_numeric > 2015: toolset_full_versions = msvc_toolset_versions(supported.msvc_version, full=True, sxs=False) - toolset_versions = version_major_list(toolset_full_versions) + toolset_versions = sdk_version_major_list(toolset_full_versions) # toolset msvc_version != current msvc_version and toolset msvc_version != 14.0 - toolset_candidates = [v for v in toolset_versions if version_major(v) not in (supported.msvc_verstr, '14.0')] + toolset_candidates = [v for v in toolset_versions if sdk_version_major(v) not in (supported.msvc_verstr, '14.0')] toolset_version = toolset_candidates[0] if toolset_candidates else None # sdk version 8.1, UWP, and msvc_verson > VS2015 @@ -202,7 +199,7 @@ def version_major_list(version_list): ) test.must_contain_all(test.stderr(), expect) - if have_140: + if have_2015: # sdk version 8.1, UWP, and msvc_toolset_version > VS2015 test.write('SConstruct', textwrap.dedent( @@ -213,7 +210,7 @@ def version_major_list(version_list): )) test.run(arguments='-Q -s', stdout='') - elif supported.msvc_vernum == 14.0: + elif supported.vs_product_numeric == 2015: # sdk version 8.1, UWP, and msvc_verson == VS2015 test.write('SConstruct', textwrap.dedent( @@ -224,9 +221,9 @@ def version_major_list(version_list): )) test.run(arguments='-Q -s', stdout='') -if GE_VS2015_unsupported_versions: +if GE_VS2015_unsupported_instances: - for unsupported, kind_str in GE_VS2015_unsupported_versions: + for unsupported, kind_str in GE_VS2015_unsupported_instances: # VS2015 Express sdk_version = default_sdk_versions_def[0] if default_sdk_versions_def else '8.1' @@ -252,9 +249,9 @@ def version_major_list(version_list): )) test.run(arguments='-Q -s', stdout='') -if LT_VS2015_unsupported_versions: +if LT_VS2015_unsupported_instances: - for unsupported in LT_VS2015_unsupported_versions: + for unsupported in LT_VS2015_unsupported_instances: # 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_SPECTRE_LIBS.py b/test/MSVC/MSVC_SPECTRE_LIBS.py index 4747e4ba42..27595348b8 100644 --- a/test/MSVC/MSVC_SPECTRE_LIBS.py +++ b/test/MSVC/MSVC_SPECTRE_LIBS.py @@ -30,20 +30,29 @@ import TestSCons import textwrap -from SCons.Tool.MSCommon.vc import get_installed_vcs_components +from SCons.Tool.MSCommon.vc import get_installed_msvc_instances from SCons.Tool.MSCommon import msvc_toolset_versions_spectre test = TestSCons.TestSCons() test.skip_if_not_msvc() -installed_versions = get_installed_vcs_components() -GE_VS2017_versions = [v for v in installed_versions if v.msvc_vernum >= 14.1] -LT_VS2017_versions = [v for v in installed_versions if v.msvc_vernum < 14.1] +installed_instances = get_installed_msvc_instances() +if not installed_instances: + test.skip_test("No MSVC instances, skipping.") -if GE_VS2017_versions: +GE_VS2017_instances = [] +LT_VS2017_instances = [] + +for msvc_instance in installed_instances: + if msvc_instance.vs_product_numeric >= 2017: + GE_VS2017_instances.append(msvc_instance) + else: + LT_VS2017_instances.append(msvc_instance) + +if GE_VS2017_instances: # VS2017 and later for toolset argument - for supported in GE_VS2017_versions: + for supported in GE_VS2017_instances: spectre_toolset_versions = msvc_toolset_versions_spectre(supported.msvc_version) spectre_toolset_version = spectre_toolset_versions[0] if spectre_toolset_versions else None @@ -121,10 +130,10 @@ )) test.run(arguments='-Q -s', stdout='') -if LT_VS2017_versions: +if LT_VS2017_instances: # VS2015 and earlier for toolset argument error - for unsupported in LT_VS2017_versions: + for unsupported in LT_VS2017_instances: # must be VS2017 or later test.write('SConstruct', textwrap.dedent( diff --git a/test/MSVC/MSVC_TOOLSET_VERSION.py b/test/MSVC/MSVC_TOOLSET_VERSION.py index 7c93938019..6d879d9ea5 100644 --- a/test/MSVC/MSVC_TOOLSET_VERSION.py +++ b/test/MSVC/MSVC_TOOLSET_VERSION.py @@ -28,7 +28,7 @@ """ import textwrap -from SCons.Tool.MSCommon.vc import get_installed_vcs_components +from SCons.Tool.MSCommon.vc import get_installed_msvc_instances from SCons.Tool.MSCommon import msvc_toolset_versions import TestSCons @@ -36,17 +36,26 @@ test = TestSCons.TestSCons() test.skip_if_not_msvc() -installed_versions = get_installed_vcs_components() -default_version = installed_versions[0] +installed_instances = get_installed_msvc_instances() +if not installed_instances: + test.skip_test("No MSVC instances, skipping.") -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] +GE_VS2017_instances = [] +LT_VS2017_instances = [] +LE_VS2013_instances = [] -if GE_VS2017_versions: +for msvc_instance in installed_instances: + if msvc_instance.vs_product_numeric >= 2017: + GE_VS2017_instances.append(msvc_instance) + else: + LT_VS2017_instances.append(msvc_instance) + if msvc_instance.vs_product_numeric <= 2013: + LE_VS2013_instances.append(msvc_instance) + +if GE_VS2017_instances: # VS2017 and later for toolset argument - for supported in GE_VS2017_versions: + for supported in GE_VS2017_instances: toolset_full_versions = msvc_toolset_versions(supported.msvc_version, full=True, sxs=False) toolset_full_version = toolset_full_versions[0] if toolset_full_versions else None @@ -193,10 +202,10 @@ ) test.must_contain_all(test.stderr(), expect) -if LT_VS2017_versions: +if LT_VS2017_instances: # VS2015 and earlier for toolset argument error - for unsupported in LT_VS2017_versions: + for unsupported in LT_VS2017_instances: # must be VS2017 or later test.write('SConstruct', textwrap.dedent( @@ -211,10 +220,10 @@ ) test.must_contain_all(test.stderr(), expect) -if LT_VS2015_versions: +if LE_VS2013_instances: # VS2013 and earlier for script argument error - for unsupported in LT_VS2015_versions: + for unsupported in LE_VS2013_instances: # must be VS2015 or later for MSVC_SCRIPT_ARGS test.write('SConstruct', textwrap.dedent( diff --git a/test/MSVC/MSVC_UWP_APP.py b/test/MSVC/MSVC_UWP_APP.py index fd52fd8f5e..726175abba 100644 --- a/test/MSVC/MSVC_UWP_APP.py +++ b/test/MSVC/MSVC_UWP_APP.py @@ -29,34 +29,32 @@ import textwrap import re -from SCons.Tool.MSCommon.vc import get_installed_vcs_components +from SCons.Tool.MSCommon.vc import get_installed_msvc_instances 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() +installed_instances = get_installed_msvc_instances() +if not installed_instances: + test.skip_test("No MSVC instances, skipping.") -GE_VS2015_supported_versions = [] -GE_VS2015_unsupported_versions = [] -LT_VS2015_unsupported_versions = [] +GE_VS2015_supported_instances = [] +GE_VS2015_unsupported_instances = [] +LT_VS2015_unsupported_instances = [] -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')) +for msvc_instance in installed_instances: + if msvc_instance.vs_product_numeric > 2015: + GE_VS2015_supported_instances.append(msvc_instance) + elif msvc_instance.vs_product_numeric == 2015: + if msvc_instance.is_buildtools: + GE_VS2015_unsupported_instances.append((msvc_instance, 'BuildTools')) else: - GE_VS2015_supported_versions.append(v) + GE_VS2015_supported_instances.append(msvc_instance) else: - LT_VS2015_unsupported_versions.append(v) + LT_VS2015_unsupported_instances.append(msvc_instance) # Look for the Store VC Lib paths in the LIBPATH: # [VS install path]\VC\LIB\store[\arch] and @@ -70,7 +68,7 @@ # 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. +# By default, 2015 BuildTools 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. @@ -82,9 +80,9 @@ 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_instance, active, output): - def _check_libpath(msvc, output): + def _check_libpath(msvc_instance, output): is_supported = True outdict = {key.strip(): val.strip() for key, val in [line.split('|') for line in output.splitlines()]} platform = outdict.get('PLATFORM', '') @@ -93,8 +91,8 @@ def _check_libpath(msvc, output): 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): + if msvc_instance.vs_product_numeric == 2015: + if msvc_instance.is_express: for regex in (re_lib_eq2015exp_1,): if regex.search(libpath): n_matches += 1 @@ -110,25 +108,25 @@ def _check_libpath(msvc, output): return n_matches > 0, 'uwp', libpath, is_supported return False, 'uwp', libpath, is_supported - found, kind, libpath, is_supported = _check_libpath(msvc, output) + found, kind, libpath, is_supported = _check_libpath(msvc_instance, output) failmsg = None if active and not found: failmsg = 'msvc version {} {} paths not found in lib path {}'.format( - repr(msvc.msvc_version), repr(kind), repr(libpath) + repr(msvc_instance.msvc_version), repr(kind), repr(libpath) ) elif not active and found: failmsg = 'msvc version {} {} paths found in lib path {}'.format( - repr(msvc.msvc_version), repr(kind), repr(libpath) + repr(msvc_instance.msvc_version), repr(kind), repr(libpath) ) return failmsg, is_supported -if GE_VS2015_supported_versions: +if GE_VS2015_supported_instances: # VS2015 and later for uwp/store argument - for supported in GE_VS2015_supported_versions: + for supported in GE_VS2015_supported_instances: for msvc_uwp_app in (True, '1', False, '0', None): active = msvc_uwp_app in (True, '1') @@ -161,15 +159,16 @@ def _check_libpath(msvc, output): if not test.stderr().strip().startswith("MSVCArgumentError: multiple uwp declarations:"): test.fail_test(message='Expected MSVCArgumentError') - if supported.msvc_verstr == '14.0' and msvc_version_is_express(supported.msvc_version): + if supported.vs_product_numeric == 2015 and supported.is_express: # 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 + from SCons.Tool.MSCommon.vc import find_msvc_instance 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']) + env = Environment(MSVC_VERSION={0}, MSVC_SCRIPT_ARGS='store', tools=['msvc']) + msvc_instance = find_msvc_instance({0}, env) + is_supported, is_target = msvc_instance.is_uwp_target_supported(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','')) @@ -195,10 +194,10 @@ def _check_libpath(msvc, output): if failmsg and is_supported: test.fail_test(message=failmsg) -if GE_VS2015_unsupported_versions: +if GE_VS2015_unsupported_instances: # VS2015 and later for uwp/store error - for unsupported, kind_str in GE_VS2015_unsupported_versions: + for unsupported, kind_str in GE_VS2015_unsupported_instances: for msvc_uwp_app in (True, '1'): @@ -227,10 +226,10 @@ def _check_libpath(msvc, output): if not failmsg: test.fail_test(message='unexpected: store found in libpath') -if LT_VS2015_unsupported_versions: +if LT_VS2015_unsupported_instances: # VS2013 and earlier for uwp/store error - for unsupported in LT_VS2015_unsupported_versions: + for unsupported in LT_VS2015_unsupported_instances: for msvc_uwp_app in (True, '1', False, '0', None): active = msvc_uwp_app in (True, '1') diff --git a/test/MSVC/VSWHERE.py b/test/MSVC/VSWHERE.py index e50e42a26e..64addd052d 100644 --- a/test/MSVC/VSWHERE.py +++ b/test/MSVC/VSWHERE.py @@ -37,8 +37,12 @@ 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: +installed_instances = SCons.Tool.MSCommon.vc.get_installed_msvc_instances() +if not installed_instances: + test.skip_test("No MSVC instances, skipping.") + +_default_instance = installed_instances[0] +if _default_instance.vs_product_numeric < 2017: test.skip_test("no installed msvc requires vswhere.exe; skipping test\n") test.dir_fixture('VSWHERE-fixture') diff --git a/test/MSVC/msvc_cache_force_defaults.py b/test/MSVC/msvc_cache_force_defaults.py index ad67304d56..ffb7fcdb1c 100644 --- a/test/MSVC/msvc_cache_force_defaults.py +++ b/test/MSVC/msvc_cache_force_defaults.py @@ -29,20 +29,19 @@ 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, -) +from SCons.Tool.MSCommon.vc import get_installed_msvc_instances import TestSCons test = TestSCons.TestSCons() test.skip_if_not_msvc() -installed_versions = get_installed_vcs_components() -default_version = installed_versions[0] -if default_version.msvc_vernum < 14.0: - test.skip_test("MSVC version earlier than 14.0, skipping.") +installed_instances = get_installed_msvc_instances() +if not installed_instances: + test.skip_test("No MSVC instances, skipping.") + +default_instance = installed_instances[0] +if default_instance.vs_product_numeric < 2015: + test.skip_test("MSVC product earlier than 2015, skipping.") # VS2015 and later # force SDK version and toolset version as msvc batch file arguments @@ -71,12 +70,12 @@ )) test.run(arguments="-Q -s", status=0, stdout=None) cache_arg = test.stdout().strip() -if default_version.msvc_verstr == '14.0': - if msvc_version_is_express(default_version.msvc_version): +if default_instance.vs_product_numeric == 2015: + if default_instance.is_express: # 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 + elif default_instance.is_buildtools: + # VS2015 BuildTools: target_arch expect = r'^SCRIPT_ARGS: [a-zA-Z0-9_]+$' else: # VS2015: target_arch msvc_sdk_version diff --git a/test/MSVC/no_msvc.py b/test/MSVC/no_msvc.py index 35cce9258d..6b24106b32 100644 --- a/test/MSVC/no_msvc.py +++ b/test/MSVC/no_msvc.py @@ -35,7 +35,7 @@ if sys.platform != 'win32': test.skip_test("Not win32 platform. Skipping test\n") -# test find_vc_pdir_vswhere by removing all other VS's reg keys +# test msvc selection by removing all other VS's reg keys test.file_fixture('no_msvc/no_regs_sconstruct.py', 'SConstruct') test.run(arguments='-Q -s', stdout='') diff --git a/test/fixture/no_msvc/no_msvcs_sconstruct.py b/test/fixture/no_msvc/no_msvcs_sconstruct.py index 1aed9ecc1c..1abd10b143 100644 --- a/test/fixture/no_msvc/no_msvcs_sconstruct.py +++ b/test/fixture/no_msvc/no_msvcs_sconstruct.py @@ -7,15 +7,15 @@ DefaultEnvironment(tools=[]) -def DummyVsWhere(msvc_version, env): - # not testing versions with vswhere, so return none - return None +def DummyVsWhereExecutables(env=None): + # not testing versions with vswhere, so return empty list + return [] -for key in SCons.Tool.MSCommon.vc._VCVER_TO_PRODUCT_DIR: - SCons.Tool.MSCommon.vc._VCVER_TO_PRODUCT_DIR[key] = [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'') +for key in SCons.Tool.MSCommon.vc._VSPRODUCT_REGISTRY_VCDIR: + SCons.Tool.MSCommon.vc._VSPRODUCT_REGISTRY_VCDIR[key] = [ + (False, False, SCons.Util.HKEY_LOCAL_MACHINE, r'') ] -SCons.Tool.MSCommon.vc.find_vc_pdir_vswhere = DummyVsWhere +SCons.Tool.MSCommon.vc._find_vswhere_executables = DummyVsWhereExecutables 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..67856a61be 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,15 +7,15 @@ DefaultEnvironment(tools=[]) -def DummyVsWhere(msvc_version, env): - # not testing versions with vswhere, so return none - return None +def DummyVsWhereExecutables(env=None): + # not testing versions with vswhere, so return empty list + return [] -for key in SCons.Tool.MSCommon.vc._VCVER_TO_PRODUCT_DIR: - SCons.Tool.MSCommon.vc._VCVER_TO_PRODUCT_DIR[key] = [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'') +for key in SCons.Tool.MSCommon.vc._VSPRODUCT_REGISTRY_VCDIR: + SCons.Tool.MSCommon.vc._VSPRODUCT_REGISTRY_VCDIR[key] = [ + (False, False, SCons.Util.HKEY_LOCAL_MACHINE, r'') ] -SCons.Tool.MSCommon.vc.find_vc_pdir_vswhere = DummyVsWhere +SCons.Tool.MSCommon.vc._find_vswhere_executables = DummyVsWhereExecutables 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..9491533ac1 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,15 +7,15 @@ DefaultEnvironment(tools=[]) -def DummyVsWhere(msvc_version, env): - # not testing versions with vswhere, so return none - return None +def DummyVsWhereExecutables(env=None): + # not testing versions with vswhere, so return empty list + return [] -for key in SCons.Tool.MSCommon.vc._VCVER_TO_PRODUCT_DIR: - SCons.Tool.MSCommon.vc._VCVER_TO_PRODUCT_DIR[key] = [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'') +for key in SCons.Tool.MSCommon.vc._VSPRODUCT_REGISTRY_VCDIR: + SCons.Tool.MSCommon.vc._VSPRODUCT_REGISTRY_VCDIR[key] = [ + (False, False, SCons.Util.HKEY_LOCAL_MACHINE, r'') ] -SCons.Tool.MSCommon.vc.find_vc_pdir_vswhere = DummyVsWhere +SCons.Tool.MSCommon.vc._find_vswhere_executables = DummyVsWhereExecutables 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..94a302c901 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,15 +7,15 @@ DefaultEnvironment(tools=[]) -def DummyVsWhere(msvc_version, env): - # not testing versions with vswhere, so return none - return None +def DummyVsWhereExecutables(env=None): + # not testing versions with vswhere, so return empty list + return [] -for key in SCons.Tool.MSCommon.vc._VCVER_TO_PRODUCT_DIR: - SCons.Tool.MSCommon.vc._VCVER_TO_PRODUCT_DIR[key] = [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'') +for key in SCons.Tool.MSCommon.vc._VSPRODUCT_REGISTRY_VCDIR: + SCons.Tool.MSCommon.vc._VSPRODUCT_REGISTRY_VCDIR[key] = [ + (False, False, SCons.Util.HKEY_LOCAL_MACHINE, r'') ] -SCons.Tool.MSCommon.vc.find_vc_pdir_vswhere = DummyVsWhere +SCons.Tool.MSCommon.vc._find_vswhere_executables = DummyVsWhereExecutables 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..f78a656846 100644 --- a/test/fixture/no_msvc/no_msvcs_sconstruct_tools.py +++ b/test/fixture/no_msvc/no_msvcs_sconstruct_tools.py @@ -7,15 +7,15 @@ DefaultEnvironment(tools=[]) -def DummyVsWhere(msvc_version, env): - # not testing versions with vswhere, so return none - return None +def DummyVsWhereExecutables(env=None): + # not testing versions with vswhere, so return empty list + return [] -for key in SCons.Tool.MSCommon.vc._VCVER_TO_PRODUCT_DIR: - SCons.Tool.MSCommon.vc._VCVER_TO_PRODUCT_DIR[key] = [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'') +for key in SCons.Tool.MSCommon.vc._VSPRODUCT_REGISTRY_VCDIR: + SCons.Tool.MSCommon.vc._VSPRODUCT_REGISTRY_VCDIR[key] = [ + (False, False, SCons.Util.HKEY_LOCAL_MACHINE, r'') ] -SCons.Tool.MSCommon.vc.find_vc_pdir_vswhere = DummyVsWhere +SCons.Tool.MSCommon.vc._find_vswhere_executables = DummyVsWhereExecutables 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..a3b25823a6 100644 --- a/test/fixture/no_msvc/no_msvcs_sconstruct_version.py +++ b/test/fixture/no_msvc/no_msvcs_sconstruct_version.py @@ -7,15 +7,15 @@ DefaultEnvironment(tools=[]) -def DummyVsWhere(msvc_version, env): - # not testing versions with vswhere, so return none - return None +def DummyVsWhereExecutables(env=None): + # not testing versions with vswhere, so return empty list + return [] -for key in SCons.Tool.MSCommon.vc._VCVER_TO_PRODUCT_DIR: - SCons.Tool.MSCommon.vc._VCVER_TO_PRODUCT_DIR[key] = [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'') +for key in SCons.Tool.MSCommon.vc._VSPRODUCT_REGISTRY_VCDIR: + SCons.Tool.MSCommon.vc._VSPRODUCT_REGISTRY_VCDIR[key] = [ + (False, False, SCons.Util.HKEY_LOCAL_MACHINE, r'') ] -SCons.Tool.MSCommon.vc.find_vc_pdir_vswhere = DummyVsWhere +SCons.Tool.MSCommon.vc._find_vswhere_executables = DummyVsWhereExecutables SCons.Tool.MSCommon.msvc_set_notfound_policy('error') env = SCons.Environment.Environment(MSVC_VERSION='14.3') diff --git a/test/fixture/no_msvc/no_regs_sconstruct.py b/test/fixture/no_msvc/no_regs_sconstruct.py index 9dcc7a8bd5..31c51a5886 100644 --- a/test/fixture/no_msvc/no_regs_sconstruct.py +++ b/test/fixture/no_msvc/no_regs_sconstruct.py @@ -7,9 +7,9 @@ DefaultEnvironment(tools=[]) -for key in SCons.Tool.MSCommon.vc._VCVER_TO_PRODUCT_DIR: - SCons.Tool.MSCommon.vc._VCVER_TO_PRODUCT_DIR[key] = [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'') +for key in SCons.Tool.MSCommon.vc._VSPRODUCT_REGISTRY_VCDIR: + SCons.Tool.MSCommon.vc._VSPRODUCT_REGISTRY_VCDIR[key] = [ + (False, False, SCons.Util.HKEY_LOCAL_MACHINE, r'') ] env = SCons.Environment.Environment() From 38de91a71e7a0ff5f825e846e1444f217e40146d Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Tue, 10 Oct 2023 13:58:07 -0400 Subject: [PATCH 13/24] Add vs/vc configuration data structures and group related data/functions using internal classes. Internal changes: * Add warning if msvs channel is set after first retrieval and is ignored. * Update test case references for internal vc function that returns vswhere executables list. * Add temporary code to allow msvs channel and vswhere location to be passed on command-line. * Move debug logging for warnings before warn function invocation in case warnings are treated as errors. * Add vs/vc configuration objects for detection to class _VSConfig. * Move all associated vswhere code to class _VSWhere. * Add msvs channel default management class _VSChannel. * Move common vs binary/script detection to class _VSCommon. * Move msvc instance detection to classes _VSDetectVSWhere and _VSDetectRegistry. * Add verification routine for _VSConfig dictionary keys. --- SCons/Tool/MSCommon/MSVC/Config.py | 97 +- SCons/Tool/MSCommon/MSVC/Validate.py | 18 +- SCons/Tool/MSCommon/MSVC/Warnings.py | 3 + SCons/Tool/MSCommon/MSVC/__init__.py | 1 - SCons/Tool/MSCommon/common.py | 4 + SCons/Tool/MSCommon/vc.py | 1918 +++++++++++++---- SCons/Tool/MSCommon/vcTests.py | 119 +- SCons/Tool/MSCommon/vs.py | 6 +- SCons/Tool/msvsTests.py | 4 +- test/fixture/no_msvc/no_msvcs_sconstruct.py | 10 +- ...s_sconstruct_msvc_query_toolset_version.py | 10 +- .../no_msvcs_sconstruct_msvc_sdk_versions.py | 10 +- ..._msvcs_sconstruct_msvc_toolset_versions.py | 10 +- .../no_msvc/no_msvcs_sconstruct_tools.py | 10 +- .../no_msvc/no_msvcs_sconstruct_version.py | 10 +- test/fixture/no_msvc/no_regs_sconstruct.py | 6 +- 16 files changed, 1606 insertions(+), 630 deletions(-) diff --git a/SCons/Tool/MSCommon/MSVC/Config.py b/SCons/Tool/MSCommon/MSVC/Config.py index 78459cf76a..cc88827e69 100644 --- a/SCons/Tool/MSCommon/MSVC/Config.py +++ b/SCons/Tool/MSCommon/MSVC/Config.py @@ -25,10 +25,14 @@ Constants and initialized data structures for Microsoft Visual C/C++. """ +import os + from collections import ( namedtuple, ) +import SCons.Util + from . import Util from .Exceptions import ( @@ -197,9 +201,12 @@ # 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() +VSWHERE_SUPPORTED_PRODUCTS = set() REGISTRY_SUPPORTED_VCVER = set() +REGISTRY_SUPPORTED_PRODUCTS = set() MSVS_PRODUCT_DEFINITION = namedtuple('MSVSProductDefinition', [ 'vs_product', @@ -307,54 +314,16 @@ VSWHERE_SUPPORTED_VCVER.add(vc_version) if vs_express: VSWHERE_SUPPORTED_VCVER.add(vc_version + 'Exp') + VSWHERE_SUPPORTED_PRODUCTS.add(vs_product) elif vs_lookup == 'registry': REGISTRY_SUPPORTED_VCVER.add(vc_version) if vs_express: REGISTRY_SUPPORTED_VCVER.add(vc_version + 'Exp') + REGISTRY_SUPPORTED_PRODUCTS.add(vs_product) MSVS_VERSION_SYMBOLS = list(MSVC_VERSION_EXTERNAL.keys()) MSVC_VERSIONS = list(MSVC_VERSION_SUFFIX.keys()) -# EXPERIMENTAL: msvc version/toolset search lists -# -# VS2017 example: -# -# defaults['14.1'] = ['14.1', '14.1Exp'] -# defaults['14.1Exp'] = ['14.1Exp'] -# -# search['14.1'] = ['14.3', '14.2', '14.1', '14.1Exp'] -# search['14.1Exp'] = ['14.1Exp'] - -MSVC_VERSION_TOOLSET_DEFAULTS_MAP = {} -MSVC_VERSION_TOOLSET_SEARCH_MAP = {} - -# Pass 1: Build defaults lists and setup express versions -for vs_product_def in MSVS_PRODUCT_DEFINITION_LIST: - if not vs_product_def.vc_buildtools_def.vc_istoolset: - continue - version_key = vs_product_def.vc_buildtools_def.vc_version - MSVC_VERSION_TOOLSET_DEFAULTS_MAP[version_key] = [version_key] - MSVC_VERSION_TOOLSET_SEARCH_MAP[version_key] = [] - if vs_product_def.vs_express: - express_key = version_key + 'Exp' - MSVC_VERSION_TOOLSET_DEFAULTS_MAP[version_key].append(express_key) - MSVC_VERSION_TOOLSET_DEFAULTS_MAP[express_key] = [express_key] - MSVC_VERSION_TOOLSET_SEARCH_MAP[express_key] = [express_key] - -# Pass 2: Extend search lists (decreasing version order) -for vs_product_def in MSVS_PRODUCT_DEFINITION_LIST: - if not vs_product_def.vc_buildtools_def.vc_istoolset: - continue - version_key = vs_product_def.vc_buildtools_def.vc_version - for vc_buildtools in vs_product_def.vc_buildtools_all: - toolset_buildtools_def = MSVC_BUILDTOOLS_INTERNAL[vc_buildtools] - toolset_vs_product_def = MSVC_VERSION_INTERNAL[toolset_buildtools_def.vc_version] - buildtools_key = toolset_buildtools_def.vc_version - MSVC_VERSION_TOOLSET_SEARCH_MAP[buildtools_key].extend(MSVC_VERSION_TOOLSET_DEFAULTS_MAP[version_key]) - -# convert string version set to string version list ranked in descending order -MSVC_SDK_VERSIONS = [str(f) for f in sorted([float(s) for s in MSVC_SDK_VERSIONS], reverse=True)] - # MSVS Channel: Release, Preview, Any MSVS_CHANNEL_DEFINITION = namedtuple('MSVSChannel', [ @@ -396,9 +365,8 @@ for symbol in vs_channel_symbols: MSVS_CHANNEL_SYMBOLS.append(symbol) - MSVS_CHANNEL_EXTERNAL[symbol] = vs_channel_def - MSVS_CHANNEL_EXTERNAL[symbol.upper()] = vs_channel_def - MSVS_CHANNEL_EXTERNAL[symbol.lower()] = vs_channel_def + for sym in [symbol, symbol.lower(), symbol.upper()]: + MSVS_CHANNEL_EXTERNAL[sym] = vs_channel_def MSVS_CHANNEL_RELEASE = MSVS_CHANNEL_INTERNAL['Release'] MSVS_CHANNEL_PREVIEW = MSVS_CHANNEL_INTERNAL['Preview'] @@ -453,8 +421,9 @@ MSVS_COMPONENTID_EXTERNAL[vs_component_id] = vs_componentid_def for symbol in vs_component_symbols: - MSVS_COMPONENTID_EXTERNAL[symbol] = vs_componentid_def MSVS_COMPONENTID_SYMBOLS.append(symbol) + for sym in [symbol, symbol.lower(), symbol.upper()]: + MSVS_COMPONENTID_EXTERNAL[sym] = vs_componentid_def MSVS_COMPONENTID_ENTERPRISE = MSVS_COMPONENTID_INTERNAL['Enterprise'] MSVS_COMPONENTID_PROFESSIONAL = MSVS_COMPONENTID_INTERNAL['Professional'] @@ -545,6 +514,46 @@ REGISTRY_COMPONENT_BUILDTOOLS = REGISTRY_COMPONENT_INTERNAL['BT'] REGISTRY_COMPONENT_EXPRESS = REGISTRY_COMPONENT_INTERNAL['Exp'] +# EXPERIMENTAL: msvc version/toolset search lists +# +# VS2017 example: +# +# defaults['14.1'] = ['14.1', '14.1Exp'] +# defaults['14.1Exp'] = ['14.1Exp'] +# +# search['14.1'] = ['14.3', '14.2', '14.1', '14.1Exp'] +# search['14.1Exp'] = ['14.1Exp'] + +MSVC_VERSION_TOOLSET_DEFAULTS_MAP = {} +MSVC_VERSION_TOOLSET_SEARCH_MAP = {} + +# Pass 1: Build defaults lists and setup express versions +for vs_product_def in MSVS_PRODUCT_DEFINITION_LIST: + if not vs_product_def.vc_buildtools_def.vc_istoolset: + continue + version_key = vs_product_def.vc_buildtools_def.vc_version + MSVC_VERSION_TOOLSET_DEFAULTS_MAP[version_key] = [version_key] + MSVC_VERSION_TOOLSET_SEARCH_MAP[version_key] = [] + if vs_product_def.vs_express: + express_key = version_key + 'Exp' + MSVC_VERSION_TOOLSET_DEFAULTS_MAP[version_key].append(express_key) + MSVC_VERSION_TOOLSET_DEFAULTS_MAP[express_key] = [express_key] + MSVC_VERSION_TOOLSET_SEARCH_MAP[express_key] = [express_key] + +# Pass 2: Extend search lists (decreasing version order) +for vs_product_def in MSVS_PRODUCT_DEFINITION_LIST: + if not vs_product_def.vc_buildtools_def.vc_istoolset: + continue + version_key = vs_product_def.vc_buildtools_def.vc_version + for vc_buildtools in vs_product_def.vc_buildtools_all: + toolset_buildtools_def = MSVC_BUILDTOOLS_INTERNAL[vc_buildtools] + toolset_vs_product_def = MSVC_VERSION_INTERNAL[toolset_buildtools_def.vc_version] + buildtools_key = toolset_buildtools_def.vc_version + MSVC_VERSION_TOOLSET_SEARCH_MAP[buildtools_key].extend(MSVC_VERSION_TOOLSET_DEFAULTS_MAP[version_key]) + +# convert string version set to string version list ranked in descending order +MSVC_SDK_VERSIONS = [str(f) for f in sorted([float(s) for s in MSVC_SDK_VERSIONS], reverse=True)] + # internal consistency check (should be last) def verify(): diff --git a/SCons/Tool/MSCommon/MSVC/Validate.py b/SCons/Tool/MSCommon/MSVC/Validate.py index 380bef2701..ad058b673e 100644 --- a/SCons/Tool/MSCommon/MSVC/Validate.py +++ b/SCons/Tool/MSCommon/MSVC/Validate.py @@ -40,7 +40,7 @@ Dispatcher.register_modulename(__name__) -def validate_msvc_version(vc_version, label): +def validate_msvc_version(vc_version, source): vc_version_def = None @@ -51,19 +51,19 @@ def validate_msvc_version(vc_version, label): if not vc_version_def: if not Util.is_version_valid(vc_version): - err_msg = f'Unsupported {label} format: {vc_version!r}' + err_msg = f'Unsupported version format: {vc_version!r} ({source})' debug(err_msg) raise MSVCArgumentError(err_msg) if vc_version not in Config.MSVC_VERSIONS: symbols = Config.MSVC_VERSIONS - err_msg = f'Unrecognized {label} {vc_version!r}\n Valid msvc versions are: {symbols}' + err_msg = f'Unrecognized msvc version {vc_version!r} ({source})\n Valid msvc versions are: {symbols}' debug(err_msg) raise MSVCVersionUnsupported(err_msg) return vc_version_def -def validate_msvs_product(vs_product, label): +def validate_msvs_product(vs_product, source): vs_product_def = None @@ -73,13 +73,13 @@ def validate_msvs_product(vs_product, label): vs_product_def = Config.MSVS_VERSION_EXTERNAL.get(vs_product) if not vs_product_def: symbols = ', '.join(Config.MSVS_VERSION_SYMBOLS) - err_msg = f'Unrecognized {label} {vs_product!r}\n Valid msvs products are: {symbols}' + err_msg = f'Unrecognized msvs product {vs_product!r} ({source})\n Valid msvs products are: {symbols}' debug(err_msg) raise MSVCArgumentError(err_msg) return vs_product_def -def validate_msvs_component(vs_component, label): +def validate_msvs_component(vs_component, source): vs_componentid_def = None @@ -89,13 +89,13 @@ def validate_msvs_component(vs_component, label): vs_componentid_def = Config.MSVS_COMPONENTID_EXTERNAL.get(vs_componentid_def) if not vs_componentid_def: symbols = ', '.join(Config.MSVS_COMPONENTID_SYMBOLS) - err_msg = f'Unrecognized {label} {vs_component!r}\n Valid msvs components are: {symbols}' + err_msg = f'Unrecognized msvs component {vs_component!r} ({source})\n Valid msvs components are: {symbols}' debug(err_msg) raise MSVCArgumentError(err_msg) return vs_componentid_def -def validate_msvs_channel(vs_channel, label): +def validate_msvs_channel(vs_channel, source): vs_channel_def = None @@ -105,7 +105,7 @@ def validate_msvs_channel(vs_channel, label): vs_channel_def = Config.MSVS_CHANNEL_EXTERNAL.get(vs_channel) if not vs_channel_def: symbols = ', '.join(Config.MSVS_CHANNEL_SYMBOLS) - err_msg = f'Unrecognized {label} {vs_channel!r}\n Valid msvs channels are: {symbols}' + err_msg = f'Unrecognized msvs channel {vs_channel!r} ({source})\n Valid msvs channels are: {symbols}' debug(err_msg) raise MSVCArgumentError(err_msg) diff --git a/SCons/Tool/MSCommon/MSVC/Warnings.py b/SCons/Tool/MSCommon/MSVC/Warnings.py index 902de299a6..ac7447c826 100644 --- a/SCons/Tool/MSCommon/MSVC/Warnings.py +++ b/SCons/Tool/MSCommon/MSVC/Warnings.py @@ -33,6 +33,9 @@ class VisualCWarning(SCons.Warnings.WarningOnByDefault): class VSWherePathWarning(VisualCWarning): pass +class MSVSChannelWarning(VisualCWarning): + pass + class MSVCScriptExecutionWarning(VisualCWarning): pass diff --git a/SCons/Tool/MSCommon/MSVC/__init__.py b/SCons/Tool/MSCommon/MSVC/__init__.py index cdf17706ea..1abedda49a 100644 --- a/SCons/Tool/MSCommon/MSVC/__init__.py +++ b/SCons/Tool/MSCommon/MSVC/__init__.py @@ -40,7 +40,6 @@ 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/common.py b/SCons/Tool/MSCommon/common.py index fe2e0a6185..b98c248939 100644 --- a/SCons/Tool/MSCommon/common.py +++ b/SCons/Tool/MSCommon/common.py @@ -148,6 +148,8 @@ def debug_extra(cls=None): extra = None return extra + DEBUG_ENABLED = True + else: def debug(x, *args, **kwargs): return None @@ -155,6 +157,8 @@ def debug(x, *args, **kwargs): def debug_extra(*args, **kwargs): return None + DEBUG_ENABLED = False + # SCONS_CACHE_MSVC_CONFIG is public, and is documented. CONFIG_CACHE = os.environ.get('SCONS_CACHE_MSVC_CONFIG', '') if CONFIG_CACHE in ('1', 'true', 'True'): diff --git a/SCons/Tool/MSCommon/vc.py b/SCons/Tool/MSCommon/vc.py index 4dca6c52f3..63be03a44d 100644 --- a/SCons/Tool/MSCommon/vc.py +++ b/SCons/Tool/MSCommon/vc.py @@ -49,17 +49,17 @@ import json from functools import cmp_to_key +import SCons.Script import SCons.Util import SCons.Warnings from SCons.Tool import find_program_path -import SCons.Script - from . import common from .common import ( CONFIG_CACHE, debug, debug_extra, + DEBUG_ENABLED, AutoInitialize, ) @@ -92,8 +92,80 @@ class MSVCUseSettingsError(MSVCUserError): class BatchFileExecutionError(VisualCException): pass +# command-line options + +class _Options(AutoInitialize): + + debug_extra = None + + options_force = False + options_evar = 'SCONS_ENABLE_MSVC_OPTIONS' + + vswhere_option = '--vswhere' + vswhere_dest = 'vswhere' + vswhere_val = None + + msvs_channel_option = '--msvs-channel' + msvs_channel_dest = 'msvs_channel' + msvs_channel_val = None + + @classmethod + def _setup(cls) -> None: + + enabled = cls.options_force + if not enabled: + val = os.environ.get(cls.options_evar) + enabled = bool(val in MSVC.Config.BOOLEAN_SYMBOLS[True]) + + if enabled: + + SCons.Script.AddOption( + cls.vswhere_option, + nargs=1, + dest=cls.vswhere_dest, + default=None, + type="string", + action="store", + help='Add vswhere executable located at EXEPATH.', + metavar='EXEPATH', + ) + + cls.vswhere_val = SCons.Script.GetOption(cls.vswhere_dest) + + SCons.Script.AddOption( + cls.msvs_channel_option, + nargs=1, + dest=cls.msvs_channel_dest, + default=None, + type="string", + action="store", + help='Set default msvs CHANNEL [Release, Preview, Any].', + metavar='CHANNEL', + ) + + cls.msvs_channel_val = SCons.Script.GetOption(cls.msvs_channel_dest) + + debug( + 'enabled=%s, vswhere=%s, msvs_channel=%s', + enabled, repr(cls.vswhere_val), repr(cls.msvs_channel_val), + extra=cls.debug_extra + ) + + @classmethod + def _initialize(cls) -> None: + cls.debug_extra = debug_extra(cls) + cls._setup() + + @classmethod + def vswhere(cls): + return cls.vswhere_val, cls.vswhere_option + + @classmethod + def msvs_channel(cls): + return cls.msvs_channel_val, cls.msvs_channel_option + # undefined object for dict.get() in case key exists and value is None -UNDEFINED = object() +UNDEFINED = MSVC.Config.UNDEFINED # powershell error sending telemetry for arm32 process on arm64 host (VS2019+): # True: force VSCMD_SKIP_SENDTELEMETRY=1 (if necessary) @@ -290,22 +362,22 @@ def _make_target_host_map(all_hosts, host_all_targets_map): # 14.3 (VS2022) and later -_GE2022_HOST_TARGET_BATCHFILE_CLPATHCOMPS = { +_GE2022_HOST_TARGET_BATCHARG_BATCHFILE_CLPATHCOMPS = { - ('amd64', 'amd64') : ('vcvars64.bat', ('bin', 'Hostx64', 'x64')), - ('amd64', 'x86') : ('vcvarsamd64_x86.bat', ('bin', 'Hostx64', 'x86')), - ('amd64', 'arm') : ('vcvarsamd64_arm.bat', ('bin', 'Hostx64', 'arm')), - ('amd64', 'arm64') : ('vcvarsamd64_arm64.bat', ('bin', 'Hostx64', 'arm64')), + ('amd64', 'amd64') : ('amd64', 'vcvars64.bat', ('bin', 'Hostx64', 'x64')), + ('amd64', 'x86') : ('amd64_x86', 'vcvarsamd64_x86.bat', ('bin', 'Hostx64', 'x86')), + ('amd64', 'arm') : ('amd64_arm', 'vcvarsamd64_arm.bat', ('bin', 'Hostx64', 'arm')), + ('amd64', 'arm64') : ('amd64_arm64', 'vcvarsamd64_arm64.bat', ('bin', 'Hostx64', 'arm64')), - ('x86', 'amd64') : ('vcvarsx86_amd64.bat', ('bin', 'Hostx86', 'x64')), - ('x86', 'x86') : ('vcvars32.bat', ('bin', 'Hostx86', 'x86')), - ('x86', 'arm') : ('vcvarsx86_arm.bat', ('bin', 'Hostx86', 'arm')), - ('x86', 'arm64') : ('vcvarsx86_arm64.bat', ('bin', 'Hostx86', 'arm64')), + ('x86', 'amd64') : ('x86_amd64', 'vcvarsx86_amd64.bat', ('bin', 'Hostx86', 'x64')), + ('x86', 'x86') : ('x86', 'vcvars32.bat', ('bin', 'Hostx86', 'x86')), + ('x86', 'arm') : ('x86_arm', 'vcvarsx86_arm.bat', ('bin', 'Hostx86', 'arm')), + ('x86', 'arm64') : ('x86_arm64', 'vcvarsx86_arm64.bat', ('bin', 'Hostx86', 'arm64')), - ('arm64', 'amd64') : ('vcvarsarm64_amd64.bat', ('bin', 'Hostarm64', 'arm64_amd64')), - ('arm64', 'x86') : ('vcvarsarm64_x86.bat', ('bin', 'Hostarm64', 'arm64_x86')), - ('arm64', 'arm') : ('vcvarsarm64_arm.bat', ('bin', 'Hostarm64', 'arm64_arm')), - ('arm64', 'arm64') : ('vcvarsarm64.bat', ('bin', 'Hostarm64', 'arm64')), + ('arm64', 'amd64') : ('arm64_amd64', 'vcvarsarm64_amd64.bat', ('bin', 'Hostarm64', 'arm64_amd64')), + ('arm64', 'x86') : ('arm64_x86', 'vcvarsarm64_x86.bat', ('bin', 'Hostarm64', 'arm64_x86')), + ('arm64', 'arm') : ('arm64_arm', 'vcvarsarm64_arm.bat', ('bin', 'Hostarm64', 'arm64_arm')), + ('arm64', 'arm64') : ('arm64', 'vcvarsarm64.bat', ('bin', 'Hostarm64', 'arm64')), } @@ -340,22 +412,22 @@ def _make_target_host_map(all_hosts, host_all_targets_map): # 14.2 (VS2019) to 14.1 (VS2017) -_LE2019_HOST_TARGET_BATCHFILE_CLPATHCOMPS = { +_LE2019_HOST_TARGET_BATCHARG_BATCHFILE_CLPATHCOMPS = { - ('amd64', 'amd64') : ('vcvars64.bat', ('bin', 'Hostx64', 'x64')), - ('amd64', 'x86') : ('vcvarsamd64_x86.bat', ('bin', 'Hostx64', 'x86')), - ('amd64', 'arm') : ('vcvarsamd64_arm.bat', ('bin', 'Hostx64', 'arm')), - ('amd64', 'arm64') : ('vcvarsamd64_arm64.bat', ('bin', 'Hostx64', 'arm64')), + ('amd64', 'amd64') : ('amd64', 'vcvars64.bat', ('bin', 'Hostx64', 'x64')), + ('amd64', 'x86') : ('amd64_x86', 'vcvarsamd64_x86.bat', ('bin', 'Hostx64', 'x86')), + ('amd64', 'arm') : ('amd64_arm', 'vcvarsamd64_arm.bat', ('bin', 'Hostx64', 'arm')), + ('amd64', 'arm64') : ('amd64_arm64', 'vcvarsamd64_arm64.bat', ('bin', 'Hostx64', 'arm64')), - ('x86', 'amd64') : ('vcvarsx86_amd64.bat', ('bin', 'Hostx86', 'x64')), - ('x86', 'x86') : ('vcvars32.bat', ('bin', 'Hostx86', 'x86')), - ('x86', 'arm') : ('vcvarsx86_arm.bat', ('bin', 'Hostx86', 'arm')), - ('x86', 'arm64') : ('vcvarsx86_arm64.bat', ('bin', 'Hostx86', 'arm64')), + ('x86', 'amd64') : ('x86_amd64', 'vcvarsx86_amd64.bat', ('bin', 'Hostx86', 'x64')), + ('x86', 'x86') : ('x86', 'vcvars32.bat', ('bin', 'Hostx86', 'x86')), + ('x86', 'arm') : ('x86_arm', 'vcvarsx86_arm.bat', ('bin', 'Hostx86', 'arm')), + ('x86', 'arm64') : ('x86_arm64', 'vcvarsx86_arm64.bat', ('bin', 'Hostx86', 'arm64')), - ('arm64', 'amd64') : ('vcvars64.bat', ('bin', 'Hostx64', 'x64')), - ('arm64', 'x86') : ('vcvarsamd64_x86.bat', ('bin', 'Hostx64', 'x86')), - ('arm64', 'arm') : ('vcvarsamd64_arm.bat', ('bin', 'Hostx64', 'arm')), - ('arm64', 'arm64') : ('vcvarsamd64_arm64.bat', ('bin', 'Hostx64', 'arm64')), + ('arm64', 'amd64') : ('amd64', 'vcvars64.bat', ('bin', 'Hostx64', 'x64')), + ('arm64', 'x86') : ('amd64_x86', 'vcvarsamd64_x86.bat', ('bin', 'Hostx64', 'x86')), + ('arm64', 'arm') : ('amd64_arm', 'vcvarsamd64_arm.bat', ('bin', 'Hostx64', 'arm')), + ('arm64', 'arm64') : ('amd64_arm64', 'vcvarsamd64_arm64.bat', ('bin', 'Hostx64', 'arm64')), } @@ -498,8 +570,13 @@ def _make_target_host_map(all_hosts, host_all_targets_map): # 7.1 (VS2003) and earlier -# For 7.1 (VS2003) and earlier, there are only x86 targets and the batch files -# take no arguments. +_LE2003_HOST_TARGET_BATCHARG_BATCHFILE_CLPATHCOMPS = { + + ('amd64', 'x86') : ('x86', 'vcvars32.bat', ('bin', )), + ('x86', 'x86') : ('x86', 'vcvars32.bat', ('bin', )), + ('arm64', 'x86') : ('x86', 'vcvars32.bat', ('bin', )), + +} _LE2003_HOST_TARGET_CFG = _host_target_config_factory( @@ -529,8 +606,6 @@ 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' @@ -651,8 +726,8 @@ def get_host_target(env, msvc_version, all_host_targets: bool=False): warn_msg = "unsupported host, target combination ({}, {}) for MSVC version {}".format( repr(host_platform), repr(target_platform), msvc_version ) - SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, warn_msg) debug(warn_msg) + SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, warn_msg) else: @@ -734,111 +809,338 @@ def _skip_sendtelemetry(env): "7.0", "6.0"] -# VS2017 and later: use a single vswhere json query to find all installations -# VS2015 and earlier: configure registry queries to probe for installed VC editions -_VSPRODUCT_REGISTRY_VCDIR = { - # vs_product: [(is_vsroot, is_vcforpython, hkroot, key), ...] - '2015': [ - (False, False, SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\14.0\Setup\VC\ProductDir'), - (True, False, SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\WDExpress\14.0\Setup\VS\ProductDir'), # vs root - (False, False, SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\14.0\Setup\VC\ProductDir'), # not populated? - ], - '2013': [ - (False, False, SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\12.0\Setup\VC\ProductDir'), - (False, False, SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\12.0\Setup\VC\ProductDir'), - ], - '2012': [ - (False, False, SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\11.0\Setup\VC\ProductDir'), - (False, False, SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\11.0\Setup\VC\ProductDir'), - ], - '2010': [ - (False, False, SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\10.0\Setup\VC\ProductDir'), - (False, False, SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\10.0\Setup\VC\ProductDir'), - ], - '2008': [ - (True, True, SCons.Util.HKEY_CURRENT_USER, r'Microsoft\DevDiv\VCForPython\9.0\InstallDir',), # vs root - (True, True, SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\DevDiv\VCForPython\9.0\InstallDir',), # vs root - (False, False, SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\9.0\Setup\VC\ProductDir',), - (False, False, SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\9.0\Setup\VC\ProductDir'), - ], - '2005': [ - (False, False, SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\8.0\Setup\VC\ProductDir'), - (False, False, SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\8.0\Setup\VC\ProductDir'), - ], - '2003': [ - (False, False, SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\7.1\Setup\VC\ProductDir'), - ], - '2002': [ - (False, False, SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\7.0\Setup\VC\ProductDir'), - ], - '1998': [ - (False, False, SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\6.0\Setup\Microsoft Visual C++\ProductDir'), - ] -} +class _VSConfig: -# detect ide binaries + # MSVS IDE binaries -VS2022_VS2002_DEV = ( - MSVC.Kind.IDE_PROGRAM_DEVENV_COM, # devenv.com -) + BITFIELD_DEVELOP = 0b_1000 + BITFIELD_EXPRESS = 0b_0100 + BITFIELD_EXPRESS_WIN = 0b_0010 + BITFIELD_EXPRESS_WEB = 0b_0001 -VS1998_DEV = ( - MSVC.Kind.IDE_PROGRAM_MSDEV_COM, # MSDEV.COM -) + EXECUTABLE_MASK = BITFIELD_DEVELOP | BITFIELD_EXPRESS -VS2017_EXP = ( - MSVC.Kind.IDE_PROGRAM_WDEXPRESS_EXE, # WDExpress.exe -) + VSProgram = namedtuple("VSProgram", [ + 'program', + 'bitfield' + ]) -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] -) + DEVENV_COM = VSProgram(program='devenv.com', bitfield=BITFIELD_DEVELOP) + MSDEV_COM = VSProgram(program='msdev.com', bitfield=BITFIELD_DEVELOP) -VS2010_VS2005_EXP = ( - MSVC.Kind.IDE_PROGRAM_VCEXPRESS_EXE, -) + WDEXPRESS_EXE = VSProgram(program='WDExpress.exe', bitfield=BITFIELD_EXPRESS) + VCEXPRESS_EXE = VSProgram(program='VCExpress.exe', bitfield=BITFIELD_EXPRESS) -# detect productdir kind + VSWINEXPRESS_EXE = VSProgram(program='VSWinExpress.exe', bitfield=BITFIELD_EXPRESS_WIN) + VWDEXPRESS_EXE = VSProgram(program='VWDExpress.exe', bitfield=BITFIELD_EXPRESS_WEB) -_VCDetectConfig = MSVC.Kind.VCDetectConfig + # MSVS IDE binary -_VSPRODUCT_KIND_DETECT = { + VSBinaryConfig = namedtuple("VSBinaryConfig", [ + 'pathcomp', # vs root -> ide dir + 'programs', # ide binaries + ]) - # 'product': (relpath from vcroot to vsroot, path from vsroot to ide binaries, ide binaries) + # MSVS batch file - '2022': _VCDetectConfig(root=os.pardir, path=r'Common7\IDE', programs=VS2022_VS2002_DEV), # 14.3 - '2019': _VCDetectConfig(root=os.pardir, path=r'Common7\IDE', programs=VS2022_VS2002_DEV), # 14.2 + VSBatchConfig = namedtuple("VSBatchConfig", [ + 'pathcomp', # vs root -> batch dir + 'script', # vs script + ]) - '2017': _VCDetectConfig(root=os.pardir, path=r'Common7\IDE', programs=VS2022_VS2002_DEV + VS2017_EXP), # 14.1 + # detect configuration - '2015': _VCDetectConfig(root=os.pardir, path=r'Common7\IDE', programs=VS2022_VS2002_DEV + VS2015_VS2012_EXP), # 14.0 - '2013': _VCDetectConfig(root=os.pardir, path=r'Common7\IDE', programs=VS2022_VS2002_DEV + VS2015_VS2012_EXP), # 12.0 - '2012': _VCDetectConfig(root=os.pardir, path=r'Common7\IDE', programs=VS2022_VS2002_DEV + VS2015_VS2012_EXP), # 11.0 + DetectConfig = namedtuple("DetectConfig", [ + 'vs_cfg', # vs configuration + 'vc_cfg', # vc configuration + ]) - '2010': _VCDetectConfig(root=os.pardir, path=r'Common7\IDE', programs=VS2022_VS2002_DEV + VS2010_VS2005_EXP), # 10.0 - '2008': _VCDetectConfig(root=os.pardir, path=r'Common7\IDE', programs=VS2022_VS2002_DEV + VS2010_VS2005_EXP), # 9.0 - '2005': _VCDetectConfig(root=os.pardir, path=r'Common7\IDE', programs=VS2022_VS2002_DEV + VS2010_VS2005_EXP), # 8.0 + VSDetectConfig = namedtuple("VSDetectConfig", [ + 'root', # relative path vc dir -> vs root + 'vs_binary_cfg', # vs executable + 'vs_batch_cfg', # vs batch file + ]) - '2003': _VCDetectConfig(root=os.pardir, path=r'Common7\IDE', programs=VS2022_VS2002_DEV), # 7.1 - '2002': _VCDetectConfig(root=os.pardir, path=r'Common7\IDE', programs=VS2022_VS2002_DEV), # 7.0 + VCDetectConfigRegistry = namedtuple("VCDetectConfigRegistry", [ + 'regkeys', + ]) - '1998': _VCDetectConfig(root=os.pardir, path=r'Common\MSDev98\Bin', programs=VS1998_DEV), # 6.0 -} + _VCRegistryKey = namedtuple("_VCRegistryKey", [ + 'hkroot', + 'key', + 'is_vsroot', + 'is_vcforpython', + ]) -def msvc_version_to_maj_min(msvc_version): - msvc_version_numeric = get_msvc_version_numeric(msvc_version) + class VCRegKey(_VCRegistryKey): - t = msvc_version_numeric.split(".") - if not len(t) == 2: - raise ValueError("Unrecognized version %s (%s)" % (msvc_version,msvc_version_numeric)) - try: - maj = int(t[0]) - min = int(t[1]) - return maj, min - except ValueError: - raise ValueError("Unrecognized version %s (%s)" % (msvc_version,msvc_version_numeric)) from None + @classmethod + def factory( + cls, *, + key, + hkroot=SCons.Util.HKEY_LOCAL_MACHINE, + is_vsroot=False, + is_vcforpython=False, + ): + + regkey = cls( + hkroot=hkroot, + key=key, + is_vsroot=is_vsroot, + is_vcforpython=is_vcforpython, + ) + + return regkey + + _regkey = VCRegKey.factory + + # vs detect configuration: vswhere + + DETECT_VSWHERE = { + + '2022': DetectConfig( # 14.3 + vs_cfg=VSDetectConfig( + root=os.pardir, + vs_binary_cfg=VSBinaryConfig( + pathcomp=r'Common7\IDE', + programs=[DEVENV_COM], + ), + vs_batch_cfg=VSBatchConfig( + pathcomp=r'Common7\Tools', + script='VsDevCmd.bat', + ), + ), + vc_cfg=None, + ), + + '2019': DetectConfig( # 14.2 + vs_cfg=VSDetectConfig( + root=os.pardir, + vs_binary_cfg=VSBinaryConfig( + pathcomp=r'Common7\IDE', + programs=[DEVENV_COM], + ), + vs_batch_cfg=VSBatchConfig( + pathcomp=r'Common7\Tools', + script='VsDevCmd.bat', + ), + ), + vc_cfg=None, + ), + + '2017': DetectConfig( # 14.1 + vs_cfg=VSDetectConfig( + root=os.pardir, + vs_binary_cfg=VSBinaryConfig( + pathcomp=r'Common7\IDE', + programs=[DEVENV_COM, WDEXPRESS_EXE], + ), + vs_batch_cfg=VSBatchConfig( + pathcomp=r'Common7\Tools', + script='VsDevCmd.bat', + ), + ), + vc_cfg=None, + ), + + } + + # vs detect configuration: registry + + DETECT_REGISTRY = { + + '2015': DetectConfig( # 14.0 + vs_cfg=VSDetectConfig( + root=os.pardir, + vs_binary_cfg=VSBinaryConfig( + pathcomp=r'Common7\IDE', + programs=[DEVENV_COM, WDEXPRESS_EXE, VSWINEXPRESS_EXE, VWDEXPRESS_EXE], + ), + vs_batch_cfg=VSBatchConfig( + pathcomp=r'Common7\Tools', + # VsDevCmd.bat and vsvars32.bat appear to be different + script='vsvars32.bat', + ), + ), + vc_cfg=VCDetectConfigRegistry( + regkeys=[ + _regkey(key=r'Microsoft\VisualStudio\14.0\Setup\VC\ProductDir'), + _regkey(key=r'Microsoft\WDExpress\14.0\Setup\VS\ProductDir', is_vsroot=True), + _regkey(key=r'Microsoft\VCExpress\14.0\Setup\VC\ProductDir'), # not populated? + ], + ), + ), + + '2013': DetectConfig( # 12.0 + vs_cfg=VSDetectConfig( + root=os.pardir, + vs_binary_cfg=VSBinaryConfig( + pathcomp=r'Common7\IDE', + programs=[DEVENV_COM, WDEXPRESS_EXE, VSWINEXPRESS_EXE, VWDEXPRESS_EXE], + ), + vs_batch_cfg=VSBatchConfig( + pathcomp=r'Common7\Tools', + # VsDevCmd.bat and vsvars32.bat appear to be different + script='vsvars32.bat', + ), + ), + vc_cfg=VCDetectConfigRegistry( + regkeys=[ + _regkey(key=r'Microsoft\VisualStudio\12.0\Setup\VC\ProductDir'), + _regkey(key=r'Microsoft\VCExpress\12.0\Setup\VC\ProductDir'), + ] + ), + ), + + '2012': DetectConfig( # 11.0 + vs_cfg=VSDetectConfig( + root=os.pardir, + vs_binary_cfg=VSBinaryConfig( + pathcomp=r'Common7\IDE', + programs=[DEVENV_COM, WDEXPRESS_EXE, VSWINEXPRESS_EXE, VWDEXPRESS_EXE], + ), + vs_batch_cfg=VSBatchConfig( + pathcomp=r'Common7\Tools', + # VsDevCmd.bat and vsvars32.bat appear to be identical + script='vsvars32.bat', + ), + ), + vc_cfg=VCDetectConfigRegistry( + regkeys=[ + _regkey(key=r'Microsoft\VisualStudio\11.0\Setup\VC\ProductDir'), + _regkey(key=r'Microsoft\VCExpress\11.0\Setup\VC\ProductDir'), + ] + ), + ), + + '2010': DetectConfig( # 10.0 + vs_cfg=VSDetectConfig( + root=os.pardir, + vs_binary_cfg=VSBinaryConfig( + pathcomp=r'Common7\IDE', + programs=[DEVENV_COM, VCEXPRESS_EXE], + ), + vs_batch_cfg=VSBatchConfig( + pathcomp=r'Common7\Tools', + script='vsvars32.bat', + ), + ), + vc_cfg=VCDetectConfigRegistry( + regkeys=[ + _regkey(key=r'Microsoft\VisualStudio\10.0\Setup\VC\ProductDir'), + _regkey(key=r'Microsoft\VCExpress\10.0\Setup\VC\ProductDir'), + ] + ), + ), + + '2008': DetectConfig( # 9.0 + vs_cfg=VSDetectConfig( + root=os.pardir, + vs_binary_cfg=VSBinaryConfig( + pathcomp=r'Common7\IDE', + programs=[DEVENV_COM, VCEXPRESS_EXE], + ), + vs_batch_cfg=VSBatchConfig( + pathcomp=r'Common7\Tools', + script='vsvars32.bat', + ), + ), + vc_cfg=VCDetectConfigRegistry( + regkeys=[ + _regkey( + hkroot=SCons.Util.HKEY_CURRENT_USER, + key=r'Microsoft\DevDiv\VCForPython\9.0\InstallDir', + is_vsroot=True, is_vcforpython=True, + ), + _regkey( + key=r'Microsoft\DevDiv\VCForPython\9.0\InstallDir', + is_vsroot=True, is_vcforpython=True + ), + _regkey(key=r'Microsoft\VisualStudio\9.0\Setup\VC\ProductDir'), + _regkey(key=r'Microsoft\VCExpress\9.0\Setup\VC\ProductDir'), + ] + ), + ), + + '2005': DetectConfig( # 8.0 + vs_cfg=VSDetectConfig( + root=os.pardir, + vs_binary_cfg=VSBinaryConfig( + pathcomp=r'Common7\IDE', + programs=[DEVENV_COM, VCEXPRESS_EXE], + ), + vs_batch_cfg=VSBatchConfig( + pathcomp=r'Common7\Tools', + script='vsvars32.bat', + ), + ), + vc_cfg=VCDetectConfigRegistry( + regkeys=[ + _regkey(key=r'Microsoft\VisualStudio\8.0\Setup\VC\ProductDir'), + _regkey(key=r'Microsoft\VCExpress\8.0\Setup\VC\ProductDir'), + ] + ), + ), + + '2003': DetectConfig( # 7.1 + vs_cfg=VSDetectConfig( + root=os.pardir, + vs_binary_cfg=VSBinaryConfig( + pathcomp=r'Common7\IDE', + programs=[DEVENV_COM], + ), + vs_batch_cfg=VSBatchConfig( + pathcomp=r'Common7\Tools', + script='vsvars32.bat', + ), + ), + vc_cfg=VCDetectConfigRegistry( + regkeys=[ + _regkey(key=r'Microsoft\VisualStudio\7.1\Setup\VC\ProductDir'), + ] + ), + ), + + '2002': DetectConfig( # 7.0 + vs_cfg=VSDetectConfig( + root=os.pardir, + vs_binary_cfg=VSBinaryConfig( + pathcomp=r'Common7\IDE', + programs=[DEVENV_COM], + ), + vs_batch_cfg=VSBatchConfig( + pathcomp=r'Common7\Tools', + script='vsvars32.bat', + ), + ), + vc_cfg=VCDetectConfigRegistry( + regkeys=[ + _regkey(key=r'Microsoft\VisualStudio\7.0\Setup\VC\ProductDir'), + ] + ), + ), + + '1998': DetectConfig( # 6.0 + vs_cfg=VSDetectConfig( + root=os.pardir, + vs_binary_cfg=VSBinaryConfig( + pathcomp=r'Common\MSDev98\Bin', + programs=[MSDEV_COM], + ), + vs_batch_cfg=VSBatchConfig( + # There is no vsvars32.bat batch file + pathcomp=r'VC98\bin', + script='vcvars32.bat', + ), + ), + vc_cfg=VCDetectConfigRegistry( + regkeys=[ + _regkey(key=r'Microsoft\VisualStudio\6.0\Setup\Microsoft Visual C++\ProductDir'), + ] + ), + ), + + } # normalized paths @@ -866,20 +1168,25 @@ def path_exists(pval): # debug('exists=%s, pval=%s', rval, repr(pval)) return rval -# initialize vswhere paths - -VSWhereExecutable = namedtuple('VSWhereExecutable', [ - 'path', - 'norm', -]) +def msvc_version_to_maj_min(msvc_version): + msvc_version_numeric = get_msvc_version_numeric(msvc_version) -# 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. + t = msvc_version_numeric.split(".") + if not len(t) == 2: + raise ValueError("Unrecognized version %s (%s)" % (msvc_version,msvc_version_numeric)) + try: + maj = int(t[0]) + min = int(t[1]) + return maj, min + except ValueError: + raise ValueError("Unrecognized version %s (%s)" % (msvc_version,msvc_version_numeric)) from None VSWHERE_PATHS = [ os.path.join(p,'vswhere.exe') for p in [ + # 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"), os.path.expandvars(r"%ChocolateyInstall%\bin"), @@ -890,136 +1197,281 @@ def path_exists(pval): if not p.startswith('%') ] -VSWHERE_EXECS_EXIST = [ - VSWhereExecutable(path=p, norm=normalize_path(p)) - for p in VSWHERE_PATHS - if path_exists(p) -] +def msvc_find_vswhere(env=None): + """ Find the location of vswhere """ + # NB: this gets called from testsuite on non-Windows platforms. + # Whether that makes sense or not, don't break it for those. + vswhere_env = env.subst('$VSWHERE') if env and 'VSWHERE' in env else None + vswhere_execs = _VSWhere.find_executables(vswhere_env) + if vswhere_execs: + vswhere_path = vswhere_execs[0].path + else: + vswhere_path = None + debug('vswhere_path=%s', vswhere_path) + return vswhere_path -debug('VSWHERE_EXECS_EXIST=%s', VSWHERE_EXECS_EXIST) +class _VSWhere(AutoInitialize): -# user-specified vswhere executables + # TODO(JCB): review reset -_cache_user_vswhere_paths = {} + # priority: env > cmdline > (user, initial, user) -def _vswhere_user_path(pval, source): - global _cache_user_vswhere_paths + VSWHERE_EXE = 'vswhere.exe' - rval = _cache_user_vswhere_paths.get(pval, UNDEFINED) - if rval != UNDEFINED: - debug('vswhere_exec=%s', repr(rval)) - return rval + VSWhereExecutable = namedtuple('VSWhereExecutable', [ + 'path', + 'norm', + ]) - vswhere_exec = None - if pval: + debug_extra = None - if not path_exists(pval): + vswhere_cmdline = None + vswhere_executables = [] - warn_msg = f'vswhere executable path not found: {source}={pval!r}' - SCons.Warnings.warn(MSVC.Warnings.VSWherePathWarning, warn_msg) - debug(warn_msg) + @classmethod + def _cmdline(cls): + vswhere, option = _Options.vswhere() + if vswhere: + vswhere_exec = cls._user_path(vswhere, option) + if vswhere_exec: + cls.vswhere_cmdline = vswhere_exec + debug('vswhere_cmdline=%s', cls.vswhere_cmdline, extra=cls.debug_extra) - else: + @classmethod + def _setup(cls) -> None: + for pval in VSWHERE_PATHS: + if not path_exists(pval): + continue + vswhere_exec = cls.VSWhereExecutable(path=pval, norm=normalize_path(pval)) + cls.vswhere_executables.append(vswhere_exec) + debug('vswhere_executables=%s', cls.vswhere_executables, extra=cls.debug_extra) + cls._cmdline() + + @classmethod + def _initialize(cls) -> None: + cls.debug_extra = debug_extra(cls) + cls.reset() + cls._setup() + + @classmethod + def reset(cls) -> None: + cls._cache_user_vswhere_paths = {} + + # validate user-specified vswhere executable + + @classmethod + def _user_path(cls, pval, source): - norm = normalize_path(pval) - tail = os.path.split(norm)[-1] - if tail != _VSWHERE_EXE: + rval = cls._cache_user_vswhere_paths.get(pval, UNDEFINED) + if rval != UNDEFINED: + debug('vswhere_exec=%s', repr(rval), extra=cls.debug_extra) + return rval + + vswhere_exec = None + if pval: - warn_msg = f'unsupported vswhere executable (expected {_VSWHERE_EXE!r}, found {tail!r}): {source}={pval!r}' + if not path_exists(pval): + + warn_msg = f'vswhere executable path not found: {pval!r} ({source})' + debug(warn_msg, extra=cls.debug_extra) SCons.Warnings.warn(MSVC.Warnings.VSWherePathWarning, warn_msg) - debug(warn_msg) else: - vswhere_exec = VSWhereExecutable(path=pval, norm=norm) - debug('vswhere_exec=%s', repr(vswhere_exec)) + norm = normalize_path(pval) + tail = os.path.split(norm)[-1] + if tail != cls.VSWHERE_EXE: - _cache_user_vswhere_paths[pval] = vswhere_exec + warn_msg = f'unsupported vswhere executable (expected {cls.VSWHERE_EXE!r}, found {tail!r}): {pval!r} ({source})' + debug(warn_msg, extra=cls.debug_extra) + SCons.Warnings.warn(MSVC.Warnings.VSWherePathWarning, warn_msg) - return vswhere_exec + else: -# register user vswhere executable location(s) + vswhere_exec = cls.VSWhereExecutable(path=pval, norm=norm) + debug('vswhere_exec=%s', repr(vswhere_exec), extra=cls.debug_extra) -def vswhere_push_location(string_or_list, front=False) -> None: - global VSWHERE_EXECS_EXIST + cls._cache_user_vswhere_paths[pval] = vswhere_exec - path_list = SCons.Util.flatten(string_or_list) - if path_list: + return vswhere_exec + + # user-specified vswhere executables - user_execs = [] + @classmethod + def user_pathlist(cls, path_list, front, source) -> None: + + user_executables = [] for pval in path_list: - vswhere_exec = _vswhere_user_path(pval, 'vswhere_push_location') + vswhere_exec = cls._user_path(pval, source) if vswhere_exec: - user_execs.append(vswhere_exec) + user_executables.append(vswhere_exec) - if user_execs: + if user_executables: if front: - all_execs = user_execs + VSWHERE_EXECS_EXIST + all_executables = user_executables + cls.vswhere_executables else: - all_execs = VSWHERE_EXECS_EXIST + user_execs + all_executables = cls.vswhere_executables + user_executables seen = set() - unique_execs = [] - for vswhere_exec in all_execs: + unique_executables = [] + for vswhere_exec in all_executables: if vswhere_exec.norm in seen: continue seen.add(vswhere_exec.norm) - unique_execs.append(vswhere_exec) + unique_executables.append(vswhere_exec) - VSWHERE_EXECS_EXIST = unique_execs - debug('VSWHERE_EXECS_EXIST=%s', VSWHERE_EXECS_EXIST) + cls.vswhere_executables = unique_executables + debug('vswhere_executables=%s', cls.vswhere_executables, extra=cls.debug_extra) -# all vswhere executables + # all vswhere executables -# TODO(JCB): command-line argument: --vswhere=EXEPATH + @classmethod + def find_executables(cls, vswhere_env=None): -def _find_vswhere_executables(vswhere_env=None): + vswhere_executables = [] - vswhere_execs = [] + # env['VSWHERE'] path + if vswhere_env: + vswhere_exec = cls._user_path(vswhere_env, "env['VSWHERE']") + if vswhere_exec: + vswhere_executables.append(vswhere_exec) - # env['VSWHERE'] path - if vswhere_env: - vswhere_exec = _vswhere_user_path(vswhere_env, "env['VSWHERE']") - if vswhere_exec: - vswhere_execs.append(vswhere_exec) + # --vswhere=EXEPATH + if cls.vswhere_cmdline: + vswhere_executables.append(cls.vswhere_cmdline) - # default paths and user paths (vswhere_push_location) - if VSWHERE_EXECS_EXIST: - vswhere_execs.extend(VSWHERE_EXECS_EXIST) + # default paths and user paths (vswhere_push_location) + if cls.vswhere_executables: + vswhere_executables.extend(cls.vswhere_executables) - return vswhere_execs + return vswhere_executables -def find_vswhere_executables(env=None): - vswhere_env = env.subst('$VSWHERE') if env and 'VSWHERE' in env else None - vswhere_execs = _find_vswhere_executables(vswhere_env) - return vswhere_execs +# register user vswhere executable location(s) -def msvc_find_vswhere(env=None): - """ Find the location of vswhere """ - # NB: this gets called from testsuite on non-Windows platforms. - # Whether that makes sense or not, don't break it for those. - vswhere_execs = find_vswhere_executables(env) - if vswhere_execs: - vswhere_path = vswhere_execs[0].path - else: - vswhere_path = None - debug('vswhere_path=%s', vswhere_path) - return vswhere_path +def vswhere_push_location(string_or_list, front=False) -> None: + # TODO(JCB): need docstring + path_list = SCons.Util.flatten(string_or_list) + if path_list: + _VSWhere.user_pathlist(path_list, front, 'vswhere_push_location') + +class _VSChannel(AutoInitialize): + + # TODO(JCB): review reset + + # priority: cmdline > user > initial + + debug_extra = None + + vs_channel_initial = None + vs_channel_cmdline = None + + # reset + + vs_channel_user = None + vs_channel_def = None + vs_channel_retrieved = False + + @classmethod + def _initial(cls) -> None: + cls.vs_channel_initial = MSVC.Config.MSVS_CHANNEL_RELEASE + cls.vs_channel_def = cls.vs_channel_initial + debug('vs_channel_initial=%s', + cls.vs_channel_initial.vs_channel_id, + extra=cls.debug_extra + ) + + @classmethod + def _cmdline(cls) -> None: + channel, option = _Options.msvs_channel() + if channel: + vs_channel_def = MSVC.Validate.validate_msvs_channel( + channel, option + ) + if vs_channel_def: + cls.vs_channel_cmdline = vs_channel_def + cls.vs_channel_def = cls.vs_channel_cmdline + debug('vs_channel_cmdline=%s', + cls.vs_channel_cmdline.vs_channel_id, + extra=cls.debug_extra + ) + + @classmethod + def _setup(cls) -> None: + cls._initial() + cls._cmdline() + + @classmethod + def _initialize(cls) -> None: + cls.debug_extra = debug_extra(cls) + cls._setup() + + @classmethod + def reset(cls) -> None: + cls.vs_channel_user = None + cls.vs_channel_def = None + cls.vs_channel_retrieved = False + cls._setup() + + @classmethod + def get_default_channel(cls): + if not cls.vs_channel_retrieved: + cls.vs_channel_retrieved = True + if cls.vs_channel_cmdline: + cls.vs_channel_def = cls.vs_channel_cmdline + elif cls.vs_channel_user: + cls.vs_channel_def = cls.vs_channel_user + else: + cls.vs_channel_def = cls.vs_channel_initial + debug( + 'vs_channel=%s', + cls.vs_channel_def.vs_channel_id, + extra=cls.debug_extra + ) + return cls.vs_channel_def + + @classmethod + def set_default_channel(cls, msvs_channel, source) -> bool: + + rval = False + + if cls.vs_channel_retrieved: + + warn_msg = f'msvs channel {msvs_channel!r} ({source}) ignored: must be set before first use.' + debug(warn_msg, extra=cls.debug_extra) + SCons.Warnings.warn(MSVC.Warnings.MSVSChannelWarning, warn_msg) -# TODO(JCB): command-line argument: --msvs-channel=Release|Preview|Any -_default_msvs_channel_def = MSVC.Config.MSVS_CHANNEL_RELEASE -debug('default msvs_channel=%s', _default_msvs_channel_def.vs_channel_id) + else: + + vs_channel_def = MSVC.Validate.validate_msvs_channel(msvs_channel, source) + if vs_channel_def: + + cls.vs_channel_user = vs_channel_def + debug( + 'vs_channel_user=%s', + cls.vs_channel_user.vs_channel_id, + extra=cls.debug_extra + ) + + if not cls.vs_channel_cmdline: + # priority: cmdline > user > initial + cls.vs_channel_def = cls.vs_channel_user + debug( + 'vs_channel=%s', + cls.vs_channel_def.vs_channel_id, + extra=cls.debug_extra + ) + rval = True -_default_msvs_channel_retrieved = False + debug( + 'vs_channel=%s', + cls.vs_channel_def.vs_channel_id, + extra=cls.debug_extra + ) -def _msvs_channel_default(): - global _default_msvs_channel_def - global _default_msvs_channel_retrieved - _default_msvs_channel_retrieved = True - return _default_msvs_channel_def + return rval -def msvs_set_channel_default(msvs_channel): +def msvs_set_channel_default(msvs_channel) -> bool: """Set the default msvs channel. Args: @@ -1034,20 +1486,7 @@ def msvs_set_channel_default(msvs_channel): False if the default msvs channel was not accepted. """ - global _default_msvs_channel_retrieved - global _default_msvs_channel_def - rval = False - if _default_msvs_channel_retrieved: - # TODO(JCB): warn msvs_channel ignored (must be set earlier) - pass - else: - vs_channel_def = MSVC.Validate.validate_msvs_channel( - msvs_channel, 'msvs_channel_set_default' - ) - if vs_channel_def: - _default_msvs_channel_def = vs_channel_def - debug('default msvs_channel=%s', _default_msvs_channel_def.vs_channel_id) - rval = True + rval = _VSChannel.set_default_channel(msvs_channel, 'msvc_set_channel_default') return rval def msvs_get_channel_default(): @@ -1057,10 +1496,15 @@ def msvs_get_channel_default(): str: default msvs channel """ - global _default_msvs_channel_def - return _default_msvs_channel_def.vs_channel_id + return _VSChannel.vs_channel_def.vs_channel_id + +class _VSKeys(AutoInitialize): + + debug_extra = None -class _VSKeys: + @classmethod + def _initialize(cls) -> None: + cls.debug_extra = debug_extra(cls) # edition key: (product, channel, component/None, seqnbr/None) @@ -1114,12 +1558,12 @@ def msvs_edition_key( if not vs_product_def: errmsg = 'vs_product_def is undefined' - debug('MSVCInternalError: %s', errmsg) + debug('MSVCInternalError: %s', errmsg, extra=cls.debug_extra) raise MSVCInternalError(errmsg) if not vs_channel_def: errmsg = 'vs_channel_def is undefined' - debug('MSVCInternalError: %s', errmsg) + debug('MSVCInternalError: %s', errmsg, extra=cls.debug_extra) raise MSVCInternalError(errmsg) vs_edition_key = cls.MSVSEditionKey.factory( @@ -1172,7 +1616,7 @@ def msvs_channel_key( if not vs_channel_def: errmsg = 'vs_channel_def is undefined' - debug('MSVCInternalError: %s', errmsg) + debug('MSVCInternalError: %s', errmsg, extra=cls.debug_extra) raise MSVCInternalError(errmsg) vs_channel_key = cls.MSVSChannelKey.factory( @@ -1315,10 +1759,10 @@ def default_order(a, b): return 1 if a.vs_sequence_nbr > b.vs_sequence_nbr else -1 return 0 - def register_msvs_instance(self, msvs_instance): + def register_msvs_instance(self, msvs_instance) -> None: self.instance_map['msvs_instance'] = msvs_instance - def register_msvc_instance(self, msvc_instance): + def register_msvc_instance(self, msvc_instance) -> None: self.instance_map['msvc_instance'] = msvc_instance @property @@ -1338,6 +1782,8 @@ def vs_component_suffix(self): 'msvs_base', 'vs_executable', 'vs_executable_norm', + 'vs_script', + 'vs_script_norm', 'vc_version_def', ]) @@ -1348,6 +1794,7 @@ def factory( cls, *, msvs_base, vs_executable, + vs_script, vc_version_def, ): @@ -1358,6 +1805,8 @@ def factory( msvs_base=msvs_base, vs_executable=vs_executable, vs_executable_norm=normalize_path(vs_executable), + vs_script=vs_script, + vs_script_norm=normalize_path(vs_script), vc_version_def=vc_version_def, ) @@ -1451,7 +1900,7 @@ class MSVSInstalled(_MSVSInstalled, AutoInitialize): debug_extra = None @classmethod - def _initialize(cls): + def _initialize(cls) -> None: cls.debug_extra = debug_extra(cls) @classmethod @@ -1545,16 +1994,7 @@ def factory( msvs_version_map = msvs_channel_map[vs_channel_def] msvs_instance_list = msvs_version_map.setdefault(vcver, []) - slot = len(msvs_instance_list) msvs_instance_list.append(msvs_instance) - debug( - 'msvs_version_map[%s][%s][%d]=%s', - repr(vs_channel_def.vs_channel_suffix), - repr(vcver), - slot, - repr(msvs_instance.id_str), - extra=cls.debug_extra, - ) msvs_installed = cls( msvs_instances=msvs_instances, @@ -1563,49 +2003,41 @@ def factory( msvs_channel_map=msvs_channel_map, ) - return msvs_installed - - def query_msvs_instances( - self, *, - vs_product_def=None, - vs_channel_def=None, - vs_componentid_def=None, - vs_sequence_nbr=None, - ): - - # TODO(JCB): error checking combinations (ignored, etc) + if DEBUG_ENABLED: + msvs_installed._debug_dump() - if not vs_channel_def: - vs_channel_def = _msvs_channel_default() + return msvs_installed - if vs_product_def: + def _debug_dump(self) -> None: - query_key = _VSKeys.msvs_edition_key( - vs_product_def=vs_product_def, - vs_channel_def=vs_channel_def, - vs_componentid_def=vs_componentid_def, - vs_sequence_nbr=vs_sequence_nbr + for vs_channel_key, msvc_instance_list in self.msvs_channel_instances_map.items(): + msvc_instances = [msvc_instance.id_str for msvc_instance in msvc_instance_list] + debug( + 'msvs_channel_instances_map[%s]=%s', + repr(vs_channel_key.serialize()), + repr(msvc_instances), + extra=self.debug_extra, ) - msvs_instances = self.msvs_edition_instances_map.get(query_key, []) - - else: - - query_key = _VSKeys.msvs_channel_key( - vs_channel_def=vs_channel_def, - vs_componentid_def=vs_componentid_def, + for vs_edition_key, msvc_instance_list in self.msvs_edition_instances_map.items(): + msvc_instances = [msvc_instance.id_str for msvc_instance in msvc_instance_list] + debug( + 'msvs_edition_instances_map[%s]=%s', + repr(vs_edition_key.serialize()), + repr(msvc_instances), + extra=self.debug_extra, ) - msvs_instances = self.msvs_channel_instances_map.get(query_key, []) - - debug( - 'query_key=%s, n_msvs_instances=%s', - repr(query_key.serialize()), - repr(len(msvs_instances)), - extra=self.debug_extra, - ) - - return query_key, msvs_instances + for vs_channel_def, msvs_version_map in self.msvs_channel_map.items(): + for vcver, msvs_instance_list in msvs_version_map.items(): + msvs_instances = [msvs_instance.id_str for msvs_instance in msvs_instance_list] + debug( + 'msvs_version_map[%s][%s]=%s', + repr(vs_channel_def.vs_channel_suffix), + repr(vcver), + repr(msvs_instances), + extra=self.debug_extra, + ) _MSVCInstance = namedtuple('_MSVCInstance', [ 'id_str', @@ -1622,7 +2054,7 @@ class MSVCInstance(_MSVCInstance, AutoInitialize): debug_extra = None @classmethod - def _initialize(cls): + def _initialize(cls) -> None: cls.debug_extra = debug_extra(cls) @classmethod @@ -1828,7 +2260,7 @@ class MSVCInstalled(_MSVCInstalled, AutoInitialize): debug_extra = None @classmethod - def _initialize(cls): + def _initialize(cls) -> None: cls.debug_extra = debug_extra(cls) @classmethod @@ -1922,16 +2354,7 @@ def factory( msvc_version_map = msvs_channel_map[vs_channel_def] msvc_instance_list = msvc_version_map.setdefault(vcver, []) - slot = len(msvc_instance_list) msvc_instance_list.append(msvc_instance) - debug( - 'msvc_version_map[%s][%s][%d]=%s', - repr(vs_channel_def.vs_channel_suffix), - repr(vcver), - slot, - repr(msvc_instance.id_str), - extra=cls.debug_extra, - ) msvc_installed = cls( msvc_instances=msvc_instances, @@ -1940,31 +2363,96 @@ def factory( msvs_channel_map=msvs_channel_map, ) + if DEBUG_ENABLED: + msvc_installed._debug_dump() + return msvc_installed - def query_msvc_instances( - self, *, - vs_product_def=None, - vs_channel_def=None, - vs_componentid_def=None, - vs_sequence_nbr=None, - ): + def _debug_dump(self) -> None: - # TODO(JCB): error checking combinations (ignored, etc) + for vs_channel_key, msvc_instance_list in self.msvs_channel_instances_map.items(): + msvc_instances = [msvc_instance.id_str for msvc_instance in msvc_instance_list] + debug( + 'msvs_channel_instances_map[%s]=%s', + repr(vs_channel_key.serialize()), + repr(msvc_instances), + extra=self.debug_extra, + ) - if not vs_channel_def: - vs_channel_def = _msvs_channel_default() + for vs_edition_key, msvc_instance_list in self.msvs_edition_instances_map.items(): + msvc_instances = [msvc_instance.id_str for msvc_instance in msvc_instance_list] + debug( + 'msvs_edition_instances_map[%s]=%s', + repr(vs_edition_key.serialize()), + repr(msvc_instances), + extra=self.debug_extra, + ) - if vs_product_def: + for vs_channel_def, msvc_version_map in self.msvs_channel_map.items(): + for vcver, msvc_instance_list in msvc_version_map.items(): + msvc_instances = [msvc_instance.id_str for msvc_instance in msvc_instance_list] + debug( + 'msvc_version_map[%s][%s]=%s', + repr(vs_channel_def.vs_channel_suffix), + repr(vcver), + repr(msvc_instances), + extra=self.debug_extra, + ) - query_key = _VSKeys.msvs_edition_key( - vs_product_def=vs_product_def, - vs_channel_def=vs_channel_def, +_MSVSManager = namedtuple('_MSVSManager', [ + 'msvc_installed', + 'msvs_installed', +]) + +class MSVSManager(_MSVSManager, AutoInitialize): + + debug_extra = None + + @classmethod + def _initialize(cls) -> None: + cls.debug_extra = debug_extra(cls) + + @classmethod + def factory(cls, msvc_installed, msvs_installed): + + msvs_manager = cls( + msvc_installed=msvc_installed, + msvs_installed=msvs_installed, + ) + + debug( + 'n_msvc_instances=%d, n_msvs_instances=%d', + len(msvc_installed.msvc_instances), + len(msvs_installed.msvs_instances), + extra=cls.debug_extra, + ) + + return msvs_manager + + def query_msvs_instances( + self, *, + vs_product_def=None, + vs_channel_def=None, + vs_componentid_def=None, + vs_sequence_nbr=None, + ): + + # TODO(JCB): take strings as input rather than objects + # TODO(JCB): error checking combinations (ignored, etc) + + if not vs_channel_def: + vs_channel_def = _msvs_channel_default() + + if vs_product_def: + + query_key = _VSKeys.msvs_edition_key( + vs_product_def=vs_product_def, + vs_channel_def=vs_channel_def, vs_componentid_def=vs_componentid_def, vs_sequence_nbr=vs_sequence_nbr ) - msvc_instances = self.msvs_edition_instances_map.get(query_key, []) + msvs_instances = self.msvs_installed.msvs_edition_instances_map.get(query_key, []) else: @@ -1973,141 +2461,172 @@ def query_msvc_instances( vs_componentid_def=vs_componentid_def, ) - msvc_instances = self.msvs_channel_instances_map.get(query_key, []) + msvs_instances = self.msvs_installed.msvs_channel_instances_map.get(query_key, []) debug( - 'query_key=%s, n_msvc_instances=%s', + 'query_key=%s, n_msvs_instances=%s', repr(query_key.serialize()), - repr(len(msvc_instances)), + repr(len(msvs_instances)), extra=self.debug_extra, ) - return query_key, msvc_instances - -_MSVSManager = namedtuple('_MSVSManager', [ - 'msvc_installed', - 'msvs_installed', -]) + return msvs_instances, query_key -class MSVSManager(_MSVSManager): + def query_msvc_instances( + self, *, + vs_product_def=None, + vs_channel_def=None, + vs_componentid_def=None, + vs_sequence_nbr=None, + ): - @classmethod - def factory(cls, msvc_installed, msvs_installed): + # TODO(JCB): take strings as input rather than objects + # TODO(JCB): error checking combinations (ignored, etc) - msvs_manager = cls( - msvc_installed=msvc_installed, - msvs_installed=msvs_installed, - ) + if not vs_channel_def: + vs_channel_def = _VSChannel.get_default_channel() - return msvs_manager + if vs_product_def: -class _VSDetect(AutoInitialize): + query_key = _VSKeys.msvs_edition_key( + vs_product_def=vs_product_def, + vs_channel_def=vs_channel_def, + vs_componentid_def=vs_componentid_def, + vs_sequence_nbr=vs_sequence_nbr + ) - debug_extra = None + msvc_instances = self.msvc_installed.msvs_edition_instances_map.get(query_key, []) - @classmethod - def _initialize(cls): - cls.debug_extra = debug_extra(cls) - cls.reset() + else: - @classmethod - def reset(cls): + query_key = _VSKeys.msvs_channel_key( + vs_channel_def=vs_channel_def, + vs_componentid_def=vs_componentid_def, + ) - cls.vswhere_executable_seen = set() - cls.vswhere_executables = [] + msvc_instances = self.msvc_installed.msvs_channel_instances_map.get(query_key, []) - cls.registry_once = False + debug( + 'query_key=%s, n_msvc_instances=%s', + repr(query_key.serialize()), + repr(len(msvc_instances)), + extra=self.debug_extra, + ) - cls.vs_dir_seen = set() # vswhere - cls.vc_dir_seen = set() # registry + return msvc_instances, query_key - cls.msvs_sequence_nbr = {} +class _VSDetectCommon(AutoInitialize): - cls.msvs_instances = [] - cls.msvc_instances = [] + debug_extra = None - # volatile: reconstructed when new instances detected - cls.msvs_manager = None + @classmethod + def _initialize(cls) -> None: + cls.debug_extra = debug_extra(cls) - reset_funcs = [] + VSDetectedBinaries = namedtuple("VSDetectedBinaries", [ + '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 + 'vs_exec', # vs binary + ]) @classmethod - def register_reset_func(cls, func): - cls.reset_funcs.append(func) + def msvs_detect(cls, vs_dir, vs_cfg): - @classmethod - def call_reset_funcs(cls): - for func in cls.reset_funcs: - func() + vs_exec = None + vs_script = None - @classmethod - def _msvc_dir_evaluate( - cls, *, - vs_product_def, - vs_component_def, - vs_channel_def, - vs_dir, - vs_version, - vs_exec, - vc_version, - vc_dir, - vc_feature_map=None, - ): + vs_binary_cfg = vs_cfg.vs_binary_cfg + vs_batch_cfg = vs_cfg.vs_batch_cfg - vs_sequence_key = (vs_product_def, vs_channel_def, vs_component_def) + ide_path = os.path.join(vs_dir, vs_binary_cfg.pathcomp) - cls.msvs_sequence_nbr.setdefault(vs_sequence_key, 0) - cls.msvs_sequence_nbr[vs_sequence_key] += 1 + bitfields = 0b_0000 + for ide_program in vs_binary_cfg.programs: + prog = os.path.join(ide_path, ide_program.program) + if not os.path.exists(prog): + continue + bitfields |= ide_program.bitfield + if vs_exec: + continue + if not ide_program.bitfield & _VSConfig.EXECUTABLE_MASK: + continue + vs_exec = prog + + have_dev = bool(bitfields & _VSConfig.BITFIELD_DEVELOP) + have_exp = bool(bitfields & _VSConfig.BITFIELD_EXPRESS) + have_exp_win = bool(bitfields & _VSConfig.BITFIELD_EXPRESS_WIN) + have_exp_web = bool(bitfields & _VSConfig.BITFIELD_EXPRESS_WEB) + + binaries_t = cls.VSDetectedBinaries( + bitfields=bitfields, + have_dev=have_dev, + have_exp=have_exp, + have_exp_win=have_exp_win, + have_exp_web=have_exp_web, + vs_exec=vs_exec, + ) - vs_sequence_nbr = cls.msvs_sequence_nbr[vs_sequence_key] + script_file = os.path.join(vs_dir, vs_batch_cfg.pathcomp, vs_batch_cfg.script) + if os.path.exists(script_file): + vs_script = script_file - msvs_base = MSVSBase.factory( - vs_product_def=vs_product_def, - vs_channel_def=vs_channel_def, - vs_component_def=vs_component_def, - vs_sequence_nbr=vs_sequence_nbr, - vs_dir=vs_dir, - vs_version=vs_version, + debug( + 'vs_dir=%s, dev=%s, exp=%s, exp_win=%s, exp_web=%s, vs_exec=%s, vs_script=%s', + repr(vs_dir), + binaries_t.have_dev, binaries_t.have_exp, + binaries_t.have_exp_win, binaries_t.have_exp_web, + repr(binaries_t.vs_exec), + repr(vs_script), + extra=cls.debug_extra, ) - if msvs_base.is_express: - vc_version += msvs_base.vs_component_suffix + return binaries_t, vs_script - vc_version_def = MSVC.Util.msvc_version_components(vc_version) +class _VSDetectVSWhere(AutoInitialize): - msvc_instance = MSVCInstance.factory( - msvs_base=msvs_base, - vc_version_def=vc_version_def, - vc_feature_map=vc_feature_map, - vc_dir=vc_dir, - ) + DETECT_CONFIG = _VSConfig.DETECT_VSWHERE - # TODO(JCB): load toolsets/tools - if not _msvc_instance_check_files_exist(msvc_instance): - # no compilers detected don't register msvc_instance - return None + # initialization - msvs_base.register_msvc_instance(msvc_instance) - cls.msvc_instances.append(msvc_instance) + debug_extra = None + reset_funcs = [] - if vs_exec: + @classmethod + def _initialize(cls) -> None: + cls.debug_extra = debug_extra(cls) + cls.reset() - msvs_instance = MSVSInstance.factory( - msvs_base=msvs_base, - vs_executable=vs_exec, - vc_version_def=vc_version_def, - ) + @classmethod + def reset(cls) -> None: - msvs_base.register_msvs_instance(msvs_instance) - cls.msvs_instances.append(msvs_instance) + cls.vswhere_executable_seen = set() + cls.vswhere_executables = [] - return msvc_instance + cls.vs_dir_seen = set() + + cls.msvs_sequence_nbr = {} + + cls.msvs_instances = [] + cls.msvc_instances = [] + + @classmethod + def register_reset_func(cls, func) -> None: + if func: + cls.reset_funcs.append(func) + + @classmethod + def call_reset_funcs(cls) -> None: + for func in cls.reset_funcs: + func() @classmethod - def _filter_vswhere_paths(cls, vswhere_env=None): + def _filter_vswhere_paths(cls, vswhere_executables): vswhere_paths = [ vswhere_exec.norm - for vswhere_exec in _find_vswhere_executables(vswhere_env) + for vswhere_exec in vswhere_executables if vswhere_exec.norm not in cls.vswhere_executable_seen ] debug('vswhere_paths=%s', vswhere_paths, extra=cls.debug_extra) @@ -2165,12 +2684,32 @@ def _vswhere_query_json_output(cls, vswhere_exe, vswhere_args): return vswhere_json @classmethod - def _msvc_instances_vswhere(cls, vswhere_env=None): + def _msvc_resolve(cls, vc_dir, vs_product_def): + + detect_cfg = cls.DETECT_CONFIG[vs_product_def.vs_product] + + vs_cfg = detect_cfg.vs_cfg + vs_dir = os.path.normpath(os.path.join(vc_dir, vs_cfg.root)) + + binaries_t, vs_script = _VSDetectCommon.msvs_detect(vs_dir, vs_cfg) + + return binaries_t.vs_exec, vs_script + + @classmethod + def _msvc_instance_toolsets(cls, msvc_instance): + # TODO(JCB): load toolsets/tools + rval = _msvc_instance_check_files_exist(msvc_instance) + return rval + + @classmethod + def detect(cls, vswhere_env): + + vswhere_executables = _VSWhere.find_executables(vswhere_env) num_instances = len(cls.msvc_instances) num_new_instances = 0 - vswhere_paths = cls._filter_vswhere_paths(vswhere_env) + vswhere_paths = cls._filter_vswhere_paths(vswhere_executables) if vswhere_paths: num_beg_instances = num_instances @@ -2239,35 +2778,355 @@ def _msvc_instances_vswhere(cls, vswhere_env=None): else: vs_channel_def = MSVC.Config.MSVS_CHANNEL_RELEASE - vc_version = MSVC.Config.VSWHERE_VSMAJOR_TO_VCVERSION[vs_major] + vs_exec, vs_script = cls._msvc_resolve(vc_dir, vs_product_def) - vs_exec = MSVC.Kind.msvc_dir_vswhere( - vc_dir, - _VSPRODUCT_KIND_DETECT[vs_product_def.vs_product], - ) + vs_sequence_key = (vs_product_def, vs_channel_def, vs_component_def) + + cls.msvs_sequence_nbr.setdefault(vs_sequence_key, 0) + cls.msvs_sequence_nbr[vs_sequence_key] += 1 - cls._msvc_dir_evaluate( + vs_sequence_nbr = cls.msvs_sequence_nbr[vs_sequence_key] + + msvs_base = MSVSBase.factory( vs_product_def=vs_product_def, - vs_component_def=vs_component_def, vs_channel_def=vs_channel_def, + vs_component_def=vs_component_def, + vs_sequence_nbr=vs_sequence_nbr, vs_dir=vs_dir, vs_version=vs_version, - vs_exec=vs_exec, - vc_version=vc_version, + ) + + vc_version = vs_product_def.vc_buildtools_def.vc_version + + if msvs_base.is_express: + vc_version += msvs_base.vs_component_suffix + + vc_version_def = MSVC.Util.msvc_version_components(vc_version) + + msvc_instance = MSVCInstance.factory( + msvs_base=msvs_base, + vc_version_def=vc_version_def, + vc_feature_map=None, vc_dir=vc_dir, ) + if not cls._msvc_instance_toolsets(msvc_instance): + # no compilers detected don't register objects + continue + + if vs_exec: + + # TODO(JCB): register iff compilers and executable? + msvs_instance = MSVSInstance.factory( + msvs_base=msvs_base, + vs_executable=vs_exec, + vs_script=vs_script, + vc_version_def=vc_version_def, + ) + + msvs_base.register_msvs_instance(msvs_instance) + cls.msvs_instances.append(msvs_instance) + + debug( + 'msvs_instance=%s', + repr(msvs_instance.id_str), + extra=cls.debug_extra, + ) + + msvs_base.register_msvc_instance(msvc_instance) + cls.msvc_instances.append(msvc_instance) + + debug( + 'msvc_instance=%s', + repr(msvc_instance.id_str), + extra=cls.debug_extra, + ) + num_instances = len(cls.msvc_instances) num_new_instances = num_instances - num_beg_instances - if num_new_instances > 0: - cls.call_reset_funcs() - debug('num_new_instances=%s, num_instances=%s', num_new_instances, num_instances, extra=cls.debug_extra) + if num_new_instances > 0: + cls.call_reset_funcs() + debug( + 'num_new_instances=%s, num_instances=%s', + num_new_instances, num_instances, extra=cls.debug_extra + ) return num_new_instances +class _VSDetectRegistry(AutoInitialize): + + DETECT_CONFIG = _VSConfig.DETECT_REGISTRY + + # initialization + + debug_extra = None + + @classmethod + def _initialize(cls) -> None: + cls.debug_extra = debug_extra(cls) + cls.reset() + + @classmethod + def reset(cls) -> None: + + cls.registry_once = False + + cls.vc_dir_seen = set() + + cls.msvs_sequence_nbr = {} + + cls.msvs_instances = [] + cls.msvc_instances = [] + + # 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) + + @classmethod + def _vs_buildtools_2015_vcvars(cls, vcvars_file): + have_buildtools_vcvars = False + with open(vcvars_file) as fh: + for line in fh: + if cls._VS2015BT_VCVARS_BUILDTOOLS.match(line): + have_buildtools_vcvars = True + break + if cls._VS2015BT_VCVARS_STOP.match(line): + break + return have_buildtools_vcvars + + @classmethod + def _vs_buildtools_2015(cls, vs_dir, vc_dir): + + is_buildtools = False + + do_once = True + while do_once: + do_once = False + + buildtools_file = os.path.join(vs_dir, cls._VS2015BT_PATH) + have_buildtools = os.path.exists(buildtools_file) + debug('have_buildtools=%s', have_buildtools, extra=cls.debug_extra) + 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, extra=cls.debug_extra) + if not have_vcvars: + break + + have_buildtools_vcvars = cls._vs_buildtools_2015_vcvars(vcvars_file) + debug('have_buildtools_vcvars=%s', have_buildtools_vcvars, extra=cls.debug_extra) + if not have_buildtools_vcvars: + break + + is_buildtools = True + + debug('is_buildtools=%s', is_buildtools, extra=cls.debug_extra) + return is_buildtools + + _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) + + @classmethod + def _vs_express_2015_vcvars(cls, vcvars_file): + n_libpath = 0 + with open(vcvars_file) as fh: + for line in fh: + if cls._VS2015EXP_VCVARS_LIBPATH.match(line): + n_libpath += 1 + elif cls._VS2015EXP_VCVARS_STOP.match(line): + break + have_uwp_fix = n_libpath >= 2 + return have_uwp_fix + + @classmethod + def _vs_express_2015(cls, vc_dir): + + have_uwp_amd64 = False + have_uwp_arm = False + + vcvars_file = os.path.join(vc_dir, r'vcvarsall.bat') + if os.path.exists(vcvars_file): + + vcvars_file = os.path.join(vc_dir, r'bin\x86_amd64\vcvarsx86_amd64.bat') + if os.path.exists(vcvars_file): + have_uwp_fix = cls._vs_express_2015_vcvars(vcvars_file) + if have_uwp_fix: + have_uwp_amd64 = True + + vcvars_file = os.path.join(vc_dir, r'bin\x86_arm\vcvarsx86_arm.bat') + if os.path.exists(vcvars_file): + have_uwp_fix = cls._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, extra=cls.debug_extra) + 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'} + + @classmethod + def _msvc_dir_is_winsdk_only(cls, vc_dir, msvc_version): + + # 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 + + if msvc_version not in cls._REGISTRY_WINSDK_VERSIONS: + + is_sdk = False + + debug('is_sdk=%s, msvc_version=%s', is_sdk, repr(msvc_version), extra=cls.debug_extra) + + else: + + vc_suffix = MSVC.Registry.vstudio_sxs_vc7(msvc_version) + vc_qresults = [record[0] for record in MSVC.Registry.microsoft_query_paths(vc_suffix)] + vc_regdir = vc_qresults[0] if vc_qresults else None + + if vc_regdir != vc_dir: + # registry vc path is not the current vc dir + + is_sdk = False + + debug( + 'is_sdk=%s, msvc_version=%s, vc_dir=%s, vc_regdir=%s', + is_sdk, repr(msvc_version), repr(vc_dir), repr(vc_regdir), extra=cls.debug_extra + ) + + else: + # registry vc dir is the current vc root + + vs_suffix = MSVC.Registry.vstudio_sxs_vs7(msvc_version) + vs_qresults = [record[0] for record in MSVC.Registry.microsoft_query_paths(vs_suffix)] + vs_dir = vs_qresults[0] if vs_qresults else None + + is_sdk = bool(not vs_dir and vc_dir) + + debug( + 'is_sdk=%s, msvc_version=%s, vc_dir=%s, vs_dir=%s', + is_sdk, repr(msvc_version), repr(vc_dir), repr(vs_dir), extra=cls.debug_extra + ) + + return is_sdk + @classmethod - def _msvc_instances_registry(cls): + def _msvc_resolve(cls, vc_dir, vs_product_def, is_vcforpython, vs_version): + + detect_cfg = cls.DETECT_CONFIG[vs_product_def.vs_product] + + vc_feature_map = {} + + vs_cfg = detect_cfg.vs_cfg + vs_dir = os.path.normpath(os.path.join(vc_dir, vs_cfg.root)) + + binaries_t, vs_script = _VSDetectCommon.msvs_detect(vs_dir, vs_cfg) + + vs_product_numeric = vs_product_def.vs_product_numeric + vc_version = vs_product_def.vc_buildtools_def.vc_version + + if binaries_t.have_dev: + vs_component_def = MSVC.Config.REGISTRY_COMPONENT_DEVELOP + elif binaries_t.have_exp: + vs_component_def = MSVC.Config.REGISTRY_COMPONENT_EXPRESS + elif vs_product_numeric == 2008 and is_vcforpython: + vs_component_def = MSVC.Config.REGISTRY_COMPONENT_PYTHON + elif binaries_t.have_exp_win: + vs_component_def = None + elif binaries_t.have_exp_web: + vs_component_def = None + elif cls._msvc_dir_is_winsdk_only(vc_dir, vc_version): + vs_component_def = None + else: + vs_component_def = MSVC.Config.REGISTRY_COMPONENT_CMDLINE + + if vs_component_def and vs_product_numeric == 2015: + + # VS2015: + # remap DEVELOP => ENTERPRISE, PROFESSIONAL, COMMUNITY + # remap CMDLINE => BUILDTOOLS [conditional] + # process EXPRESS + + if vs_component_def == MSVC.Config.REGISTRY_COMPONENT_DEVELOP: + + for reg_component, reg_component_def in [ + ('community', MSVC.Config.REGISTRY_COMPONENT_COMMUNITY), + ('professional', MSVC.Config.REGISTRY_COMPONENT_PROFESSIONAL), + ('enterprise', MSVC.Config.REGISTRY_COMPONENT_ENTERPRISE), + ]: + suffix = MSVC.Registry.devdiv_vs_servicing_component(vs_version, reg_component) + qresults = MSVC.Registry.microsoft_query_keys(suffix, usrval=reg_component_def) + if not qresults: + continue + vs_component_def = qresults[0][-1] + break + + elif vs_component_def == MSVC.Config.REGISTRY_COMPONENT_CMDLINE: + + if cls._vs_buildtools_2015(vs_dir, vc_dir): + vs_component_def = MSVC.Config.REGISTRY_COMPONENT_BUILDTOOLS + + elif vs_component_def == MSVC.Config.REGISTRY_COMPONENT_EXPRESS: + + have_uwp_amd64, have_uwp_arm = cls._vs_express_2015(vc_dir) + + uwp_target_is_supported = { + 'x86': True, + 'amd64': have_uwp_amd64, + 'arm': have_uwp_arm, + } + + vc_feature_map['uwp_target_is_supported'] = uwp_target_is_supported + + debug( + 'vs_product=%s, vs_component=%s, vc_dir=%s, vc_feature_map=%s', + repr(vs_product_numeric), + repr(vs_component_def.vs_componentid_def.vs_component_id) if vs_component_def else None, + repr(vc_dir), + repr(vc_feature_map), + extra=cls.debug_extra + ) + + return vs_component_def, vs_dir, binaries_t.vs_exec, vs_script, vc_feature_map + + @classmethod + def _msvc_instance_toolsets(cls, msvc_instance): + # TODO(JCB): load toolsets/tools + rval = _msvc_instance_check_files_exist(msvc_instance) + return rval + + @classmethod + def detect(cls): num_instances = len(cls.msvc_instances) num_new_instances = 0 @@ -2279,31 +3138,31 @@ def _msvc_instances_registry(cls): is_win64 = common.is_win64() - for vs_product, regkeys in _VSPRODUCT_REGISTRY_VCDIR.items(): + for vs_product, config in cls.DETECT_CONFIG.items(): key_prefix = 'Software\\' - for is_vsroot, is_vcforpython, hkroot, key in regkeys: + for regkey in config.vc_cfg.regkeys: - if not hkroot or not key: + if not regkey.hkroot or not regkey.key: continue if is_win64: - msregkeys = [key_prefix + 'Wow6432Node\\' + key, key_prefix + key] + mskeys = [key_prefix + 'Wow6432Node\\' + regkey.key, key_prefix + regkey.key] else: - msregkeys = [key_prefix + key] + mskeys = [key_prefix + regkey.key] vc_dir = None - for msregkey in msregkeys: - debug('trying VC registry key %s', repr(msregkey), extra=cls.debug_extra) + for mskey in mskeys: + debug('trying VC registry key %s', repr(mskey), extra=cls.debug_extra) try: - vc_dir = common.read_reg(msregkey, hkroot) + vc_dir = common.read_reg(mskey, regkey.hkroot) except OSError: continue if vc_dir: break if not vc_dir: - debug('no VC registry key %s', repr(key), extra=cls.debug_extra) + debug('no VC registry key %s', repr(regkey.key), extra=cls.debug_extra) continue if vc_dir in cls.vc_dir_seen: @@ -2317,7 +3176,7 @@ def _msvc_instances_registry(cls): ) continue - if is_vsroot: + if regkey.is_vsroot: vc_dir = os.path.join(vc_dir, 'VC') debug('convert vs dir to vc dir: %s', repr(vc_dir), extra=cls.debug_extra) @@ -2336,13 +3195,11 @@ def _msvc_instances_registry(cls): cls.vc_dir_seen.add(vc_norm) vs_product_def = MSVC.Config.MSVS_VERSION_INTERNAL[vs_product] - vc_version = vs_product_def.vc_buildtools_def.vc_version - vs_component_def, vs_dir, vs_exec, vc_feature_map = MSVC.Kind.msvc_dir_registry( + vs_component_def, vs_dir, vs_exec, vs_script, vc_feature_map = cls._msvc_resolve( vc_norm, - _VSPRODUCT_KIND_DETECT[vs_product_def.vs_product], - vc_version, - is_vcforpython, + vs_product_def, + regkey.is_vcforpython, vs_product_def.vs_version ) @@ -2351,16 +3208,66 @@ def _msvc_instances_registry(cls): vs_channel_def = MSVC.Config.MSVS_CHANNEL_RELEASE - cls._msvc_dir_evaluate( + vs_sequence_key = (vs_product_def, vs_channel_def, vs_component_def) + + cls.msvs_sequence_nbr.setdefault(vs_sequence_key, 0) + cls.msvs_sequence_nbr[vs_sequence_key] += 1 + + vs_sequence_nbr = cls.msvs_sequence_nbr[vs_sequence_key] + + msvs_base = MSVSBase.factory( vs_product_def=vs_product_def, - vs_component_def=vs_component_def, vs_channel_def=vs_channel_def, + vs_component_def=vs_component_def, + vs_sequence_nbr=vs_sequence_nbr, vs_dir=vs_dir, vs_version=vs_product_def.vs_version, - vs_exec=vs_exec, - vc_version=vc_version, - vc_dir=vc_dir, + ) + + vc_version = vs_product_def.vc_buildtools_def.vc_version + + if msvs_base.is_express: + vc_version += msvs_base.vs_component_suffix + + vc_version_def = MSVC.Util.msvc_version_components(vc_version) + + msvc_instance = MSVCInstance.factory( + msvs_base=msvs_base, + vc_version_def=vc_version_def, vc_feature_map=vc_feature_map, + vc_dir=vc_dir, + ) + + if not cls._msvc_instance_toolsets(msvc_instance): + # no compilers detected don't register objects + continue + + if vs_exec: + + # TODO(JCB): register iff compilers and executable? + msvs_instance = MSVSInstance.factory( + msvs_base=msvs_base, + vs_executable=vs_exec, + vs_script=vs_script, + vc_version_def=vc_version_def, + ) + + msvs_base.register_msvs_instance(msvs_instance) + cls.msvs_instances.append(msvs_instance) + + debug( + 'msvs_instance=%s', + repr(msvs_instance.id_str), + extra=cls.debug_extra, + ) + + msvs_base.register_msvc_instance(msvc_instance) + cls.msvc_instances.append(msvc_instance) + + debug( + 'msvc_instance=%s', + repr(msvc_instance.id_str), + extra=cls.debug_extra, ) num_instances = len(cls.msvc_instances) @@ -2368,21 +3275,53 @@ def _msvc_instances_registry(cls): return num_new_instances +class _VSDetect(AutoInitialize): + + debug_extra = None + + @classmethod + def _initialize(cls) -> None: + cls.debug_extra = debug_extra(cls) + cls._reset() + + @classmethod + def _reset(cls) -> None: + # volatile: reconstructed when new instances detected + cls.msvs_manager = None + + @classmethod + def reset(cls) -> None: + cls._reset() + _VSDetectVSWhere.reset() + _VSDetectRegistry.reset() + @classmethod - def detect(cls, vswhere_env=None): + def register_reset_func(cls, func) -> None: + _VSDetectVSWhere.register_reset_func(func) + + @classmethod + def detect(cls, vswhere_env): num_new_instances = 0 - num_new_instances += cls._msvc_instances_vswhere(vswhere_env) - num_new_instances += cls._msvc_instances_registry() + num_new_instances += _VSDetectVSWhere.detect(vswhere_env) + num_new_instances += _VSDetectRegistry.detect() if num_new_instances > 0 or cls.msvs_manager is None: + msvc_instances = [] + msvc_instances.extend(_VSDetectVSWhere.msvc_instances) + msvc_instances.extend(_VSDetectRegistry.msvc_instances) + + msvs_instances = [] + msvs_instances.extend(_VSDetectVSWhere.msvs_instances) + msvs_instances.extend(_VSDetectRegistry.msvs_instances) + msvc_installed = MSVCInstalled.factory( - msvc_instances=cls.msvc_instances, + msvc_instances=msvc_instances, ) msvs_installed = MSVSInstalled.factory( - msvs_instances=cls.msvs_instances, + msvs_instances=msvs_instances, ) cls.msvs_manager = MSVSManager.factory( @@ -2428,7 +3367,7 @@ def _find_msvc_instance(msvc_version, env=None): vs_product_def, vs_componentid_def, vs_channel_def = _query_key_components(msvc_version, env) - query_key, msvc_instances = vs_manager.msvc_installed.query_msvc_instances( + msvc_instances, query_key = vs_manager.query_msvc_instances( vs_product_def=vs_product_def, vs_componentid_def=vs_componentid_def, vs_channel_def=vs_channel_def, @@ -2459,11 +3398,6 @@ def find_msvc_instance(msvc_version, env=None): msvc_instance, _ = _find_msvc_instance(msvc_version, env) return msvc_instance -def _reset_installed_queries(): - debug('reset') - global _cache_user_vswhere_path - _cache_user_vswhere_paths = {} - def find_batch_file(msvc_instance, host_arch, target_arch): """ Find the location of the batch script which should set up the compiler @@ -2484,12 +3418,12 @@ def find_batch_file(msvc_instance, host_arch, target_arch): if msvc_instance.vs_product_numeric >= 2022: # 14.3 (VS2022) and later batfiledir = os.path.join(pdir, "Auxiliary", "Build") - batfile, _ = _GE2022_HOST_TARGET_BATCHFILE_CLPATHCOMPS[(host_arch, target_arch)] + _, batfile, _ = _GE2022_HOST_TARGET_BATCHARG_BATCHFILE_CLPATHCOMPS[(host_arch, target_arch)] batfilename = os.path.join(batfiledir, batfile) elif 2022 > msvc_instance.vs_product_numeric >= 2017: # 14.2 (VS2019) to 14.1 (VS2017) batfiledir = os.path.join(pdir, "Auxiliary", "Build") - batfile, _ = _LE2019_HOST_TARGET_BATCHFILE_CLPATHCOMPS[(host_arch, target_arch)] + _, batfile, _ = _LE2019_HOST_TARGET_BATCHARG_BATCHFILE_CLPATHCOMPS[(host_arch, target_arch)] batfilename = os.path.join(batfiledir, batfile) elif 2017 > msvc_instance.vs_product_numeric >= 2010: # 14.0 (VS2015) to 10.0 (VS2010) @@ -2513,9 +3447,9 @@ def find_batch_file(msvc_instance, host_arch, target_arch): clexe = os.path.join(pdir, *cl_path_comps, _CL_EXE_NAME) else: # 2005 > vc_product_numeric >= 6.0 # 7.1 (VS2003) and earlier - pdir = os.path.join(pdir, "Bin") - batfilename = os.path.join(pdir, "vcvars32.bat") - clexe = os.path.join(pdir, _CL_EXE_NAME) + _, batfile, cl_path_comps = _LE2003_HOST_TARGET_BATCHARG_BATCHFILE_CLPATHCOMPS[(host_arch, target_arch)] + batfilename = os.path.join(pdir, *cl_path_comps, batfile) + clexe = os.path.join(pdir, *cl_path_comps, _CL_EXE_NAME) if not os.path.exists(batfilename): debug("batch file not found: %s", batfilename) @@ -2555,14 +3489,14 @@ def find_batch_file_sdk(host_arch, target_arch, sdk_pdir): _cache_installed_msvc_versions = None _cache_installed_msvc_instances = None -def _reset_installed_vcs(): +def _reset_installed_vcs() -> None: global _cache_installed_msvc_versions global _cache_installed_msvc_instances _cache_installed_msvc_versions = None _cache_installed_msvc_instances = None debug('reset') -# register vcs cache reset function with vsdetect state manager +# register vcs cache reset function with vs detection _VSDetect.register_reset_func(_reset_installed_vcs) _VC_TOOLS_VERSION_FILE_PATH = ['Auxiliary', 'Build', 'Microsoft.VCToolsVersion.default.txt'] @@ -2608,10 +3542,10 @@ def _msvc_instance_check_files_exist(msvc_instance) -> bool: if msvc_instance.vs_product_numeric >= 2022: # 14.3 (VS2022) and later - host_target_batchfile_clpathcomps = _GE2022_HOST_TARGET_BATCHFILE_CLPATHCOMPS + host_target_batcharg_batchfile_clpathcomps = _GE2022_HOST_TARGET_BATCHARG_BATCHFILE_CLPATHCOMPS else: # 14.2 (VS2019) to 14.1 (VS2017) - host_target_batchfile_clpathcomps = _LE2019_HOST_TARGET_BATCHFILE_CLPATHCOMPS + host_target_batcharg_batchfile_clpathcomps = _LE2019_HOST_TARGET_BATCHARG_BATCHFILE_CLPATHCOMPS for host_platform, target_platform in host_target_list: @@ -2620,12 +3554,14 @@ def _msvc_instance_check_files_exist(msvc_instance) -> bool: host_platform, target_platform, msvc_instance.msvc_version ) - batchfile_clpathcomps = host_target_batchfile_clpathcomps.get((host_platform, target_platform), None) - if batchfile_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 - batfile, cl_path_comps = batchfile_clpathcomps + _, batfile, cl_path_comps = batcharg_batchfile_clpathcomps batfile_path = os.path.join(msvc_instance.vc_dir, "Auxiliary", "Build", batfile) if not os.path.exists(batfile_path): @@ -2700,20 +3636,37 @@ def _msvc_instance_check_files_exist(msvc_instance) -> bool: elif 2005 > msvc_instance.vs_product_numeric >= 1998: # 7.1 (VS2003) to 6.0 (VS6) - bin_dir = os.path.join(msvc_instance.vc_dir, "bin") + host_target_batcharg_batchfile_clpathcomps = _LE2003_HOST_TARGET_BATCHARG_BATCHFILE_CLPATHCOMPS - batfile_path = os.path.join(bin_dir, "vcvars32.bat") - 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: - cl_path = os.path.join(bin_dir, _CL_EXE_NAME) - if not os.path.exists(cl_path): - debug("%s not found: %s", _CL_EXE_NAME, cl_path) - return False + debug( + 'host_platform=%s, target_platform=%s, msvc_version=%s', + host_platform, target_platform, msvc_instance.msvc_version + ) - debug('%s found: %s', _CL_EXE_NAME, cl_path) - return True + 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 + + _, batfile, cl_path_comps = batcharg_batchfile_clpathcomps + + batfile_path = os.path.join(msvc_instance.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(msvc_instance.vc_dir, *cl_path_comps, _CL_EXE_NAME) + if not os.path.exists(cl_path): + debug("%s not found: %s", _CL_EXE_NAME, cl_path) + continue + + debug('%s found: %s', _CL_EXE_NAME, cl_path) + return True else: # version not supported return false @@ -2733,7 +3686,7 @@ def get_installed_msvc_instances(env=None): msvc_installed = vs_manager.msvc_installed # TODO(JCB): first-use default channel - vs_channel_def = _msvs_channel_default() + vs_channel_def = _VSChannel.get_default_channel() msvs_channel_map = msvc_installed.msvs_channel_map msvc_version_map = msvs_channel_map.get(vs_channel_def) @@ -2759,7 +3712,7 @@ def get_installed_vcs(env=None): msvc_installed = vs_manager.msvc_installed # TODO(JCB): first-use default channel - vs_channel_def = _msvs_channel_default() + vs_channel_def = _VSChannel.get_default_channel() msvs_channel_map = msvc_installed.msvs_channel_map msvc_version_map = msvs_channel_map.get(vs_channel_def) @@ -2776,7 +3729,8 @@ def get_installed_vcs(env=None): def reset_installed_vcs() -> None: """Make it try again to find VC. This is just for the tests.""" _reset_installed_vcs() - _reset_installed_queries() + _VSWhere.reset() + _VSChannel.reset() _VSDetect.reset() MSVC._reset() @@ -3436,7 +4390,25 @@ def msvc_query_version_toolset(version=None, prefer_newest: bool=True): msg = f'MSVC toolset version {version!r} not found' raise MSVCToolsetVersionNotFound(msg) +def _verify() -> None: + + def _compare_product_sets(set_config, set_local, label): + diff = set_config - set_local + if diff: + keys = ', '.join([repr(s) for s in sorted(diff, reverse=True)]) + errmsg = f'{label} missing keys: {keys}' + debug('MSVCInternalError: %s', errmsg) + raise MSVCInternalError(errmsg) + + vswhere_config = MSVC.Config.VSWHERE_SUPPORTED_PRODUCTS + vswhere_local = set(_VSConfig.DETECT_VSWHERE.keys()) + _compare_product_sets(vswhere_config, vswhere_local, '_VSConfig.DETECT_VSWHERE') + + registry_config = MSVC.Config.REGISTRY_SUPPORTED_PRODUCTS + registry_local = set(_VSConfig.DETECT_REGISTRY.keys()) + _compare_product_sets(registry_config, registry_local, '_VSConfig.DETECT_REGISTRY') -# internal consistency check (should be last) +# internal consistency checks (should be last) MSVC._verify() +_verify() diff --git a/SCons/Tool/MSCommon/vcTests.py b/SCons/Tool/MSCommon/vcTests.py index db2d46fe89..19130f9812 100644 --- a/SCons/Tool/MSCommon/vcTests.py +++ b/SCons/Tool/MSCommon/vcTests.py @@ -44,6 +44,7 @@ MS_TOOLS_VERSION = '1.1.1' +native_host = SCons.Tool.MSCommon.vc.get_native_host_platform() class VswhereTestCase(unittest.TestCase): @staticmethod @@ -63,26 +64,26 @@ def testDefaults(self) -> None: Verify that msvc_find_vswhere() find's files in the specified paths """ - restore_vswhere_execs_exist = MSCommon.vc.VSWHERE_EXECS_EXIST[:] + restore_vswhere_execs_exist = MSCommon.vc._VSWhere.vswhere_executables[:] base_dir = test.workpath('fake_vswhere') norm_dir = os.path.normcase(os.path.normpath(base_dir)) # import pdb; pdb.set_trace() test_vswhere_dirs = [ - MSCommon.vc.VSWhereExecutable( + MSCommon.vc._VSWhere.VSWhereExecutable( path=os.path.join(base_dir,t[0][1:]), norm=os.path.join(norm_dir,t[1][1:]), ) for t in [ (os.path.splitdrive(vswexec.path)[1], os.path.splitdrive(vswexec.norm)[1]) - for vswexec in MSCommon.vc.VSWHERE_EXECS_EXIST + for vswexec in MSCommon.vc._VSWhere.vswhere_executables ] ] for vswexec in test_vswhere_dirs: VswhereTestCase._createVSWhere(vswexec.path) - MSCommon.vc.VSWHERE_EXECS_EXIST = [vswexec] + MSCommon.vc._VSWhere.vswhere_executables = [vswexec] find_path = MSCommon.vc.msvc_find_vswhere() self.assertTrue(vswexec.path == find_path, "Didn't find vswhere in %s found in %s" % (vswexec.path, find_path)) os.remove(vswexec.path) @@ -100,13 +101,13 @@ def testDefaults(self) -> None: VswhereTestCase._createVSWhere(vswpath) VswhereTestCase._existing_path = vswpath for front in (True, False): - MSCommon.vc.VSWHERE_EXECS_EXIST = [] + MSCommon.vc._VSWhere.vswhere_executables = [] MSCommon.vc.vswhere_push_location(vswpath, front=front) find_path = MSCommon.vc.msvc_find_vswhere() self.assertTrue(vswpath == find_path, "Didn't find vswhere in %s found in %s" % (vswpath, find_path)) os.remove(vswpath) - MSCommon.vc.VSWHERE_EXECS_EXIST = restore_vswhere_execs_exist + MSCommon.vc._VSWhere.vswhere_executables = restore_vswhere_execs_exist # def specifiedVswherePathTest(self): # "Verify that msvc.generate() respects VSWHERE Specified" @@ -189,71 +190,71 @@ def runTest(self) -> None: # Test 14.3 (VS2022) and later msvc_instance_VS2022 = MSVcTestCase._createDummyMSVCInstance('14.3', 'Community', '.') - 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)) - - 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._createDummyFile(path, 'cl.exe', add_bin=False) - result=check(msvc_instance_VS2022) - # print("for:(%s, %s) got :%s"%(host, target, result)) - self.assertTrue(result, "Checking host: %s target: %s" % (host, target)) + for host in SCons.Tool.MSCommon.vc._GE2022_HOST_TARGET_CFG.host_all_hosts[native_host]: + for target in SCons.Tool.MSCommon.vc._GE2022_HOST_TARGET_CFG.host_all_targets[host]: + _, batfile, clpathcomps = SCons.Tool.MSCommon.vc._GE2022_HOST_TARGET_BATCHARG_BATCHFILE_CLPATHCOMPS[(host,target)] + # print("GE 14.3 Got: (%s, %s) -> (%s, %s)"%(host,target,batfile,clpathcomps)) + 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._createDummyFile(path, 'cl.exe', add_bin=False) + result=check(msvc_instance_VS2022) + # print("for:(%s, %s) got :%s"%(host, target, result)) + self.assertTrue(result, "Checking host: %s target: %s" % (host, target)) # Test 14.2 (VS2019) to 14.1 (VS2017) versions msvc_instance_VS2017 = MSVcTestCase._createDummyMSVCInstance('14.1', 'Community', '.') - 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)) - - path = os.path.join('.', 'Tools', 'MSVC', MS_TOOLS_VERSION, *clpathcomps) - MSVcTestCase._createDummyFile(path, 'cl.exe', add_bin=False) - result=check(msvc_instance_VS2017) - # print("for:(%s, %s) got :%s"%(host, target, result)) - self.assertTrue(result, "Checking host: %s target: %s" % (host, target)) + for host in SCons.Tool.MSCommon.vc._LE2019_HOST_TARGET_CFG.host_all_hosts[native_host]: + for target in SCons.Tool.MSCommon.vc._LE2019_HOST_TARGET_CFG.host_all_targets[host]: + _, batfile, clpathcomps = SCons.Tool.MSCommon.vc._LE2019_HOST_TARGET_BATCHARG_BATCHFILE_CLPATHCOMPS[(host,target)] + # print("LE 14.2 Got: (%s, %s) -> (%s, %s)"%(host,target,batfile,clpathcomps)) + path = os.path.join('.', 'Tools', 'MSVC', MS_TOOLS_VERSION, *clpathcomps) + MSVcTestCase._createDummyFile(path, 'cl.exe', add_bin=False) + result=check(msvc_instance_VS2017) + # print("for:(%s, %s) got :%s"%(host, target, result)) + self.assertTrue(result, "Checking host: %s target: %s" % (host, target)) # Test 14.0 (VS2015) to 10.0 (VS2010) versions msvc_instance_VS2010 = MSVcTestCase._createDummyMSVCInstance('10.0', 'Develop', '.') - vc_le2015_list = SCons.Tool.MSCommon.vc._LE2015_HOST_TARGET_CFG.all_pairs - for host, target in vc_le2015_list: - 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)) - 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) - result=check(msvc_instance_VS2010) - # print("for:(%s, %s) got :%s"%(host, target, result)) - self.assertTrue(result, "Checking host: %s target: %s" % (host, target)) + for host in SCons.Tool.MSCommon.vc._LE2015_HOST_TARGET_CFG.host_all_hosts[native_host]: + for target in SCons.Tool.MSCommon.vc._LE2015_HOST_TARGET_CFG.host_all_targets[host]: + 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)) + 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) + result=check(msvc_instance_VS2010) + # print("for:(%s, %s) got :%s"%(host, target, result)) + self.assertTrue(result, "Checking host: %s target: %s" % (host, target)) # Test 9.0 (VC2008) to 8.0 (VS2005) msvc_instance_VS2005 = MSVcTestCase._createDummyMSVCInstance('8.0', 'Develop', '.') - 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)) - 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(msvc_instance_VS2005) - # print("for:(%s, %s) got :%s"%(host, target, result)) - self.assertTrue(result, "Checking host: %s target: %s" % (host, target)) + for host in SCons.Tool.MSCommon.vc._LE2008_HOST_TARGET_CFG.host_all_hosts[native_host]: + for target in SCons.Tool.MSCommon.vc._LE2008_HOST_TARGET_CFG.host_all_targets[host]: + 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)) + 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(msvc_instance_VS2005) + # 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 msvc_instance_VS6 = MSVcTestCase._createDummyMSVCInstance('6.0', 'Develop', '.') - 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)) - path = os.path.join('.') - MSVcTestCase._createDummyFile(path, 'cl.exe') - result=check(msvc_instance_VS6) - # print("for:(%s, %s) got :%s"%(host, target, result)) - self.assertTrue(result, "Checking host: %s target: %s" % (host, target)) + for host in SCons.Tool.MSCommon.vc._LE2003_HOST_TARGET_CFG.host_all_hosts[native_host]: + for target in SCons.Tool.MSCommon.vc._LE2003_HOST_TARGET_CFG.host_all_targets[host]: + batarg, batfile, clpathcomps = SCons.Tool.MSCommon.vc._LE2003_HOST_TARGET_BATCHARG_BATCHFILE_CLPATHCOMPS[(host, target)] + # print("LE 7.1 Got: (%s, %s)"%(host,target)) + path = os.path.join('.', *clpathcomps) + MSVcTestCase._createDummyFile(path, batfile, add_bin=False) + MSVcTestCase._createDummyFile(path, 'cl.exe', add_bin=False) + result=check(msvc_instance_VS6) + # print("for:(%s, %s) got :%s"%(host, target, result)) + self.assertTrue(result, "Checking host: %s target: %s" % (host, target)) check = SCons.Tool.MSCommon.vc.get_host_target diff --git a/SCons/Tool/MSCommon/vs.py b/SCons/Tool/MSCommon/vs.py index 316d809b1b..6820ada089 100644 --- a/SCons/Tool/MSCommon/vs.py +++ b/SCons/Tool/MSCommon/vs.py @@ -194,8 +194,10 @@ def reset(self) -> None: # good money for in preference to whatever Microsoft makes available # for free. # -# If you update this list, update _VCVER and _VSPRODUCT_REGISTRY_VCDIR in -# Tool/MSCommon/vc.py, and the MSVC_VERSION documentation in Tool/msvc.xml. +# If you update this list, update: +# MSVS_PRODUCT_DEFINITIONS in Tool/MSCommon/MSVC/Config.py, +# _VCVER in Tool/MSCommon/vc.py, and MSVC_VERSION documentation +# in Tool/msvc.xml. SupportedVSList = [ # Visual Studio 2022 diff --git a/SCons/Tool/msvsTests.py b/SCons/Tool/msvsTests.py index 0e9a19fa6b..ef14d651de 100644 --- a/SCons/Tool/msvsTests.py +++ b/SCons/Tool/msvsTests.py @@ -579,7 +579,7 @@ def DummyQueryValue(key, value): def DummyExists(path) -> bool: return True -def DummyVsWhereExecutables(env=None): +def DummyVsWhereExecutables(vswhere_env=None): # not testing versions with vswhere, so return empty list return [] @@ -947,7 +947,7 @@ class msvsEmptyTestCase(msvsTestCase): SCons.Util.RegEnumKey = DummyEnumKey SCons.Util.RegEnumValue = DummyEnumValue SCons.Util.RegQueryValueEx = DummyQueryValue - SCons.Tool.MSCommon.vc._find_vswhere_executables = DummyVsWhereExecutables + SCons.Tool.MSCommon.vc._VSWhere.find_executables = DummyVsWhereExecutables os.path.exists = DummyExists # make sure all files exist :-) os.path.isfile = DummyExists # make sure all files are files :-) diff --git a/test/fixture/no_msvc/no_msvcs_sconstruct.py b/test/fixture/no_msvc/no_msvcs_sconstruct.py index 1abd10b143..ef199aab25 100644 --- a/test/fixture/no_msvc/no_msvcs_sconstruct.py +++ b/test/fixture/no_msvc/no_msvcs_sconstruct.py @@ -7,15 +7,13 @@ DefaultEnvironment(tools=[]) -def DummyVsWhereExecutables(env=None): +def DummyVsWhereExecutables(vswhere_env=None): # not testing versions with vswhere, so return empty list return [] -for key in SCons.Tool.MSCommon.vc._VSPRODUCT_REGISTRY_VCDIR: - SCons.Tool.MSCommon.vc._VSPRODUCT_REGISTRY_VCDIR[key] = [ - (False, False, SCons.Util.HKEY_LOCAL_MACHINE, r'') - ] +for detect_cfg in SCons.Tool.MSCommon.vc._VSDetectRegistry.DETECT_CONFIG.values(): + detect_cfg.vc_cfg.regkeys.clear() -SCons.Tool.MSCommon.vc._find_vswhere_executables = DummyVsWhereExecutables +SCons.Tool.MSCommon.vc._VSWhere.find_executables = DummyVsWhereExecutables 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 67856a61be..c21d5277ff 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,15 +7,13 @@ DefaultEnvironment(tools=[]) -def DummyVsWhereExecutables(env=None): +def DummyVsWhereExecutables(vswhere_env=None): # not testing versions with vswhere, so return empty list return [] -for key in SCons.Tool.MSCommon.vc._VSPRODUCT_REGISTRY_VCDIR: - SCons.Tool.MSCommon.vc._VSPRODUCT_REGISTRY_VCDIR[key] = [ - (False, False, SCons.Util.HKEY_LOCAL_MACHINE, r'') - ] +for detect_cfg in SCons.Tool.MSCommon.vc._VSDetectRegistry.DETECT_CONFIG.values(): + detect_cfg.vc_cfg.regkeys.clear() -SCons.Tool.MSCommon.vc._find_vswhere_executables = DummyVsWhereExecutables +SCons.Tool.MSCommon.vc._VSWhere.find_executables = DummyVsWhereExecutables 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 9491533ac1..ac2ea88543 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,15 +7,13 @@ DefaultEnvironment(tools=[]) -def DummyVsWhereExecutables(env=None): +def DummyVsWhereExecutables(vswhere_env=None): # not testing versions with vswhere, so return empty list return [] -for key in SCons.Tool.MSCommon.vc._VSPRODUCT_REGISTRY_VCDIR: - SCons.Tool.MSCommon.vc._VSPRODUCT_REGISTRY_VCDIR[key] = [ - (False, False, SCons.Util.HKEY_LOCAL_MACHINE, r'') - ] +for detect_cfg in SCons.Tool.MSCommon.vc._VSDetectRegistry.DETECT_CONFIG.values(): + detect_cfg.vc_cfg.regkeys.clear() -SCons.Tool.MSCommon.vc._find_vswhere_executables = DummyVsWhereExecutables +SCons.Tool.MSCommon.vc._VSWhere.find_executables = DummyVsWhereExecutables 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 94a302c901..3535ec952a 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,15 +7,13 @@ DefaultEnvironment(tools=[]) -def DummyVsWhereExecutables(env=None): +def DummyVsWhereExecutables(vswhere_env=None): # not testing versions with vswhere, so return empty list return [] -for key in SCons.Tool.MSCommon.vc._VSPRODUCT_REGISTRY_VCDIR: - SCons.Tool.MSCommon.vc._VSPRODUCT_REGISTRY_VCDIR[key] = [ - (False, False, SCons.Util.HKEY_LOCAL_MACHINE, r'') - ] +for detect_cfg in SCons.Tool.MSCommon.vc._VSDetectRegistry.DETECT_CONFIG.values(): + detect_cfg.vc_cfg.regkeys.clear() -SCons.Tool.MSCommon.vc._find_vswhere_executables = DummyVsWhereExecutables +SCons.Tool.MSCommon.vc._VSWhere.find_executables = DummyVsWhereExecutables 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 f78a656846..ca6d16a4e7 100644 --- a/test/fixture/no_msvc/no_msvcs_sconstruct_tools.py +++ b/test/fixture/no_msvc/no_msvcs_sconstruct_tools.py @@ -7,15 +7,13 @@ DefaultEnvironment(tools=[]) -def DummyVsWhereExecutables(env=None): +def DummyVsWhereExecutables(vswhere_env=None): # not testing versions with vswhere, so return empty list return [] -for key in SCons.Tool.MSCommon.vc._VSPRODUCT_REGISTRY_VCDIR: - SCons.Tool.MSCommon.vc._VSPRODUCT_REGISTRY_VCDIR[key] = [ - (False, False, SCons.Util.HKEY_LOCAL_MACHINE, r'') - ] +for detect_cfg in SCons.Tool.MSCommon.vc._VSDetectRegistry.DETECT_CONFIG.values(): + detect_cfg.vc_cfg.regkeys.clear() -SCons.Tool.MSCommon.vc._find_vswhere_executables = DummyVsWhereExecutables +SCons.Tool.MSCommon.vc._VSWhere.find_executables = DummyVsWhereExecutables 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 a3b25823a6..068fcb7898 100644 --- a/test/fixture/no_msvc/no_msvcs_sconstruct_version.py +++ b/test/fixture/no_msvc/no_msvcs_sconstruct_version.py @@ -7,15 +7,13 @@ DefaultEnvironment(tools=[]) -def DummyVsWhereExecutables(env=None): +def DummyVsWhereExecutables(vswhere_env=None): # not testing versions with vswhere, so return empty list return [] -for key in SCons.Tool.MSCommon.vc._VSPRODUCT_REGISTRY_VCDIR: - SCons.Tool.MSCommon.vc._VSPRODUCT_REGISTRY_VCDIR[key] = [ - (False, False, SCons.Util.HKEY_LOCAL_MACHINE, r'') - ] +for detect_cfg in SCons.Tool.MSCommon.vc._VSDetectRegistry.DETECT_CONFIG.values(): + detect_cfg.vc_cfg.regkeys.clear() -SCons.Tool.MSCommon.vc._find_vswhere_executables = DummyVsWhereExecutables +SCons.Tool.MSCommon.vc._VSWhere.find_executables = DummyVsWhereExecutables SCons.Tool.MSCommon.msvc_set_notfound_policy('error') env = SCons.Environment.Environment(MSVC_VERSION='14.3') diff --git a/test/fixture/no_msvc/no_regs_sconstruct.py b/test/fixture/no_msvc/no_regs_sconstruct.py index 31c51a5886..876e7c542a 100644 --- a/test/fixture/no_msvc/no_regs_sconstruct.py +++ b/test/fixture/no_msvc/no_regs_sconstruct.py @@ -7,9 +7,7 @@ DefaultEnvironment(tools=[]) -for key in SCons.Tool.MSCommon.vc._VSPRODUCT_REGISTRY_VCDIR: - SCons.Tool.MSCommon.vc._VSPRODUCT_REGISTRY_VCDIR[key] = [ - (False, False, SCons.Util.HKEY_LOCAL_MACHINE, r'') - ] +for detect_cfg in SCons.Tool.MSCommon.vc._VSDetectRegistry.DETECT_CONFIG.values(): + detect_cfg.vc_cfg.regkeys.clear() env = SCons.Environment.Environment() From 9b6d7f381c5143a662d696a9600abab262bdd005 Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Tue, 10 Oct 2023 14:03:48 -0400 Subject: [PATCH 14/24] Remove MSCommon/MSVC/Kind.py --- SCons/Tool/MSCommon/MSVC/Kind.py | 384 ------------------------------- 1 file changed, 384 deletions(-) delete mode 100644 SCons/Tool/MSCommon/MSVC/Kind.py diff --git a/SCons/Tool/MSCommon/MSVC/Kind.py b/SCons/Tool/MSCommon/MSVC/Kind.py deleted file mode 100644 index 9dbb371d6d..0000000000 --- a/SCons/Tool/MSCommon/MSVC/Kind.py +++ /dev/null @@ -1,384 +0,0 @@ -# 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 Config -from . import Registry -from . import Util - -from . import Dispatcher -Dispatcher.register_modulename(__name__) - - -BITFIELD_KIND_DEVELOP = 0b_1000 -BITFIELD_KIND_EXPRESS = 0b_0100 -BITFIELD_KIND_EXPRESS_WIN = 0b_0010 -BITFIELD_KIND_EXPRESS_WEB = 0b_0001 - -_BITFIELD_KIND_VSEXECUTABLE = (BITFIELD_KIND_DEVELOP, BITFIELD_KIND_EXPRESS) - -VCBinary = namedtuple("VCBinary", [ - 'program', - 'bitfield', -]) - -# IDE binaries - -IDE_PROGRAM_DEVENV_COM = VCBinary( - program='devenv.com', - bitfield=BITFIELD_KIND_DEVELOP, -) - -IDE_PROGRAM_MSDEV_COM = VCBinary( - program='msdev.com', - bitfield=BITFIELD_KIND_DEVELOP, -) - -IDE_PROGRAM_WDEXPRESS_EXE = VCBinary( - program='WDExpress.exe', - bitfield=BITFIELD_KIND_EXPRESS, -) - -IDE_PROGRAM_VCEXPRESS_EXE = VCBinary( - program='VCExpress.exe', - bitfield=BITFIELD_KIND_EXPRESS, -) - -IDE_PROGRAM_VSWINEXPRESS_EXE = VCBinary( - program='VSWinExpress.exe', - bitfield=BITFIELD_KIND_EXPRESS_WIN, -) - -IDE_PROGRAM_VWDEXPRESS_EXE = VCBinary( - program='VWDExpress.exe', - bitfield=BITFIELD_KIND_EXPRESS_WEB, -) - -# detection configuration - -VCDetectConfig = namedtuple("VCDetectConfig", [ - 'root', # relpath from pdir to vsroot - 'path', # vsroot to ide dir - 'programs', # ide binaries -]) - -# detected IDE binaries - -VCDetectedBinaries = namedtuple("VCDetectedBinaries", [ - '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 - 'vs_exec', # vs binary -]) - -def _msvc_dir_detect_binaries(vc_dir, detect): - - vs_exec = None - - vs_dir = os.path.join(vc_dir, detect.root) - ide_path = os.path.join(vs_dir, 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 - if vs_exec: - continue - if ide_program.bitfield not in _BITFIELD_KIND_VSEXECUTABLE: - continue - vs_exec = prog - - 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 = VCDetectedBinaries( - bitfields=bitfields, - have_dev=have_dev, - have_exp=have_exp, - have_exp_win=have_exp_win, - have_exp_web=have_exp_web, - vs_exec=vs_exec, - ) - - debug( - 'vs_dir=%s, dev=%s, exp=%s, exp_win=%s, exp_web=%s, vs_exec=%s, vc_dir=%s', - repr(vs_dir), - binaries_t.have_dev, binaries_t.have_exp, - binaries_t.have_exp_win, binaries_t.have_exp_web, - repr(binaries_t.vs_exec), - repr(vc_dir) - ) - - return vs_dir, binaries_t - -def msvc_dir_vswhere(vc_dir, detect_t): - - _, binaries_t = _msvc_dir_detect_binaries(vc_dir, detect_t) - - return binaries_t.vs_exec - -# 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_dir, vc_dir): - - is_buildtools = False - - do_once = True - while do_once: - do_once = False - - buildtools_file = os.path.join(vs_dir, _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_buildtools = True - - debug('is_buildtools=%s', is_buildtools) - return is_buildtools - -_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(vc_dir): - - have_uwp_amd64 = False - have_uwp_arm = False - - vcvars_file = os.path.join(vc_dir, r'vcvarsall.bat') - if os.path.exists(vcvars_file): - - vcvars_file = os.path.join(vc_dir, 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(vc_dir, 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'} - -def _msvc_dir_is_winsdk_only(vc_dir, msvc_version): - - # 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 - - 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_suffix = Registry.vstudio_sxs_vc7(msvc_version) - vc_qresults = [record[0] for record in Registry.microsoft_query_paths(vc_suffix)] - vc_regdir = vc_qresults[0] if vc_qresults else None - - if vc_regdir != vc_dir: - # registry vc path is not the current vc dir - - is_sdk = False - - debug( - 'is_sdk=%s, msvc_version=%s, vc_dir=%s, vc_regdir=%s', - is_sdk, repr(msvc_version), repr(vc_dir), repr(vc_regdir) - ) - - else: - # registry vc dir is the current vc root - - vs_suffix = Registry.vstudio_sxs_vs7(msvc_version) - vs_qresults = [record[0] for record in Registry.microsoft_query_paths(vs_suffix)] - vs_dir = vs_qresults[0] if vs_qresults else None - - is_sdk = bool(not vs_dir and vc_dir) - - debug( - 'is_sdk=%s, msvc_version=%s, vc_dir=%s, vs_dir=%s', - is_sdk, repr(msvc_version), repr(vc_dir), repr(vs_dir) - ) - - return is_sdk - -def msvc_dir_registry(vc_dir, detect_t, msvc_version, is_vcforpython, vs_version): - - vc_feature_map = {} - - vs_dir, binaries_t = _msvc_dir_detect_binaries(vc_dir, detect_t) - - if binaries_t.have_dev: - vs_component_def = Config.REGISTRY_COMPONENT_DEVELOP - elif binaries_t.have_exp: - vs_component_def = Config.REGISTRY_COMPONENT_EXPRESS - elif msvc_version == '9.0' and is_vcforpython: - vs_component_def = Config.REGISTRY_COMPONENT_PYTHON - elif binaries_t.have_exp_win: - vs_component_def = None - elif binaries_t.have_exp_web: - vs_component_def = None - elif _msvc_dir_is_winsdk_only(vc_dir, msvc_version): - vs_component_def = None - else: - vs_component_def = Config.REGISTRY_COMPONENT_CMDLINE - - if vs_component_def and msvc_version == '14.0': - - # VS2015: - # remap DEVELOP => ENTERPRISE, PROFESSIONAL, COMMUNITY - # remap CMDLINE => BUILDTOOLS [conditional] - # process EXPRESS - - if vs_component_def == Config.REGISTRY_COMPONENT_DEVELOP: - - for reg_component, reg_component_def in [ - ('community', Config.REGISTRY_COMPONENT_COMMUNITY), - ('professional', Config.REGISTRY_COMPONENT_PROFESSIONAL), - ('enterprise', Config.REGISTRY_COMPONENT_ENTERPRISE), - ]: - suffix = Registry.devdiv_vs_servicing_component(vs_version, reg_component) - qresults = Registry.microsoft_query_keys(suffix, usrval=reg_component_def) - if not qresults: - continue - vs_component_def = qresults[0][-1] - break - - elif vs_component_def == Config.REGISTRY_COMPONENT_CMDLINE: - - if _vs_buildtools_2015(vs_dir, vc_dir): - vs_component_def = Config.REGISTRY_COMPONENT_BUILDTOOLS - - elif vs_component_def == Config.REGISTRY_COMPONENT_EXPRESS: - - have_uwp_amd64, have_uwp_arm = _vs_express_2015(vc_dir) - - uwp_target_is_supported = { - 'x86': True, - 'amd64': have_uwp_amd64, - 'arm': have_uwp_arm, - } - - vc_feature_map['uwp_target_is_supported'] = uwp_target_is_supported - - debug( - 'msvc_version=%s, vs_component=%s, vc_dir=%s, vc_feature_map=%s', - repr(msvc_version), - repr(vs_component_def.vs_componentid_def.vs_component_id) if vs_component_def else None, - repr(vc_dir), - repr(vc_feature_map), - ) - - return vs_component_def, vs_dir, binaries_t.vs_exec, vc_feature_map - From 0d687865347a1fa33404ac544b32b03c6b5261c8 Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Wed, 11 Oct 2023 10:25:30 -0400 Subject: [PATCH 15/24] Update msvs/msvc query implementation. Internal changes: * Move msvs and msvc instance query methods to appropriate classes. * Rework msvc action implementation and logging messages. * Move new internal functions with cached return values to class _Util. * Add return type annotations for None and bool. --- CHANGES.txt | 10 +- RELEASE.txt | 10 +- SCons/Tool/MSCommon/vc.py | 387 +++++++++++++++++++++----------------- 3 files changed, 225 insertions(+), 182 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 349f9e8a26..88f00e4d7f 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -42,6 +42,11 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER registry query that returns a path that does not exist. Multiple invocation paths were not prepared to handle the MissingConfiguration exception. The MissingConfiguration exception type was removed. + - The MSCommon module import was changed from a relative import to a top-level + absolute import in the following Microsoft tools: midl, mslib, mslink, mssdk, msvc, + msvs. Moving any of these tools that used relative imports to the scons site tools + folder would fail on import (i.e., the relative import paths become invalid when + moved). - For msvc version specifications without an 'Exp' suffix, an express installation is used when no other edition is detected for the msvc version. - VS2015 Express (14.1Exp) may not have been detected. The registry keys written @@ -60,11 +65,6 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER 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. - - The MSCommon module import was changed from a relative import to a top-level - absolute import in the following Microsoft tools: midl, mslib, mslink, mssdk, msvc, - msvs. Moving any of these tools that used relative imports to the scons site tools - folder would fail on import (i.e., the relative import paths become invalid when - moved). - 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 diff --git a/RELEASE.txt b/RELEASE.txt index a79f24e2b6..9cd7f65c3e 100644 --- a/RELEASE.txt +++ b/RELEASE.txt @@ -148,17 +148,17 @@ IMPROVEMENTS ------------ - Now tries to find mingw if it comes from Chocolatey install of msys2. +- MSVC: Module imports were changed from a relative import to a top-level + absolute import in the following Microsoft tools: midl, mslib, mslink, mssdk, msvc, + msvs. Moving any of these tools that used relative imports to the scons site tools + folder would fail on import (i.e., the relative import paths become invalid when + moved). - MSVC: VS2015 Express (14.1Exp) 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. - MSVC: 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. -- MSVC: Module imports were changed from a relative import to a top-level - absolute import in the following Microsoft tools: midl, mslib, mslink, mssdk, msvc, - msvs. Moving any of these tools that used relative imports to the scons site tools - folder would fail on import (i.e., the relative import paths become invalid when - moved). PACKAGING --------- diff --git a/SCons/Tool/MSCommon/vc.py b/SCons/Tool/MSCommon/vc.py index 63be03a44d..b6d180f3ad 100644 --- a/SCons/Tool/MSCommon/vc.py +++ b/SCons/Tool/MSCommon/vc.py @@ -606,6 +606,48 @@ def _make_target_host_map(all_hosts, host_all_targets_map): _CL_EXE_NAME = 'cl.exe' +# internal utilities + +class _Util(AutoInitialize): + + debug_extra = None + + # cached values + _normalized_path = {} + _path_exists = {} + + @classmethod + def reset(cls) -> None: + cls._normalized_path = {} + cls._path_exists = {} + + @classmethod + def _initialize(cls) -> None: + cls.debug_extra = debug_extra(cls) + cls.reset() + + # normalized paths + + @classmethod + def normalize_path(cls, pval): + rval = cls._normalized_path.get(pval, UNDEFINED) + if rval == UNDEFINED: + rval = MSVC.Util.normalize_path(pval) + cls._normalized_path[pval] = rval + debug('norm=%s, pval=%s', repr(rval), repr(pval), extra=cls.debug_extra) + return rval + + # path existence + + @classmethod + def path_exists(cls, pval): + rval = cls._path_exists.get(pval, UNDEFINED) + if rval == UNDEFINED: + rval = os.path.exists(pval) + cls._path_exists[pval] = rval + debug('exists=%s, pval=%s', rval, repr(pval), extra=cls.debug_extra) + return rval + 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' @@ -1142,32 +1184,6 @@ def factory( } -# normalized paths - -_normalize_path = {} - -def normalize_path(pval): - global _normalize_path - rval = _normalize_path.get(pval, UNDEFINED) - if rval == UNDEFINED: - rval = MSVC.Util.normalize_path(pval) - _normalize_path[pval] = rval - # debug('pval=%s, orig=%s', repr(rval), repr(pval)) - return rval - -# path existence - -_path_exists = {} - -def path_exists(pval): - global _path_exists - rval = _path_exists.get(pval, UNDEFINED) - if rval == UNDEFINED: - rval = os.path.exists(pval) - _path_exists[pval] = rval - # debug('exists=%s, pval=%s', rval, repr(pval)) - return rval - def msvc_version_to_maj_min(msvc_version): msvc_version_numeric = get_msvc_version_numeric(msvc_version) @@ -1202,9 +1218,9 @@ def msvc_find_vswhere(env=None): # NB: this gets called from testsuite on non-Windows platforms. # Whether that makes sense or not, don't break it for those. vswhere_env = env.subst('$VSWHERE') if env and 'VSWHERE' in env else None - vswhere_execs = _VSWhere.find_executables(vswhere_env) - if vswhere_execs: - vswhere_path = vswhere_execs[0].path + vswhere_executables = _VSWhere.find_executables(vswhere_env) + if vswhere_executables: + vswhere_path = vswhere_executables[0].path else: vswhere_path = None debug('vswhere_path=%s', vswhere_path) @@ -1229,7 +1245,7 @@ class _VSWhere(AutoInitialize): vswhere_executables = [] @classmethod - def _cmdline(cls): + def _cmdline(cls) -> None: vswhere, option = _Options.vswhere() if vswhere: vswhere_exec = cls._user_path(vswhere, option) @@ -1240,9 +1256,9 @@ def _cmdline(cls): @classmethod def _setup(cls) -> None: for pval in VSWHERE_PATHS: - if not path_exists(pval): + if not _Util.path_exists(pval): continue - vswhere_exec = cls.VSWhereExecutable(path=pval, norm=normalize_path(pval)) + vswhere_exec = cls.VSWhereExecutable(path=pval, norm=_Util.normalize_path(pval)) cls.vswhere_executables.append(vswhere_exec) debug('vswhere_executables=%s', cls.vswhere_executables, extra=cls.debug_extra) cls._cmdline() @@ -1270,7 +1286,7 @@ def _user_path(cls, pval, source): vswhere_exec = None if pval: - if not path_exists(pval): + if not _Util.path_exists(pval): warn_msg = f'vswhere executable path not found: {pval!r} ({source})' debug(warn_msg, extra=cls.debug_extra) @@ -1278,7 +1294,7 @@ def _user_path(cls, pval, source): else: - norm = normalize_path(pval) + norm = _Util.normalize_path(pval) tail = os.path.split(norm)[-1] if tail != cls.VSWHERE_EXE: @@ -1649,19 +1665,19 @@ def msvs_channel_key( class MSVSBase(_MSVSBase): - def _is_express(vs_component_def): + def _is_express(vs_component_def) -> bool: vs_componentid_def = vs_component_def.vs_componentid_def is_express = bool(vs_componentid_def == MSVC.Config.MSVS_COMPONENTID_EXPRESS) return is_express @staticmethod - def _is_buildtools(vs_component_def): + def _is_buildtools(vs_component_def) -> bool: vs_componentid_def = vs_component_def.vs_componentid_def is_buildtools = bool(vs_componentid_def == MSVC.Config.MSVS_COMPONENTID_BUILDTOOLS) return is_buildtools @staticmethod - def _is_vcforpython(vs_component_def): + def _is_vcforpython(vs_component_def) -> bool: vs_componentid_def = vs_component_def.vs_componentid_def is_vcforpython = bool(vs_componentid_def == MSVC.Config.MSVS_COMPONENTID_PYTHON) return is_vcforpython @@ -1728,7 +1744,7 @@ def factory( vs_component_def=vs_component_def, vs_sequence_nbr=vs_sequence_nbr, vs_dir=vs_dir, - vs_dir_norm=normalize_path(vs_dir), + vs_dir_norm=_Util.normalize_path(vs_dir), vs_version=vs_version, is_express=cls._is_express(vs_component_def), is_buildtools=cls._is_buildtools(vs_component_def), @@ -1804,9 +1820,9 @@ def factory( id_str=id_str, msvs_base=msvs_base, vs_executable=vs_executable, - vs_executable_norm=normalize_path(vs_executable), + vs_executable_norm=_Util.normalize_path(vs_executable), vs_script=vs_script, - vs_script_norm=normalize_path(vs_script), + vs_script_norm=_Util.normalize_path(vs_script), vc_version_def=vc_version_def, ) @@ -2039,6 +2055,46 @@ def _debug_dump(self) -> None: extra=self.debug_extra, ) + def _msvs_instances_internal( + self, *, + vs_product_def=None, + vs_channel_def=None, + vs_componentid_def=None, + vs_sequence_nbr=None, + ): + + if not vs_channel_def: + vs_channel_def = _VSChannel.get_default_channel() + + if vs_product_def: + + query_key = _VSKeys.msvs_edition_key( + vs_product_def=vs_product_def, + vs_channel_def=vs_channel_def, + vs_componentid_def=vs_componentid_def, + vs_sequence_nbr=vs_sequence_nbr + ) + + msvs_instances = self.msvs_edition_instances_map.get(query_key, []) + + else: + + query_key = _VSKeys.msvs_channel_key( + vs_channel_def=vs_channel_def, + vs_componentid_def=vs_componentid_def, + ) + + msvs_instances = self.msvs_channel_instances_map.get(query_key, []) + + debug( + 'query_key=%s, n_msvs_instances=%s', + repr(query_key.serialize()), + repr(len(msvs_instances)), + extra=self.debug_extra, + ) + + return msvs_instances, query_key + _MSVCInstance = namedtuple('_MSVCInstance', [ 'id_str', 'msvs_base', @@ -2077,7 +2133,7 @@ def factory( vc_version_def=vc_version_def, vc_feature_map=vc_feature_map, vc_dir=vc_dir, - vc_dir_norm=normalize_path(vc_dir), + vc_dir_norm=_Util.normalize_path(vc_dir), is_sdkversion_supported=cls._is_sdkversion_supported(msvs_base), ) @@ -2096,7 +2152,7 @@ def msvs_instance(self): return self.msvs_base.msvs_instance @staticmethod - def _is_sdkversion_supported(msvs_base): + def _is_sdkversion_supported(msvs_base) -> bool: vs_componentid_def = msvs_base.vs_component_def.vs_componentid_def vs_product_numeric = msvs_base.vs_product_def.vs_product_numeric @@ -2120,7 +2176,7 @@ def _is_sdkversion_supported(msvs_base): return is_supported - def skip_uwp_target(self, env): + def skip_uwp_target(self, env) -> bool: skip = False if self.vc_feature_map: target_arch = env.get('TARGET_ARCH') @@ -2133,7 +2189,7 @@ def skip_uwp_target(self, env): ) return skip - def is_uwp_target_supported(self, target_arch=None): + def is_uwp_target_supported(self, target_arch=None) -> bool: vs_componentid_def = self.msvs_base.vs_component_def.vs_componentid_def vs_product_numeric = self.msvs_base.vs_product_def.vs_product_numeric @@ -2173,15 +2229,15 @@ def is_uwp_target_supported(self, target_arch=None): # convenience properties: reduce member lookup chain for readability @property - def is_express(self): + def is_express(self) -> bool: return self.msvs_base.is_express @property - def is_buildtools(self): + def is_buildtools(self) -> bool: return self.msvs_base.is_buildtools @property - def is_vcforpython(self): + def is_vcforpython(self) -> bool: return self.msvs_base.is_vcforpython @property @@ -2399,6 +2455,46 @@ def _debug_dump(self) -> None: extra=self.debug_extra, ) + def _msvc_instances_internal( + self, *, + vs_product_def=None, + vs_channel_def=None, + vs_componentid_def=None, + vs_sequence_nbr=None, + ): + + if not vs_channel_def: + vs_channel_def = _VSChannel.get_default_channel() + + if vs_product_def: + + query_key = _VSKeys.msvs_edition_key( + vs_product_def=vs_product_def, + vs_channel_def=vs_channel_def, + vs_componentid_def=vs_componentid_def, + vs_sequence_nbr=vs_sequence_nbr + ) + + msvc_instances = self.msvs_edition_instances_map.get(query_key, []) + + else: + + query_key = _VSKeys.msvs_channel_key( + vs_channel_def=vs_channel_def, + vs_componentid_def=vs_componentid_def, + ) + + msvc_instances = self.msvs_channel_instances_map.get(query_key, []) + + debug( + 'query_key=%s, n_msvc_instances=%s', + repr(query_key.serialize()), + repr(len(msvc_instances)), + extra=self.debug_extra, + ) + + return msvc_instances, query_key + _MSVSManager = namedtuple('_MSVSManager', [ 'msvc_installed', 'msvs_installed', @@ -2431,86 +2527,52 @@ def factory(cls, msvc_installed, msvs_installed): def query_msvs_instances( self, *, - vs_product_def=None, - vs_channel_def=None, - vs_componentid_def=None, - vs_sequence_nbr=None, + msvc_version, ): - # TODO(JCB): take strings as input rather than objects - # TODO(JCB): error checking combinations (ignored, etc) + # TODO(JCB): temporary placeholder - if not vs_channel_def: - vs_channel_def = _msvs_channel_default() - - if vs_product_def: + prefix, suffix = MSVC.Util.get_msvc_version_prefix_suffix(msvc_version) - query_key = _VSKeys.msvs_edition_key( - vs_product_def=vs_product_def, - vs_channel_def=vs_channel_def, - vs_componentid_def=vs_componentid_def, - vs_sequence_nbr=vs_sequence_nbr - ) + vs_product_def = MSVC.Config.MSVC_VERSION_INTERNAL[prefix] - msvs_instances = self.msvs_installed.msvs_edition_instances_map.get(query_key, []) + vs_channel_def = None + if suffix == 'Exp': + vs_componentid_def = MSVC.Config.MSVS_COMPONENTID_EXPRESS else: + vs_componentid_def = None - query_key = _VSKeys.msvs_channel_key( - vs_channel_def=vs_channel_def, - vs_componentid_def=vs_componentid_def, - ) - - msvs_instances = self.msvs_installed.msvs_channel_instances_map.get(query_key, []) - - debug( - 'query_key=%s, n_msvs_instances=%s', - repr(query_key.serialize()), - repr(len(msvs_instances)), - extra=self.debug_extra, + msvs_instances, query_key = self.msvs_installed._msvs_instances_internal( + vs_product_def=vs_product_def, + vs_channel_def=vs_channel_def, + vs_componentid_def=vs_componentid_def, ) return msvs_instances, query_key def query_msvc_instances( self, *, - vs_product_def=None, - vs_channel_def=None, - vs_componentid_def=None, - vs_sequence_nbr=None, + msvc_version, ): - # TODO(JCB): take strings as input rather than objects - # TODO(JCB): error checking combinations (ignored, etc) - - if not vs_channel_def: - vs_channel_def = _VSChannel.get_default_channel() + # TODO(JCB): temporary placeholder - if vs_product_def: + prefix, suffix = MSVC.Util.get_msvc_version_prefix_suffix(msvc_version) - query_key = _VSKeys.msvs_edition_key( - vs_product_def=vs_product_def, - vs_channel_def=vs_channel_def, - vs_componentid_def=vs_componentid_def, - vs_sequence_nbr=vs_sequence_nbr - ) + vs_product_def = MSVC.Config.MSVC_VERSION_INTERNAL[prefix] - msvc_instances = self.msvc_installed.msvs_edition_instances_map.get(query_key, []) + vs_channel_def = None + if suffix == 'Exp': + vs_componentid_def = MSVC.Config.MSVS_COMPONENTID_EXPRESS else: + vs_componentid_def = None - query_key = _VSKeys.msvs_channel_key( - vs_channel_def=vs_channel_def, - vs_componentid_def=vs_componentid_def, - ) - - msvc_instances = self.msvc_installed.msvs_channel_instances_map.get(query_key, []) - - debug( - 'query_key=%s, n_msvc_instances=%s', - repr(query_key.serialize()), - repr(len(msvc_instances)), - extra=self.debug_extra, + msvc_instances, query_key = self.msvc_installed._msvc_instances_internal( + vs_product_def=vs_product_def, + vs_channel_def=vs_channel_def, + vs_componentid_def=vs_componentid_def, ) return msvc_instances, query_key @@ -2623,7 +2685,8 @@ def call_reset_funcs(cls) -> None: func() @classmethod - def _filter_vswhere_paths(cls, vswhere_executables): + def _filter_vswhere_paths(cls, vswhere_env): + vswhere_executables = _VSWhere.find_executables(vswhere_env) vswhere_paths = [ vswhere_exec.norm for vswhere_exec in vswhere_executables @@ -2704,12 +2767,10 @@ def _msvc_instance_toolsets(cls, msvc_instance): @classmethod def detect(cls, vswhere_env): - vswhere_executables = _VSWhere.find_executables(vswhere_env) - num_instances = len(cls.msvc_instances) num_new_instances = 0 - vswhere_paths = cls._filter_vswhere_paths(vswhere_executables) + vswhere_paths = cls._filter_vswhere_paths(vswhere_env) if vswhere_paths: num_beg_instances = num_instances @@ -2737,10 +2798,10 @@ def detect(cls, vswhere_env): #print(json.dumps(instance, indent=4, sort_keys=True)) vs_dir = instance.get('installationPath') - if not vs_dir or not path_exists(vs_dir): + if not vs_dir or not _Util.path_exists(vs_dir): continue - vs_norm = normalize_path(vs_dir) + vs_norm = _Util.normalize_path(vs_dir) if vs_norm in cls.vs_dir_seen: continue @@ -2756,7 +2817,7 @@ def detect(cls, vswhere_env): cls.vs_dir_seen.add(vs_norm) vc_dir = os.path.join(vs_dir, 'VC') - if not path_exists(vc_dir): + if not _Util.path_exists(vc_dir): continue vs_major = vs_version.split('.')[0] @@ -2894,7 +2955,7 @@ def reset(cls) -> None: _VS2015BT_VCVARS_STOP = re.compile(r'^\s*[:]Setup_VS\s*$', re.IGNORECASE) @classmethod - def _vs_buildtools_2015_vcvars(cls, vcvars_file): + def _vs_buildtools_2015_vcvars(cls, vcvars_file) -> bool: have_buildtools_vcvars = False with open(vcvars_file) as fh: for line in fh: @@ -2906,7 +2967,7 @@ def _vs_buildtools_2015_vcvars(cls, vcvars_file): return have_buildtools_vcvars @classmethod - def _vs_buildtools_2015(cls, vs_dir, vc_dir): + def _vs_buildtools_2015(cls, vs_dir, vc_dir) -> bool: is_buildtools = False @@ -2947,7 +3008,7 @@ def _vs_buildtools_2015(cls, vs_dir, vc_dir): _VS2015EXP_VCVARS_STOP = re.compile(r'^\s*[:]GetVSCommonToolsDir\s*$', re.IGNORECASE) @classmethod - def _vs_express_2015_vcvars(cls, vcvars_file): + def _vs_express_2015_vcvars(cls, vcvars_file) -> bool: n_libpath = 0 with open(vcvars_file) as fh: for line in fh: @@ -2955,7 +3016,7 @@ def _vs_express_2015_vcvars(cls, vcvars_file): n_libpath += 1 elif cls._VS2015EXP_VCVARS_STOP.match(line): break - have_uwp_fix = n_libpath >= 2 + have_uwp_fix = bool(n_libpath >= 2) return have_uwp_fix @classmethod @@ -2987,7 +3048,7 @@ def _vs_express_2015(cls, vc_dir): _REGISTRY_WINSDK_VERSIONS = {'10.0', '9.0'} @classmethod - def _msvc_dir_is_winsdk_only(cls, vc_dir, msvc_version): + def _msvc_dir_is_winsdk_only(cls, vc_dir, msvc_version) -> bool: # detect winsdk-only installations # @@ -3189,7 +3250,7 @@ def detect(cls): debug('vc dir does not exist. (ignoring)', repr(vc_dir), extra=cls.debug_extra) continue - vc_norm = normalize_path(vc_dir) + vc_norm = _Util.normalize_path(vc_dir) if vc_norm in cls.vc_dir_seen: continue cls.vc_dir_seen.add(vc_norm) @@ -3336,23 +3397,6 @@ def vs_detect(env=None): msvs_manager = _VSDetect.detect(vswhere_env) return msvs_manager -def _query_key_components(msvc_version, env=None): - - # TODO(JCB): temporary placeholder - - prefix, suffix = MSVC.Util.get_msvc_version_prefix_suffix(msvc_version) - - vs_product_def = MSVC.Config.MSVC_VERSION_INTERNAL[prefix] - - vs_channel_def = None - - if suffix == 'Exp': - vs_componentid_def = MSVC.Config.MSVS_COMPONENTID_EXPRESS - else: - vs_componentid_def = None - - return vs_product_def, vs_componentid_def, vs_channel_def - def _find_msvc_instance(msvc_version, env=None): msvc_instance = None @@ -3365,13 +3409,7 @@ def _find_msvc_instance(msvc_version, env=None): vs_manager = vs_detect(env) - vs_product_def, vs_componentid_def, vs_channel_def = _query_key_components(msvc_version, env) - - msvc_instances, query_key = vs_manager.query_msvc_instances( - vs_product_def=vs_product_def, - vs_componentid_def=vs_componentid_def, - vs_channel_def=vs_channel_def, - ) + msvc_instances, query_key = vs_manager.query_msvc_instances(msvc_version=msvc_version) msvc_instance = msvc_instances[0] if msvc_instances else None @@ -3729,6 +3767,7 @@ def get_installed_vcs(env=None): def reset_installed_vcs() -> None: """Make it try again to find VC. This is just for the tests.""" _reset_installed_vcs() + _Util.reset() _VSWhere.reset() _VSChannel.reset() _VSDetect.reset() @@ -3995,14 +4034,16 @@ def msvc_find_valid_batch_script(env, version): return d -MSVCAction = namedtuple("MSVCAction", [ - 'action', -]) +class _MSVCAction: -MSVC_ACTION_SCRIPT = MSVCAction(action='SCRIPT') -MSVC_ACTION_SELECT = MSVCAction(action='SELECT') -MSVC_ACTION_SETTINGS = MSVCAction(action='SETTINGS') -MSVC_ACTION_BYPASS = MSVCAction(action='BYPASS') + MSVCAction = namedtuple("MSVCAction", [ + 'label', + ]) + + SCRIPT = MSVCAction(label='script') + SELECT = MSVCAction(label='select') + SETTINGS = MSVCAction(label='settings') + BYPASS = MSVCAction(label='bypass') def _msvc_action(env): @@ -4023,34 +4064,36 @@ def _msvc_action(env): # use script defined if SCons.Util.is_String(use_script): # use_script is string, use_settings ignored - msvc_action = MSVC_ACTION_SCRIPT + msvc_action = _MSVCAction.SCRIPT elif not use_script: # use_script eval false, use_settings ignored - msvc_action = MSVC_ACTION_BYPASS + msvc_action = _MSVCAction.BYPASS else: # use script eval true, use_settings ignored - msvc_action = MSVC_ACTION_SELECT + msvc_action = _MSVCAction.SELECT elif use_settings is not None: # use script undefined, use_settings defined and not None (type checked) - msvc_action = MSVC_ACTION_SETTINGS + msvc_action = _MSVCAction.SETTINGS else: # use script undefined, use_settings undefined or None - msvc_action = MSVC_ACTION_SELECT + msvc_action = _MSVCAction.SELECT if not msvc_action: errmsg = 'msvc_action is undefined' debug('MSVCInternalError: %s', errmsg) raise MSVCInternalError(errmsg) + debug('msvc_action=%s', repr(msvc_action.label)) + return msvc_action, use_script, use_settings -def msvc_setup_env(env): +def msvc_setup_env(env) -> None: debug('called') version = get_default_version(env) if version is None: if not msvc_setup_env_user(env): MSVC.SetupEnvDefault.set_nodefault() - return None + return # XXX: we set-up both MSVS version for backward # compatibility with the msvs tool @@ -4059,35 +4102,35 @@ def msvc_setup_env(env): env['MSVS'] = {} msvc_action, use_script, use_settings = _msvc_action(env) - if msvc_action == MSVC_ACTION_SCRIPT: + if msvc_action == _MSVCAction.SCRIPT: use_script = use_script.strip() if not os.path.exists(use_script): error_msg = f'Script specified by MSVC_USE_SCRIPT not found: {use_script!r}' debug(error_msg) raise MSVCScriptNotFound(error_msg) args = env.subst('$MSVC_USE_SCRIPT_ARGS') - debug('use_script 1 %s %s', repr(use_script), repr(args)) + debug('msvc_action=%s, script=%s, args=%s', repr(msvc_action.label), repr(use_script), repr(args)) d = script_env(env, use_script, args) - elif msvc_action == MSVC_ACTION_SELECT: + elif msvc_action == _MSVCAction.SELECT: d = msvc_find_valid_batch_script(env, version) - debug('use_script 2 %s', d) + debug('msvc_action=%s, d=%s', repr(msvc_action.label), d) if not d: - return d - elif msvc_action == MSVC_ACTION_SETTINGS: + return + elif msvc_action == _MSVCAction.SETTINGS: if not SCons.Util.is_Dict(use_settings): error_msg = f'MSVC_USE_SETTINGS type error: expected a dictionary, found {type(use_settings).__name__}' debug(error_msg) raise MSVCUseSettingsError(error_msg) d = use_settings - debug('use_settings %s', d) - elif msvc_action == MSVC_ACTION_BYPASS: - debug('MSVC_USE_SCRIPT set to False') + debug('msvc_action=%s, d=%s', repr(msvc_action.label), d) + elif msvc_action == _MSVCAction.BYPASS: + debug('msvc_action=%s', repr(msvc_action.label)) warn_msg = "MSVC_USE_SCRIPT set to False, assuming environment set correctly." SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, warn_msg) - return None + return else: - action = msvc_action.action if msvc_action else None - errmsg = f'unhandled msvc_action ({action!r})' + label = msvc_action.label if msvc_action else None + errmsg = f'unhandled msvc_action ({label!r})' debug('MSVCInternalError: %s', errmsg) raise MSVCInternalError(errmsg) @@ -4118,7 +4161,7 @@ def msvc_setup_env(env): debug(warn_msg, propose) SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, warn_msg % propose) -def msvc_exists(env=None, version=None): +def msvc_exists(env=None, version=None) -> bool: vcs = get_installed_vcs(env) if version is None: rval = len(vcs) > 0 @@ -4127,7 +4170,7 @@ def msvc_exists(env=None, version=None): debug('version=%s, return=%s', repr(version), rval) return rval -def msvc_setup_env_user(env=None): +def msvc_setup_env_user(env=None) -> bool: rval = False if env: @@ -4161,7 +4204,7 @@ def msvc_setup_env_user(env=None): debug('return=%s', rval) return rval -def msvc_setup_env_tool(env=None, version=None, tool=None): +def msvc_setup_env_tool(env=None, version=None, tool=None) -> bool: MSVC.SetupEnvDefault.register_tool(env, tool, msvc_exists) rval = False if not rval and msvc_exists(env, version): @@ -4392,7 +4435,7 @@ def msvc_query_version_toolset(version=None, prefer_newest: bool=True): def _verify() -> None: - def _compare_product_sets(set_config, set_local, label): + def _compare_product_sets(set_config, set_local, label) -> None: diff = set_config - set_local if diff: keys = ', '.join([repr(s) for s in sorted(diff, reverse=True)]) From f89eb5aa22b9f0ea092ea60f3f0589dd7062650d Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Wed, 11 Oct 2023 10:35:59 -0400 Subject: [PATCH 16/24] Remove unnecessary imports from MSVC/Config.py --- SCons/Tool/MSCommon/MSVC/Config.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/SCons/Tool/MSCommon/MSVC/Config.py b/SCons/Tool/MSCommon/MSVC/Config.py index cc88827e69..e978d3a134 100644 --- a/SCons/Tool/MSCommon/MSVC/Config.py +++ b/SCons/Tool/MSCommon/MSVC/Config.py @@ -25,14 +25,10 @@ Constants and initialized data structures for Microsoft Visual C/C++. """ -import os - from collections import ( namedtuple, ) -import SCons.Util - from . import Util from .Exceptions import ( From 98f6b3f68d1a28ebc66c7c69c4a5449e86f712f0 Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Wed, 11 Oct 2023 13:37:55 -0400 Subject: [PATCH 17/24] Move AutoInitialize from common.py to MSVC/Util.py and fix log record prefix handling for debug output to stdout. --- SCons/Tool/MSCommon/MSVC/Util.py | 9 +++++++++ SCons/Tool/MSCommon/common.py | 17 +++++++---------- SCons/Tool/MSCommon/vc.py | 3 ++- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/SCons/Tool/MSCommon/MSVC/Util.py b/SCons/Tool/MSCommon/MSVC/Util.py index 345922c334..02e061b9f1 100644 --- a/SCons/Tool/MSCommon/MSVC/Util.py +++ b/SCons/Tool/MSCommon/MSVC/Util.py @@ -35,6 +35,15 @@ from . import Config +# class utilities + +class AutoInitialize: + # Automatically call _initialize classmethod upon class definition completion. + 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:') diff --git a/SCons/Tool/MSCommon/common.py b/SCons/Tool/MSCommon/common.py index b98c248939..2b8c67b213 100644 --- a/SCons/Tool/MSCommon/common.py +++ b/SCons/Tool/MSCommon/common.py @@ -40,13 +40,6 @@ class MSVCCacheInvalidWarning(SCons.Warnings.WarningOnByDefault): pass -class AutoInitialize: - # Automatically call _initialize classmethod upon class definition completion. - def __init_subclass__(cls, **kwargs): - super().__init_subclass__(**kwargs) - if hasattr(cls, '_initialize') and callable(getattr(cls, '_initialize', None)): - cls._initialize() - # SCONS_MSCOMMON_DEBUG is internal-use so undocumented: # set to '-' to print to console, else set to filename to log to LOGFILE = os.environ.get('SCONS_MSCOMMON_DEBUG') @@ -105,8 +98,11 @@ class _CustomFormatter(logging.Formatter): ': %(message)s' ) - def __init__(self): + def __init__(self, log_prefix): super().__init__() + if log_prefix: + self.log_format = log_prefix + self.log_format + self.log_format_classname = log_prefix + self.log_format_classname log_record = logging.LogRecord( '', # name (str) 0, # level (int) @@ -129,11 +125,12 @@ def format(self, record): return formatter.format(record) if LOGFILE == '-': - log_format = 'debug: ' + log_format + log_prefix = 'debug: ' log_handler = logging.StreamHandler(sys.stdout) else: + log_prefix = '' log_handler = logging.FileHandler(filename=LOGFILE) - log_formatter = _CustomFormatter() + log_formatter = _CustomFormatter(log_prefix) log_handler.setFormatter(log_formatter) logger = logging.getLogger(name=__name__) logger.setLevel(level=logging.DEBUG) diff --git a/SCons/Tool/MSCommon/vc.py b/SCons/Tool/MSCommon/vc.py index b6d180f3ad..e2ab747e0e 100644 --- a/SCons/Tool/MSCommon/vc.py +++ b/SCons/Tool/MSCommon/vc.py @@ -60,11 +60,12 @@ debug, debug_extra, DEBUG_ENABLED, - AutoInitialize, ) from . import MSVC +from .MSVC.Util import AutoInitialize + from .MSVC.Exceptions import ( VisualCException, MSVCInternalError, From 1563ae0f6559209d5c4498e9d3b4f880f125112a Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Wed, 11 Oct 2023 16:07:22 -0400 Subject: [PATCH 18/24] Reorder changes and release blurbs based on sequence of smaller PRs. --- CHANGES.txt | 14 +++++++------- RELEASE.txt | 12 ++++++------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 88f00e4d7f..cd5722b6ef 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -47,6 +47,13 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER msvs. Moving any of these tools that used relative imports to the scons site tools folder would fail on import (i.e., the relative import paths become invalid when moved). + - The detection of the msvc compiler executable (cl.exe) has been modified. The + compiler detection no longer considers the host os environment path. In addition, + existence of the msvc compiler executable is checked in the detection dictionary + and the scons ENV path before the detection dictionary is merged into the scons + ENV. Different warnings are produced when the msvc compiler is not detected in the + detection dictionary based on whether or not an msvc compiler was detected in the + scons ENV path (i.e., already exists in the user's ENV path prior to detection). - For msvc version specifications without an 'Exp' suffix, an express installation is used when no other edition is detected for the msvc version. - VS2015 Express (14.1Exp) may not have been detected. The registry keys written @@ -88,13 +95,6 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER of the results from all vswhere executables). There is a single invocation for each vswhere executable that processes the output for all installations. Prior implementations called the vswhere executable for each supported msvc version. - - The detection of the msvc compiler executable (cl.exe) has been modified. The - compiler detection no longer considers the host os environment path. In addition, - existence of the msvc compiler executable is checked in the detection dictionary - and the scons ENV path before the detection dictionary is merged into the scons - ENV. Different warnings are produced when the msvc compiler is not detected in the - detection dictionary based on whether or not an msvc compiler was detected in the - scons ENV path (i.e., already exists in the user's ENV path prior to detection). - Previously, the installed vcs list was constructed once and cached at runtime. If a vswhere executable was specified via the construction variable VSWHERE and found additional msvc installations, the new installations were not reflected in the diff --git a/RELEASE.txt b/RELEASE.txt index 9cd7f65c3e..caee10ad69 100644 --- a/RELEASE.txt +++ b/RELEASE.txt @@ -59,14 +59,14 @@ CHANGED/ENHANCED EXISTING FUNCTIONALITY batch file exists but an individual msvc toolset may not support the host/target architecture combination. For example, when using VS2022 on arm64, the arm64 native tools are only installed for the 14.3x toolsets. -- 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 VS2008 (9.0) through VS2015 (14.0). This behavior - was extended to VS2017 (14.1) and VS2008 (8.0). - MSVC: When the msvc compiler executable is not found during setup of the msvc environment, the warning message issued takes into account whether or not a possibly erroneous compiler executable was already present in the scons environment path. +- 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 VS2008 (9.0) through VS2015 (14.0). This behavior + was extended to VS2017 (14.1) and VS2008 (8.0). - Extend range of recognized Java versions to 20. - Builder calls (like Program()) now accept pathlib objects in source lists. - The Help() function now takes an additional keyword argument keep_local: @@ -109,6 +109,8 @@ FIXES - MSVC: Erroneous construction of the installed msvc list (as described above) caused an index error in the msvc support code. An explicit check was added to prevent indexing into an empty list. Fixes #4312. +- MSVC: The search for the msvc compiler executable (cl.exe) no longer inspects the + OS system path in certain situations when setting up the msvc environment. - MSVC: VS2015 Express (14.1Exp) may not have been detected. VS2015 Express should be correctly detected. - MSVC: VS2010 (10.0) could be inadvertently detected due to an sdk-only install @@ -122,8 +124,6 @@ FIXES - MSVC: VS2008 (9.0) Visual C++ For Python was not detected when installed using the ALLUSERS=1 option (i.e., msiexec /i VCForPython27.msi ALLUSERS=1). When installed for all users, VS2008 (9.0) Visual C++ For Python is now correctly detected. -- MSVC: The search for the msvc compiler executable (cl.exe) no longer inspects the - OS system path in certain situations when setting up the msvc environment. - MSVC: The installed vcs list was constructed and cached during the initial invocation. If a vswhere executable was specified via the construction variable VSWHERE and found additional msvc installations, the new installations were not From 5e63e556b90b5940667b982e7bd88bb213f90fb0 Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Wed, 11 Oct 2023 16:50:59 -0400 Subject: [PATCH 19/24] Remove key.upper() in'PATH' literal comparison when searching for cl.exe. --- SCons/Tool/MSCommon/vc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SCons/Tool/MSCommon/vc.py b/SCons/Tool/MSCommon/vc.py index e2ab747e0e..04d91c4f30 100644 --- a/SCons/Tool/MSCommon/vc.py +++ b/SCons/Tool/MSCommon/vc.py @@ -4140,7 +4140,7 @@ def msvc_setup_env(env) -> None: seen_path = False for k, v in d.items(): - if not seen_path and k.upper() == 'PATH': + if not seen_path and k == 'PATH': seen_path = True found_cl_path = SCons.Util.WhereIs('cl', v) found_cl_envpath = SCons.Util.WhereIs('cl', env['ENV'].get(k, [])) From 98dd971c4c8bab34af306b442e838e633b9dd13f Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Thu, 12 Oct 2023 15:13:07 -0400 Subject: [PATCH 20/24] New code movement to MSVC modules. Internal changes: * Move command-line option handling to MSVC/Options.py * Move vswhere code to MSVC/VSWhere.py * Move new vs detection code to MSVC/VSDetect.py * Update test suite accordingly. --- SCons/Tool/MSCommon/MSVC/Options.py | 112 + SCons/Tool/MSCommon/MSVC/VSDetect.py | 2527 +++++++++++++ SCons/Tool/MSCommon/MSVC/VSWhere.py | 210 ++ SCons/Tool/MSCommon/MSVC/__init__.py | 15 +- SCons/Tool/MSCommon/__init__.py | 12 +- SCons/Tool/MSCommon/vc.py | 3181 ++--------------- SCons/Tool/MSCommon/vcTests.py | 21 +- SCons/Tool/msvsTests.py | 3 +- test/MSVC/VSWHERE-fixture/SConstruct | 2 +- test/fixture/no_msvc/no_msvcs_sconstruct.py | 4 +- ...s_sconstruct_msvc_query_toolset_version.py | 4 +- .../no_msvcs_sconstruct_msvc_sdk_versions.py | 4 +- ..._msvcs_sconstruct_msvc_toolset_versions.py | 4 +- .../no_msvc/no_msvcs_sconstruct_tools.py | 4 +- .../no_msvc/no_msvcs_sconstruct_version.py | 4 +- test/fixture/no_msvc/no_regs_sconstruct.py | 2 +- 16 files changed, 3144 insertions(+), 2965 deletions(-) create mode 100644 SCons/Tool/MSCommon/MSVC/Options.py create mode 100644 SCons/Tool/MSCommon/MSVC/VSDetect.py create mode 100644 SCons/Tool/MSCommon/MSVC/VSWhere.py diff --git a/SCons/Tool/MSCommon/MSVC/Options.py b/SCons/Tool/MSCommon/MSVC/Options.py new file mode 100644 index 0000000000..bf13c23bf7 --- /dev/null +++ b/SCons/Tool/MSCommon/MSVC/Options.py @@ -0,0 +1,112 @@ +# 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. + +""" +Command-line options for Microsoft Visual C/C++. +""" + +import os +from collections import namedtuple + +import SCons.Script + +from ..common import ( + debug, + debug_extra, +) + +from . import Config +from . import Util + +from . import Dispatcher +Dispatcher.register_modulename(__name__) + + +CMDLINE_OPTIONS_FORCE = False +CMDLINE_OPTIONS_EVAR = 'SCONS_ENABLE_MSVC_OPTIONS' + +class _Options(Util.AutoInitialize): + + enabled = CMDLINE_OPTIONS_FORCE + + vswhere_val = None + vswhere_opt = '--vswhere' + vswhere_dest = 'vswhere' + + msvs_channel_val = None + msvs_channel_opt = '--msvs-channel' + msvs_channel_dest = 'msvs_channel' + + debug_extra = None + + @classmethod + def _setup(cls) -> None: + + if not cls.enabled: + val = os.environ.get(CMDLINE_OPTIONS_EVAR) + cls.enabled = bool(val in Config.BOOLEAN_SYMBOLS[True]) + + if cls.enabled: + + SCons.Script.AddOption( + cls.vswhere_opt, + nargs=1, + dest=cls.vswhere_dest, + default=None, + type="string", + action="store", + help='Add vswhere executable located at EXEPATH.', + metavar='EXEPATH', + ) + + cls.vswhere_val = SCons.Script.GetOption(cls.vswhere_dest) + + SCons.Script.AddOption( + cls.msvs_channel_opt, + nargs=1, + dest=cls.msvs_channel_dest, + default=None, + type="string", + action="store", + help='Set default msvs CHANNEL [Release, Preview, Any].', + metavar='CHANNEL', + ) + + cls.msvs_channel_val = SCons.Script.GetOption(cls.msvs_channel_dest) + + debug( + 'enabled=%s, vswhere=%s, msvs_channel=%s', + cls.enabled, repr(cls.vswhere_val), repr(cls.msvs_channel_val), + extra=cls.debug_extra + ) + + @classmethod + def _initialize(cls) -> None: + cls.debug_extra = debug_extra(cls) + cls._setup() + +def vswhere(): + return _Options.vswhere_val, _Options.vswhere_opt + +def msvs_channel(): + return _Options.msvs_channel_val, _Options.msvs_channel_opt diff --git a/SCons/Tool/MSCommon/MSVC/VSDetect.py b/SCons/Tool/MSCommon/MSVC/VSDetect.py new file mode 100644 index 0000000000..42109aa673 --- /dev/null +++ b/SCons/Tool/MSCommon/MSVC/VSDetect.py @@ -0,0 +1,2527 @@ +# 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. + +""" +VSWhere executable locations for Microsoft Visual C/C++. +""" + +import json +import os +import re +import subprocess + +from collections import namedtuple +from functools import cmp_to_key + +import SCons.Util +import SCons.Warnings + +from .. import common + +from ..common import ( + debug, + debug_extra, + DEBUG_ENABLED, +) + +from . import Config +from . import Exceptions +from . import Options +from . import Util +from . import Registry +from . import Validate +from . import VSWhere +from . import Warnings + +from . import Dispatcher +Dispatcher.register_modulename(__name__) + +# TODO(JCB): temporary + +_msvc_instance_check_files_exist = None + +def register_msvc_instance_check_files_exist(func): + global _msvc_instance_check_files_exist + _msvc_instance_check_files_exist = func + +class _VSUtil(Util.AutoInitialize): + + debug_extra = None + + # cached values + _normalized_path = {} + _path_exists = {} + + @classmethod + def reset(cls) -> None: + cls._normalized_path = {} + cls._path_exists = {} + + @classmethod + def _initialize(cls) -> None: + cls.debug_extra = debug_extra(cls) + cls.reset() + + # normalized paths + + @classmethod + def normalize_path(cls, pval): + rval = cls._normalized_path.get(pval, Config.UNDEFINED) + if rval == Config.UNDEFINED: + rval = Util.normalize_path(pval) + cls._normalized_path[pval] = rval + debug('norm=%s, pval=%s', repr(rval), repr(pval), extra=cls.debug_extra) + return rval + + # path existence + + @classmethod + def path_exists(cls, pval): + rval = cls._path_exists.get(pval, Config.UNDEFINED) + if rval == Config.UNDEFINED: + rval = os.path.exists(pval) + cls._path_exists[pval] = rval + debug('exists=%s, pval=%s', rval, repr(pval), extra=cls.debug_extra) + return rval + + +class _VSConfig: + + # MSVS IDE binaries + + BITFIELD_DEVELOP = 0b_1000 + BITFIELD_EXPRESS = 0b_0100 + BITFIELD_EXPRESS_WIN = 0b_0010 + BITFIELD_EXPRESS_WEB = 0b_0001 + + EXECUTABLE_MASK = BITFIELD_DEVELOP | BITFIELD_EXPRESS + + VSProgram = namedtuple("VSProgram", [ + 'program', + 'bitfield' + ]) + + DEVENV_COM = VSProgram(program='devenv.com', bitfield=BITFIELD_DEVELOP) + MSDEV_COM = VSProgram(program='msdev.com', bitfield=BITFIELD_DEVELOP) + + WDEXPRESS_EXE = VSProgram(program='WDExpress.exe', bitfield=BITFIELD_EXPRESS) + VCEXPRESS_EXE = VSProgram(program='VCExpress.exe', bitfield=BITFIELD_EXPRESS) + + VSWINEXPRESS_EXE = VSProgram(program='VSWinExpress.exe', bitfield=BITFIELD_EXPRESS_WIN) + VWDEXPRESS_EXE = VSProgram(program='VWDExpress.exe', bitfield=BITFIELD_EXPRESS_WEB) + + # MSVS IDE binary + + VSBinaryConfig = namedtuple("VSBinaryConfig", [ + 'pathcomp', # vs root -> ide dir + 'programs', # ide binaries + ]) + + # MSVS batch file + + VSBatchConfig = namedtuple("VSBatchConfig", [ + 'pathcomp', # vs root -> batch dir + 'script', # vs script + ]) + + # detect configuration + + DetectConfig = namedtuple("DetectConfig", [ + 'vs_cfg', # vs configuration + 'vc_cfg', # vc configuration + ]) + + VSDetectConfig = namedtuple("VSDetectConfig", [ + 'root', # relative path vc dir -> vs root + 'vs_binary_cfg', # vs executable + 'vs_batch_cfg', # vs batch file + ]) + + VCDetectConfigRegistry = namedtuple("VCDetectConfigRegistry", [ + 'regkeys', + ]) + + _VCRegistryKey = namedtuple("_VCRegistryKey", [ + 'hkroot', + 'key', + 'is_vsroot', + 'is_vcforpython', + ]) + + class VCRegKey(_VCRegistryKey): + + @classmethod + def factory( + cls, *, + key, + hkroot=SCons.Util.HKEY_LOCAL_MACHINE, + is_vsroot=False, + is_vcforpython=False, + ): + + regkey = cls( + hkroot=hkroot, + key=key, + is_vsroot=is_vsroot, + is_vcforpython=is_vcforpython, + ) + + return regkey + + _regkey = VCRegKey.factory + + # vs detect configuration: vswhere + + DETECT_VSWHERE = { + + '2022': DetectConfig( # 14.3 + vs_cfg=VSDetectConfig( + root=os.pardir, + vs_binary_cfg=VSBinaryConfig( + pathcomp=r'Common7\IDE', + programs=[DEVENV_COM], + ), + vs_batch_cfg=VSBatchConfig( + pathcomp=r'Common7\Tools', + script='VsDevCmd.bat', + ), + ), + vc_cfg=None, + ), + + '2019': DetectConfig( # 14.2 + vs_cfg=VSDetectConfig( + root=os.pardir, + vs_binary_cfg=VSBinaryConfig( + pathcomp=r'Common7\IDE', + programs=[DEVENV_COM], + ), + vs_batch_cfg=VSBatchConfig( + pathcomp=r'Common7\Tools', + script='VsDevCmd.bat', + ), + ), + vc_cfg=None, + ), + + '2017': DetectConfig( # 14.1 + vs_cfg=VSDetectConfig( + root=os.pardir, + vs_binary_cfg=VSBinaryConfig( + pathcomp=r'Common7\IDE', + programs=[DEVENV_COM, WDEXPRESS_EXE], + ), + vs_batch_cfg=VSBatchConfig( + pathcomp=r'Common7\Tools', + script='VsDevCmd.bat', + ), + ), + vc_cfg=None, + ), + + } + + # vs detect configuration: registry + + DETECT_REGISTRY = { + + '2015': DetectConfig( # 14.0 + vs_cfg=VSDetectConfig( + root=os.pardir, + vs_binary_cfg=VSBinaryConfig( + pathcomp=r'Common7\IDE', + programs=[DEVENV_COM, WDEXPRESS_EXE, VSWINEXPRESS_EXE, VWDEXPRESS_EXE], + ), + vs_batch_cfg=VSBatchConfig( + pathcomp=r'Common7\Tools', + # VsDevCmd.bat and vsvars32.bat appear to be different + script='vsvars32.bat', + ), + ), + vc_cfg=VCDetectConfigRegistry( + regkeys=[ + _regkey(key=r'Microsoft\VisualStudio\14.0\Setup\VC\ProductDir'), + _regkey(key=r'Microsoft\WDExpress\14.0\Setup\VS\ProductDir', is_vsroot=True), + _regkey(key=r'Microsoft\VCExpress\14.0\Setup\VC\ProductDir'), # not populated? + ], + ), + ), + + '2013': DetectConfig( # 12.0 + vs_cfg=VSDetectConfig( + root=os.pardir, + vs_binary_cfg=VSBinaryConfig( + pathcomp=r'Common7\IDE', + programs=[DEVENV_COM, WDEXPRESS_EXE, VSWINEXPRESS_EXE, VWDEXPRESS_EXE], + ), + vs_batch_cfg=VSBatchConfig( + pathcomp=r'Common7\Tools', + # VsDevCmd.bat and vsvars32.bat appear to be different + script='vsvars32.bat', + ), + ), + vc_cfg=VCDetectConfigRegistry( + regkeys=[ + _regkey(key=r'Microsoft\VisualStudio\12.0\Setup\VC\ProductDir'), + _regkey(key=r'Microsoft\VCExpress\12.0\Setup\VC\ProductDir'), + ] + ), + ), + + '2012': DetectConfig( # 11.0 + vs_cfg=VSDetectConfig( + root=os.pardir, + vs_binary_cfg=VSBinaryConfig( + pathcomp=r'Common7\IDE', + programs=[DEVENV_COM, WDEXPRESS_EXE, VSWINEXPRESS_EXE, VWDEXPRESS_EXE], + ), + vs_batch_cfg=VSBatchConfig( + pathcomp=r'Common7\Tools', + # VsDevCmd.bat and vsvars32.bat appear to be identical + script='vsvars32.bat', + ), + ), + vc_cfg=VCDetectConfigRegistry( + regkeys=[ + _regkey(key=r'Microsoft\VisualStudio\11.0\Setup\VC\ProductDir'), + _regkey(key=r'Microsoft\VCExpress\11.0\Setup\VC\ProductDir'), + ] + ), + ), + + '2010': DetectConfig( # 10.0 + vs_cfg=VSDetectConfig( + root=os.pardir, + vs_binary_cfg=VSBinaryConfig( + pathcomp=r'Common7\IDE', + programs=[DEVENV_COM, VCEXPRESS_EXE], + ), + vs_batch_cfg=VSBatchConfig( + pathcomp=r'Common7\Tools', + script='vsvars32.bat', + ), + ), + vc_cfg=VCDetectConfigRegistry( + regkeys=[ + _regkey(key=r'Microsoft\VisualStudio\10.0\Setup\VC\ProductDir'), + _regkey(key=r'Microsoft\VCExpress\10.0\Setup\VC\ProductDir'), + ] + ), + ), + + '2008': DetectConfig( # 9.0 + vs_cfg=VSDetectConfig( + root=os.pardir, + vs_binary_cfg=VSBinaryConfig( + pathcomp=r'Common7\IDE', + programs=[DEVENV_COM, VCEXPRESS_EXE], + ), + vs_batch_cfg=VSBatchConfig( + pathcomp=r'Common7\Tools', + script='vsvars32.bat', + ), + ), + vc_cfg=VCDetectConfigRegistry( + regkeys=[ + _regkey( + hkroot=SCons.Util.HKEY_CURRENT_USER, + key=r'Microsoft\DevDiv\VCForPython\9.0\InstallDir', + is_vsroot=True, is_vcforpython=True, + ), + _regkey( + key=r'Microsoft\DevDiv\VCForPython\9.0\InstallDir', + is_vsroot=True, is_vcforpython=True + ), + _regkey(key=r'Microsoft\VisualStudio\9.0\Setup\VC\ProductDir'), + _regkey(key=r'Microsoft\VCExpress\9.0\Setup\VC\ProductDir'), + ] + ), + ), + + '2005': DetectConfig( # 8.0 + vs_cfg=VSDetectConfig( + root=os.pardir, + vs_binary_cfg=VSBinaryConfig( + pathcomp=r'Common7\IDE', + programs=[DEVENV_COM, VCEXPRESS_EXE], + ), + vs_batch_cfg=VSBatchConfig( + pathcomp=r'Common7\Tools', + script='vsvars32.bat', + ), + ), + vc_cfg=VCDetectConfigRegistry( + regkeys=[ + _regkey(key=r'Microsoft\VisualStudio\8.0\Setup\VC\ProductDir'), + _regkey(key=r'Microsoft\VCExpress\8.0\Setup\VC\ProductDir'), + ] + ), + ), + + '2003': DetectConfig( # 7.1 + vs_cfg=VSDetectConfig( + root=os.pardir, + vs_binary_cfg=VSBinaryConfig( + pathcomp=r'Common7\IDE', + programs=[DEVENV_COM], + ), + vs_batch_cfg=VSBatchConfig( + pathcomp=r'Common7\Tools', + script='vsvars32.bat', + ), + ), + vc_cfg=VCDetectConfigRegistry( + regkeys=[ + _regkey(key=r'Microsoft\VisualStudio\7.1\Setup\VC\ProductDir'), + ] + ), + ), + + '2002': DetectConfig( # 7.0 + vs_cfg=VSDetectConfig( + root=os.pardir, + vs_binary_cfg=VSBinaryConfig( + pathcomp=r'Common7\IDE', + programs=[DEVENV_COM], + ), + vs_batch_cfg=VSBatchConfig( + pathcomp=r'Common7\Tools', + script='vsvars32.bat', + ), + ), + vc_cfg=VCDetectConfigRegistry( + regkeys=[ + _regkey(key=r'Microsoft\VisualStudio\7.0\Setup\VC\ProductDir'), + ] + ), + ), + + '1998': DetectConfig( # 6.0 + vs_cfg=VSDetectConfig( + root=os.pardir, + vs_binary_cfg=VSBinaryConfig( + pathcomp=r'Common\MSDev98\Bin', + programs=[MSDEV_COM], + ), + vs_batch_cfg=VSBatchConfig( + # There is no vsvars32.bat batch file + pathcomp=r'VC98\bin', + script='vcvars32.bat', + ), + ), + vc_cfg=VCDetectConfigRegistry( + regkeys=[ + _regkey(key=r'Microsoft\VisualStudio\6.0\Setup\Microsoft Visual C++\ProductDir'), + ] + ), + ), + + } + + +class _VSChannel(Util.AutoInitialize): + + # TODO(JCB): review reset + + # priority: cmdline > user > initial + + debug_extra = None + + vs_channel_initial = None + vs_channel_cmdline = None + + # reset + + vs_channel_user = None + vs_channel_def = None + vs_channel_retrieved = False + + @classmethod + def _initial(cls) -> None: + cls.vs_channel_initial = Config.MSVS_CHANNEL_RELEASE + cls.vs_channel_def = cls.vs_channel_initial + debug('vs_channel_initial=%s', + cls.vs_channel_initial.vs_channel_id, + extra=cls.debug_extra + ) + + @classmethod + def _cmdline(cls) -> None: + channel, option = Options.msvs_channel() + if channel: + vs_channel_def = Validate.validate_msvs_channel( + channel, option + ) + if vs_channel_def: + cls.vs_channel_cmdline = vs_channel_def + cls.vs_channel_def = cls.vs_channel_cmdline + debug('vs_channel_cmdline=%s', + cls.vs_channel_cmdline.vs_channel_id, + extra=cls.debug_extra + ) + + @classmethod + def _setup(cls) -> None: + cls._initial() + cls._cmdline() + + @classmethod + def _initialize(cls) -> None: + cls.debug_extra = debug_extra(cls) + cls._setup() + + @classmethod + def reset(cls) -> None: + cls.vs_channel_user = None + cls.vs_channel_def = None + cls.vs_channel_retrieved = False + cls._setup() + + @classmethod + def get_default_channel(cls): + if not cls.vs_channel_retrieved: + cls.vs_channel_retrieved = True + if cls.vs_channel_cmdline: + cls.vs_channel_def = cls.vs_channel_cmdline + elif cls.vs_channel_user: + cls.vs_channel_def = cls.vs_channel_user + else: + cls.vs_channel_def = cls.vs_channel_initial + debug( + 'vs_channel=%s', + cls.vs_channel_def.vs_channel_id, + extra=cls.debug_extra + ) + return cls.vs_channel_def + + @classmethod + def set_default_channel(cls, msvs_channel, source) -> bool: + + rval = False + + if cls.vs_channel_retrieved: + + warn_msg = f'msvs channel {msvs_channel!r} ({source}) ignored: must be set before first use.' + debug(warn_msg, extra=cls.debug_extra) + SCons.Warnings.warn(Warnings.MSVSChannelWarning, warn_msg) + + else: + + vs_channel_def = Validate.validate_msvs_channel(msvs_channel, source) + if vs_channel_def: + + cls.vs_channel_user = vs_channel_def + debug( + 'vs_channel_user=%s', + cls.vs_channel_user.vs_channel_id, + extra=cls.debug_extra + ) + + if not cls.vs_channel_cmdline: + # priority: cmdline > user > initial + cls.vs_channel_def = cls.vs_channel_user + debug( + 'vs_channel=%s', + cls.vs_channel_def.vs_channel_id, + extra=cls.debug_extra + ) + rval = True + + debug( + 'vs_channel=%s', + cls.vs_channel_def.vs_channel_id, + extra=cls.debug_extra + ) + + return rval + +def msvs_set_channel_default(msvs_channel) -> bool: + """Set the default msvs channel. + + Args: + msvs_channel: str + string representing the msvs channel + + Case-insensitive values are: + Release, Rel, Preview, Pre, Any, * + + Returns: + bool: True if the default msvs channel was accepted. + False if the default msvs channel was not accepted. + + """ + rval = _VSChannel.set_default_channel(msvs_channel, 'msvs_set_channel_default') + return rval + +def msvs_get_channel_default(): + """Get the default msvs channel. + + Returns: + str: default msvs channel + + """ + return _VSChannel.vs_channel_def.vs_channel_id + + +class _VSKeys(Util.AutoInitialize): + + debug_extra = None + + @classmethod + def _initialize(cls) -> None: + cls.debug_extra = debug_extra(cls) + + # edition key: (product, channel, component/None, seqnbr/None) + + _MSVSEditionKey = namedtuple('_MSVSEditionKey', [ + 'vs_product_def', + 'vs_channel_def', + 'vs_componentid_def', + 'vs_sequence_nbr', + ]) + + class MSVSEditionKey(_MSVSEditionKey): + + def serialize(self): + values = [ + self.vs_product_def.vs_product, + self.vs_channel_def.vs_channel_suffix, + ] + if self.vs_componentid_def: + values.append(self.vs_componentid_def.vs_component_suffix) + if self.vs_sequence_nbr: + values.append(str(self.vs_sequence_nbr)) + rval = '-'.join(values) + return rval + + @classmethod + def factory( + cls, *, + vs_product_def, + vs_channel_def, + vs_componentid_def, + vs_sequence_nbr, + ): + + vs_edition_key = cls( + vs_product_def=vs_product_def, + vs_channel_def=vs_channel_def, + vs_componentid_def=vs_componentid_def, + vs_sequence_nbr=vs_sequence_nbr + ) + + return vs_edition_key + + @classmethod + def msvs_edition_key( + cls, *, + vs_product_def=None, + vs_channel_def=None, + vs_componentid_def=None, + vs_sequence_nbr=None, + ): + + if not vs_product_def: + errmsg = 'vs_product_def is undefined' + debug('MSVCInternalError: %s', errmsg, extra=cls.debug_extra) + raise Exceptions.MSVCInternalError(errmsg) + + if not vs_channel_def: + errmsg = 'vs_channel_def is undefined' + debug('MSVCInternalError: %s', errmsg, extra=cls.debug_extra) + raise Exceptions.MSVCInternalError(errmsg) + + vs_edition_key = cls.MSVSEditionKey.factory( + vs_product_def=vs_product_def, + vs_channel_def=vs_channel_def, + vs_componentid_def=vs_componentid_def, + vs_sequence_nbr=vs_sequence_nbr + ) + + return vs_edition_key + + # channel key: (channel, component/None) + + _MSVSChannelKey = namedtuple('_MSVSChannelKey', [ + 'vs_channel_def', + 'vs_componentid_def', + ]) + + class MSVSChannelKey(_MSVSChannelKey): + + def serialize(self): + values = [ + self.vs_channel_def.vs_channel_suffix, + ] + if self.vs_componentid_def: + values.append(self.vs_componentid_def.vs_component_suffix) + rval = '-'.join(values) + return rval + + @classmethod + def factory( + cls, *, + vs_channel_def, + vs_componentid_def, + ): + + vs_channel_key = cls( + vs_channel_def=vs_channel_def, + vs_componentid_def=vs_componentid_def, + ) + + return vs_channel_key + + @classmethod + def msvs_channel_key( + cls, *, + vs_channel_def=None, + vs_componentid_def=None, + ): + + if not vs_channel_def: + errmsg = 'vs_channel_def is undefined' + debug('MSVCInternalError: %s', errmsg, extra=cls.debug_extra) + raise Exceptions.MSVCInternalError(errmsg) + + vs_channel_key = cls.MSVSChannelKey.factory( + vs_channel_def=vs_channel_def, + vs_componentid_def=vs_componentid_def, + ) + + return vs_channel_key + + +_MSVSBase = namedtuple('_MSVSBase', [ + 'id_str', + 'id_comps', + 'vs_product_def', + 'vs_channel_def', + 'vs_component_def', + 'vs_sequence_nbr', + 'vs_dir', + 'vs_dir_norm', + 'vs_version', + 'is_express', + 'is_buildtools', + 'is_vcforpython', + 'vs_edition_channel_component_seqnbr_key', + 'vs_edition_channel_component_key', + 'vs_edition_channel_key', + 'vs_channel_component_key', + 'vs_channel_key', + 'instance_map', +]) + +class MSVSBase(_MSVSBase): + + def _is_express(vs_component_def) -> bool: + vs_componentid_def = vs_component_def.vs_componentid_def + is_express = bool(vs_componentid_def == Config.MSVS_COMPONENTID_EXPRESS) + return is_express + + @staticmethod + def _is_buildtools(vs_component_def) -> bool: + vs_componentid_def = vs_component_def.vs_componentid_def + is_buildtools = bool(vs_componentid_def == Config.MSVS_COMPONENTID_BUILDTOOLS) + return is_buildtools + + @staticmethod + def _is_vcforpython(vs_component_def) -> bool: + vs_componentid_def = vs_component_def.vs_componentid_def + is_vcforpython = bool(vs_componentid_def == Config.MSVS_COMPONENTID_PYTHON) + return is_vcforpython + + @classmethod + def factory( + cls, *, + vs_product_def, + vs_channel_def, + vs_component_def, + vs_sequence_nbr, + vs_dir, + vs_version, + ): + + vs_componentid_def = vs_component_def.vs_componentid_def + + vs_edition_channel_component_seqnbr_key = _VSKeys.msvs_edition_key( + vs_product_def=vs_product_def, + vs_channel_def=vs_channel_def, + vs_componentid_def=vs_componentid_def, + vs_sequence_nbr=vs_sequence_nbr, + ) + + vs_edition_channel_component_key = _VSKeys.msvs_edition_key( + vs_product_def=vs_product_def, + vs_channel_def=vs_channel_def, + vs_componentid_def=vs_componentid_def, + ) + + vs_edition_channel_key = _VSKeys.msvs_edition_key( + vs_product_def=vs_product_def, + vs_channel_def=vs_channel_def, + ) + + vs_channel_component_key = _VSKeys.msvs_channel_key( + vs_channel_def=vs_channel_def, + vs_componentid_def=vs_componentid_def, + ) + + vs_channel_key = _VSKeys.msvs_channel_key( + vs_channel_def=vs_channel_def, + ) + + instance_map = { + 'msvs_instance': None, + 'msvc_instance': None, + } + + id_comps = ( + vs_product_def.vs_product, + vs_channel_def.vs_channel_suffix, + vs_component_def.vs_componentid_def.vs_component_suffix, + str(vs_sequence_nbr), + ) + + id_str = '{}({})'.format(cls.__name__, ', '.join(id_comps)) + + msvs_base = cls( + id_str=id_str, + id_comps=id_comps, + vs_product_def=vs_product_def, + vs_channel_def=vs_channel_def, + vs_component_def=vs_component_def, + vs_sequence_nbr=vs_sequence_nbr, + vs_dir=vs_dir, + vs_dir_norm=_VSUtil.normalize_path(vs_dir), + vs_version=vs_version, + is_express=cls._is_express(vs_component_def), + is_buildtools=cls._is_buildtools(vs_component_def), + is_vcforpython=cls._is_vcforpython(vs_component_def), + vs_edition_channel_component_seqnbr_key=vs_edition_channel_component_seqnbr_key, + vs_edition_channel_component_key=vs_edition_channel_component_key, + vs_edition_channel_key=vs_edition_channel_key, + vs_channel_component_key=vs_channel_component_key, + vs_channel_key=vs_channel_key, + instance_map=instance_map, + ) + + return msvs_base + + @staticmethod + def default_order(a, b): + # vs product numeric: descending order + if a.vs_product_def.vs_product_numeric != b.vs_product_def.vs_product_numeric: + return 1 if a.vs_product_def.vs_product_numeric < b.vs_product_def.vs_product_numeric else -1 + # vs channel: ascending order (release, preview) + if a.vs_channel_def.vs_channel_rank != b.vs_channel_def.vs_channel_rank: + return 1 if a.vs_channel_def.vs_channel_rank > b.vs_channel_def.vs_channel_rank else -1 + # component rank: descending order + if a.vs_component_def.vs_component_rank != b.vs_component_def.vs_component_rank: + return 1 if a.vs_component_def.vs_component_rank < b.vs_component_def.vs_component_rank else -1 + # sequence number: ascending order + if a.vs_sequence_nbr != b.vs_sequence_nbr: + return 1 if a.vs_sequence_nbr > b.vs_sequence_nbr else -1 + return 0 + + def register_msvs_instance(self, msvs_instance) -> None: + self.instance_map['msvs_instance'] = msvs_instance + + def register_msvc_instance(self, msvc_instance) -> None: + self.instance_map['msvc_instance'] = msvc_instance + + @property + def msvs_instance(self): + return self.instance_map.get('msvs_instance') + + @property + def msvc_instance(self): + return self.instance_map.get('msvc_instance') + + @property + def vs_component_suffix(self): + return self.vs_component_def.vs_componentid_def.vs_component_suffix + + +_MSVSInstance = namedtuple('_MSVSInstance', [ + 'id_str', + 'msvs_base', + 'vs_executable', + 'vs_executable_norm', + 'vs_script', + 'vs_script_norm', + 'vc_version_def', +]) + +class MSVSInstance(_MSVSInstance): + + @classmethod + def factory( + cls, *, + msvs_base, + vs_executable, + vs_script, + vc_version_def, + ): + + id_str = '{}({})'.format(cls.__name__, ', '.join(msvs_base.id_comps)) + + msvs_instance = cls( + id_str=id_str, + msvs_base=msvs_base, + vs_executable=vs_executable, + vs_executable_norm=_VSUtil.normalize_path(vs_executable), + vs_script=vs_script, + vs_script_norm=_VSUtil.normalize_path(vs_script), + vc_version_def=vc_version_def, + ) + + return msvs_instance + + @staticmethod + def default_order(a, b): + return MSVSBase.default_order(a.msvs_base, b.msvs_base) + + @property + def id_comps(self): + return self.msvs_base.id_comps + + @property + def msvc_instance(self): + return self.msvs_base.msvc_instance + + @property + def vs_dir(self): + return self.msvs_base.vs_dir + + @property + def vs_version(self): + return self.msvs_base.vs_version + + @property + def vs_edition_channel_component_seqnbr_key(self): + return self.msvs_base.vs_edition_channel_component_seqnbr_key + + @property + def vs_edition_channel_component_key(self): + return self.msvs_base.vs_edition_channel_component_key + + @property + def vs_edition_channel_key(self): + return self.msvs_base.vs_edition_channel_key + + @property + def vs_channel_component_key(self): + return self.msvs_base.vs_channel_component_key + + @property + def vs_channel_key(self): + return self.msvs_base.vs_channel_key + + @property + def msvc_version(self): + return self.vc_version_def.msvc_version + + @property + def msvc_verstr(self): + return self.vc_version_def.msvc_verstr + + @property + def vs_product_def(self): + return self.msvs_base.vs_product_def + + @property + def vs_channel_def(self): + return self.msvs_base.vs_channel_def + + @property + def vs_componentid_def(self): + return self.msvs_base.vs_component_def.vs_componentid_def + + @property + def vs_product(self): + return self.msvs_base.vs_product_def.vs_product + + @property + def vs_channel_id(self): + return self.msvs_base.vs_channel_def.vs_channel_id + + @property + def vs_component_id(self): + return self.msvs_base.vs_component_def.vs_componentid_def.vs_component_id + + @property + def vs_sequence_nbr(self): + return self.msvs_base.vs_sequence_nbr + + +_MSVSInstalled = namedtuple('_MSVSInstalled', [ + 'msvs_instances', + 'msvs_edition_instances_map', + 'msvs_channel_instances_map', + 'msvs_channel_map', # TODO(JCB): remove? +]) + +class MSVSInstalled(_MSVSInstalled, Util.AutoInitialize): + + debug_extra = None + + @classmethod + def _initialize(cls) -> None: + cls.debug_extra = debug_extra(cls) + + @classmethod + def factory( + cls, *, + msvs_instances, + ): + + msvs_instances = sorted( + msvs_instances, key=cmp_to_key(MSVSInstance.default_order) + ) + + msvs_channel_map = { + vs_channel_def: {} + for vs_channel_def in Config.MSVS_CHANNEL_DEFINITION_LIST + } + + vs_edition_instances = {} + vs_channel_instances = {} + + # channel key: (ANY, None) + vs_anychannel_key = _VSKeys.msvs_channel_key( + vs_channel_def=Config.MSVS_CHANNEL_ANY, + ) + + for msvs_instance in msvs_instances: + + debug( + 'msvs instance: id_str=%s, msvc_version=%s, vs_dir=%s, vs_exec=%s', + repr(msvs_instance.id_str), + repr(msvs_instance.msvc_version), + repr(msvs_instance.vs_dir), + repr(msvs_instance.vs_executable_norm), + extra=cls.debug_extra, + ) + + # edition key: (product, ANY, None, None) + vs_edition_any_key = _VSKeys.msvs_edition_key( + vs_product_def=msvs_instance.vs_product_def, + vs_channel_def=Config.MSVS_CHANNEL_ANY, + ) + + # edition key: (product, ANY, component, None) + vs_edition_anychannel_component_key = _VSKeys.msvs_edition_key( + vs_product_def=msvs_instance.vs_product_def, + vs_channel_def=Config.MSVS_CHANNEL_ANY, + vs_componentid_def=msvs_instance.vs_componentid_def, + ) + + # all editions keys + vs_edition_keys = ( + vs_edition_any_key, + msvs_instance.vs_edition_channel_key, + vs_edition_anychannel_component_key, + msvs_instance.vs_edition_channel_component_key, + msvs_instance.vs_edition_channel_component_seqnbr_key, + ) + + # channel key: (ANY, component) + vs_anychannel_component_key = _VSKeys.msvs_channel_key( + vs_channel_def=Config.MSVS_CHANNEL_ANY, + vs_componentid_def=msvs_instance.vs_componentid_def, + ) + + # all channel keys + vs_channel_keys = ( + vs_anychannel_key, + msvs_instance.vs_channel_key, + vs_anychannel_component_key, + msvs_instance.vs_channel_component_key, + ) + + for vs_edition_key in vs_edition_keys: + vs_edition_instances.setdefault(vs_edition_key, []).append(msvs_instance) + + for vs_channel_key in vs_channel_keys: + vs_channel_instances.setdefault(vs_channel_key, []).append(msvs_instance) + + # msvc_version support + + if msvs_instance.msvc_version != msvs_instance.msvc_verstr: + versions = [msvs_instance.msvc_verstr, msvs_instance.msvc_version] + else: + versions = [msvs_instance.msvc_verstr] + + vs_channel_defs = Config.MSVS_CHANNEL_MEMBERLISTS[msvs_instance.vs_channel_def] + + for vcver in versions: + for vs_channel_def in vs_channel_defs: + + msvs_version_map = msvs_channel_map[vs_channel_def] + + msvs_instance_list = msvs_version_map.setdefault(vcver, []) + msvs_instance_list.append(msvs_instance) + + msvs_installed = cls( + msvs_instances=msvs_instances, + msvs_edition_instances_map=vs_edition_instances, + msvs_channel_instances_map=vs_channel_instances, + msvs_channel_map=msvs_channel_map, + ) + + if DEBUG_ENABLED: + msvs_installed._debug_dump() + + return msvs_installed + + def _debug_dump(self) -> None: + + for vs_channel_key, msvc_instance_list in self.msvs_channel_instances_map.items(): + msvc_instances = [msvc_instance.id_str for msvc_instance in msvc_instance_list] + debug( + 'msvs_channel_instances_map[%s]=%s', + repr(vs_channel_key.serialize()), + repr(msvc_instances), + extra=self.debug_extra, + ) + + for vs_edition_key, msvc_instance_list in self.msvs_edition_instances_map.items(): + msvc_instances = [msvc_instance.id_str for msvc_instance in msvc_instance_list] + debug( + 'msvs_edition_instances_map[%s]=%s', + repr(vs_edition_key.serialize()), + repr(msvc_instances), + extra=self.debug_extra, + ) + + for vs_channel_def, msvs_version_map in self.msvs_channel_map.items(): + for vcver, msvs_instance_list in msvs_version_map.items(): + msvs_instances = [msvs_instance.id_str for msvs_instance in msvs_instance_list] + debug( + 'msvs_version_map[%s][%s]=%s', + repr(vs_channel_def.vs_channel_suffix), + repr(vcver), + repr(msvs_instances), + extra=self.debug_extra, + ) + + def _msvs_instances_internal( + self, *, + vs_product_def=None, + vs_channel_def=None, + vs_componentid_def=None, + vs_sequence_nbr=None, + ): + + if not vs_channel_def: + vs_channel_def = _VSChannel.get_default_channel() + + if vs_product_def: + + query_key = _VSKeys.msvs_edition_key( + vs_product_def=vs_product_def, + vs_channel_def=vs_channel_def, + vs_componentid_def=vs_componentid_def, + vs_sequence_nbr=vs_sequence_nbr + ) + + msvs_instances = self.msvs_edition_instances_map.get(query_key, []) + + else: + + query_key = _VSKeys.msvs_channel_key( + vs_channel_def=vs_channel_def, + vs_componentid_def=vs_componentid_def, + ) + + msvs_instances = self.msvs_channel_instances_map.get(query_key, []) + + debug( + 'query_key=%s, n_msvs_instances=%s', + repr(query_key.serialize()), + repr(len(msvs_instances)), + extra=self.debug_extra, + ) + + return msvs_instances, query_key + + +_MSVCInstance = namedtuple('_MSVCInstance', [ + 'id_str', + 'msvs_base', + 'vc_version_def', + 'vc_feature_map', + 'vc_dir', + 'vc_dir_norm', + 'is_sdkversion_supported', +]) + +class MSVCInstance(_MSVCInstance, Util.AutoInitialize): + + debug_extra = None + + @classmethod + def _initialize(cls) -> None: + cls.debug_extra = debug_extra(cls) + + @classmethod + def factory( + cls, *, + msvs_base, + vc_version_def, + vc_feature_map, + vc_dir, + ): + + id_str = '{}({})'.format(cls.__name__, ', '.join(msvs_base.id_comps)) + + if vc_feature_map is None: + vc_feature_map = {} + + msvc_instance = cls( + id_str=id_str, + msvs_base=msvs_base, + vc_version_def=vc_version_def, + vc_feature_map=vc_feature_map, + vc_dir=vc_dir, + vc_dir_norm=_VSUtil.normalize_path(vc_dir), + is_sdkversion_supported=cls._is_sdkversion_supported(msvs_base), + ) + + return msvc_instance + + @staticmethod + def default_order(a, b): + return MSVSBase.default_order(a.msvs_base, b.msvs_base) + + @property + def id_comps(self): + return self.msvs_base.id_comps + + @property + def msvs_instance(self): + return self.msvs_base.msvs_instance + + @staticmethod + def _is_sdkversion_supported(msvs_base) -> bool: + + vs_componentid_def = msvs_base.vs_component_def.vs_componentid_def + vs_product_numeric = msvs_base.vs_product_def.vs_product_numeric + + if vs_product_numeric >= 2017: + # VS2017 and later + is_supported = True + elif vs_product_numeric == 2015: + # VS2015: + # False: Express, BuildTools + # True: Develop, CmdLine + if vs_componentid_def == Config.MSVS_COMPONENTID_EXPRESS: + is_supported = False + elif vs_componentid_def == Config.MSVS_COMPONENTID_BUILDTOOLS: + is_supported = False + else: + is_supported = True + else: + # VS2013 and earlier + is_supported = False + + return is_supported + + def skip_uwp_target(self, env) -> bool: + skip = False + if self.vc_feature_map: + target_arch = env.get('TARGET_ARCH') + uwp_target_is_supported = self.vc_feature_map.get('uwp_target_is_supported', {}) + is_supported = uwp_target_is_supported.get(target_arch, True) + skip = bool(not is_supported) + debug( + 'skip=%s, msvc_version=%s', + repr(skip), repr(self.vc_version_def.msvc_version), extra=self.debug_extra + ) + return skip + + def is_uwp_target_supported(self, target_arch=None) -> bool: + + vs_componentid_def = self.msvs_base.vs_component_def.vs_componentid_def + vs_product_numeric = self.msvs_base.vs_product_def.vs_product_numeric + + is_target = False + if vs_product_numeric >= 2017: + # VS2017 and later + is_supported = True + elif vs_product_numeric == 2015: + # VS2015: + # Maybe: Express + # False: BuildTools + # True: Develop, CmdLine + if vs_componentid_def == Config.MSVS_COMPONENTID_EXPRESS: + uwp_target_is_supported = self.vc_feature_map.get('uwp_target_is_supported', {}) + is_supported = uwp_target_is_supported.get(target_arch, True) + is_target = True + elif vs_componentid_def == Config.MSVS_COMPONENTID_BUILDTOOLS: + is_supported = False + else: + is_supported = True + else: + # VS2013 and earlier + is_supported = False + + debug( + 'is_supported=%s, is_target=%s, msvc_version=%s, msvs_component=%s, target_arch=%s', + is_supported, is_target, + repr(self.vc_version_def.msvc_version), + repr(vs_componentid_def.vs_component_id), + repr(target_arch), + extra=self.debug_extra, + ) + + return is_supported, is_target + + # convenience properties: reduce member lookup chain for readability + + @property + def is_express(self) -> bool: + return self.msvs_base.is_express + + @property + def is_buildtools(self) -> bool: + return self.msvs_base.is_buildtools + + @property + def is_vcforpython(self) -> bool: + return self.msvs_base.is_vcforpython + + @property + def vs_edition_channel_component_seqnbr_key(self): + return self.msvs_base.vs_edition_channel_component_seqnbr_key + + @property + def vs_edition_channel_component_key(self): + return self.msvs_base.vs_edition_channel_component_key + + @property + def vs_edition_channel_key(self): + return self.msvs_base.vs_edition_channel_key + + @property + def vs_channel_component_key(self): + return self.msvs_base.vs_channel_component_key + + @property + def vs_channel_key(self): + return self.msvs_base.vs_channel_key + + @property + def msvc_version(self): + return self.vc_version_def.msvc_version + + @property + def msvc_vernum(self): + return self.vc_version_def.msvc_vernum + + @property + def msvc_verstr(self): + return self.vc_version_def.msvc_verstr + + @property + def vs_product_def(self): + return self.msvs_base.vs_product_def + + @property + def vs_channel_def(self): + return self.msvs_base.vs_channel_def + + @property + def vs_componentid_def(self): + return self.msvs_base.vs_component_def.vs_componentid_def + + @property + def vs_product(self): + return self.msvs_base.vs_product_def.vs_product + + @property + def vs_product_numeric(self): + return self.msvs_base.vs_product_def.vs_product_numeric + + @property + def vs_channel_id(self): + return self.msvs_base.vs_channel_def.vs_channel_id + + @property + def vs_component_id(self): + return self.msvs_base.vs_component_def.vs_componentid_def.vs_component_id + + @property + def vs_sequence_nbr(self): + return self.msvs_base.vs_sequence_nbr + + +_MSVCInstalled = namedtuple('_MSVCInstalled', [ + 'msvc_instances', + 'msvs_edition_instances_map', + 'msvs_channel_instances_map', + 'msvs_channel_map', # TODO(JCB): remove? +]) + +class MSVCInstalled(_MSVCInstalled, Util.AutoInitialize): + + debug_extra = None + + @classmethod + def _initialize(cls) -> None: + cls.debug_extra = debug_extra(cls) + + @classmethod + def factory( + cls, *, + msvc_instances, + ): + + msvc_instances = sorted( + msvc_instances, key=cmp_to_key(MSVCInstance.default_order) + ) + + msvs_channel_map = { + vs_channel_def: {} + for vs_channel_def in Config.MSVS_CHANNEL_DEFINITION_LIST + } + + vs_edition_instances = {} + vs_channel_instances = {} + + # channel key: (ANY, None) + vs_anychannel_key = _VSKeys.msvs_channel_key( + vs_channel_def=Config.MSVS_CHANNEL_ANY, + ) + + for msvc_instance in msvc_instances: + + debug( + 'msvc_instance: id_str=%s, msvc_version=%s, vc_dir=%s', + repr(msvc_instance.id_str), + repr(msvc_instance.msvc_version), + repr(msvc_instance.vc_dir), + extra=cls.debug_extra, + ) + + # edition key: (product, ANY, None, None) + vs_edition_any_key = _VSKeys.msvs_edition_key( + vs_product_def=msvc_instance.vs_product_def, + vs_channel_def=Config.MSVS_CHANNEL_ANY, + ) + + # edition key: (product, ANY, component, None) + vs_edition_anychannel_component_key = _VSKeys.msvs_edition_key( + vs_product_def=msvc_instance.vs_product_def, + vs_channel_def=Config.MSVS_CHANNEL_ANY, + vs_componentid_def=msvc_instance.vs_componentid_def, + ) + + # all editions keys + vs_edition_keys = ( + vs_edition_any_key, + msvc_instance.vs_edition_channel_key, + vs_edition_anychannel_component_key, + msvc_instance.vs_edition_channel_component_key, + msvc_instance.vs_edition_channel_component_seqnbr_key, + ) + + # channel key: (ANY, component) + vs_anychannel_component_key = _VSKeys.msvs_channel_key( + vs_channel_def=Config.MSVS_CHANNEL_ANY, + vs_componentid_def=msvc_instance.vs_componentid_def, + ) + + # all channel keys + vs_channel_keys = ( + vs_anychannel_key, + msvc_instance.vs_channel_key, + vs_anychannel_component_key, + msvc_instance.vs_channel_component_key, + ) + + for vs_edition_key in vs_edition_keys: + vs_edition_instances.setdefault(vs_edition_key, []).append(msvc_instance) + + for vs_channel_key in vs_channel_keys: + vs_channel_instances.setdefault(vs_channel_key, []).append(msvc_instance) + + # msvc_version support + + if msvc_instance.msvc_version != msvc_instance.msvc_verstr: + versions = [msvc_instance.msvc_verstr, msvc_instance.msvc_version] + else: + versions = [msvc_instance.msvc_verstr] + + vs_channel_defs = Config.MSVS_CHANNEL_MEMBERLISTS[msvc_instance.vs_channel_def] + + for vcver in versions: + + for vs_channel_def in vs_channel_defs: + + msvc_version_map = msvs_channel_map[vs_channel_def] + + msvc_instance_list = msvc_version_map.setdefault(vcver, []) + msvc_instance_list.append(msvc_instance) + + msvc_installed = cls( + msvc_instances=msvc_instances, + msvs_edition_instances_map=vs_edition_instances, + msvs_channel_instances_map=vs_channel_instances, + msvs_channel_map=msvs_channel_map, + ) + + if DEBUG_ENABLED: + msvc_installed._debug_dump() + + return msvc_installed + + def _debug_dump(self) -> None: + + for vs_channel_key, msvc_instance_list in self.msvs_channel_instances_map.items(): + msvc_instances = [msvc_instance.id_str for msvc_instance in msvc_instance_list] + debug( + 'msvs_channel_instances_map[%s]=%s', + repr(vs_channel_key.serialize()), + repr(msvc_instances), + extra=self.debug_extra, + ) + + for vs_edition_key, msvc_instance_list in self.msvs_edition_instances_map.items(): + msvc_instances = [msvc_instance.id_str for msvc_instance in msvc_instance_list] + debug( + 'msvs_edition_instances_map[%s]=%s', + repr(vs_edition_key.serialize()), + repr(msvc_instances), + extra=self.debug_extra, + ) + + for vs_channel_def, msvc_version_map in self.msvs_channel_map.items(): + for vcver, msvc_instance_list in msvc_version_map.items(): + msvc_instances = [msvc_instance.id_str for msvc_instance in msvc_instance_list] + debug( + 'msvc_version_map[%s][%s]=%s', + repr(vs_channel_def.vs_channel_suffix), + repr(vcver), + repr(msvc_instances), + extra=self.debug_extra, + ) + + def _msvc_instances_internal( + self, *, + vs_product_def=None, + vs_channel_def=None, + vs_componentid_def=None, + vs_sequence_nbr=None, + ): + + if not vs_channel_def: + vs_channel_def = _VSChannel.get_default_channel() + + if vs_product_def: + + query_key = _VSKeys.msvs_edition_key( + vs_product_def=vs_product_def, + vs_channel_def=vs_channel_def, + vs_componentid_def=vs_componentid_def, + vs_sequence_nbr=vs_sequence_nbr + ) + + msvc_instances = self.msvs_edition_instances_map.get(query_key, []) + + else: + + query_key = _VSKeys.msvs_channel_key( + vs_channel_def=vs_channel_def, + vs_componentid_def=vs_componentid_def, + ) + + msvc_instances = self.msvs_channel_instances_map.get(query_key, []) + + debug( + 'query_key=%s, n_msvc_instances=%s', + repr(query_key.serialize()), + repr(len(msvc_instances)), + extra=self.debug_extra, + ) + + return msvc_instances, query_key + + +_MSVSManager = namedtuple('_MSVSManager', [ + 'msvc_installed', + 'msvs_installed', +]) + +class MSVSManager(_MSVSManager, Util.AutoInitialize): + + debug_extra = None + + @classmethod + def _initialize(cls) -> None: + cls.debug_extra = debug_extra(cls) + + @classmethod + def factory(cls, msvc_installed, msvs_installed): + + msvs_manager = cls( + msvc_installed=msvc_installed, + msvs_installed=msvs_installed, + ) + + debug( + 'n_msvc_instances=%d, n_msvs_instances=%d', + len(msvc_installed.msvc_instances), + len(msvs_installed.msvs_instances), + extra=cls.debug_extra, + ) + + return msvs_manager + + def default_msvs_channel(self): + vs_channel_def = _VSChannel.get_default_channel() + return vs_channel_def + + def get_installed_msvc_instances(self): + # TODO(JCB): channel? + vs_channel_def = _VSChannel.get_default_channel() + msvs_channel_map = self.msvc_installed.msvs_channel_map + msvc_version_map = msvs_channel_map.get(vs_channel_def) + installed_msvc_instances = [ + msvc_version_map[vc_version][0] + for vc_version in msvc_version_map.keys() + ] + return installed_msvc_instances + + def get_installed_msvc_versions(self): + # TODO(JCB): channel? + vs_channel_def = _VSChannel.get_default_channel() + msvs_channel_map = self.msvc_installed.msvs_channel_map + msvc_version_map = msvs_channel_map.get(vs_channel_def) + installed_msvc_versions = [ + vc_version + for vc_version in msvc_version_map.keys() + ] + return installed_msvc_versions + + def query_msvs_instances( + self, *, + msvc_version, + ): + + # TODO(JCB): temporary placeholder + + prefix, suffix = Util.get_msvc_version_prefix_suffix(msvc_version) + + vs_product_def = Config.MSVC_VERSION_INTERNAL[prefix] + + vs_channel_def = None + + if suffix == 'Exp': + vs_componentid_def = Config.MSVS_COMPONENTID_EXPRESS + else: + vs_componentid_def = None + + msvs_instances, query_key = self.msvs_installed._msvs_instances_internal( + vs_product_def=vs_product_def, + vs_channel_def=vs_channel_def, + vs_componentid_def=vs_componentid_def, + ) + + return msvs_instances, query_key + + def query_msvc_instances( + self, *, + msvc_version, + ): + + # TODO(JCB): temporary placeholder + + prefix, suffix = Util.get_msvc_version_prefix_suffix(msvc_version) + + vs_product_def = Config.MSVC_VERSION_INTERNAL[prefix] + + vs_channel_def = None + + if suffix == 'Exp': + vs_componentid_def = Config.MSVS_COMPONENTID_EXPRESS + else: + vs_componentid_def = None + + msvc_instances, query_key = self.msvc_installed._msvc_instances_internal( + vs_product_def=vs_product_def, + vs_channel_def=vs_channel_def, + vs_componentid_def=vs_componentid_def, + ) + + return msvc_instances, query_key + + +class _VSDetectCommon(Util.AutoInitialize): + + debug_extra = None + + @classmethod + def _initialize(cls) -> None: + cls.debug_extra = debug_extra(cls) + + VSDetectedBinaries = namedtuple("VSDetectedBinaries", [ + '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 + 'vs_exec', # vs binary + ]) + + @classmethod + def msvs_detect(cls, vs_dir, vs_cfg): + + vs_exec = None + vs_script = None + + vs_binary_cfg = vs_cfg.vs_binary_cfg + vs_batch_cfg = vs_cfg.vs_batch_cfg + + ide_path = os.path.join(vs_dir, vs_binary_cfg.pathcomp) + + bitfields = 0b_0000 + for ide_program in vs_binary_cfg.programs: + prog = os.path.join(ide_path, ide_program.program) + if not os.path.exists(prog): + continue + bitfields |= ide_program.bitfield + if vs_exec: + continue + if not ide_program.bitfield & _VSConfig.EXECUTABLE_MASK: + continue + vs_exec = prog + + have_dev = bool(bitfields & _VSConfig.BITFIELD_DEVELOP) + have_exp = bool(bitfields & _VSConfig.BITFIELD_EXPRESS) + have_exp_win = bool(bitfields & _VSConfig.BITFIELD_EXPRESS_WIN) + have_exp_web = bool(bitfields & _VSConfig.BITFIELD_EXPRESS_WEB) + + binaries_t = cls.VSDetectedBinaries( + bitfields=bitfields, + have_dev=have_dev, + have_exp=have_exp, + have_exp_win=have_exp_win, + have_exp_web=have_exp_web, + vs_exec=vs_exec, + ) + + script_file = os.path.join(vs_dir, vs_batch_cfg.pathcomp, vs_batch_cfg.script) + if os.path.exists(script_file): + vs_script = script_file + + debug( + 'vs_dir=%s, dev=%s, exp=%s, exp_win=%s, exp_web=%s, vs_exec=%s, vs_script=%s', + repr(vs_dir), + binaries_t.have_dev, binaries_t.have_exp, + binaries_t.have_exp_win, binaries_t.have_exp_web, + repr(binaries_t.vs_exec), + repr(vs_script), + extra=cls.debug_extra, + ) + + return binaries_t, vs_script + + +class _VSDetectVSWhere(Util.AutoInitialize): + + DETECT_CONFIG = _VSConfig.DETECT_VSWHERE + + # initialization + + debug_extra = None + reset_funcs = [] + + @classmethod + def _initialize(cls) -> None: + cls.debug_extra = debug_extra(cls) + cls.reset() + + @classmethod + def reset(cls) -> None: + + cls.vswhere_executable_seen = set() + cls.vswhere_executables = [] + + cls.vs_dir_seen = set() + + cls.msvs_sequence_nbr = {} + + cls.msvs_instances = [] + cls.msvc_instances = [] + + @classmethod + def register_reset_func(cls, func) -> None: + if func: + cls.reset_funcs.append(func) + + @classmethod + def call_reset_funcs(cls) -> None: + for func in cls.reset_funcs: + func() + + @classmethod + def _filter_vswhere_paths(cls, vswhere_env): + vswhere_executables = VSWhere.vswhere_get_executables(vswhere_env) + vswhere_paths = [ + vswhere_exec.norm + for vswhere_exec in vswhere_executables + if vswhere_exec.norm not in cls.vswhere_executable_seen + ] + debug('vswhere_paths=%s', vswhere_paths, extra=cls.debug_extra) + return vswhere_paths + + @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=subprocess.PIPE, stderr=subprocess.PIPE, check=False + ) + 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 + + debug( + 'vswhere_json=%s, vswhere_exe=%s', + bool(vswhere_json), repr(vswhere_exe), extra=cls.debug_extra + ) + + return vswhere_json + + @classmethod + def _msvc_resolve(cls, vc_dir, vs_product_def): + + detect_cfg = cls.DETECT_CONFIG[vs_product_def.vs_product] + + vs_cfg = detect_cfg.vs_cfg + vs_dir = os.path.normpath(os.path.join(vc_dir, vs_cfg.root)) + + binaries_t, vs_script = _VSDetectCommon.msvs_detect(vs_dir, vs_cfg) + + return binaries_t.vs_exec, vs_script + + @classmethod + def _msvc_instance_toolsets(cls, msvc_instance): + # TODO(JCB): load toolsets/tools + rval = _msvc_instance_check_files_exist(msvc_instance) + return rval + + @classmethod + def detect(cls, vswhere_env): + + num_instances = len(cls.msvc_instances) + num_new_instances = 0 + + vswhere_paths = cls._filter_vswhere_paths(vswhere_env) + if vswhere_paths: + + num_beg_instances = num_instances + + for vswhere_exe in vswhere_paths: + + if vswhere_exe in cls.vswhere_executable_seen: + continue + + cls.vswhere_executable_seen.add(vswhere_exe) + cls.vswhere_executables.append(vswhere_exe) + + debug('vswhere_exe=%s', repr(vswhere_exe), extra=cls.debug_extra) + + vswhere_json = cls._vswhere_query_json_output( + vswhere_exe, + ['-all', '-products', '*', '-prerelease'] + ) + + if not vswhere_json: + continue + + for instance in vswhere_json: + + #print(json.dumps(instance, indent=4, sort_keys=True)) + + vs_dir = instance.get('installationPath') + if not vs_dir or not _VSUtil.path_exists(vs_dir): + continue + + vs_norm = _VSUtil.normalize_path(vs_dir) + if vs_norm in cls.vs_dir_seen: + continue + + vs_version = instance.get('installationVersion') + if not vs_version: + continue + + product_id = instance.get('productId') + if not product_id: + continue + + # consider msvs root evaluated at this point + cls.vs_dir_seen.add(vs_norm) + + vc_dir = os.path.join(vs_dir, 'VC') + if not _VSUtil.path_exists(vc_dir): + continue + + vs_major = vs_version.split('.')[0] + if vs_major not in Config.MSVS_VERSION_MAJOR_MAP: + debug('ignore vs_major: %s', vs_major, extra=cls.debug_extra) + continue + + vs_product_def = Config.MSVS_VERSION_MAJOR_MAP[vs_major] + + component_id = product_id.split('.')[-1] + vs_component_def = Config.VSWHERE_COMPONENT_INTERNAL.get(component_id) + if not vs_component_def: + debug('ignore component_id: %s', component_id, extra=cls.debug_extra) + continue + + is_prerelease = True if instance.get('isPrerelease', False) else False + if is_prerelease: + vs_channel_def = Config.MSVS_CHANNEL_PREVIEW + else: + vs_channel_def = Config.MSVS_CHANNEL_RELEASE + + vs_exec, vs_script = cls._msvc_resolve(vc_dir, vs_product_def) + + vs_sequence_key = (vs_product_def, vs_channel_def, vs_component_def) + + cls.msvs_sequence_nbr.setdefault(vs_sequence_key, 0) + cls.msvs_sequence_nbr[vs_sequence_key] += 1 + + vs_sequence_nbr = cls.msvs_sequence_nbr[vs_sequence_key] + + msvs_base = MSVSBase.factory( + vs_product_def=vs_product_def, + vs_channel_def=vs_channel_def, + vs_component_def=vs_component_def, + vs_sequence_nbr=vs_sequence_nbr, + vs_dir=vs_dir, + vs_version=vs_version, + ) + + vc_version = vs_product_def.vc_buildtools_def.vc_version + + if msvs_base.is_express: + vc_version += msvs_base.vs_component_suffix + + vc_version_def = Util.msvc_version_components(vc_version) + + msvc_instance = MSVCInstance.factory( + msvs_base=msvs_base, + vc_version_def=vc_version_def, + vc_feature_map=None, + vc_dir=vc_dir, + ) + + if not cls._msvc_instance_toolsets(msvc_instance): + # no compilers detected don't register objects + continue + + if vs_exec: + + # TODO(JCB): register iff compilers and executable? + msvs_instance = MSVSInstance.factory( + msvs_base=msvs_base, + vs_executable=vs_exec, + vs_script=vs_script, + vc_version_def=vc_version_def, + ) + + msvs_base.register_msvs_instance(msvs_instance) + cls.msvs_instances.append(msvs_instance) + + debug( + 'msvs_instance=%s', + repr(msvs_instance.id_str), + extra=cls.debug_extra, + ) + + msvs_base.register_msvc_instance(msvc_instance) + cls.msvc_instances.append(msvc_instance) + + debug( + 'msvc_instance=%s', + repr(msvc_instance.id_str), + extra=cls.debug_extra, + ) + + num_instances = len(cls.msvc_instances) + num_new_instances = num_instances - num_beg_instances + + if num_new_instances > 0: + cls.call_reset_funcs() + debug( + 'num_new_instances=%s, num_instances=%s', + num_new_instances, num_instances, extra=cls.debug_extra + ) + + return num_new_instances + + +class _VSDetectRegistry(Util.AutoInitialize): + + DETECT_CONFIG = _VSConfig.DETECT_REGISTRY + + # initialization + + debug_extra = None + + @classmethod + def _initialize(cls) -> None: + cls.debug_extra = debug_extra(cls) + cls.reset() + + @classmethod + def reset(cls) -> None: + + cls.registry_once = False + + cls.vc_dir_seen = set() + + cls.msvs_sequence_nbr = {} + + cls.msvs_instances = [] + cls.msvc_instances = [] + + # 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) + + @classmethod + def _vs_buildtools_2015_vcvars(cls, vcvars_file) -> bool: + have_buildtools_vcvars = False + with open(vcvars_file) as fh: + for line in fh: + if cls._VS2015BT_VCVARS_BUILDTOOLS.match(line): + have_buildtools_vcvars = True + break + if cls._VS2015BT_VCVARS_STOP.match(line): + break + return have_buildtools_vcvars + + @classmethod + def _vs_buildtools_2015(cls, vs_dir, vc_dir) -> bool: + + is_buildtools = False + + do_once = True + while do_once: + do_once = False + + buildtools_file = os.path.join(vs_dir, cls._VS2015BT_PATH) + have_buildtools = os.path.exists(buildtools_file) + debug('have_buildtools=%s', have_buildtools, extra=cls.debug_extra) + 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, extra=cls.debug_extra) + if not have_vcvars: + break + + have_buildtools_vcvars = cls._vs_buildtools_2015_vcvars(vcvars_file) + debug('have_buildtools_vcvars=%s', have_buildtools_vcvars, extra=cls.debug_extra) + if not have_buildtools_vcvars: + break + + is_buildtools = True + + debug('is_buildtools=%s', is_buildtools, extra=cls.debug_extra) + return is_buildtools + + _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) + + @classmethod + def _vs_express_2015_vcvars(cls, vcvars_file) -> bool: + n_libpath = 0 + with open(vcvars_file) as fh: + for line in fh: + if cls._VS2015EXP_VCVARS_LIBPATH.match(line): + n_libpath += 1 + elif cls._VS2015EXP_VCVARS_STOP.match(line): + break + have_uwp_fix = bool(n_libpath >= 2) + return have_uwp_fix + + @classmethod + def _vs_express_2015(cls, vc_dir): + + have_uwp_amd64 = False + have_uwp_arm = False + + vcvars_file = os.path.join(vc_dir, r'vcvarsall.bat') + if os.path.exists(vcvars_file): + + vcvars_file = os.path.join(vc_dir, r'bin\x86_amd64\vcvarsx86_amd64.bat') + if os.path.exists(vcvars_file): + have_uwp_fix = cls._vs_express_2015_vcvars(vcvars_file) + if have_uwp_fix: + have_uwp_amd64 = True + + vcvars_file = os.path.join(vc_dir, r'bin\x86_arm\vcvarsx86_arm.bat') + if os.path.exists(vcvars_file): + have_uwp_fix = cls._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, extra=cls.debug_extra) + 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'} + + @classmethod + def _msvc_dir_is_winsdk_only(cls, vc_dir, msvc_version) -> bool: + + # 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 + + if msvc_version not in cls._REGISTRY_WINSDK_VERSIONS: + + is_sdk = False + + debug('is_sdk=%s, msvc_version=%s', is_sdk, repr(msvc_version), extra=cls.debug_extra) + + else: + + vc_suffix = Registry.vstudio_sxs_vc7(msvc_version) + vc_qresults = [record[0] for record in Registry.microsoft_query_paths(vc_suffix)] + vc_regdir = vc_qresults[0] if vc_qresults else None + + if vc_regdir != vc_dir: + # registry vc path is not the current vc dir + + is_sdk = False + + debug( + 'is_sdk=%s, msvc_version=%s, vc_dir=%s, vc_regdir=%s', + is_sdk, repr(msvc_version), repr(vc_dir), repr(vc_regdir), extra=cls.debug_extra + ) + + else: + # registry vc dir is the current vc root + + vs_suffix = Registry.vstudio_sxs_vs7(msvc_version) + vs_qresults = [record[0] for record in Registry.microsoft_query_paths(vs_suffix)] + vs_dir = vs_qresults[0] if vs_qresults else None + + is_sdk = bool(not vs_dir and vc_dir) + + debug( + 'is_sdk=%s, msvc_version=%s, vc_dir=%s, vs_dir=%s', + is_sdk, repr(msvc_version), repr(vc_dir), repr(vs_dir), extra=cls.debug_extra + ) + + return is_sdk + + @classmethod + def _msvc_resolve(cls, vc_dir, vs_product_def, is_vcforpython, vs_version): + + detect_cfg = cls.DETECT_CONFIG[vs_product_def.vs_product] + + vc_feature_map = {} + + vs_cfg = detect_cfg.vs_cfg + vs_dir = os.path.normpath(os.path.join(vc_dir, vs_cfg.root)) + + binaries_t, vs_script = _VSDetectCommon.msvs_detect(vs_dir, vs_cfg) + + vs_product_numeric = vs_product_def.vs_product_numeric + vc_version = vs_product_def.vc_buildtools_def.vc_version + + if binaries_t.have_dev: + vs_component_def = Config.REGISTRY_COMPONENT_DEVELOP + elif binaries_t.have_exp: + vs_component_def = Config.REGISTRY_COMPONENT_EXPRESS + elif vs_product_numeric == 2008 and is_vcforpython: + vs_component_def = Config.REGISTRY_COMPONENT_PYTHON + elif binaries_t.have_exp_win: + vs_component_def = None + elif binaries_t.have_exp_web: + vs_component_def = None + elif cls._msvc_dir_is_winsdk_only(vc_dir, vc_version): + vs_component_def = None + else: + vs_component_def = Config.REGISTRY_COMPONENT_CMDLINE + + if vs_component_def and vs_product_numeric == 2015: + + # VS2015: + # remap DEVELOP => ENTERPRISE, PROFESSIONAL, COMMUNITY + # remap CMDLINE => BUILDTOOLS [conditional] + # process EXPRESS + + if vs_component_def == Config.REGISTRY_COMPONENT_DEVELOP: + + for reg_component, reg_component_def in [ + ('community', Config.REGISTRY_COMPONENT_COMMUNITY), + ('professional', Config.REGISTRY_COMPONENT_PROFESSIONAL), + ('enterprise', Config.REGISTRY_COMPONENT_ENTERPRISE), + ]: + suffix = Registry.devdiv_vs_servicing_component(vs_version, reg_component) + qresults = Registry.microsoft_query_keys(suffix, usrval=reg_component_def) + if not qresults: + continue + vs_component_def = qresults[0][-1] + break + + elif vs_component_def == Config.REGISTRY_COMPONENT_CMDLINE: + + if cls._vs_buildtools_2015(vs_dir, vc_dir): + vs_component_def = Config.REGISTRY_COMPONENT_BUILDTOOLS + + elif vs_component_def == Config.REGISTRY_COMPONENT_EXPRESS: + + have_uwp_amd64, have_uwp_arm = cls._vs_express_2015(vc_dir) + + uwp_target_is_supported = { + 'x86': True, + 'amd64': have_uwp_amd64, + 'arm': have_uwp_arm, + } + + vc_feature_map['uwp_target_is_supported'] = uwp_target_is_supported + + debug( + 'vs_product=%s, vs_component=%s, vc_dir=%s, vc_feature_map=%s', + repr(vs_product_numeric), + repr(vs_component_def.vs_componentid_def.vs_component_id) if vs_component_def else None, + repr(vc_dir), + repr(vc_feature_map), + extra=cls.debug_extra + ) + + return vs_component_def, vs_dir, binaries_t.vs_exec, vs_script, vc_feature_map + + @classmethod + def _msvc_instance_toolsets(cls, msvc_instance): + # TODO(JCB): load toolsets/tools + rval = _msvc_instance_check_files_exist(msvc_instance) + return rval + + @classmethod + def detect(cls): + + num_instances = len(cls.msvc_instances) + num_new_instances = 0 + + if not cls.registry_once: + cls.registry_once = True + + num_beg_instances = num_instances + + is_win64 = common.is_win64() + + for vs_product, config in cls.DETECT_CONFIG.items(): + + key_prefix = 'Software\\' + for regkey in config.vc_cfg.regkeys: + + if not regkey.hkroot or not regkey.key: + continue + + if is_win64: + mskeys = [key_prefix + 'Wow6432Node\\' + regkey.key, key_prefix + regkey.key] + else: + mskeys = [key_prefix + regkey.key] + + vc_dir = None + for mskey in mskeys: + debug('trying VC registry key %s', repr(mskey), extra=cls.debug_extra) + try: + vc_dir = common.read_reg(mskey, regkey.hkroot) + except OSError: + continue + if vc_dir: + break + + if not vc_dir: + debug('no VC registry key %s', repr(regkey.key), extra=cls.debug_extra) + continue + + if vc_dir in cls.vc_dir_seen: + continue + cls.vc_dir_seen.add(vc_dir) + + if not os.path.exists(vc_dir): + debug( + 'reg says vc dir is %s, but it does not exist. (ignoring)', + repr(vc_dir), extra=cls.debug_extra + ) + continue + + if regkey.is_vsroot: + + vc_dir = os.path.join(vc_dir, 'VC') + debug('convert vs dir to vc dir: %s', repr(vc_dir), extra=cls.debug_extra) + + if vc_dir in cls.vc_dir_seen: + continue + cls.vc_dir_seen.add(vc_dir) + + if not os.path.exists(vc_dir): + debug('vc dir does not exist. (ignoring)', repr(vc_dir), extra=cls.debug_extra) + continue + + vc_norm = _VSUtil.normalize_path(vc_dir) + if vc_norm in cls.vc_dir_seen: + continue + cls.vc_dir_seen.add(vc_norm) + + vs_product_def = Config.MSVS_VERSION_INTERNAL[vs_product] + + vs_component_def, vs_dir, vs_exec, vs_script, vc_feature_map = cls._msvc_resolve( + vc_norm, + vs_product_def, + regkey.is_vcforpython, + vs_product_def.vs_version + ) + + if not vs_component_def: + continue + + vs_channel_def = Config.MSVS_CHANNEL_RELEASE + + vs_sequence_key = (vs_product_def, vs_channel_def, vs_component_def) + + cls.msvs_sequence_nbr.setdefault(vs_sequence_key, 0) + cls.msvs_sequence_nbr[vs_sequence_key] += 1 + + vs_sequence_nbr = cls.msvs_sequence_nbr[vs_sequence_key] + + msvs_base = MSVSBase.factory( + vs_product_def=vs_product_def, + vs_channel_def=vs_channel_def, + vs_component_def=vs_component_def, + vs_sequence_nbr=vs_sequence_nbr, + vs_dir=vs_dir, + vs_version=vs_product_def.vs_version, + ) + + vc_version = vs_product_def.vc_buildtools_def.vc_version + + if msvs_base.is_express: + vc_version += msvs_base.vs_component_suffix + + vc_version_def = Util.msvc_version_components(vc_version) + + msvc_instance = MSVCInstance.factory( + msvs_base=msvs_base, + vc_version_def=vc_version_def, + vc_feature_map=vc_feature_map, + vc_dir=vc_dir, + ) + + if not cls._msvc_instance_toolsets(msvc_instance): + # no compilers detected don't register objects + continue + + if vs_exec: + + # TODO(JCB): register iff compilers and executable? + msvs_instance = MSVSInstance.factory( + msvs_base=msvs_base, + vs_executable=vs_exec, + vs_script=vs_script, + vc_version_def=vc_version_def, + ) + + msvs_base.register_msvs_instance(msvs_instance) + cls.msvs_instances.append(msvs_instance) + + debug( + 'msvs_instance=%s', + repr(msvs_instance.id_str), + extra=cls.debug_extra, + ) + + msvs_base.register_msvc_instance(msvc_instance) + cls.msvc_instances.append(msvc_instance) + + debug( + 'msvc_instance=%s', + repr(msvc_instance.id_str), + extra=cls.debug_extra, + ) + + num_instances = len(cls.msvc_instances) + num_new_instances = num_instances - num_beg_instances + + return num_new_instances + + +class _VSDetect(Util.AutoInitialize): + + debug_extra = None + + @classmethod + def _initialize(cls) -> None: + cls.debug_extra = debug_extra(cls) + cls._reset() + + @classmethod + def _reset(cls) -> None: + # volatile: reconstructed when new instances detected + cls.msvs_manager = None + + @classmethod + def reset(cls) -> None: + cls._reset() + _VSDetectVSWhere.reset() + _VSDetectRegistry.reset() + + @classmethod + def detect(cls, vswhere_env): + + num_new_instances = 0 + num_new_instances += _VSDetectVSWhere.detect(vswhere_env) + num_new_instances += _VSDetectRegistry.detect() + + if num_new_instances > 0 or cls.msvs_manager is None: + + msvc_instances = [] + msvc_instances.extend(_VSDetectVSWhere.msvc_instances) + msvc_instances.extend(_VSDetectRegistry.msvc_instances) + + msvs_instances = [] + msvs_instances.extend(_VSDetectVSWhere.msvs_instances) + msvs_instances.extend(_VSDetectRegistry.msvs_instances) + + msvc_installed = MSVCInstalled.factory( + msvc_instances=msvc_instances, + ) + + msvs_installed = MSVSInstalled.factory( + msvs_instances=msvs_instances, + ) + + cls.msvs_manager = MSVSManager.factory( + msvc_installed=msvc_installed, + msvs_installed=msvs_installed, + ) + + return cls.msvs_manager + + +def register_reset_func(func) -> None: + _VSDetectVSWhere.register_reset_func(func) + +def msvs_manager(vswhere_env=None): + vs_manager = _VSDetect.detect(vswhere_env) + return vs_manager + + +def reset() -> None: + _VSUtil.reset() + _VSChannel.reset() + _VSDetect.reset() + +def verify() -> None: + + def _compare_product_sets(set_config, set_local, label) -> None: + diff = set_config - set_local + if diff: + keys = ', '.join([repr(s) for s in sorted(diff, reverse=True)]) + errmsg = f'{label} missing keys: {keys}' + debug('MSVCInternalError: %s', errmsg) + raise MSVCInternalError(errmsg) + + vswhere_config = Config.VSWHERE_SUPPORTED_PRODUCTS + vswhere_local = set(_VSConfig.DETECT_VSWHERE.keys()) + _compare_product_sets(vswhere_config, vswhere_local, '_VSConfig.DETECT_VSWHERE') + + registry_config = Config.REGISTRY_SUPPORTED_PRODUCTS + registry_local = set(_VSConfig.DETECT_REGISTRY.keys()) + _compare_product_sets(registry_config, registry_local, '_VSConfig.DETECT_REGISTRY') + diff --git a/SCons/Tool/MSCommon/MSVC/VSWhere.py b/SCons/Tool/MSCommon/MSVC/VSWhere.py new file mode 100644 index 0000000000..ffb2eefd17 --- /dev/null +++ b/SCons/Tool/MSCommon/MSVC/VSWhere.py @@ -0,0 +1,210 @@ +# 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. + +""" +VSWhere executable locations for Microsoft Visual C/C++. +""" + +import os +from collections import namedtuple + +import SCons.Util + +from ..common import ( + debug, + debug_extra, +) + +from . import Config +from . import Util +from . import Options +from . import Warnings + +from . import Dispatcher +Dispatcher.register_modulename(__name__) + +# priority: env > cmdline > (user, initial, user) + +VSWHERE_EXE = 'vswhere.exe' + +VSWHERE_PATHS = [ + 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"), + 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"), + ] + if not p.startswith('%') +] + +class _VSWhere(Util.AutoInitialize): + + # TODO(JCB): review reset + + VSWhereExecutable = namedtuple('VSWhereExecutable', [ + 'path', + 'norm', + ]) + + debug_extra = None + + vswhere_cmdline = None + vswhere_executables = [] + + _cache_user_vswhere_paths = {} + + @classmethod + def _cmdline(cls) -> None: + vswhere, option = Options.vswhere() + if vswhere: + vswhere_exec = cls.user_path(vswhere, option) + if vswhere_exec: + cls.vswhere_cmdline = vswhere_exec + debug('vswhere_cmdline=%s', cls.vswhere_cmdline, extra=cls.debug_extra) + + @classmethod + def _setup(cls) -> None: + for pval in VSWHERE_PATHS: + if not os.path.exists(pval): + continue + vswhere_exec = cls.VSWhereExecutable(path=pval, norm=Util.normalize_path(pval)) + cls.vswhere_executables.append(vswhere_exec) + debug('vswhere_executables=%s', cls.vswhere_executables, extra=cls.debug_extra) + cls._cmdline() + + @classmethod + def _initialize(cls) -> None: + cls.debug_extra = debug_extra(cls) + cls.reset() + cls._setup() + + @classmethod + def reset(cls) -> None: + cls._cache_user_vswhere_paths = {} + + # validate user-specified vswhere executable + + @classmethod + def user_path(cls, pval, source): + + rval = cls._cache_user_vswhere_paths.get(pval, Config.UNDEFINED) + if rval != Config.UNDEFINED: + debug('vswhere_exec=%s', repr(rval), extra=cls.debug_extra) + return rval + + vswhere_exec = None + if pval: + + if not os.path.exists(pval): + + warn_msg = f'vswhere executable path not found: {pval!r} ({source})' + debug(warn_msg, extra=cls.debug_extra) + SCons.Warnings.warn(Warnings.VSWherePathWarning, warn_msg) + + else: + + norm = Util.normalize_path(pval) + tail = os.path.split(norm)[-1] + if tail != VSWHERE_EXE: + + warn_msg = f'unsupported vswhere executable (expected {VSWHERE_EXE!r}, found {tail!r}): {pval!r} ({source})' + debug(warn_msg, extra=cls.debug_extra) + SCons.Warnings.warn(Warnings.VSWherePathWarning, warn_msg) + + else: + + vswhere_exec = cls.VSWhereExecutable(path=pval, norm=norm) + debug('vswhere_exec=%s', repr(vswhere_exec), extra=cls.debug_extra) + + cls._cache_user_vswhere_paths[pval] = vswhere_exec + + return vswhere_exec + + # user-specified vswhere executables + + @classmethod + def user_pathlist(cls, path_list, front, source) -> None: + + user_executables = [] + for pval in path_list: + vswhere_exec = cls.user_path(pval, source) + if vswhere_exec: + user_executables.append(vswhere_exec) + + if user_executables: + + if front: + all_executables = user_executables + cls.vswhere_executables + else: + all_executables = cls.vswhere_executables + user_executables + + seen = set() + unique_executables = [] + for vswhere_exec in all_executables: + if vswhere_exec.norm in seen: + continue + seen.add(vswhere_exec.norm) + unique_executables.append(vswhere_exec) + + cls.vswhere_executables = unique_executables + debug('vswhere_executables=%s', cls.vswhere_executables) + +# user vswhere executable location(s) + +def vswhere_push_location(string_or_list, front=False) -> None: + # TODO(JCB): need docstring + path_list = SCons.Util.flatten(string_or_list) + if path_list: + _VSWhere.user_pathlist(path_list, front, 'vswhere_push_location') + +# all vswhere executables + +def vswhere_get_executables(vswhere_env=None): + + vswhere_executables = [] + + # env['VSWHERE'] path + if vswhere_env: + vswhere_exec = _VSWhere.user_path(vswhere_env, "env['VSWHERE']") + if vswhere_exec: + vswhere_executables.append(vswhere_exec) + + # --vswhere=EXEPATH + if _VSWhere.vswhere_cmdline: + vswhere_executables.append(_VSWhere.vswhere_cmdline) + + # default paths and user paths (vswhere_push_location) + if _VSWhere.vswhere_executables: + vswhere_executables.extend(_VSWhere.vswhere_executables) + + return vswhere_executables + +def reset() -> None: + _VSWhere.reset() + diff --git a/SCons/Tool/MSCommon/MSVC/__init__.py b/SCons/Tool/MSCommon/MSVC/__init__.py index 1abedda49a..262c23fdc7 100644 --- a/SCons/Tool/MSCommon/MSVC/__init__.py +++ b/SCons/Tool/MSCommon/MSVC/__init__.py @@ -35,16 +35,19 @@ * _verify is invoked from the last line in the vc module. """ -from . import Exceptions # noqa: F401 - from . import Config # noqa: F401 -from . import Util # noqa: F401 +from . import Exceptions # noqa: F401 +from . import Options # noqa: F401 +from . import Policy # noqa: F401 from . import Registry # noqa: F401 from . import SetupEnvDefault # noqa: F401 -from . import Policy # noqa: F401 -from . import WinSDK # noqa: F401 -from . import Validate # noqa: F401 from . import ScriptArguments # noqa: F401 +from . import Util # noqa: F401 +from . import Validate # noqa: F401 +from . import VSDetect # noqa: F401 +from . import VSWhere # noqa: F401 +from . import Warnings # noqa: F401 +from . import WinSDK # noqa: F401 from . import Dispatcher as _Dispatcher diff --git a/SCons/Tool/MSCommon/__init__.py b/SCons/Tool/MSCommon/__init__.py index 29ab4163a1..e624e29d7e 100644 --- a/SCons/Tool/MSCommon/__init__.py +++ b/SCons/Tool/MSCommon/__init__.py @@ -45,9 +45,6 @@ msvc_toolset_versions, msvc_toolset_versions_spectre, msvc_query_version_toolset, - vswhere_push_location, - msvs_set_channel_default, - msvs_get_channel_default, ) from SCons.Tool.MSCommon.vs import ( # noqa: F401 @@ -58,6 +55,15 @@ query_versions, ) +from .MSVC.VSWhere import ( # noqa: F401 + vswhere_push_location, +) + +from .MSVC.VSDetect import ( # noqa: F401 + msvs_set_channel_default, + msvs_get_channel_default, +) + from .MSVC.Policy import ( # noqa: F401 msvc_set_notfound_policy, msvc_get_notfound_policy, diff --git a/SCons/Tool/MSCommon/vc.py b/SCons/Tool/MSCommon/vc.py index 04d91c4f30..cff8a5850e 100644 --- a/SCons/Tool/MSCommon/vc.py +++ b/SCons/Tool/MSCommon/vc.py @@ -35,7 +35,6 @@ # * Assembly """ -import subprocess import os import platform import sysconfig @@ -46,26 +45,18 @@ namedtuple, OrderedDict, ) -import json -from functools import cmp_to_key -import SCons.Script import SCons.Util import SCons.Warnings -from SCons.Tool import find_program_path from . import common from .common import ( CONFIG_CACHE, debug, - debug_extra, - DEBUG_ENABLED, ) from . import MSVC -from .MSVC.Util import AutoInitialize - from .MSVC.Exceptions import ( VisualCException, MSVCInternalError, @@ -93,78 +84,6 @@ class MSVCUseSettingsError(MSVCUserError): class BatchFileExecutionError(VisualCException): pass -# command-line options - -class _Options(AutoInitialize): - - debug_extra = None - - options_force = False - options_evar = 'SCONS_ENABLE_MSVC_OPTIONS' - - vswhere_option = '--vswhere' - vswhere_dest = 'vswhere' - vswhere_val = None - - msvs_channel_option = '--msvs-channel' - msvs_channel_dest = 'msvs_channel' - msvs_channel_val = None - - @classmethod - def _setup(cls) -> None: - - enabled = cls.options_force - if not enabled: - val = os.environ.get(cls.options_evar) - enabled = bool(val in MSVC.Config.BOOLEAN_SYMBOLS[True]) - - if enabled: - - SCons.Script.AddOption( - cls.vswhere_option, - nargs=1, - dest=cls.vswhere_dest, - default=None, - type="string", - action="store", - help='Add vswhere executable located at EXEPATH.', - metavar='EXEPATH', - ) - - cls.vswhere_val = SCons.Script.GetOption(cls.vswhere_dest) - - SCons.Script.AddOption( - cls.msvs_channel_option, - nargs=1, - dest=cls.msvs_channel_dest, - default=None, - type="string", - action="store", - help='Set default msvs CHANNEL [Release, Preview, Any].', - metavar='CHANNEL', - ) - - cls.msvs_channel_val = SCons.Script.GetOption(cls.msvs_channel_dest) - - debug( - 'enabled=%s, vswhere=%s, msvs_channel=%s', - enabled, repr(cls.vswhere_val), repr(cls.msvs_channel_val), - extra=cls.debug_extra - ) - - @classmethod - def _initialize(cls) -> None: - cls.debug_extra = debug_extra(cls) - cls._setup() - - @classmethod - def vswhere(cls): - return cls.vswhere_val, cls.vswhere_option - - @classmethod - def msvs_channel(cls): - return cls.msvs_channel_val, cls.msvs_channel_option - # undefined object for dict.get() in case key exists and value is None UNDEFINED = MSVC.Config.UNDEFINED @@ -524,2879 +443,322 @@ def _make_target_host_map(all_hosts, host_all_targets_map): # 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 - -_LE2003_HOST_TARGET_BATCHARG_BATCHFILE_CLPATHCOMPS = { - - ('amd64', 'x86') : ('x86', 'vcvars32.bat', ('bin', )), - ('x86', 'x86') : ('x86', 'vcvars32.bat', ('bin', )), - ('arm64', 'x86') : ('x86', 'vcvars32.bat', ('bin', )), - -} - -_LE2003_HOST_TARGET_CFG = _host_target_config_factory( - - label = 'LE2003', - - host_all_hosts = OrderedDict([ - ('amd64', ['x86']), - ('x86', ['x86']), - ('arm64', ['x86']), - ]), - - host_all_targets = { - 'amd64': ['x86'], - 'x86': ['x86'], - 'arm64': ['x86'], - }, - - host_def_targets = { - 'amd64': ['x86'], - 'x86': ['x86'], - 'arm64': ['x86'], - }, - -) - -# debug("_LE2003_HOST_TARGET_CFG: %s", _LE2003_HOST_TARGET_CFG) - -_CL_EXE_NAME = 'cl.exe' - -# internal utilities - -class _Util(AutoInitialize): - - debug_extra = None - - # cached values - _normalized_path = {} - _path_exists = {} - - @classmethod - def reset(cls) -> None: - cls._normalized_path = {} - cls._path_exists = {} - - @classmethod - def _initialize(cls) -> None: - cls.debug_extra = debug_extra(cls) - cls.reset() - - # normalized paths - - @classmethod - def normalize_path(cls, pval): - rval = cls._normalized_path.get(pval, UNDEFINED) - if rval == UNDEFINED: - rval = MSVC.Util.normalize_path(pval) - cls._normalized_path[pval] = rval - debug('norm=%s, pval=%s', repr(rval), repr(pval), extra=cls.debug_extra) - return rval - - # path existence - - @classmethod - def path_exists(cls, pval): - rval = cls._path_exists.get(pval, UNDEFINED) - if rval == UNDEFINED: - rval = os.path.exists(pval) - cls._path_exists[pval] = rval - debug('exists=%s, pval=%s', rval, repr(pval), extra=cls.debug_extra) - return rval - -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' - would get converted to '14.0'. - - Args: - msvc_version: str - string representing the version number, could contain non - digit characters - - Returns: - str: the value converted to a numeric only string - - """ - return ''.join([x for x in msvc_version if x in string_digits + '.']) - -def get_host_platform(host_platform): - - host_platform = host_platform.lower() - - # Solaris returns i86pc for both 32 and 64 bit architectures - if host_platform == 'i86pc': - if platform.architecture()[0] == "64bit": - host_platform = "amd64" - else: - host_platform = "x86" - - try: - host =_ARCH_TO_CANONICAL[host_platform] - except KeyError: - msg = "Unrecognized host architecture %s" - raise MSVCUnsupportedHostArch(msg % repr(host_platform)) from None - - return host - -_native_host_architecture = None - -def get_native_host_architecture(): - """Return the native host architecture.""" - global _native_host_architecture - - if _native_host_architecture is None: - - try: - arch = common.read_reg( - r'SYSTEM\CurrentControlSet\Control\Session Manager\Environment\PROCESSOR_ARCHITECTURE' - ) - except OSError: - arch = None - - if not arch: - arch = platform.machine() - - _native_host_architecture = arch - - return _native_host_architecture - -_native_host_platform = None - -def get_native_host_platform(): - global _native_host_platform - - if _native_host_platform is None: - arch = get_native_host_architecture() - _native_host_platform = get_host_platform(arch) - - return _native_host_platform - -def get_host_target(env, msvc_version, all_host_targets: bool=False): - - vernum = float(get_msvc_version_numeric(msvc_version)) - vernum_int = int(vernum * 10) - - if vernum_int >= 143: - # 14.3 (VS2022) and later - host_target_cfg = _GE2022_HOST_TARGET_CFG - elif 143 > vernum_int >= 141: - # 14.2 (VS2019) to 14.1 (VS2017) - host_target_cfg = _LE2019_HOST_TARGET_CFG - 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 - - host_arch = env.get('HOST_ARCH') if env else None - debug("HOST_ARCH:%s", str(host_arch)) - - if host_arch: - host_platform = get_host_platform(host_arch) - else: - host_platform = get_native_host_platform() - - target_arch = env.get('TARGET_ARCH') if env else None - debug("TARGET_ARCH:%s", str(target_arch)) - - if target_arch: - - try: - target_platform = _ARCH_TO_CANONICAL[target_arch.lower()] - except KeyError: - all_archs = str(list(_ARCH_TO_CANONICAL.keys())) - raise MSVCUnsupportedTargetArch( - "Unrecognized target architecture %s\n\tValid architectures: %s" - % (repr(target_arch), all_archs) - ) from None - - target_host_map = host_target_cfg.target_host_map - - try: - host_target_list = target_host_map[target_platform][host_platform] - except KeyError: - host_target_list = [] - warn_msg = "unsupported host, target combination ({}, {}) for MSVC version {}".format( - repr(host_platform), repr(target_platform), msvc_version - ) - debug(warn_msg) - SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, warn_msg) - - else: - - target_platform = None - - if all_host_targets: - host_targets_map = host_target_cfg.host_all_targets_map - else: - host_targets_map = host_target_cfg.host_def_targets_map - - try: - host_target_list = host_targets_map[host_platform] - except KeyError: - msg = "Unrecognized host architecture %s for version %s" - raise MSVCUnsupportedHostArch(msg % (repr(host_platform), msvc_version)) from None - - return host_platform, target_platform, host_target_list - -_arm32_process_arm64_host = None - -def is_arm32_process_arm64_host(): - global _arm32_process_arm64_host - - if _arm32_process_arm64_host is None: - - host = get_native_host_architecture() - host = _ARCH_TO_CANONICAL.get(host.lower(),'') - host_isarm64 = host == 'arm64' - - process = sysconfig.get_platform() - process_isarm32 = process == 'win-arm32' - - _arm32_process_arm64_host = host_isarm64 and process_isarm32 - - return _arm32_process_arm64_host - -_check_skip_sendtelemetry = None - -def _skip_sendtelemetry(env): - global _check_skip_sendtelemetry - - if _check_skip_sendtelemetry is None: - - if _ARM32_ON_ARM64_SKIP_SENDTELEMETRY and is_arm32_process_arm64_host(): - _check_skip_sendtelemetry = True - else: - _check_skip_sendtelemetry = False - - if not _check_skip_sendtelemetry: - return False - - msvc_version = env.get('MSVC_VERSION') if env else None - if not msvc_version: - msvc_version = msvc_default_version(env) - - if not msvc_version: - return False - - vernum = float(get_msvc_version_numeric(msvc_version)) - if vernum < 14.2: # VS2019 - return False - - # arm32 process, arm64 host, VS2019+ - return True - -# If you update this, update SupportedVSList in Tool/MSCommon/vs.py, and the -# MSVC_VERSION documentation in Tool/msvc.xml. -_VCVER = [ - "14.3", - "14.2", - "14.1", "14.1Exp", - "14.0", "14.0Exp", - "12.0", "12.0Exp", - "11.0", "11.0Exp", - "10.0", "10.0Exp", - "9.0", "9.0Exp", - "8.0", "8.0Exp", - "7.1", - "7.0", - "6.0"] - -class _VSConfig: - - # MSVS IDE binaries - - BITFIELD_DEVELOP = 0b_1000 - BITFIELD_EXPRESS = 0b_0100 - BITFIELD_EXPRESS_WIN = 0b_0010 - BITFIELD_EXPRESS_WEB = 0b_0001 - - EXECUTABLE_MASK = BITFIELD_DEVELOP | BITFIELD_EXPRESS - - VSProgram = namedtuple("VSProgram", [ - 'program', - 'bitfield' - ]) - - DEVENV_COM = VSProgram(program='devenv.com', bitfield=BITFIELD_DEVELOP) - MSDEV_COM = VSProgram(program='msdev.com', bitfield=BITFIELD_DEVELOP) - - WDEXPRESS_EXE = VSProgram(program='WDExpress.exe', bitfield=BITFIELD_EXPRESS) - VCEXPRESS_EXE = VSProgram(program='VCExpress.exe', bitfield=BITFIELD_EXPRESS) - - VSWINEXPRESS_EXE = VSProgram(program='VSWinExpress.exe', bitfield=BITFIELD_EXPRESS_WIN) - VWDEXPRESS_EXE = VSProgram(program='VWDExpress.exe', bitfield=BITFIELD_EXPRESS_WEB) - - # MSVS IDE binary - - VSBinaryConfig = namedtuple("VSBinaryConfig", [ - 'pathcomp', # vs root -> ide dir - 'programs', # ide binaries - ]) - - # MSVS batch file - - VSBatchConfig = namedtuple("VSBatchConfig", [ - 'pathcomp', # vs root -> batch dir - 'script', # vs script - ]) - - # detect configuration - - DetectConfig = namedtuple("DetectConfig", [ - 'vs_cfg', # vs configuration - 'vc_cfg', # vc configuration - ]) - - VSDetectConfig = namedtuple("VSDetectConfig", [ - 'root', # relative path vc dir -> vs root - 'vs_binary_cfg', # vs executable - 'vs_batch_cfg', # vs batch file - ]) - - VCDetectConfigRegistry = namedtuple("VCDetectConfigRegistry", [ - 'regkeys', - ]) - - _VCRegistryKey = namedtuple("_VCRegistryKey", [ - 'hkroot', - 'key', - 'is_vsroot', - 'is_vcforpython', - ]) - - class VCRegKey(_VCRegistryKey): - - @classmethod - def factory( - cls, *, - key, - hkroot=SCons.Util.HKEY_LOCAL_MACHINE, - is_vsroot=False, - is_vcforpython=False, - ): - - regkey = cls( - hkroot=hkroot, - key=key, - is_vsroot=is_vsroot, - is_vcforpython=is_vcforpython, - ) - - return regkey - - _regkey = VCRegKey.factory - - # vs detect configuration: vswhere - - DETECT_VSWHERE = { - - '2022': DetectConfig( # 14.3 - vs_cfg=VSDetectConfig( - root=os.pardir, - vs_binary_cfg=VSBinaryConfig( - pathcomp=r'Common7\IDE', - programs=[DEVENV_COM], - ), - vs_batch_cfg=VSBatchConfig( - pathcomp=r'Common7\Tools', - script='VsDevCmd.bat', - ), - ), - vc_cfg=None, - ), - - '2019': DetectConfig( # 14.2 - vs_cfg=VSDetectConfig( - root=os.pardir, - vs_binary_cfg=VSBinaryConfig( - pathcomp=r'Common7\IDE', - programs=[DEVENV_COM], - ), - vs_batch_cfg=VSBatchConfig( - pathcomp=r'Common7\Tools', - script='VsDevCmd.bat', - ), - ), - vc_cfg=None, - ), - - '2017': DetectConfig( # 14.1 - vs_cfg=VSDetectConfig( - root=os.pardir, - vs_binary_cfg=VSBinaryConfig( - pathcomp=r'Common7\IDE', - programs=[DEVENV_COM, WDEXPRESS_EXE], - ), - vs_batch_cfg=VSBatchConfig( - pathcomp=r'Common7\Tools', - script='VsDevCmd.bat', - ), - ), - vc_cfg=None, - ), - - } - - # vs detect configuration: registry - - DETECT_REGISTRY = { - - '2015': DetectConfig( # 14.0 - vs_cfg=VSDetectConfig( - root=os.pardir, - vs_binary_cfg=VSBinaryConfig( - pathcomp=r'Common7\IDE', - programs=[DEVENV_COM, WDEXPRESS_EXE, VSWINEXPRESS_EXE, VWDEXPRESS_EXE], - ), - vs_batch_cfg=VSBatchConfig( - pathcomp=r'Common7\Tools', - # VsDevCmd.bat and vsvars32.bat appear to be different - script='vsvars32.bat', - ), - ), - vc_cfg=VCDetectConfigRegistry( - regkeys=[ - _regkey(key=r'Microsoft\VisualStudio\14.0\Setup\VC\ProductDir'), - _regkey(key=r'Microsoft\WDExpress\14.0\Setup\VS\ProductDir', is_vsroot=True), - _regkey(key=r'Microsoft\VCExpress\14.0\Setup\VC\ProductDir'), # not populated? - ], - ), - ), - - '2013': DetectConfig( # 12.0 - vs_cfg=VSDetectConfig( - root=os.pardir, - vs_binary_cfg=VSBinaryConfig( - pathcomp=r'Common7\IDE', - programs=[DEVENV_COM, WDEXPRESS_EXE, VSWINEXPRESS_EXE, VWDEXPRESS_EXE], - ), - vs_batch_cfg=VSBatchConfig( - pathcomp=r'Common7\Tools', - # VsDevCmd.bat and vsvars32.bat appear to be different - script='vsvars32.bat', - ), - ), - vc_cfg=VCDetectConfigRegistry( - regkeys=[ - _regkey(key=r'Microsoft\VisualStudio\12.0\Setup\VC\ProductDir'), - _regkey(key=r'Microsoft\VCExpress\12.0\Setup\VC\ProductDir'), - ] - ), - ), - - '2012': DetectConfig( # 11.0 - vs_cfg=VSDetectConfig( - root=os.pardir, - vs_binary_cfg=VSBinaryConfig( - pathcomp=r'Common7\IDE', - programs=[DEVENV_COM, WDEXPRESS_EXE, VSWINEXPRESS_EXE, VWDEXPRESS_EXE], - ), - vs_batch_cfg=VSBatchConfig( - pathcomp=r'Common7\Tools', - # VsDevCmd.bat and vsvars32.bat appear to be identical - script='vsvars32.bat', - ), - ), - vc_cfg=VCDetectConfigRegistry( - regkeys=[ - _regkey(key=r'Microsoft\VisualStudio\11.0\Setup\VC\ProductDir'), - _regkey(key=r'Microsoft\VCExpress\11.0\Setup\VC\ProductDir'), - ] - ), - ), - - '2010': DetectConfig( # 10.0 - vs_cfg=VSDetectConfig( - root=os.pardir, - vs_binary_cfg=VSBinaryConfig( - pathcomp=r'Common7\IDE', - programs=[DEVENV_COM, VCEXPRESS_EXE], - ), - vs_batch_cfg=VSBatchConfig( - pathcomp=r'Common7\Tools', - script='vsvars32.bat', - ), - ), - vc_cfg=VCDetectConfigRegistry( - regkeys=[ - _regkey(key=r'Microsoft\VisualStudio\10.0\Setup\VC\ProductDir'), - _regkey(key=r'Microsoft\VCExpress\10.0\Setup\VC\ProductDir'), - ] - ), - ), - - '2008': DetectConfig( # 9.0 - vs_cfg=VSDetectConfig( - root=os.pardir, - vs_binary_cfg=VSBinaryConfig( - pathcomp=r'Common7\IDE', - programs=[DEVENV_COM, VCEXPRESS_EXE], - ), - vs_batch_cfg=VSBatchConfig( - pathcomp=r'Common7\Tools', - script='vsvars32.bat', - ), - ), - vc_cfg=VCDetectConfigRegistry( - regkeys=[ - _regkey( - hkroot=SCons.Util.HKEY_CURRENT_USER, - key=r'Microsoft\DevDiv\VCForPython\9.0\InstallDir', - is_vsroot=True, is_vcforpython=True, - ), - _regkey( - key=r'Microsoft\DevDiv\VCForPython\9.0\InstallDir', - is_vsroot=True, is_vcforpython=True - ), - _regkey(key=r'Microsoft\VisualStudio\9.0\Setup\VC\ProductDir'), - _regkey(key=r'Microsoft\VCExpress\9.0\Setup\VC\ProductDir'), - ] - ), - ), - - '2005': DetectConfig( # 8.0 - vs_cfg=VSDetectConfig( - root=os.pardir, - vs_binary_cfg=VSBinaryConfig( - pathcomp=r'Common7\IDE', - programs=[DEVENV_COM, VCEXPRESS_EXE], - ), - vs_batch_cfg=VSBatchConfig( - pathcomp=r'Common7\Tools', - script='vsvars32.bat', - ), - ), - vc_cfg=VCDetectConfigRegistry( - regkeys=[ - _regkey(key=r'Microsoft\VisualStudio\8.0\Setup\VC\ProductDir'), - _regkey(key=r'Microsoft\VCExpress\8.0\Setup\VC\ProductDir'), - ] - ), - ), - - '2003': DetectConfig( # 7.1 - vs_cfg=VSDetectConfig( - root=os.pardir, - vs_binary_cfg=VSBinaryConfig( - pathcomp=r'Common7\IDE', - programs=[DEVENV_COM], - ), - vs_batch_cfg=VSBatchConfig( - pathcomp=r'Common7\Tools', - script='vsvars32.bat', - ), - ), - vc_cfg=VCDetectConfigRegistry( - regkeys=[ - _regkey(key=r'Microsoft\VisualStudio\7.1\Setup\VC\ProductDir'), - ] - ), - ), - - '2002': DetectConfig( # 7.0 - vs_cfg=VSDetectConfig( - root=os.pardir, - vs_binary_cfg=VSBinaryConfig( - pathcomp=r'Common7\IDE', - programs=[DEVENV_COM], - ), - vs_batch_cfg=VSBatchConfig( - pathcomp=r'Common7\Tools', - script='vsvars32.bat', - ), - ), - vc_cfg=VCDetectConfigRegistry( - regkeys=[ - _regkey(key=r'Microsoft\VisualStudio\7.0\Setup\VC\ProductDir'), - ] - ), - ), - - '1998': DetectConfig( # 6.0 - vs_cfg=VSDetectConfig( - root=os.pardir, - vs_binary_cfg=VSBinaryConfig( - pathcomp=r'Common\MSDev98\Bin', - programs=[MSDEV_COM], - ), - vs_batch_cfg=VSBatchConfig( - # There is no vsvars32.bat batch file - pathcomp=r'VC98\bin', - script='vcvars32.bat', - ), - ), - vc_cfg=VCDetectConfigRegistry( - regkeys=[ - _regkey(key=r'Microsoft\VisualStudio\6.0\Setup\Microsoft Visual C++\ProductDir'), - ] - ), - ), - - } - -def msvc_version_to_maj_min(msvc_version): - msvc_version_numeric = get_msvc_version_numeric(msvc_version) - - t = msvc_version_numeric.split(".") - if not len(t) == 2: - raise ValueError("Unrecognized version %s (%s)" % (msvc_version,msvc_version_numeric)) - try: - maj = int(t[0]) - min = int(t[1]) - return maj, min - except ValueError: - raise ValueError("Unrecognized version %s (%s)" % (msvc_version,msvc_version_numeric)) from None - -VSWHERE_PATHS = [ - os.path.join(p,'vswhere.exe') - for p in [ - # 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"), - 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"), - ] - if not p.startswith('%') -] - -def msvc_find_vswhere(env=None): - """ Find the location of vswhere """ - # NB: this gets called from testsuite on non-Windows platforms. - # Whether that makes sense or not, don't break it for those. - vswhere_env = env.subst('$VSWHERE') if env and 'VSWHERE' in env else None - vswhere_executables = _VSWhere.find_executables(vswhere_env) - if vswhere_executables: - vswhere_path = vswhere_executables[0].path - else: - vswhere_path = None - debug('vswhere_path=%s', vswhere_path) - return vswhere_path - -class _VSWhere(AutoInitialize): - - # TODO(JCB): review reset - - # priority: env > cmdline > (user, initial, user) - - VSWHERE_EXE = 'vswhere.exe' - - VSWhereExecutable = namedtuple('VSWhereExecutable', [ - 'path', - 'norm', - ]) - - debug_extra = None - - vswhere_cmdline = None - vswhere_executables = [] - - @classmethod - def _cmdline(cls) -> None: - vswhere, option = _Options.vswhere() - if vswhere: - vswhere_exec = cls._user_path(vswhere, option) - if vswhere_exec: - cls.vswhere_cmdline = vswhere_exec - debug('vswhere_cmdline=%s', cls.vswhere_cmdline, extra=cls.debug_extra) - - @classmethod - def _setup(cls) -> None: - for pval in VSWHERE_PATHS: - if not _Util.path_exists(pval): - continue - vswhere_exec = cls.VSWhereExecutable(path=pval, norm=_Util.normalize_path(pval)) - cls.vswhere_executables.append(vswhere_exec) - debug('vswhere_executables=%s', cls.vswhere_executables, extra=cls.debug_extra) - cls._cmdline() - - @classmethod - def _initialize(cls) -> None: - cls.debug_extra = debug_extra(cls) - cls.reset() - cls._setup() - - @classmethod - def reset(cls) -> None: - cls._cache_user_vswhere_paths = {} - - # validate user-specified vswhere executable - - @classmethod - def _user_path(cls, pval, source): - - rval = cls._cache_user_vswhere_paths.get(pval, UNDEFINED) - if rval != UNDEFINED: - debug('vswhere_exec=%s', repr(rval), extra=cls.debug_extra) - return rval - - vswhere_exec = None - if pval: - - if not _Util.path_exists(pval): - - warn_msg = f'vswhere executable path not found: {pval!r} ({source})' - debug(warn_msg, extra=cls.debug_extra) - SCons.Warnings.warn(MSVC.Warnings.VSWherePathWarning, warn_msg) - - else: - - norm = _Util.normalize_path(pval) - tail = os.path.split(norm)[-1] - if tail != cls.VSWHERE_EXE: - - warn_msg = f'unsupported vswhere executable (expected {cls.VSWHERE_EXE!r}, found {tail!r}): {pval!r} ({source})' - debug(warn_msg, extra=cls.debug_extra) - SCons.Warnings.warn(MSVC.Warnings.VSWherePathWarning, warn_msg) - - else: - - vswhere_exec = cls.VSWhereExecutable(path=pval, norm=norm) - debug('vswhere_exec=%s', repr(vswhere_exec), extra=cls.debug_extra) - - cls._cache_user_vswhere_paths[pval] = vswhere_exec - - return vswhere_exec - - # user-specified vswhere executables - - @classmethod - def user_pathlist(cls, path_list, front, source) -> None: - - user_executables = [] - for pval in path_list: - vswhere_exec = cls._user_path(pval, source) - if vswhere_exec: - user_executables.append(vswhere_exec) - - if user_executables: - - if front: - all_executables = user_executables + cls.vswhere_executables - else: - all_executables = cls.vswhere_executables + user_executables - - seen = set() - unique_executables = [] - for vswhere_exec in all_executables: - if vswhere_exec.norm in seen: - continue - seen.add(vswhere_exec.norm) - unique_executables.append(vswhere_exec) - - cls.vswhere_executables = unique_executables - debug('vswhere_executables=%s', cls.vswhere_executables, extra=cls.debug_extra) - - # all vswhere executables - - @classmethod - def find_executables(cls, vswhere_env=None): - - vswhere_executables = [] - - # env['VSWHERE'] path - if vswhere_env: - vswhere_exec = cls._user_path(vswhere_env, "env['VSWHERE']") - if vswhere_exec: - vswhere_executables.append(vswhere_exec) - - # --vswhere=EXEPATH - if cls.vswhere_cmdline: - vswhere_executables.append(cls.vswhere_cmdline) - - # default paths and user paths (vswhere_push_location) - if cls.vswhere_executables: - vswhere_executables.extend(cls.vswhere_executables) - - return vswhere_executables - -# register user vswhere executable location(s) - -def vswhere_push_location(string_or_list, front=False) -> None: - # TODO(JCB): need docstring - path_list = SCons.Util.flatten(string_or_list) - if path_list: - _VSWhere.user_pathlist(path_list, front, 'vswhere_push_location') - -class _VSChannel(AutoInitialize): - - # TODO(JCB): review reset - - # priority: cmdline > user > initial - - debug_extra = None - - vs_channel_initial = None - vs_channel_cmdline = None - - # reset - - vs_channel_user = None - vs_channel_def = None - vs_channel_retrieved = False - - @classmethod - def _initial(cls) -> None: - cls.vs_channel_initial = MSVC.Config.MSVS_CHANNEL_RELEASE - cls.vs_channel_def = cls.vs_channel_initial - debug('vs_channel_initial=%s', - cls.vs_channel_initial.vs_channel_id, - extra=cls.debug_extra - ) - - @classmethod - def _cmdline(cls) -> None: - channel, option = _Options.msvs_channel() - if channel: - vs_channel_def = MSVC.Validate.validate_msvs_channel( - channel, option - ) - if vs_channel_def: - cls.vs_channel_cmdline = vs_channel_def - cls.vs_channel_def = cls.vs_channel_cmdline - debug('vs_channel_cmdline=%s', - cls.vs_channel_cmdline.vs_channel_id, - extra=cls.debug_extra - ) - - @classmethod - def _setup(cls) -> None: - cls._initial() - cls._cmdline() - - @classmethod - def _initialize(cls) -> None: - cls.debug_extra = debug_extra(cls) - cls._setup() - - @classmethod - def reset(cls) -> None: - cls.vs_channel_user = None - cls.vs_channel_def = None - cls.vs_channel_retrieved = False - cls._setup() - - @classmethod - def get_default_channel(cls): - if not cls.vs_channel_retrieved: - cls.vs_channel_retrieved = True - if cls.vs_channel_cmdline: - cls.vs_channel_def = cls.vs_channel_cmdline - elif cls.vs_channel_user: - cls.vs_channel_def = cls.vs_channel_user - else: - cls.vs_channel_def = cls.vs_channel_initial - debug( - 'vs_channel=%s', - cls.vs_channel_def.vs_channel_id, - extra=cls.debug_extra - ) - return cls.vs_channel_def - - @classmethod - def set_default_channel(cls, msvs_channel, source) -> bool: - - rval = False - - if cls.vs_channel_retrieved: - - warn_msg = f'msvs channel {msvs_channel!r} ({source}) ignored: must be set before first use.' - debug(warn_msg, extra=cls.debug_extra) - SCons.Warnings.warn(MSVC.Warnings.MSVSChannelWarning, warn_msg) - - else: - - vs_channel_def = MSVC.Validate.validate_msvs_channel(msvs_channel, source) - if vs_channel_def: - - cls.vs_channel_user = vs_channel_def - debug( - 'vs_channel_user=%s', - cls.vs_channel_user.vs_channel_id, - extra=cls.debug_extra - ) - - if not cls.vs_channel_cmdline: - # priority: cmdline > user > initial - cls.vs_channel_def = cls.vs_channel_user - debug( - 'vs_channel=%s', - cls.vs_channel_def.vs_channel_id, - extra=cls.debug_extra - ) - rval = True - - debug( - 'vs_channel=%s', - cls.vs_channel_def.vs_channel_id, - extra=cls.debug_extra - ) - - return rval - -def msvs_set_channel_default(msvs_channel) -> bool: - """Set the default msvs channel. - - Args: - msvs_channel: str - string representing the msvs channel - - Case-insensitive values are: - Release, Rel, Preview, Pre, Any, * - - Returns: - bool: True if the default msvs channel was accepted. - False if the default msvs channel was not accepted. - - """ - rval = _VSChannel.set_default_channel(msvs_channel, 'msvc_set_channel_default') - return rval - -def msvs_get_channel_default(): - """Get the default msvs channel. - - Returns: - str: default msvs channel - - """ - return _VSChannel.vs_channel_def.vs_channel_id - -class _VSKeys(AutoInitialize): - - debug_extra = None - - @classmethod - def _initialize(cls) -> None: - cls.debug_extra = debug_extra(cls) - - # edition key: (product, channel, component/None, seqnbr/None) - - _MSVSEditionKey = namedtuple('_MSVSEditionKey', [ - 'vs_product_def', - 'vs_channel_def', - 'vs_componentid_def', - 'vs_sequence_nbr', - ]) - - class MSVSEditionKey(_MSVSEditionKey): - - def serialize(self): - values = [ - self.vs_product_def.vs_product, - self.vs_channel_def.vs_channel_suffix, - ] - if self.vs_componentid_def: - values.append(self.vs_componentid_def.vs_component_suffix) - if self.vs_sequence_nbr: - values.append(str(self.vs_sequence_nbr)) - rval = '-'.join(values) - return rval - - @classmethod - def factory( - cls, *, - vs_product_def, - vs_channel_def, - vs_componentid_def, - vs_sequence_nbr, - ): - - vs_edition_key = cls( - vs_product_def=vs_product_def, - vs_channel_def=vs_channel_def, - vs_componentid_def=vs_componentid_def, - vs_sequence_nbr=vs_sequence_nbr - ) - - return vs_edition_key - - @classmethod - def msvs_edition_key( - cls, *, - vs_product_def=None, - vs_channel_def=None, - vs_componentid_def=None, - vs_sequence_nbr=None, - ): - - if not vs_product_def: - errmsg = 'vs_product_def is undefined' - debug('MSVCInternalError: %s', errmsg, extra=cls.debug_extra) - raise MSVCInternalError(errmsg) - - if not vs_channel_def: - errmsg = 'vs_channel_def is undefined' - debug('MSVCInternalError: %s', errmsg, extra=cls.debug_extra) - raise MSVCInternalError(errmsg) - - vs_edition_key = cls.MSVSEditionKey.factory( - vs_product_def=vs_product_def, - vs_channel_def=vs_channel_def, - vs_componentid_def=vs_componentid_def, - vs_sequence_nbr=vs_sequence_nbr - ) - - return vs_edition_key - - # channel key: (channel, component/None) - - _MSVSChannelKey = namedtuple('_MSVSChannelKey', [ - 'vs_channel_def', - 'vs_componentid_def', - ]) - - class MSVSChannelKey(_MSVSChannelKey): - - def serialize(self): - values = [ - self.vs_channel_def.vs_channel_suffix, - ] - if self.vs_componentid_def: - values.append(self.vs_componentid_def.vs_component_suffix) - rval = '-'.join(values) - return rval - - @classmethod - def factory( - cls, *, - vs_channel_def, - vs_componentid_def, - ): - - vs_channel_key = cls( - vs_channel_def=vs_channel_def, - vs_componentid_def=vs_componentid_def, - ) - - return vs_channel_key - - @classmethod - def msvs_channel_key( - cls, *, - vs_channel_def=None, - vs_componentid_def=None, - ): - - if not vs_channel_def: - errmsg = 'vs_channel_def is undefined' - debug('MSVCInternalError: %s', errmsg, extra=cls.debug_extra) - raise MSVCInternalError(errmsg) - - vs_channel_key = cls.MSVSChannelKey.factory( - vs_channel_def=vs_channel_def, - vs_componentid_def=vs_componentid_def, - ) - - return vs_channel_key - -_MSVSBase = namedtuple('_MSVSBase', [ - 'id_str', - 'id_comps', - 'vs_product_def', - 'vs_channel_def', - 'vs_component_def', - 'vs_sequence_nbr', - 'vs_dir', - 'vs_dir_norm', - 'vs_version', - 'is_express', - 'is_buildtools', - 'is_vcforpython', - 'vs_edition_channel_component_seqnbr_key', - 'vs_edition_channel_component_key', - 'vs_edition_channel_key', - 'vs_channel_component_key', - 'vs_channel_key', - 'instance_map', -]) - -class MSVSBase(_MSVSBase): - - def _is_express(vs_component_def) -> bool: - vs_componentid_def = vs_component_def.vs_componentid_def - is_express = bool(vs_componentid_def == MSVC.Config.MSVS_COMPONENTID_EXPRESS) - return is_express - - @staticmethod - def _is_buildtools(vs_component_def) -> bool: - vs_componentid_def = vs_component_def.vs_componentid_def - is_buildtools = bool(vs_componentid_def == MSVC.Config.MSVS_COMPONENTID_BUILDTOOLS) - return is_buildtools - - @staticmethod - def _is_vcforpython(vs_component_def) -> bool: - vs_componentid_def = vs_component_def.vs_componentid_def - is_vcforpython = bool(vs_componentid_def == MSVC.Config.MSVS_COMPONENTID_PYTHON) - return is_vcforpython - - @classmethod - def factory( - cls, *, - vs_product_def, - vs_channel_def, - vs_component_def, - vs_sequence_nbr, - vs_dir, - vs_version, - ): - - vs_componentid_def = vs_component_def.vs_componentid_def - - vs_edition_channel_component_seqnbr_key = _VSKeys.msvs_edition_key( - vs_product_def=vs_product_def, - vs_channel_def=vs_channel_def, - vs_componentid_def=vs_componentid_def, - vs_sequence_nbr=vs_sequence_nbr, - ) - - vs_edition_channel_component_key = _VSKeys.msvs_edition_key( - vs_product_def=vs_product_def, - vs_channel_def=vs_channel_def, - vs_componentid_def=vs_componentid_def, - ) - - vs_edition_channel_key = _VSKeys.msvs_edition_key( - vs_product_def=vs_product_def, - vs_channel_def=vs_channel_def, - ) - - vs_channel_component_key = _VSKeys.msvs_channel_key( - vs_channel_def=vs_channel_def, - vs_componentid_def=vs_componentid_def, - ) - - vs_channel_key = _VSKeys.msvs_channel_key( - vs_channel_def=vs_channel_def, - ) - - instance_map = { - 'msvs_instance': None, - 'msvc_instance': None, - } - - id_comps = ( - vs_product_def.vs_product, - vs_channel_def.vs_channel_suffix, - vs_component_def.vs_componentid_def.vs_component_suffix, - str(vs_sequence_nbr), - ) - - id_str = '{}({})'.format(cls.__name__, ', '.join(id_comps)) - - msvs_base = cls( - id_str=id_str, - id_comps=id_comps, - vs_product_def=vs_product_def, - vs_channel_def=vs_channel_def, - vs_component_def=vs_component_def, - vs_sequence_nbr=vs_sequence_nbr, - vs_dir=vs_dir, - vs_dir_norm=_Util.normalize_path(vs_dir), - vs_version=vs_version, - is_express=cls._is_express(vs_component_def), - is_buildtools=cls._is_buildtools(vs_component_def), - is_vcforpython=cls._is_vcforpython(vs_component_def), - vs_edition_channel_component_seqnbr_key=vs_edition_channel_component_seqnbr_key, - vs_edition_channel_component_key=vs_edition_channel_component_key, - vs_edition_channel_key=vs_edition_channel_key, - vs_channel_component_key=vs_channel_component_key, - vs_channel_key=vs_channel_key, - instance_map=instance_map, - ) - - return msvs_base - - @staticmethod - def default_order(a, b): - # vs product numeric: descending order - if a.vs_product_def.vs_product_numeric != b.vs_product_def.vs_product_numeric: - return 1 if a.vs_product_def.vs_product_numeric < b.vs_product_def.vs_product_numeric else -1 - # vs channel: ascending order (release, preview) - if a.vs_channel_def.vs_channel_rank != b.vs_channel_def.vs_channel_rank: - return 1 if a.vs_channel_def.vs_channel_rank > b.vs_channel_def.vs_channel_rank else -1 - # component rank: descending order - if a.vs_component_def.vs_component_rank != b.vs_component_def.vs_component_rank: - return 1 if a.vs_component_def.vs_component_rank < b.vs_component_def.vs_component_rank else -1 - # sequence number: ascending order - if a.vs_sequence_nbr != b.vs_sequence_nbr: - return 1 if a.vs_sequence_nbr > b.vs_sequence_nbr else -1 - return 0 - - def register_msvs_instance(self, msvs_instance) -> None: - self.instance_map['msvs_instance'] = msvs_instance - - def register_msvc_instance(self, msvc_instance) -> None: - self.instance_map['msvc_instance'] = msvc_instance - - @property - def msvs_instance(self): - return self.instance_map.get('msvs_instance') - - @property - def msvc_instance(self): - return self.instance_map.get('msvc_instance') - - @property - def vs_component_suffix(self): - return self.vs_component_def.vs_componentid_def.vs_component_suffix - -_MSVSInstance = namedtuple('_MSVSInstance', [ - 'id_str', - 'msvs_base', - 'vs_executable', - 'vs_executable_norm', - 'vs_script', - 'vs_script_norm', - 'vc_version_def', -]) - -class MSVSInstance(_MSVSInstance): - - @classmethod - def factory( - cls, *, - msvs_base, - vs_executable, - vs_script, - vc_version_def, - ): - - id_str = '{}({})'.format(cls.__name__, ', '.join(msvs_base.id_comps)) - - msvs_instance = cls( - id_str=id_str, - msvs_base=msvs_base, - vs_executable=vs_executable, - vs_executable_norm=_Util.normalize_path(vs_executable), - vs_script=vs_script, - vs_script_norm=_Util.normalize_path(vs_script), - vc_version_def=vc_version_def, - ) - - return msvs_instance - - @staticmethod - def default_order(a, b): - return MSVSBase.default_order(a.msvs_base, b.msvs_base) - - @property - def id_comps(self): - return self.msvs_base.id_comps - - @property - def msvc_instance(self): - return self.msvs_base.msvc_instance - - @property - def vs_dir(self): - return self.msvs_base.vs_dir - - @property - def vs_version(self): - return self.msvs_base.vs_version - - @property - def vs_edition_channel_component_seqnbr_key(self): - return self.msvs_base.vs_edition_channel_component_seqnbr_key - - @property - def vs_edition_channel_component_key(self): - return self.msvs_base.vs_edition_channel_component_key - - @property - def vs_edition_channel_key(self): - return self.msvs_base.vs_edition_channel_key - - @property - def vs_channel_component_key(self): - return self.msvs_base.vs_channel_component_key - - @property - def vs_channel_key(self): - return self.msvs_base.vs_channel_key - - @property - def msvc_version(self): - return self.vc_version_def.msvc_version - - @property - def msvc_verstr(self): - return self.vc_version_def.msvc_verstr - - @property - def vs_product_def(self): - return self.msvs_base.vs_product_def - - @property - def vs_channel_def(self): - return self.msvs_base.vs_channel_def - - @property - def vs_componentid_def(self): - return self.msvs_base.vs_component_def.vs_componentid_def - - @property - def vs_product(self): - return self.msvs_base.vs_product_def.vs_product - - @property - def vs_channel_id(self): - return self.msvs_base.vs_channel_def.vs_channel_id - - @property - def vs_component_id(self): - return self.msvs_base.vs_component_def.vs_componentid_def.vs_component_id - - @property - def vs_sequence_nbr(self): - return self.msvs_base.vs_sequence_nbr - -_MSVSInstalled = namedtuple('_MSVSInstalled', [ - 'msvs_instances', - 'msvs_edition_instances_map', - 'msvs_channel_instances_map', - 'msvs_channel_map', # TODO(JCB): remove? -]) - -class MSVSInstalled(_MSVSInstalled, AutoInitialize): - - debug_extra = None - - @classmethod - def _initialize(cls) -> None: - cls.debug_extra = debug_extra(cls) - - @classmethod - def factory( - cls, *, - msvs_instances, - ): - - msvs_instances = sorted( - msvs_instances, key=cmp_to_key(MSVSInstance.default_order) - ) - - msvs_channel_map = { - vs_channel_def: {} - for vs_channel_def in MSVC.Config.MSVS_CHANNEL_DEFINITION_LIST - } - - vs_edition_instances = {} - vs_channel_instances = {} - - # channel key: (ANY, None) - vs_anychannel_key = _VSKeys.msvs_channel_key( - vs_channel_def=MSVC.Config.MSVS_CHANNEL_ANY, - ) - - for msvs_instance in msvs_instances: - - debug( - 'msvs instance: id_str=%s, msvc_version=%s, vs_dir=%s, vs_exec=%s', - repr(msvs_instance.id_str), - repr(msvs_instance.msvc_version), - repr(msvs_instance.vs_dir), - repr(msvs_instance.vs_executable_norm), - extra=cls.debug_extra, - ) - - # edition key: (product, ANY, None, None) - vs_edition_any_key = _VSKeys.msvs_edition_key( - vs_product_def=msvs_instance.vs_product_def, - vs_channel_def=MSVC.Config.MSVS_CHANNEL_ANY, - ) - - # edition key: (product, ANY, component, None) - vs_edition_anychannel_component_key = _VSKeys.msvs_edition_key( - vs_product_def=msvs_instance.vs_product_def, - vs_channel_def=MSVC.Config.MSVS_CHANNEL_ANY, - vs_componentid_def=msvs_instance.vs_componentid_def, - ) - - # all editions keys - vs_edition_keys = ( - vs_edition_any_key, - msvs_instance.vs_edition_channel_key, - vs_edition_anychannel_component_key, - msvs_instance.vs_edition_channel_component_key, - msvs_instance.vs_edition_channel_component_seqnbr_key, - ) - - # channel key: (ANY, component) - vs_anychannel_component_key = _VSKeys.msvs_channel_key( - vs_channel_def=MSVC.Config.MSVS_CHANNEL_ANY, - vs_componentid_def=msvs_instance.vs_componentid_def, - ) - - # all channel keys - vs_channel_keys = ( - vs_anychannel_key, - msvs_instance.vs_channel_key, - vs_anychannel_component_key, - msvs_instance.vs_channel_component_key, - ) - - for vs_edition_key in vs_edition_keys: - vs_edition_instances.setdefault(vs_edition_key, []).append(msvs_instance) - - for vs_channel_key in vs_channel_keys: - vs_channel_instances.setdefault(vs_channel_key, []).append(msvs_instance) - - # msvc_version support - - if msvs_instance.msvc_version != msvs_instance.msvc_verstr: - versions = [msvs_instance.msvc_verstr, msvs_instance.msvc_version] - else: - versions = [msvs_instance.msvc_verstr] - - vs_channel_defs = MSVC.Config.MSVS_CHANNEL_MEMBERLISTS[msvs_instance.vs_channel_def] - - for vcver in versions: - for vs_channel_def in vs_channel_defs: - - msvs_version_map = msvs_channel_map[vs_channel_def] - - msvs_instance_list = msvs_version_map.setdefault(vcver, []) - msvs_instance_list.append(msvs_instance) - - msvs_installed = cls( - msvs_instances=msvs_instances, - msvs_edition_instances_map=vs_edition_instances, - msvs_channel_instances_map=vs_channel_instances, - msvs_channel_map=msvs_channel_map, - ) - - if DEBUG_ENABLED: - msvs_installed._debug_dump() - - return msvs_installed - - def _debug_dump(self) -> None: - - for vs_channel_key, msvc_instance_list in self.msvs_channel_instances_map.items(): - msvc_instances = [msvc_instance.id_str for msvc_instance in msvc_instance_list] - debug( - 'msvs_channel_instances_map[%s]=%s', - repr(vs_channel_key.serialize()), - repr(msvc_instances), - extra=self.debug_extra, - ) - - for vs_edition_key, msvc_instance_list in self.msvs_edition_instances_map.items(): - msvc_instances = [msvc_instance.id_str for msvc_instance in msvc_instance_list] - debug( - 'msvs_edition_instances_map[%s]=%s', - repr(vs_edition_key.serialize()), - repr(msvc_instances), - extra=self.debug_extra, - ) - - for vs_channel_def, msvs_version_map in self.msvs_channel_map.items(): - for vcver, msvs_instance_list in msvs_version_map.items(): - msvs_instances = [msvs_instance.id_str for msvs_instance in msvs_instance_list] - debug( - 'msvs_version_map[%s][%s]=%s', - repr(vs_channel_def.vs_channel_suffix), - repr(vcver), - repr(msvs_instances), - extra=self.debug_extra, - ) - - def _msvs_instances_internal( - self, *, - vs_product_def=None, - vs_channel_def=None, - vs_componentid_def=None, - vs_sequence_nbr=None, - ): - - if not vs_channel_def: - vs_channel_def = _VSChannel.get_default_channel() - - if vs_product_def: - - query_key = _VSKeys.msvs_edition_key( - vs_product_def=vs_product_def, - vs_channel_def=vs_channel_def, - vs_componentid_def=vs_componentid_def, - vs_sequence_nbr=vs_sequence_nbr - ) - - msvs_instances = self.msvs_edition_instances_map.get(query_key, []) - - else: - - query_key = _VSKeys.msvs_channel_key( - vs_channel_def=vs_channel_def, - vs_componentid_def=vs_componentid_def, - ) - - msvs_instances = self.msvs_channel_instances_map.get(query_key, []) - - debug( - 'query_key=%s, n_msvs_instances=%s', - repr(query_key.serialize()), - repr(len(msvs_instances)), - extra=self.debug_extra, - ) - - return msvs_instances, query_key - -_MSVCInstance = namedtuple('_MSVCInstance', [ - 'id_str', - 'msvs_base', - 'vc_version_def', - 'vc_feature_map', - 'vc_dir', - 'vc_dir_norm', - 'is_sdkversion_supported', -]) - -class MSVCInstance(_MSVCInstance, AutoInitialize): - - debug_extra = None - - @classmethod - def _initialize(cls) -> None: - cls.debug_extra = debug_extra(cls) - - @classmethod - def factory( - cls, *, - msvs_base, - vc_version_def, - vc_feature_map, - vc_dir, - ): - - id_str = '{}({})'.format(cls.__name__, ', '.join(msvs_base.id_comps)) - - if vc_feature_map is None: - vc_feature_map = {} - - msvc_instance = cls( - id_str=id_str, - msvs_base=msvs_base, - vc_version_def=vc_version_def, - vc_feature_map=vc_feature_map, - vc_dir=vc_dir, - vc_dir_norm=_Util.normalize_path(vc_dir), - is_sdkversion_supported=cls._is_sdkversion_supported(msvs_base), - ) - - return msvc_instance - - @staticmethod - def default_order(a, b): - return MSVSBase.default_order(a.msvs_base, b.msvs_base) - - @property - def id_comps(self): - return self.msvs_base.id_comps - - @property - def msvs_instance(self): - return self.msvs_base.msvs_instance - - @staticmethod - def _is_sdkversion_supported(msvs_base) -> bool: - - vs_componentid_def = msvs_base.vs_component_def.vs_componentid_def - vs_product_numeric = msvs_base.vs_product_def.vs_product_numeric - - if vs_product_numeric >= 2017: - # VS2017 and later - is_supported = True - elif vs_product_numeric == 2015: - # VS2015: - # False: Express, BuildTools - # True: Develop, CmdLine - if vs_componentid_def == MSVC.Config.MSVS_COMPONENTID_EXPRESS: - is_supported = False - elif vs_componentid_def == MSVC.Config.MSVS_COMPONENTID_BUILDTOOLS: - is_supported = False - else: - is_supported = True - else: - # VS2013 and earlier - is_supported = False - - return is_supported - - def skip_uwp_target(self, env) -> bool: - skip = False - if self.vc_feature_map: - target_arch = env.get('TARGET_ARCH') - uwp_target_is_supported = self.vc_feature_map.get('uwp_target_is_supported', {}) - is_supported = uwp_target_is_supported.get(target_arch, True) - skip = bool(not is_supported) - debug( - 'skip=%s, msvc_version=%s', - repr(skip), repr(self.vc_version_def.msvc_version), extra=self.debug_extra - ) - return skip - - def is_uwp_target_supported(self, target_arch=None) -> bool: - - vs_componentid_def = self.msvs_base.vs_component_def.vs_componentid_def - vs_product_numeric = self.msvs_base.vs_product_def.vs_product_numeric - - is_target = False - if vs_product_numeric >= 2017: - # VS2017 and later - is_supported = True - elif vs_product_numeric == 2015: - # VS2015: - # Maybe: Express - # False: BuildTools - # True: Develop, CmdLine - if vs_componentid_def == MSVC.Config.MSVS_COMPONENTID_EXPRESS: - uwp_target_is_supported = self.vc_feature_map.get('uwp_target_is_supported', {}) - is_supported = uwp_target_is_supported.get(target_arch, True) - is_target = True - elif vs_componentid_def == MSVC.Config.MSVS_COMPONENTID_BUILDTOOLS: - is_supported = False - else: - is_supported = True - else: - # VS2013 and earlier - is_supported = False - - debug( - 'is_supported=%s, is_target=%s, msvc_version=%s, msvs_component=%s, target_arch=%s', - is_supported, is_target, - repr(self.vc_version_def.msvc_version), - repr(vs_componentid_def.vs_component_id), - repr(target_arch), - extra=self.debug_extra, - ) - - return is_supported, is_target - - # convenience properties: reduce member lookup chain for readability - - @property - def is_express(self) -> bool: - return self.msvs_base.is_express - - @property - def is_buildtools(self) -> bool: - return self.msvs_base.is_buildtools - - @property - def is_vcforpython(self) -> bool: - return self.msvs_base.is_vcforpython - - @property - def vs_edition_channel_component_seqnbr_key(self): - return self.msvs_base.vs_edition_channel_component_seqnbr_key - - @property - def vs_edition_channel_component_key(self): - return self.msvs_base.vs_edition_channel_component_key - - @property - def vs_edition_channel_key(self): - return self.msvs_base.vs_edition_channel_key - - @property - def vs_channel_component_key(self): - return self.msvs_base.vs_channel_component_key - - @property - def vs_channel_key(self): - return self.msvs_base.vs_channel_key - - @property - def msvc_version(self): - return self.vc_version_def.msvc_version - - @property - def msvc_vernum(self): - return self.vc_version_def.msvc_vernum - - @property - def msvc_verstr(self): - return self.vc_version_def.msvc_verstr - - @property - def vs_product_def(self): - return self.msvs_base.vs_product_def - - @property - def vs_channel_def(self): - return self.msvs_base.vs_channel_def - - @property - def vs_componentid_def(self): - return self.msvs_base.vs_component_def.vs_componentid_def - - @property - def vs_product(self): - return self.msvs_base.vs_product_def.vs_product - - @property - def vs_product_numeric(self): - return self.msvs_base.vs_product_def.vs_product_numeric - - @property - def vs_channel_id(self): - return self.msvs_base.vs_channel_def.vs_channel_id - - @property - def vs_component_id(self): - return self.msvs_base.vs_component_def.vs_componentid_def.vs_component_id - - @property - def vs_sequence_nbr(self): - return self.msvs_base.vs_sequence_nbr - -_MSVCInstalled = namedtuple('_MSVCInstalled', [ - 'msvc_instances', - 'msvs_edition_instances_map', - 'msvs_channel_instances_map', - 'msvs_channel_map', # TODO(JCB): remove? -]) - -class MSVCInstalled(_MSVCInstalled, AutoInitialize): - - debug_extra = None - - @classmethod - def _initialize(cls) -> None: - cls.debug_extra = debug_extra(cls) - - @classmethod - def factory( - cls, *, - msvc_instances, - ): - - msvc_instances = sorted( - msvc_instances, key=cmp_to_key(MSVCInstance.default_order) - ) - - msvs_channel_map = { - vs_channel_def: {} - for vs_channel_def in MSVC.Config.MSVS_CHANNEL_DEFINITION_LIST - } - - vs_edition_instances = {} - vs_channel_instances = {} - - # channel key: (ANY, None) - vs_anychannel_key = _VSKeys.msvs_channel_key( - vs_channel_def=MSVC.Config.MSVS_CHANNEL_ANY, - ) - - for msvc_instance in msvc_instances: - - debug( - 'msvc_instance: id_str=%s, msvc_version=%s, vc_dir=%s', - repr(msvc_instance.id_str), - repr(msvc_instance.msvc_version), - repr(msvc_instance.vc_dir), - extra=cls.debug_extra, - ) - - # edition key: (product, ANY, None, None) - vs_edition_any_key = _VSKeys.msvs_edition_key( - vs_product_def=msvc_instance.vs_product_def, - vs_channel_def=MSVC.Config.MSVS_CHANNEL_ANY, - ) - - # edition key: (product, ANY, component, None) - vs_edition_anychannel_component_key = _VSKeys.msvs_edition_key( - vs_product_def=msvc_instance.vs_product_def, - vs_channel_def=MSVC.Config.MSVS_CHANNEL_ANY, - vs_componentid_def=msvc_instance.vs_componentid_def, - ) - - # all editions keys - vs_edition_keys = ( - vs_edition_any_key, - msvc_instance.vs_edition_channel_key, - vs_edition_anychannel_component_key, - msvc_instance.vs_edition_channel_component_key, - msvc_instance.vs_edition_channel_component_seqnbr_key, - ) - - # channel key: (ANY, component) - vs_anychannel_component_key = _VSKeys.msvs_channel_key( - vs_channel_def=MSVC.Config.MSVS_CHANNEL_ANY, - vs_componentid_def=msvc_instance.vs_componentid_def, - ) - - # all channel keys - vs_channel_keys = ( - vs_anychannel_key, - msvc_instance.vs_channel_key, - vs_anychannel_component_key, - msvc_instance.vs_channel_component_key, - ) - - for vs_edition_key in vs_edition_keys: - vs_edition_instances.setdefault(vs_edition_key, []).append(msvc_instance) - - for vs_channel_key in vs_channel_keys: - vs_channel_instances.setdefault(vs_channel_key, []).append(msvc_instance) - - # msvc_version support - - if msvc_instance.msvc_version != msvc_instance.msvc_verstr: - versions = [msvc_instance.msvc_verstr, msvc_instance.msvc_version] - else: - versions = [msvc_instance.msvc_verstr] - - vs_channel_defs = MSVC.Config.MSVS_CHANNEL_MEMBERLISTS[msvc_instance.vs_channel_def] - - for vcver in versions: - - for vs_channel_def in vs_channel_defs: - - msvc_version_map = msvs_channel_map[vs_channel_def] - - msvc_instance_list = msvc_version_map.setdefault(vcver, []) - msvc_instance_list.append(msvc_instance) - - msvc_installed = cls( - msvc_instances=msvc_instances, - msvs_edition_instances_map=vs_edition_instances, - msvs_channel_instances_map=vs_channel_instances, - msvs_channel_map=msvs_channel_map, - ) - - if DEBUG_ENABLED: - msvc_installed._debug_dump() - - return msvc_installed - - def _debug_dump(self) -> None: - - for vs_channel_key, msvc_instance_list in self.msvs_channel_instances_map.items(): - msvc_instances = [msvc_instance.id_str for msvc_instance in msvc_instance_list] - debug( - 'msvs_channel_instances_map[%s]=%s', - repr(vs_channel_key.serialize()), - repr(msvc_instances), - extra=self.debug_extra, - ) - - for vs_edition_key, msvc_instance_list in self.msvs_edition_instances_map.items(): - msvc_instances = [msvc_instance.id_str for msvc_instance in msvc_instance_list] - debug( - 'msvs_edition_instances_map[%s]=%s', - repr(vs_edition_key.serialize()), - repr(msvc_instances), - extra=self.debug_extra, - ) - - for vs_channel_def, msvc_version_map in self.msvs_channel_map.items(): - for vcver, msvc_instance_list in msvc_version_map.items(): - msvc_instances = [msvc_instance.id_str for msvc_instance in msvc_instance_list] - debug( - 'msvc_version_map[%s][%s]=%s', - repr(vs_channel_def.vs_channel_suffix), - repr(vcver), - repr(msvc_instances), - extra=self.debug_extra, - ) - - def _msvc_instances_internal( - self, *, - vs_product_def=None, - vs_channel_def=None, - vs_componentid_def=None, - vs_sequence_nbr=None, - ): - - if not vs_channel_def: - vs_channel_def = _VSChannel.get_default_channel() - - if vs_product_def: - - query_key = _VSKeys.msvs_edition_key( - vs_product_def=vs_product_def, - vs_channel_def=vs_channel_def, - vs_componentid_def=vs_componentid_def, - vs_sequence_nbr=vs_sequence_nbr - ) - - msvc_instances = self.msvs_edition_instances_map.get(query_key, []) - - else: - - query_key = _VSKeys.msvs_channel_key( - vs_channel_def=vs_channel_def, - vs_componentid_def=vs_componentid_def, - ) - - msvc_instances = self.msvs_channel_instances_map.get(query_key, []) - - debug( - 'query_key=%s, n_msvc_instances=%s', - repr(query_key.serialize()), - repr(len(msvc_instances)), - extra=self.debug_extra, - ) - - return msvc_instances, query_key - -_MSVSManager = namedtuple('_MSVSManager', [ - 'msvc_installed', - 'msvs_installed', -]) - -class MSVSManager(_MSVSManager, AutoInitialize): - - debug_extra = None - - @classmethod - def _initialize(cls) -> None: - cls.debug_extra = debug_extra(cls) - - @classmethod - def factory(cls, msvc_installed, msvs_installed): - - msvs_manager = cls( - msvc_installed=msvc_installed, - msvs_installed=msvs_installed, - ) - - debug( - 'n_msvc_instances=%d, n_msvs_instances=%d', - len(msvc_installed.msvc_instances), - len(msvs_installed.msvs_instances), - extra=cls.debug_extra, - ) - - return msvs_manager - - def query_msvs_instances( - self, *, - msvc_version, - ): - - # TODO(JCB): temporary placeholder - - prefix, suffix = MSVC.Util.get_msvc_version_prefix_suffix(msvc_version) - - vs_product_def = MSVC.Config.MSVC_VERSION_INTERNAL[prefix] - - vs_channel_def = None - - if suffix == 'Exp': - vs_componentid_def = MSVC.Config.MSVS_COMPONENTID_EXPRESS - else: - vs_componentid_def = None - - msvs_instances, query_key = self.msvs_installed._msvs_instances_internal( - vs_product_def=vs_product_def, - vs_channel_def=vs_channel_def, - vs_componentid_def=vs_componentid_def, - ) - - return msvs_instances, query_key - - def query_msvc_instances( - self, *, - msvc_version, - ): - - # TODO(JCB): temporary placeholder - - prefix, suffix = MSVC.Util.get_msvc_version_prefix_suffix(msvc_version) - - vs_product_def = MSVC.Config.MSVC_VERSION_INTERNAL[prefix] - - vs_channel_def = None - - if suffix == 'Exp': - vs_componentid_def = MSVC.Config.MSVS_COMPONENTID_EXPRESS - else: - vs_componentid_def = None - - msvc_instances, query_key = self.msvc_installed._msvc_instances_internal( - vs_product_def=vs_product_def, - vs_channel_def=vs_channel_def, - vs_componentid_def=vs_componentid_def, - ) - - return msvc_instances, query_key - -class _VSDetectCommon(AutoInitialize): - - debug_extra = None - - @classmethod - def _initialize(cls) -> None: - cls.debug_extra = debug_extra(cls) - - VSDetectedBinaries = namedtuple("VSDetectedBinaries", [ - '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 - 'vs_exec', # vs binary - ]) - - @classmethod - def msvs_detect(cls, vs_dir, vs_cfg): - - vs_exec = None - vs_script = None - - vs_binary_cfg = vs_cfg.vs_binary_cfg - vs_batch_cfg = vs_cfg.vs_batch_cfg - - ide_path = os.path.join(vs_dir, vs_binary_cfg.pathcomp) - - bitfields = 0b_0000 - for ide_program in vs_binary_cfg.programs: - prog = os.path.join(ide_path, ide_program.program) - if not os.path.exists(prog): - continue - bitfields |= ide_program.bitfield - if vs_exec: - continue - if not ide_program.bitfield & _VSConfig.EXECUTABLE_MASK: - continue - vs_exec = prog - - have_dev = bool(bitfields & _VSConfig.BITFIELD_DEVELOP) - have_exp = bool(bitfields & _VSConfig.BITFIELD_EXPRESS) - have_exp_win = bool(bitfields & _VSConfig.BITFIELD_EXPRESS_WIN) - have_exp_web = bool(bitfields & _VSConfig.BITFIELD_EXPRESS_WEB) - - binaries_t = cls.VSDetectedBinaries( - bitfields=bitfields, - have_dev=have_dev, - have_exp=have_exp, - have_exp_win=have_exp_win, - have_exp_web=have_exp_web, - vs_exec=vs_exec, - ) - - script_file = os.path.join(vs_dir, vs_batch_cfg.pathcomp, vs_batch_cfg.script) - if os.path.exists(script_file): - vs_script = script_file - - debug( - 'vs_dir=%s, dev=%s, exp=%s, exp_win=%s, exp_web=%s, vs_exec=%s, vs_script=%s', - repr(vs_dir), - binaries_t.have_dev, binaries_t.have_exp, - binaries_t.have_exp_win, binaries_t.have_exp_web, - repr(binaries_t.vs_exec), - repr(vs_script), - extra=cls.debug_extra, - ) - - return binaries_t, vs_script - -class _VSDetectVSWhere(AutoInitialize): - - DETECT_CONFIG = _VSConfig.DETECT_VSWHERE - - # initialization - - debug_extra = None - reset_funcs = [] - - @classmethod - def _initialize(cls) -> None: - cls.debug_extra = debug_extra(cls) - cls.reset() - - @classmethod - def reset(cls) -> None: - - cls.vswhere_executable_seen = set() - cls.vswhere_executables = [] - - cls.vs_dir_seen = set() - - cls.msvs_sequence_nbr = {} - - cls.msvs_instances = [] - cls.msvc_instances = [] - - @classmethod - def register_reset_func(cls, func) -> None: - if func: - cls.reset_funcs.append(func) - - @classmethod - def call_reset_funcs(cls) -> None: - for func in cls.reset_funcs: - func() - - @classmethod - def _filter_vswhere_paths(cls, vswhere_env): - vswhere_executables = _VSWhere.find_executables(vswhere_env) - vswhere_paths = [ - vswhere_exec.norm - for vswhere_exec in vswhere_executables - if vswhere_exec.norm not in cls.vswhere_executable_seen - ] - debug('vswhere_paths=%s', vswhere_paths, extra=cls.debug_extra) - return vswhere_paths - - @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=subprocess.PIPE, stderr=subprocess.PIPE, check=False - ) - 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 - - debug( - 'vswhere_json=%s, vswhere_exe=%s', - bool(vswhere_json), repr(vswhere_exe), extra=cls.debug_extra - ) - - return vswhere_json - - @classmethod - def _msvc_resolve(cls, vc_dir, vs_product_def): - - detect_cfg = cls.DETECT_CONFIG[vs_product_def.vs_product] - - vs_cfg = detect_cfg.vs_cfg - vs_dir = os.path.normpath(os.path.join(vc_dir, vs_cfg.root)) - - binaries_t, vs_script = _VSDetectCommon.msvs_detect(vs_dir, vs_cfg) - - return binaries_t.vs_exec, vs_script - - @classmethod - def _msvc_instance_toolsets(cls, msvc_instance): - # TODO(JCB): load toolsets/tools - rval = _msvc_instance_check_files_exist(msvc_instance) - return rval - - @classmethod - def detect(cls, vswhere_env): - - num_instances = len(cls.msvc_instances) - num_new_instances = 0 - - vswhere_paths = cls._filter_vswhere_paths(vswhere_env) - if vswhere_paths: - - num_beg_instances = num_instances - - for vswhere_exe in vswhere_paths: - - if vswhere_exe in cls.vswhere_executable_seen: - continue - - cls.vswhere_executable_seen.add(vswhere_exe) - cls.vswhere_executables.append(vswhere_exe) - - debug('vswhere_exe=%s', repr(vswhere_exe), extra=cls.debug_extra) - - vswhere_json = cls._vswhere_query_json_output( - vswhere_exe, - ['-all', '-products', '*', '-prerelease'] - ) - - if not vswhere_json: - continue - - for instance in vswhere_json: - - #print(json.dumps(instance, indent=4, sort_keys=True)) - - vs_dir = instance.get('installationPath') - if not vs_dir or not _Util.path_exists(vs_dir): - continue - - vs_norm = _Util.normalize_path(vs_dir) - if vs_norm in cls.vs_dir_seen: - continue - - vs_version = instance.get('installationVersion') - if not vs_version: - continue - - product_id = instance.get('productId') - if not product_id: - continue - - # consider msvs root evaluated at this point - cls.vs_dir_seen.add(vs_norm) - - vc_dir = os.path.join(vs_dir, 'VC') - if not _Util.path_exists(vc_dir): - continue - - vs_major = vs_version.split('.')[0] - if vs_major not in MSVC.Config.MSVS_VERSION_MAJOR_MAP: - debug('ignore vs_major: %s', vs_major, extra=cls.debug_extra) - continue - - vs_product_def = MSVC.Config.MSVS_VERSION_MAJOR_MAP[vs_major] - - component_id = product_id.split('.')[-1] - vs_component_def = MSVC.Config.VSWHERE_COMPONENT_INTERNAL.get(component_id) - if not vs_component_def: - debug('ignore component_id: %s', component_id, extra=cls.debug_extra) - continue - - is_prerelease = True if instance.get('isPrerelease', False) else False - if is_prerelease: - vs_channel_def = MSVC.Config.MSVS_CHANNEL_PREVIEW - else: - vs_channel_def = MSVC.Config.MSVS_CHANNEL_RELEASE - - vs_exec, vs_script = cls._msvc_resolve(vc_dir, vs_product_def) - - vs_sequence_key = (vs_product_def, vs_channel_def, vs_component_def) - - cls.msvs_sequence_nbr.setdefault(vs_sequence_key, 0) - cls.msvs_sequence_nbr[vs_sequence_key] += 1 - - vs_sequence_nbr = cls.msvs_sequence_nbr[vs_sequence_key] - - msvs_base = MSVSBase.factory( - vs_product_def=vs_product_def, - vs_channel_def=vs_channel_def, - vs_component_def=vs_component_def, - vs_sequence_nbr=vs_sequence_nbr, - vs_dir=vs_dir, - vs_version=vs_version, - ) - - vc_version = vs_product_def.vc_buildtools_def.vc_version - - if msvs_base.is_express: - vc_version += msvs_base.vs_component_suffix - - vc_version_def = MSVC.Util.msvc_version_components(vc_version) - - msvc_instance = MSVCInstance.factory( - msvs_base=msvs_base, - vc_version_def=vc_version_def, - vc_feature_map=None, - vc_dir=vc_dir, - ) - - if not cls._msvc_instance_toolsets(msvc_instance): - # no compilers detected don't register objects - continue - - if vs_exec: - - # TODO(JCB): register iff compilers and executable? - msvs_instance = MSVSInstance.factory( - msvs_base=msvs_base, - vs_executable=vs_exec, - vs_script=vs_script, - vc_version_def=vc_version_def, - ) - - msvs_base.register_msvs_instance(msvs_instance) - cls.msvs_instances.append(msvs_instance) - - debug( - 'msvs_instance=%s', - repr(msvs_instance.id_str), - extra=cls.debug_extra, - ) - - msvs_base.register_msvc_instance(msvc_instance) - cls.msvc_instances.append(msvc_instance) - - debug( - 'msvc_instance=%s', - repr(msvc_instance.id_str), - extra=cls.debug_extra, - ) - - num_instances = len(cls.msvc_instances) - num_new_instances = num_instances - num_beg_instances - - if num_new_instances > 0: - cls.call_reset_funcs() - debug( - 'num_new_instances=%s, num_instances=%s', - num_new_instances, num_instances, extra=cls.debug_extra - ) - - return num_new_instances - -class _VSDetectRegistry(AutoInitialize): - - DETECT_CONFIG = _VSConfig.DETECT_REGISTRY - - # initialization - - debug_extra = None - - @classmethod - def _initialize(cls) -> None: - cls.debug_extra = debug_extra(cls) - cls.reset() - - @classmethod - def reset(cls) -> None: - - cls.registry_once = False - - cls.vc_dir_seen = set() - - cls.msvs_sequence_nbr = {} - - cls.msvs_instances = [] - cls.msvc_instances = [] - - # 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) - - @classmethod - def _vs_buildtools_2015_vcvars(cls, vcvars_file) -> bool: - have_buildtools_vcvars = False - with open(vcvars_file) as fh: - for line in fh: - if cls._VS2015BT_VCVARS_BUILDTOOLS.match(line): - have_buildtools_vcvars = True - break - if cls._VS2015BT_VCVARS_STOP.match(line): - break - return have_buildtools_vcvars - - @classmethod - def _vs_buildtools_2015(cls, vs_dir, vc_dir) -> bool: - - is_buildtools = False - - do_once = True - while do_once: - do_once = False - - buildtools_file = os.path.join(vs_dir, cls._VS2015BT_PATH) - have_buildtools = os.path.exists(buildtools_file) - debug('have_buildtools=%s', have_buildtools, extra=cls.debug_extra) - 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, extra=cls.debug_extra) - if not have_vcvars: - break - - have_buildtools_vcvars = cls._vs_buildtools_2015_vcvars(vcvars_file) - debug('have_buildtools_vcvars=%s', have_buildtools_vcvars, extra=cls.debug_extra) - if not have_buildtools_vcvars: - break - - is_buildtools = True - - debug('is_buildtools=%s', is_buildtools, extra=cls.debug_extra) - return is_buildtools - - _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) - - @classmethod - def _vs_express_2015_vcvars(cls, vcvars_file) -> bool: - n_libpath = 0 - with open(vcvars_file) as fh: - for line in fh: - if cls._VS2015EXP_VCVARS_LIBPATH.match(line): - n_libpath += 1 - elif cls._VS2015EXP_VCVARS_STOP.match(line): - break - have_uwp_fix = bool(n_libpath >= 2) - return have_uwp_fix - - @classmethod - def _vs_express_2015(cls, vc_dir): - - have_uwp_amd64 = False - have_uwp_arm = False - - vcvars_file = os.path.join(vc_dir, r'vcvarsall.bat') - if os.path.exists(vcvars_file): - - vcvars_file = os.path.join(vc_dir, r'bin\x86_amd64\vcvarsx86_amd64.bat') - if os.path.exists(vcvars_file): - have_uwp_fix = cls._vs_express_2015_vcvars(vcvars_file) - if have_uwp_fix: - have_uwp_amd64 = True - - vcvars_file = os.path.join(vc_dir, r'bin\x86_arm\vcvarsx86_arm.bat') - if os.path.exists(vcvars_file): - have_uwp_fix = cls._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, extra=cls.debug_extra) - 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'} - - @classmethod - def _msvc_dir_is_winsdk_only(cls, vc_dir, msvc_version) -> bool: - - # 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 - - if msvc_version not in cls._REGISTRY_WINSDK_VERSIONS: - - is_sdk = False - - debug('is_sdk=%s, msvc_version=%s', is_sdk, repr(msvc_version), extra=cls.debug_extra) - - else: - - vc_suffix = MSVC.Registry.vstudio_sxs_vc7(msvc_version) - vc_qresults = [record[0] for record in MSVC.Registry.microsoft_query_paths(vc_suffix)] - vc_regdir = vc_qresults[0] if vc_qresults else None - - if vc_regdir != vc_dir: - # registry vc path is not the current vc dir - - is_sdk = False - - debug( - 'is_sdk=%s, msvc_version=%s, vc_dir=%s, vc_regdir=%s', - is_sdk, repr(msvc_version), repr(vc_dir), repr(vc_regdir), extra=cls.debug_extra - ) - - else: - # registry vc dir is the current vc root +_LE2008_HOST_TARGET_BATCHARG_BATCHFILE_CLPATHCOMPS = { - vs_suffix = MSVC.Registry.vstudio_sxs_vs7(msvc_version) - vs_qresults = [record[0] for record in MSVC.Registry.microsoft_query_paths(vs_suffix)] - vs_dir = vs_qresults[0] if vs_qresults else None + ('amd64', 'amd64') : ('amd64', 'vcvarsamd64.bat', ('bin', 'amd64')), + ('amd64', 'x86') : ('x86', 'vcvars32.bat', ('bin', )), - is_sdk = bool(not vs_dir and vc_dir) + ('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')), - debug( - 'is_sdk=%s, msvc_version=%s, vc_dir=%s, vs_dir=%s', - is_sdk, repr(msvc_version), repr(vc_dir), repr(vs_dir), extra=cls.debug_extra - ) + ('arm64', 'amd64') : ('amd64', 'vcvarsamd64.bat', ('bin', 'amd64')), + ('arm64', 'x86') : ('x86', 'vcvars32.bat', ('bin', )), - return is_sdk + ('ia64', 'ia64') : ('ia64', 'vcvarsia64.bat', ('bin', 'ia64')), - @classmethod - def _msvc_resolve(cls, vc_dir, vs_product_def, is_vcforpython, vs_version): +} - detect_cfg = cls.DETECT_CONFIG[vs_product_def.vs_product] +_LE2008_HOST_TARGET_CFG = _host_target_config_factory( - vc_feature_map = {} + label = 'LE2008', - vs_cfg = detect_cfg.vs_cfg - vs_dir = os.path.normpath(os.path.join(vc_dir, vs_cfg.root)) + host_all_hosts = OrderedDict([ + ('amd64', ['amd64', 'x86']), + ('x86', ['x86']), + ('arm64', ['amd64', 'x86']), + ('ia64', ['ia64']), + ]), - binaries_t, vs_script = _VSDetectCommon.msvs_detect(vs_dir, vs_cfg) + host_all_targets = { + 'amd64': ['amd64', 'x86'], + 'x86': ['x86', 'amd64', 'ia64'], + 'arm64': ['amd64', 'x86'], + 'ia64': ['ia64'], + }, - vs_product_numeric = vs_product_def.vs_product_numeric - vc_version = vs_product_def.vc_buildtools_def.vc_version + host_def_targets = { + 'amd64': ['amd64', 'x86'], + 'x86': ['x86'], + 'arm64': ['amd64', 'x86'], + 'ia64': ['ia64'], + }, - if binaries_t.have_dev: - vs_component_def = MSVC.Config.REGISTRY_COMPONENT_DEVELOP - elif binaries_t.have_exp: - vs_component_def = MSVC.Config.REGISTRY_COMPONENT_EXPRESS - elif vs_product_numeric == 2008 and is_vcforpython: - vs_component_def = MSVC.Config.REGISTRY_COMPONENT_PYTHON - elif binaries_t.have_exp_win: - vs_component_def = None - elif binaries_t.have_exp_web: - vs_component_def = None - elif cls._msvc_dir_is_winsdk_only(vc_dir, vc_version): - vs_component_def = None - else: - vs_component_def = MSVC.Config.REGISTRY_COMPONENT_CMDLINE +) - if vs_component_def and vs_product_numeric == 2015: +# debug("_LE2008_HOST_TARGET_CFG: %s", _LE2008_HOST_TARGET_CFG) - # VS2015: - # remap DEVELOP => ENTERPRISE, PROFESSIONAL, COMMUNITY - # remap CMDLINE => BUILDTOOLS [conditional] - # process EXPRESS +# 7.1 (VS2003) and earlier - if vs_component_def == MSVC.Config.REGISTRY_COMPONENT_DEVELOP: +_LE2003_HOST_TARGET_BATCHARG_BATCHFILE_CLPATHCOMPS = { - for reg_component, reg_component_def in [ - ('community', MSVC.Config.REGISTRY_COMPONENT_COMMUNITY), - ('professional', MSVC.Config.REGISTRY_COMPONENT_PROFESSIONAL), - ('enterprise', MSVC.Config.REGISTRY_COMPONENT_ENTERPRISE), - ]: - suffix = MSVC.Registry.devdiv_vs_servicing_component(vs_version, reg_component) - qresults = MSVC.Registry.microsoft_query_keys(suffix, usrval=reg_component_def) - if not qresults: - continue - vs_component_def = qresults[0][-1] - break + ('amd64', 'x86') : ('x86', 'vcvars32.bat', ('bin', )), + ('x86', 'x86') : ('x86', 'vcvars32.bat', ('bin', )), + ('arm64', 'x86') : ('x86', 'vcvars32.bat', ('bin', )), - elif vs_component_def == MSVC.Config.REGISTRY_COMPONENT_CMDLINE: +} - if cls._vs_buildtools_2015(vs_dir, vc_dir): - vs_component_def = MSVC.Config.REGISTRY_COMPONENT_BUILDTOOLS +_LE2003_HOST_TARGET_CFG = _host_target_config_factory( - elif vs_component_def == MSVC.Config.REGISTRY_COMPONENT_EXPRESS: + label = 'LE2003', - have_uwp_amd64, have_uwp_arm = cls._vs_express_2015(vc_dir) + host_all_hosts = OrderedDict([ + ('amd64', ['x86']), + ('x86', ['x86']), + ('arm64', ['x86']), + ]), - uwp_target_is_supported = { - 'x86': True, - 'amd64': have_uwp_amd64, - 'arm': have_uwp_arm, - } + host_all_targets = { + 'amd64': ['x86'], + 'x86': ['x86'], + 'arm64': ['x86'], + }, - vc_feature_map['uwp_target_is_supported'] = uwp_target_is_supported + host_def_targets = { + 'amd64': ['x86'], + 'x86': ['x86'], + 'arm64': ['x86'], + }, - debug( - 'vs_product=%s, vs_component=%s, vc_dir=%s, vc_feature_map=%s', - repr(vs_product_numeric), - repr(vs_component_def.vs_componentid_def.vs_component_id) if vs_component_def else None, - repr(vc_dir), - repr(vc_feature_map), - extra=cls.debug_extra - ) +) - return vs_component_def, vs_dir, binaries_t.vs_exec, vs_script, vc_feature_map +# debug("_LE2003_HOST_TARGET_CFG: %s", _LE2003_HOST_TARGET_CFG) - @classmethod - def _msvc_instance_toolsets(cls, msvc_instance): - # TODO(JCB): load toolsets/tools - rval = _msvc_instance_check_files_exist(msvc_instance) - return rval +_CL_EXE_NAME = 'cl.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' + would get converted to '14.0'. - @classmethod - def detect(cls): + Args: + msvc_version: str + string representing the version number, could contain non + digit characters - num_instances = len(cls.msvc_instances) - num_new_instances = 0 + Returns: + str: the value converted to a numeric only string - if not cls.registry_once: - cls.registry_once = True + """ + return ''.join([x for x in msvc_version if x in string_digits + '.']) - num_beg_instances = num_instances +def get_host_platform(host_platform): - is_win64 = common.is_win64() + host_platform = host_platform.lower() - for vs_product, config in cls.DETECT_CONFIG.items(): + # Solaris returns i86pc for both 32 and 64 bit architectures + if host_platform == 'i86pc': + if platform.architecture()[0] == "64bit": + host_platform = "amd64" + else: + host_platform = "x86" - key_prefix = 'Software\\' - for regkey in config.vc_cfg.regkeys: + try: + host =_ARCH_TO_CANONICAL[host_platform] + except KeyError: + msg = "Unrecognized host architecture %s" + raise MSVCUnsupportedHostArch(msg % repr(host_platform)) from None - if not regkey.hkroot or not regkey.key: - continue + return host - if is_win64: - mskeys = [key_prefix + 'Wow6432Node\\' + regkey.key, key_prefix + regkey.key] - else: - mskeys = [key_prefix + regkey.key] +_native_host_architecture = None - vc_dir = None - for mskey in mskeys: - debug('trying VC registry key %s', repr(mskey), extra=cls.debug_extra) - try: - vc_dir = common.read_reg(mskey, regkey.hkroot) - except OSError: - continue - if vc_dir: - break +def get_native_host_architecture(): + """Return the native host architecture.""" + global _native_host_architecture - if not vc_dir: - debug('no VC registry key %s', repr(regkey.key), extra=cls.debug_extra) - continue + if _native_host_architecture is None: - if vc_dir in cls.vc_dir_seen: - continue - cls.vc_dir_seen.add(vc_dir) + try: + arch = common.read_reg( + r'SYSTEM\CurrentControlSet\Control\Session Manager\Environment\PROCESSOR_ARCHITECTURE' + ) + except OSError: + arch = None - if not os.path.exists(vc_dir): - debug( - 'reg says vc dir is %s, but it does not exist. (ignoring)', - repr(vc_dir), extra=cls.debug_extra - ) - continue + if not arch: + arch = platform.machine() - if regkey.is_vsroot: + _native_host_architecture = arch - vc_dir = os.path.join(vc_dir, 'VC') - debug('convert vs dir to vc dir: %s', repr(vc_dir), extra=cls.debug_extra) + return _native_host_architecture - if vc_dir in cls.vc_dir_seen: - continue - cls.vc_dir_seen.add(vc_dir) +_native_host_platform = None - if not os.path.exists(vc_dir): - debug('vc dir does not exist. (ignoring)', repr(vc_dir), extra=cls.debug_extra) - continue +def get_native_host_platform(): + global _native_host_platform - vc_norm = _Util.normalize_path(vc_dir) - if vc_norm in cls.vc_dir_seen: - continue - cls.vc_dir_seen.add(vc_norm) + if _native_host_platform is None: + arch = get_native_host_architecture() + _native_host_platform = get_host_platform(arch) - vs_product_def = MSVC.Config.MSVS_VERSION_INTERNAL[vs_product] + return _native_host_platform - vs_component_def, vs_dir, vs_exec, vs_script, vc_feature_map = cls._msvc_resolve( - vc_norm, - vs_product_def, - regkey.is_vcforpython, - vs_product_def.vs_version - ) +def get_host_target(env, msvc_version, all_host_targets: bool=False): - if not vs_component_def: - continue + vernum = float(get_msvc_version_numeric(msvc_version)) + vernum_int = int(vernum * 10) - vs_channel_def = MSVC.Config.MSVS_CHANNEL_RELEASE + if vernum_int >= 143: + # 14.3 (VS2022) and later + host_target_cfg = _GE2022_HOST_TARGET_CFG + elif 143 > vernum_int >= 141: + # 14.2 (VS2019) to 14.1 (VS2017) + host_target_cfg = _LE2019_HOST_TARGET_CFG + 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 - vs_sequence_key = (vs_product_def, vs_channel_def, vs_component_def) + host_arch = env.get('HOST_ARCH') if env else None + debug("HOST_ARCH:%s", str(host_arch)) - cls.msvs_sequence_nbr.setdefault(vs_sequence_key, 0) - cls.msvs_sequence_nbr[vs_sequence_key] += 1 + if host_arch: + host_platform = get_host_platform(host_arch) + else: + host_platform = get_native_host_platform() - vs_sequence_nbr = cls.msvs_sequence_nbr[vs_sequence_key] + target_arch = env.get('TARGET_ARCH') if env else None + debug("TARGET_ARCH:%s", str(target_arch)) - msvs_base = MSVSBase.factory( - vs_product_def=vs_product_def, - vs_channel_def=vs_channel_def, - vs_component_def=vs_component_def, - vs_sequence_nbr=vs_sequence_nbr, - vs_dir=vs_dir, - vs_version=vs_product_def.vs_version, - ) + if target_arch: - vc_version = vs_product_def.vc_buildtools_def.vc_version + try: + target_platform = _ARCH_TO_CANONICAL[target_arch.lower()] + except KeyError: + all_archs = str(list(_ARCH_TO_CANONICAL.keys())) + raise MSVCUnsupportedTargetArch( + "Unrecognized target architecture %s\n\tValid architectures: %s" + % (repr(target_arch), all_archs) + ) from None - if msvs_base.is_express: - vc_version += msvs_base.vs_component_suffix + target_host_map = host_target_cfg.target_host_map - vc_version_def = MSVC.Util.msvc_version_components(vc_version) + try: + host_target_list = target_host_map[target_platform][host_platform] + except KeyError: + host_target_list = [] + warn_msg = "unsupported host, target combination ({}, {}) for MSVC version {}".format( + repr(host_platform), repr(target_platform), msvc_version + ) + debug(warn_msg) + SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, warn_msg) - msvc_instance = MSVCInstance.factory( - msvs_base=msvs_base, - vc_version_def=vc_version_def, - vc_feature_map=vc_feature_map, - vc_dir=vc_dir, - ) + else: - if not cls._msvc_instance_toolsets(msvc_instance): - # no compilers detected don't register objects - continue + target_platform = None - if vs_exec: + if all_host_targets: + host_targets_map = host_target_cfg.host_all_targets_map + else: + host_targets_map = host_target_cfg.host_def_targets_map - # TODO(JCB): register iff compilers and executable? - msvs_instance = MSVSInstance.factory( - msvs_base=msvs_base, - vs_executable=vs_exec, - vs_script=vs_script, - vc_version_def=vc_version_def, - ) + try: + host_target_list = host_targets_map[host_platform] + except KeyError: + msg = "Unrecognized host architecture %s for version %s" + raise MSVCUnsupportedHostArch(msg % (repr(host_platform), msvc_version)) from None - msvs_base.register_msvs_instance(msvs_instance) - cls.msvs_instances.append(msvs_instance) + return host_platform, target_platform, host_target_list - debug( - 'msvs_instance=%s', - repr(msvs_instance.id_str), - extra=cls.debug_extra, - ) +_arm32_process_arm64_host = None - msvs_base.register_msvc_instance(msvc_instance) - cls.msvc_instances.append(msvc_instance) +def is_arm32_process_arm64_host(): + global _arm32_process_arm64_host - debug( - 'msvc_instance=%s', - repr(msvc_instance.id_str), - extra=cls.debug_extra, - ) + if _arm32_process_arm64_host is None: - num_instances = len(cls.msvc_instances) - num_new_instances = num_instances - num_beg_instances + host = get_native_host_architecture() + host = _ARCH_TO_CANONICAL.get(host.lower(),'') + host_isarm64 = host == 'arm64' - return num_new_instances + process = sysconfig.get_platform() + process_isarm32 = process == 'win-arm32' -class _VSDetect(AutoInitialize): + _arm32_process_arm64_host = host_isarm64 and process_isarm32 - debug_extra = None + return _arm32_process_arm64_host - @classmethod - def _initialize(cls) -> None: - cls.debug_extra = debug_extra(cls) - cls._reset() +_check_skip_sendtelemetry = None - @classmethod - def _reset(cls) -> None: - # volatile: reconstructed when new instances detected - cls.msvs_manager = None +def _skip_sendtelemetry(env): + global _check_skip_sendtelemetry - @classmethod - def reset(cls) -> None: - cls._reset() - _VSDetectVSWhere.reset() - _VSDetectRegistry.reset() + if _check_skip_sendtelemetry is None: - @classmethod - def register_reset_func(cls, func) -> None: - _VSDetectVSWhere.register_reset_func(func) + if _ARM32_ON_ARM64_SKIP_SENDTELEMETRY and is_arm32_process_arm64_host(): + _check_skip_sendtelemetry = True + else: + _check_skip_sendtelemetry = False - @classmethod - def detect(cls, vswhere_env): + if not _check_skip_sendtelemetry: + return False - num_new_instances = 0 - num_new_instances += _VSDetectVSWhere.detect(vswhere_env) - num_new_instances += _VSDetectRegistry.detect() + msvc_version = env.get('MSVC_VERSION') if env else None + if not msvc_version: + msvc_version = msvc_default_version(env) - if num_new_instances > 0 or cls.msvs_manager is None: + if not msvc_version: + return False - msvc_instances = [] - msvc_instances.extend(_VSDetectVSWhere.msvc_instances) - msvc_instances.extend(_VSDetectRegistry.msvc_instances) + vernum = float(get_msvc_version_numeric(msvc_version)) + if vernum < 14.2: # VS2019 + return False - msvs_instances = [] - msvs_instances.extend(_VSDetectVSWhere.msvs_instances) - msvs_instances.extend(_VSDetectRegistry.msvs_instances) + # arm32 process, arm64 host, VS2019+ + return True - msvc_installed = MSVCInstalled.factory( - msvc_instances=msvc_instances, - ) +# If you update this, update SupportedVSList in Tool/MSCommon/vs.py, and the +# MSVC_VERSION documentation in Tool/msvc.xml. +_VCVER = [ + "14.3", + "14.2", + "14.1", "14.1Exp", + "14.0", "14.0Exp", + "12.0", "12.0Exp", + "11.0", "11.0Exp", + "10.0", "10.0Exp", + "9.0", "9.0Exp", + "8.0", "8.0Exp", + "7.1", + "7.0", + "6.0"] - msvs_installed = MSVSInstalled.factory( - msvs_instances=msvs_instances, - ) +def msvc_version_to_maj_min(msvc_version): + msvc_version_numeric = get_msvc_version_numeric(msvc_version) - cls.msvs_manager = MSVSManager.factory( - msvc_installed=msvc_installed, - msvs_installed=msvs_installed, - ) + t = msvc_version_numeric.split(".") + if not len(t) == 2: + raise ValueError("Unrecognized version %s (%s)" % (msvc_version,msvc_version_numeric)) + try: + maj = int(t[0]) + min = int(t[1]) + return maj, min + except ValueError: + raise ValueError("Unrecognized version %s (%s)" % (msvc_version,msvc_version_numeric)) from None - return cls.msvs_manager +def msvc_find_vswhere(env=None): + """ Find the location of vswhere """ + # NB: this gets called from testsuite on non-Windows platforms. + # Whether that makes sense or not, don't break it for those. + vswhere_env = env.subst('$VSWHERE') if env and 'VSWHERE' in env else None + vswhere_executables = MSVC.VSWhere.vswhere_get_executables(vswhere_env) + if vswhere_executables: + vswhere_path = vswhere_executables[0].path + else: + vswhere_path = None + debug('vswhere_path=%s', vswhere_path) + return vswhere_path -def vs_detect(env=None): +def msvs_manager(env=None): vswhere_env = env.subst('$VSWHERE') if env and 'VSWHERE' in env else None - msvs_manager = _VSDetect.detect(vswhere_env) - return msvs_manager + vs_manager = MSVC.VSDetect.msvs_manager(vswhere_env) + return vs_manager def _find_msvc_instance(msvc_version, env=None): @@ -3408,7 +770,7 @@ def _find_msvc_instance(msvc_version, env=None): debug("Unknown version of MSVC: %s", repr(msvc_version)) return msvc_instance, query_key - vs_manager = vs_detect(env) + vs_manager = msvs_manager(env) msvc_instances, query_key = vs_manager.query_msvc_instances(msvc_version=msvc_version) @@ -3536,7 +898,7 @@ def _reset_installed_vcs() -> None: debug('reset') # register vcs cache reset function with vs detection -_VSDetect.register_reset_func(_reset_installed_vcs) +MSVC.VSDetect.register_reset_func(_reset_installed_vcs) _VC_TOOLS_VERSION_FILE_PATH = ['Auxiliary', 'Build', 'Microsoft.VCToolsVersion.default.txt'] _VC_TOOLS_VERSION_FILE = os.sep.join(_VC_TOOLS_VERSION_FILE_PATH) @@ -3713,27 +1075,19 @@ def _msvc_instance_check_files_exist(msvc_instance) -> bool: return False +# TODO(JCB): register callback (temporary until toolset detection is added) +MSVC.VSDetect.register_msvc_instance_check_files_exist(_msvc_instance_check_files_exist) + def get_installed_msvc_instances(env=None): global _cache_installed_msvc_instances # the installed instance cache is cleared if new instances are discovered - vs_manager = vs_detect(env) + vs_manager = msvs_manager(env) if _cache_installed_msvc_instances is not None: return _cache_installed_msvc_instances - msvc_installed = vs_manager.msvc_installed - - # TODO(JCB): first-use default channel - vs_channel_def = _VSChannel.get_default_channel() - - msvs_channel_map = msvc_installed.msvs_channel_map - msvc_version_map = msvs_channel_map.get(vs_channel_def) - - installed_msvc_instances = [ - msvc_version_map[vc_version][0] - for vc_version in msvc_version_map.keys() - ] + installed_msvc_instances = vs_manager.get_installed_msvc_instances() _cache_installed_msvc_instances = installed_msvc_instances # debug("installed_msvc_instances=%s", _cache_installed_msvc_versions) @@ -3743,23 +1097,12 @@ def get_installed_vcs(env=None): global _cache_installed_msvc_versions # the installed version cache is cleared if new instances are discovered - vs_manager = vs_detect(env) + vs_manager = msvs_manager(env) if _cache_installed_msvc_versions is not None: return _cache_installed_msvc_versions - msvc_installed = vs_manager.msvc_installed - - # TODO(JCB): first-use default channel - vs_channel_def = _VSChannel.get_default_channel() - - msvs_channel_map = msvc_installed.msvs_channel_map - msvc_version_map = msvs_channel_map.get(vs_channel_def) - - installed_msvc_versions = [ - vc_version - for vc_version in msvc_version_map.keys() - ] + installed_msvc_versions = vs_manager.get_installed_msvc_versions() _cache_installed_msvc_versions = installed_msvc_versions debug("installed_msvc_versions=%s", _cache_installed_msvc_versions) @@ -3768,10 +1111,6 @@ def get_installed_vcs(env=None): def reset_installed_vcs() -> None: """Make it try again to find VC. This is just for the tests.""" _reset_installed_vcs() - _Util.reset() - _VSWhere.reset() - _VSChannel.reset() - _VSDetect.reset() MSVC._reset() def msvc_default_version(env=None): @@ -4151,16 +1490,15 @@ def msvc_setup_env(env) -> None: # final check to issue a warning if the requested compiler is not present if not found_cl_path: + warn_msg = "Could not find requested MSVC compiler 'cl'." if CONFIG_CACHE: - propose = f"SCONS_CACHE_MSVC_CONFIG caching enabled, remove cache file {CONFIG_CACHE} if out of date." + warn_msg += f" SCONS_CACHE_MSVC_CONFIG caching enabled, remove cache file {CONFIG_CACHE} if out of date." else: - propose = "It may need to be installed separately with Visual Studio." - warn_msg = "Could not find requested MSVC compiler 'cl'." + warn_msg += " It may need to be installed separately with Visual Studio." if found_cl_envpath: warn_msg += " A 'cl' was found on the scons ENV path which may be erroneous." - warn_msg += " %s" - debug(warn_msg, propose) - SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, warn_msg % propose) + debug(warn_msg) + SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, warn_msg) def msvc_exists(env=None, version=None) -> bool: vcs = get_installed_vcs(env) @@ -4434,25 +1772,6 @@ def msvc_query_version_toolset(version=None, prefer_newest: bool=True): msg = f'MSVC toolset version {version!r} not found' raise MSVCToolsetVersionNotFound(msg) -def _verify() -> None: - - def _compare_product_sets(set_config, set_local, label) -> None: - diff = set_config - set_local - if diff: - keys = ', '.join([repr(s) for s in sorted(diff, reverse=True)]) - errmsg = f'{label} missing keys: {keys}' - debug('MSVCInternalError: %s', errmsg) - raise MSVCInternalError(errmsg) - - vswhere_config = MSVC.Config.VSWHERE_SUPPORTED_PRODUCTS - vswhere_local = set(_VSConfig.DETECT_VSWHERE.keys()) - _compare_product_sets(vswhere_config, vswhere_local, '_VSConfig.DETECT_VSWHERE') - - registry_config = MSVC.Config.REGISTRY_SUPPORTED_PRODUCTS - registry_local = set(_VSConfig.DETECT_REGISTRY.keys()) - _compare_product_sets(registry_config, registry_local, '_VSConfig.DETECT_REGISTRY') - # internal consistency checks (should be last) MSVC._verify() -_verify() diff --git a/SCons/Tool/MSCommon/vcTests.py b/SCons/Tool/MSCommon/vcTests.py index 19130f9812..315b684de1 100644 --- a/SCons/Tool/MSCommon/vcTests.py +++ b/SCons/Tool/MSCommon/vcTests.py @@ -29,6 +29,7 @@ import SCons.Node.FS import SCons.Warnings import SCons.Tool.MSCommon.vc +from SCons.Tool.MSCommon.MSVC import VSWhere from SCons.Tool import MSCommon import TestCmd @@ -64,26 +65,26 @@ def testDefaults(self) -> None: Verify that msvc_find_vswhere() find's files in the specified paths """ - restore_vswhere_execs_exist = MSCommon.vc._VSWhere.vswhere_executables[:] + restore_vswhere_execs_exist = list(VSWhere._VSWhere.vswhere_executables) base_dir = test.workpath('fake_vswhere') norm_dir = os.path.normcase(os.path.normpath(base_dir)) # import pdb; pdb.set_trace() test_vswhere_dirs = [ - MSCommon.vc._VSWhere.VSWhereExecutable( + VSWhere._VSWhere.VSWhereExecutable( path=os.path.join(base_dir,t[0][1:]), norm=os.path.join(norm_dir,t[1][1:]), ) for t in [ (os.path.splitdrive(vswexec.path)[1], os.path.splitdrive(vswexec.norm)[1]) - for vswexec in MSCommon.vc._VSWhere.vswhere_executables + for vswexec in VSWhere._VSWhere.vswhere_executables ] ] for vswexec in test_vswhere_dirs: VswhereTestCase._createVSWhere(vswexec.path) - MSCommon.vc._VSWhere.vswhere_executables = [vswexec] + VSWhere._VSWhere.vswhere_executables = [vswexec] find_path = MSCommon.vc.msvc_find_vswhere() self.assertTrue(vswexec.path == find_path, "Didn't find vswhere in %s found in %s" % (vswexec.path, find_path)) os.remove(vswexec.path) @@ -92,7 +93,7 @@ def testDefaults(self) -> None: os.path.join(base_dir,d[1:]) for d in [ os.path.splitdrive(p)[1] - for p in MSCommon.vc.VSWHERE_PATHS + for p in VSWhere.VSWHERE_PATHS ] ] MSCommon.vc.path_exists = VswhereTestCase._path_exists @@ -101,13 +102,13 @@ def testDefaults(self) -> None: VswhereTestCase._createVSWhere(vswpath) VswhereTestCase._existing_path = vswpath for front in (True, False): - MSCommon.vc._VSWhere.vswhere_executables = [] - MSCommon.vc.vswhere_push_location(vswpath, front=front) + VSWhere._VSWhere.vswhere_executables = [] + MSCommon.MSVC.VSWhere.vswhere_push_location(vswpath, front=front) find_path = MSCommon.vc.msvc_find_vswhere() self.assertTrue(vswpath == find_path, "Didn't find vswhere in %s found in %s" % (vswpath, find_path)) os.remove(vswpath) - MSCommon.vc._VSWhere.vswhere_executables = restore_vswhere_execs_exist + VSWhere._VSWhere.vswhere_executables = restore_vswhere_execs_exist # def specifiedVswherePathTest(self): # "Verify that msvc.generate() respects VSWHERE Specified" @@ -119,7 +120,7 @@ class MSVcTestCase(unittest.TestCase): def _createDummyMSVSBase(vc_version, vs_component, vs_dir): vs_product_def = SCons.Tool.MSCommon.MSVC.Config.MSVC_VERSION_INTERNAL[vc_version] vs_component_key = (vs_product_def.vs_lookup, vs_component) - msvs_base = SCons.Tool.MSCommon.vc.MSVSBase.factory( + msvs_base = SCons.Tool.MSCommon.MSVC.VSDetect.MSVSBase.factory( vs_product_def=vs_product_def, vs_channel_def=SCons.Tool.MSCommon.MSVC.Config.MSVS_CHANNEL_RELEASE, vs_component_def=SCons.Tool.MSCommon.MSVC.Config.MSVS_COMPONENT_INTERNAL[vs_component_key], @@ -131,7 +132,7 @@ def _createDummyMSVSBase(vc_version, vs_component, vs_dir): @classmethod def _createDummyMSVCInstance(cls, vc_version, vs_component, vc_dir): - msvc_instance = SCons.Tool.MSCommon.vc.MSVCInstance.factory( + msvc_instance = SCons.Tool.MSCommon.MSVC.VSDetect.MSVCInstance.factory( msvs_base=cls._createDummyMSVSBase(vc_version, vs_component, vc_dir), vc_version_def=SCons.Tool.MSCommon.MSVC.Util.msvc_version_components(vc_version), vc_feature_map=None, diff --git a/SCons/Tool/msvsTests.py b/SCons/Tool/msvsTests.py index ef14d651de..e303ea2db9 100644 --- a/SCons/Tool/msvsTests.py +++ b/SCons/Tool/msvsTests.py @@ -947,7 +947,8 @@ class msvsEmptyTestCase(msvsTestCase): SCons.Util.RegEnumKey = DummyEnumKey SCons.Util.RegEnumValue = DummyEnumValue SCons.Util.RegQueryValueEx = DummyQueryValue - SCons.Tool.MSCommon.vc._VSWhere.find_executables = DummyVsWhereExecutables + + SCons.Tool.MSCommon.MSVC.VSWhere.vswhere_get_executables = DummyVsWhereExecutables os.path.exists = DummyExists # make sure all files exist :-) os.path.isfile = DummyExists # make sure all files are files :-) diff --git a/test/MSVC/VSWHERE-fixture/SConstruct b/test/MSVC/VSWHERE-fixture/SConstruct index 74eea18c16..aff4c784b3 100644 --- a/test/MSVC/VSWHERE-fixture/SConstruct +++ b/test/MSVC/VSWHERE-fixture/SConstruct @@ -5,7 +5,7 @@ import os import os.path -from SCons.Tool.MSCommon.vc import VSWHERE_PATHS +from SCons.Tool.MSCommon.MSVC.VSWhere import VSWHERE_PATHS # Dump out expected paths for vw_path in VSWHERE_PATHS: diff --git a/test/fixture/no_msvc/no_msvcs_sconstruct.py b/test/fixture/no_msvc/no_msvcs_sconstruct.py index ef199aab25..437383c950 100644 --- a/test/fixture/no_msvc/no_msvcs_sconstruct.py +++ b/test/fixture/no_msvc/no_msvcs_sconstruct.py @@ -11,9 +11,9 @@ def DummyVsWhereExecutables(vswhere_env=None): # not testing versions with vswhere, so return empty list return [] -for detect_cfg in SCons.Tool.MSCommon.vc._VSDetectRegistry.DETECT_CONFIG.values(): +for detect_cfg in SCons.Tool.MSCommon.MSVC.VSDetect._VSDetectRegistry.DETECT_CONFIG.values(): detect_cfg.vc_cfg.regkeys.clear() -SCons.Tool.MSCommon.vc._VSWhere.find_executables = DummyVsWhereExecutables +SCons.Tool.MSCommon.MSVC.VSWhere.vswhere_get_executables = DummyVsWhereExecutables 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 c21d5277ff..6bf0e58665 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 @@ -11,9 +11,9 @@ def DummyVsWhereExecutables(vswhere_env=None): # not testing versions with vswhere, so return empty list return [] -for detect_cfg in SCons.Tool.MSCommon.vc._VSDetectRegistry.DETECT_CONFIG.values(): +for detect_cfg in SCons.Tool.MSCommon.MSVC.VSDetect._VSDetectRegistry.DETECT_CONFIG.values(): detect_cfg.vc_cfg.regkeys.clear() -SCons.Tool.MSCommon.vc._VSWhere.find_executables = DummyVsWhereExecutables +SCons.Tool.MSCommon.MSVC.VSWhere.vswhere_get_executables = DummyVsWhereExecutables 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 ac2ea88543..64699b724f 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 @@ -11,9 +11,9 @@ def DummyVsWhereExecutables(vswhere_env=None): # not testing versions with vswhere, so return empty list return [] -for detect_cfg in SCons.Tool.MSCommon.vc._VSDetectRegistry.DETECT_CONFIG.values(): +for detect_cfg in SCons.Tool.MSCommon.MSVC.VSDetect._VSDetectRegistry.DETECT_CONFIG.values(): detect_cfg.vc_cfg.regkeys.clear() -SCons.Tool.MSCommon.vc._VSWhere.find_executables = DummyVsWhereExecutables +SCons.Tool.MSCommon.MSVC.VSWhere.vswhere_get_executables = DummyVsWhereExecutables 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 3535ec952a..e1d4bd10ce 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 @@ -11,9 +11,9 @@ def DummyVsWhereExecutables(vswhere_env=None): # not testing versions with vswhere, so return empty list return [] -for detect_cfg in SCons.Tool.MSCommon.vc._VSDetectRegistry.DETECT_CONFIG.values(): +for detect_cfg in SCons.Tool.MSCommon.MSVC.VSDetect._VSDetectRegistry.DETECT_CONFIG.values(): detect_cfg.vc_cfg.regkeys.clear() -SCons.Tool.MSCommon.vc._VSWhere.find_executables = DummyVsWhereExecutables +SCons.Tool.MSCommon.MSVC.VSWhere.vswhere_get_executables = DummyVsWhereExecutables 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 ca6d16a4e7..0724b81d25 100644 --- a/test/fixture/no_msvc/no_msvcs_sconstruct_tools.py +++ b/test/fixture/no_msvc/no_msvcs_sconstruct_tools.py @@ -11,9 +11,9 @@ def DummyVsWhereExecutables(vswhere_env=None): # not testing versions with vswhere, so return empty list return [] -for detect_cfg in SCons.Tool.MSCommon.vc._VSDetectRegistry.DETECT_CONFIG.values(): +for detect_cfg in SCons.Tool.MSCommon.MSVC.VSDetect._VSDetectRegistry.DETECT_CONFIG.values(): detect_cfg.vc_cfg.regkeys.clear() -SCons.Tool.MSCommon.vc._VSWhere.find_executables = DummyVsWhereExecutables +SCons.Tool.MSCommon.MSVC.VSWhere.vswhere_get_executables = DummyVsWhereExecutables 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 068fcb7898..e8a6caf9cb 100644 --- a/test/fixture/no_msvc/no_msvcs_sconstruct_version.py +++ b/test/fixture/no_msvc/no_msvcs_sconstruct_version.py @@ -11,9 +11,9 @@ def DummyVsWhereExecutables(vswhere_env=None): # not testing versions with vswhere, so return empty list return [] -for detect_cfg in SCons.Tool.MSCommon.vc._VSDetectRegistry.DETECT_CONFIG.values(): +for detect_cfg in SCons.Tool.MSCommon.MSVC.VSDetect._VSDetectRegistry.DETECT_CONFIG.values(): detect_cfg.vc_cfg.regkeys.clear() -SCons.Tool.MSCommon.vc._VSWhere.find_executables = DummyVsWhereExecutables +SCons.Tool.MSCommon.MSVC.VSWhere.vswhere_get_executables = DummyVsWhereExecutables SCons.Tool.MSCommon.msvc_set_notfound_policy('error') env = SCons.Environment.Environment(MSVC_VERSION='14.3') diff --git a/test/fixture/no_msvc/no_regs_sconstruct.py b/test/fixture/no_msvc/no_regs_sconstruct.py index 876e7c542a..1b4e5cec38 100644 --- a/test/fixture/no_msvc/no_regs_sconstruct.py +++ b/test/fixture/no_msvc/no_regs_sconstruct.py @@ -7,7 +7,7 @@ DefaultEnvironment(tools=[]) -for detect_cfg in SCons.Tool.MSCommon.vc._VSDetectRegistry.DETECT_CONFIG.values(): +for detect_cfg in SCons.Tool.MSCommon.MSVC.VSDetect._VSDetectRegistry.DETECT_CONFIG.values(): detect_cfg.vc_cfg.regkeys.clear() env = SCons.Environment.Environment() From 26bdb85e2d14e5aa8ffa0cbd728332d5aa930f09 Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Sat, 14 Oct 2023 17:27:31 -0400 Subject: [PATCH 21/24] Changes: * Log OSError exception for resolve_path in MSCommon/MSVC/Util.py. * Fix file docstring for VSDetect.py. * Update MSVC action when MSVC_USE_SCRIPT is None and MSVC_USE_SETTINGS is not None (SETTINGS instead of BYPASS). * Replace process_path function with resolve_path and normalize_path functions in MSCommon/MSVC/Util.py. * Replace process_path invocations with normalize_path invocations. * Protect against inadvertent resolve/realpath for windows drive specifications in resolve_path and normalize_path. * Additional options for normalize_path with defaults consistent with process_path. --- SCons/Tool/MSCommon/MSVC/Util.py | 10 ++- SCons/Tool/MSCommon/MSVC/UtilTests.py | 99 ++++++++++++++++++++++++--- SCons/Tool/MSCommon/MSVC/VSDetect.py | 2 +- SCons/Tool/MSCommon/vc.py | 46 ++++++++----- 4 files changed, 128 insertions(+), 29 deletions(-) diff --git a/SCons/Tool/MSCommon/MSVC/Util.py b/SCons/Tool/MSCommon/MSVC/Util.py index 02e061b9f1..9bde744468 100644 --- a/SCons/Tool/MSCommon/MSVC/Util.py +++ b/SCons/Tool/MSCommon/MSVC/Util.py @@ -33,6 +33,8 @@ namedtuple, ) +from ..common import debug + from . import Config # class utilities @@ -100,9 +102,11 @@ def resolve_path(p, ignore_drivespec=True): p = os.path.abspath(p) try: p = str(pathlib.Path(p).resolve()) - except OSError: - # TODO(JCB): log error - pass + except OSError as e: + debug( + 'caught exception: path=%s, exception=%s(%s)', + repr(p), type(e).__name__, repr(str(e)) + ) return p diff --git a/SCons/Tool/MSCommon/MSVC/UtilTests.py b/SCons/Tool/MSCommon/MSVC/UtilTests.py index 8e17326ff5..d92d2ca5c8 100644 --- a/SCons/Tool/MSCommon/MSVC/UtilTests.py +++ b/SCons/Tool/MSCommon/MSVC/UtilTests.py @@ -28,14 +28,46 @@ import unittest import os import re +import sys +import pathlib from SCons.Tool.MSCommon.MSVC import Config from SCons.Tool.MSCommon.MSVC import Util from SCons.Tool.MSCommon.MSVC import WinSDK +def resolve(p): + p = os.path.abspath(p) + p = str(pathlib.Path(p).resolve()) + return p + +def normalize(*comps): + p = os.path.join(*comps) + p = os.path.normpath(p) + p = os.path.normcase(p) + return os.path.normcase(p) + class Data: - UTIL_PARENT_DIR = os.path.join(os.path.dirname(Util.__file__), os.pardir) + IS_WINDOWS = sys.platform == 'win32' + + CWD = os.getcwd() + + UTIL_MODULE = os.path.dirname(Util.__file__) + UTIL_MODULE_PARENT = os.path.join(UTIL_MODULE, os.pardir) + + HOME = pathlib.Path.home() + HOMEDRIVE = HOME.drive + HOMEPATH = str(HOME) + + REALPATH_CWD = resolve(CWD) + + REALPATH_UTIL_MODULE = resolve(UTIL_MODULE) + REALPATH_UTIL_MODULE_PARENT = resolve(UTIL_MODULE_PARENT) + + REALPATH_HOMEPATH = resolve(HOMEPATH) + REALPATH_HOMEPATH_PARENT = resolve(os.path.join(HOMEPATH, os.pardir)) + REALPATH_HOMEDRIVE = resolve(HOMEDRIVE) + REALPATH_HOMEDRIVE_CWD = resolve(HOMEDRIVE) class UtilTests(unittest.TestCase): @@ -43,21 +75,72 @@ def test_listdir_dirs(self) -> None: func = Util.listdir_dirs for dirname, expect in [ (None, False), ('', False), ('doesnotexist.xyz.abc', False), - (Data.UTIL_PARENT_DIR, True), + (Data.UTIL_MODULE_PARENT, True), ]: dirs = func(dirname) self.assertTrue((len(dirs) > 0) == expect, "{}({}): {}".format( func.__name__, repr(dirname), 'list is empty' if expect else 'list is not empty' )) + def test_resolve_path(self) -> None: + func = Util.resolve_path + # default kwargs: + # ignore_drivespec=True + test_list = [ + (Data.UTIL_MODULE, Data.REALPATH_UTIL_MODULE, {}), + (os.path.join(Data.UTIL_MODULE, os.pardir), Data.REALPATH_UTIL_MODULE_PARENT, {}), + (Data.HOMEPATH, Data.REALPATH_HOMEPATH, {}), + (os.path.join(Data.HOMEPATH, os.pardir), Data.REALPATH_HOMEPATH_PARENT, {}), + ] + if Data.IS_WINDOWS: + test_list.extend([ + (Data.HOMEDRIVE, Data.HOMEDRIVE, {}), + (Data.HOMEDRIVE, Data.HOMEDRIVE, {'ignore_drivespec': True}), + (Data.HOMEDRIVE, Data.REALPATH_HOMEDRIVE_CWD, {'ignore_drivespec': False}), + ]) + for p, expect, kwargs in test_list: + rval = func(p, **kwargs) + # print(repr(p), repr(expect), repr(rval)) + self.assertTrue(rval == expect, "{}({}): {}".format( + func.__name__, repr(p), repr(rval) + )) + def test_normalize_path(self) -> None: func = Util.normalize_path - for p, expect in [ - (None, True), ('', True), - ('doesnotexist.xyz.abc', False), (Data.UTIL_PARENT_DIR, False), - ]: - rval = func(p) - self.assertTrue((p == rval) == expect, "{}({}): {}".format( + # default kwargs: + # strip=True + # preserve_trailing=False + # expand=False + # realpath=True + # ignore_drivespec=True + test_list = [ + (Data.UTIL_MODULE, normalize(Data.REALPATH_UTIL_MODULE), {}), + (os.path.join(Data.UTIL_MODULE, os.pardir), normalize(Data.REALPATH_UTIL_MODULE_PARENT), {}), + (None, None, {}), + ('', '', {'realpath': False}), + ('', '', {'realpath': True}), + ('DoesNotExist.xyz.abc', normalize('DoesNotExist.xyz.abc'), {'realpath': False}), + ('DoesNotExist.xyz.abc', normalize(Data.REALPATH_CWD, 'DoesNotExist.xyz.abc'), {'realpath': True}), + (' DoesNotExist.xyz.abc ', normalize(Data.REALPATH_CWD, 'DoesNotExist.xyz.abc'), {'realpath': True}), + (' ~ ', '~', {'realpath': False, 'expand': False}), + (' ~ ', normalize(Data.REALPATH_HOMEPATH), {'realpath': True, 'expand': True}), + ] + if Data.IS_WINDOWS: + test_list.extend([ + ('DoesNotExist.xyz.abc/', normalize('DoesNotExist.xyz.abc') + os.path.sep, {'realpath': False, 'preserve_trailing': True}), + (' DoesNotExist.xyz.abc\\ ', normalize('DoesNotExist.xyz.abc') + os.path.sep, {'realpath': False, 'preserve_trailing': True}), + (' ~/ ', normalize(Data.REALPATH_HOMEPATH) + os.path.sep, {'realpath': True, 'expand': True, 'preserve_trailing': True}), + (' ~\\ ', normalize(Data.REALPATH_HOMEPATH) + os.path.sep, {'realpath': True, 'expand': True, 'preserve_trailing': True}), + (' ~/ ', normalize(Data.REALPATH_CWD, '~') + os.path.sep, {'realpath': True, 'expand': False, 'preserve_trailing': True}), + (' ~\\ ', normalize(Data.REALPATH_CWD, '~') + os.path.sep, {'realpath': True, 'expand': False, 'preserve_trailing': True}), + (Data.HOMEDRIVE, normalize(Data.HOMEDRIVE), {}), + (Data.HOMEDRIVE, normalize(Data.HOMEDRIVE), {'ignore_drivespec': True}), + (Data.HOMEDRIVE, normalize(Data.REALPATH_HOMEDRIVE_CWD), {'ignore_drivespec': False}), + ]) + for p, expect, kwargs in test_list: + rval = func(p, **kwargs) + # print(repr(p), repr(expect), repr(rval)) + self.assertTrue(rval == expect, "{}({}): {}".format( func.__name__, repr(p), repr(rval) )) diff --git a/SCons/Tool/MSCommon/MSVC/VSDetect.py b/SCons/Tool/MSCommon/MSVC/VSDetect.py index 42109aa673..375de1e721 100644 --- a/SCons/Tool/MSCommon/MSVC/VSDetect.py +++ b/SCons/Tool/MSCommon/MSVC/VSDetect.py @@ -22,7 +22,7 @@ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -VSWhere executable locations for Microsoft Visual C/C++. +Visual Studio detection for Microsoft Visual C/C++. """ import json diff --git a/SCons/Tool/MSCommon/vc.py b/SCons/Tool/MSCommon/vc.py index cff8a5850e..a1c07f14a7 100644 --- a/SCons/Tool/MSCommon/vc.py +++ b/SCons/Tool/MSCommon/vc.py @@ -1387,13 +1387,15 @@ class _MSVCAction: def _msvc_action(env): - # use_script use_settings return description - # ---------- -------------- -------- ---------------- - # string ignored SCRIPT use script - # eval false ignored BYPASS bypass detection - # eval true ignored SELECT msvc selection - # undefined value not None SETTINGS use dictionary - # undefined undefined/None SELECT msvc selection + # use_script use_settings action description + # ------------- ------------ -------- ---------------- + # defined/None not None SETTINGS use dictionary + # defined/None None BYPASS bypass detection + # defined/string ignored SCRIPT use script + # defined/false ignored BYPASS bypass detection + # defined/true ignored SELECT msvc selection + # undefined not None SETTINGS use dictionary + # undefined None SELECT msvc selection msvc_action = None @@ -1402,17 +1404,27 @@ def _msvc_action(env): if use_script != UNDEFINED: # use script defined - if SCons.Util.is_String(use_script): - # use_script is string, use_settings ignored - msvc_action = _MSVCAction.SCRIPT - elif not use_script: - # use_script eval false, use_settings ignored - msvc_action = _MSVCAction.BYPASS + if use_script is None: + # use script is None + if use_settings is not None: + # use script is None, use_settings is not None + msvc_action = _MSVCAction.SETTINGS + else: + # use script is None, use_settings is None + msvc_action = _MSVCAction.BYPASS else: - # use script eval true, use_settings ignored - msvc_action = _MSVCAction.SELECT + # use script is not None + if SCons.Util.is_String(use_script): + # use_script is string, use_settings ignored + msvc_action = _MSVCAction.SCRIPT + elif not use_script: + # use_script eval false, use_settings ignored + msvc_action = _MSVCAction.BYPASS + else: + # use script eval true, use_settings ignored + msvc_action = _MSVCAction.SELECT elif use_settings is not None: - # use script undefined, use_settings defined and not None (type checked) + # use script undefined, use_settings is defined and not None msvc_action = _MSVCAction.SETTINGS else: # use script undefined, use_settings undefined or None @@ -1465,7 +1477,7 @@ def msvc_setup_env(env) -> None: debug('msvc_action=%s, d=%s', repr(msvc_action.label), d) elif msvc_action == _MSVCAction.BYPASS: debug('msvc_action=%s', repr(msvc_action.label)) - warn_msg = "MSVC_USE_SCRIPT set to False, assuming environment set correctly." + warn_msg = "MSVC detection bypassed, assuming environment set correctly." SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, warn_msg) return else: From 7e55fdb673cd275cb8127b5a8cc8003feb26ede4 Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Sat, 14 Oct 2023 18:13:20 -0400 Subject: [PATCH 22/24] Temporarily use SCONS_MSVS_CHANNEL_DEFAULT environment variable to set default channel. --- SCons/Tool/MSCommon/MSVC/Options.py | 40 ++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/SCons/Tool/MSCommon/MSVC/Options.py b/SCons/Tool/MSCommon/MSVC/Options.py index bf13c23bf7..fed7111d7a 100644 --- a/SCons/Tool/MSCommon/MSVC/Options.py +++ b/SCons/Tool/MSCommon/MSVC/Options.py @@ -45,22 +45,28 @@ CMDLINE_OPTIONS_FORCE = False CMDLINE_OPTIONS_EVAR = 'SCONS_ENABLE_MSVC_OPTIONS' +MSVS_CHANNEL_DEFAULT_EVAR = 'SCONS_MSVS_CHANNEL_DEFAULT' + class _Options(Util.AutoInitialize): enabled = CMDLINE_OPTIONS_FORCE - vswhere_val = None vswhere_opt = '--vswhere' vswhere_dest = 'vswhere' - msvs_channel_val = None + vswhere_val = None + vswhere_src = vswhere_opt + msvs_channel_opt = '--msvs-channel' msvs_channel_dest = 'msvs_channel' + msvs_channel_val = None + msvs_channel_src = msvs_channel_opt + debug_extra = None @classmethod - def _setup(cls) -> None: + def _add_options(cls) -> None: if not cls.enabled: val = os.environ.get(CMDLINE_OPTIONS_EVAR) @@ -79,8 +85,6 @@ def _setup(cls) -> None: metavar='EXEPATH', ) - cls.vswhere_val = SCons.Script.GetOption(cls.vswhere_dest) - SCons.Script.AddOption( cls.msvs_channel_opt, nargs=1, @@ -92,7 +96,6 @@ def _setup(cls) -> None: metavar='CHANNEL', ) - cls.msvs_channel_val = SCons.Script.GetOption(cls.msvs_channel_dest) debug( 'enabled=%s, vswhere=%s, msvs_channel=%s', @@ -100,13 +103,32 @@ def _setup(cls) -> None: extra=cls.debug_extra ) + @classmethod + def _get_options(cls) -> None: + + try: + cls.vswhere_val = SCons.Script.GetOption(cls.vswhere_dest) + except AttributeError: + pass + + try: + cls.msvs_channel_val = SCons.Script.GetOption(cls.msvs_channel_dest) + except AttributeError: + pass + + if cls.msvs_channel_val is None: + cls.msvs_channel_val = os.environ.get(MSVS_CHANNEL_DEFAULT_EVAR) + if cls.msvs_channel_val is not None: + cls.msvs_channel_src = MSVS_CHANNEL_DEFAULT_EVAR + @classmethod def _initialize(cls) -> None: cls.debug_extra = debug_extra(cls) - cls._setup() + cls._add_options() + cls._get_options() def vswhere(): - return _Options.vswhere_val, _Options.vswhere_opt + return _Options.vswhere_val, _Options.vswhere_src def msvs_channel(): - return _Options.msvs_channel_val, _Options.msvs_channel_opt + return _Options.msvs_channel_val, _Options.msvs_channel_src From e56efcc9bec7fafb0e274939c6812f6ac06efa4c Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Mon, 16 Oct 2023 09:01:04 -0400 Subject: [PATCH 23/24] Changes: * Add env_query function to return the value in a mapping associated with the key, invoking subst method when specified and available. Support general mapping in addition to scons Environments. * Add methods that accept env mapping in MSCommon/MSVC/VSDetect.py and MSCommon/MSVC/VSWhere.py. --- SCons/Tool/MSCommon/MSVC/Util.py | 29 ++++++++++++++++++++++++++++ SCons/Tool/MSCommon/MSVC/VSDetect.py | 16 ++++++++++----- SCons/Tool/MSCommon/MSVC/VSWhere.py | 9 ++++++++- SCons/Tool/MSCommon/vc.py | 20 +++++++------------ 4 files changed, 55 insertions(+), 19 deletions(-) diff --git a/SCons/Tool/MSCommon/MSVC/Util.py b/SCons/Tool/MSCommon/MSVC/Util.py index 9bde744468..083f4cdf52 100644 --- a/SCons/Tool/MSCommon/MSVC/Util.py +++ b/SCons/Tool/MSCommon/MSVC/Util.py @@ -46,6 +46,35 @@ def __init_subclass__(cls, **kwargs): if hasattr(cls, '_initialize') and callable(getattr(cls, '_initialize', None)): cls._initialize() +# env utilities + +def env_query(env, key, default=None, subst=False): + """ + Query a mapping object for the value associated with the key. + + Args: + env: Mapping[str, object] + mapping for key retrieval + key: str + key for object retrieval + default: object + return value when key is not in mapping + subst: bool + call substr method if it exists when True + + Returns: + object: value retrieved from mapping or the default value + + """ + + if not env or key not in env: + rval = default + elif subst and hasattr(env, 'subst') and callable(getattr(env, 'subst', None)): + rval = env.subst('$' + key) + else: + rval = env[key] + return rval + # path utilities # windows drive specification (e.g., 'C:') diff --git a/SCons/Tool/MSCommon/MSVC/VSDetect.py b/SCons/Tool/MSCommon/MSVC/VSDetect.py index 375de1e721..2e631f7f52 100644 --- a/SCons/Tool/MSCommon/MSVC/VSDetect.py +++ b/SCons/Tool/MSCommon/MSVC/VSDetect.py @@ -1127,7 +1127,7 @@ def _debug_dump(self) -> None: extra=self.debug_extra, ) - def _msvs_instances_internal( + def _query_instances_internal( self, *, vs_product_def=None, vs_channel_def=None, @@ -1529,7 +1529,7 @@ def _debug_dump(self) -> None: extra=self.debug_extra, ) - def _msvc_instances_internal( + def _query_instances_internal( self, *, vs_product_def=None, vs_channel_def=None, @@ -1644,7 +1644,7 @@ def query_msvs_instances( else: vs_componentid_def = None - msvs_instances, query_key = self.msvs_installed._msvs_instances_internal( + msvs_instances, query_key = self.msvs_installed._query_instances_internal( vs_product_def=vs_product_def, vs_channel_def=vs_channel_def, vs_componentid_def=vs_componentid_def, @@ -1670,7 +1670,7 @@ def query_msvc_instances( else: vs_componentid_def = None - msvc_instances, query_key = self.msvc_installed._msvc_instances_internal( + msvc_instances, query_key = self.msvc_installed._query_instances_internal( vs_product_def=vs_product_def, vs_channel_def=vs_channel_def, vs_componentid_def=vs_componentid_def, @@ -2497,7 +2497,13 @@ def detect(cls, vswhere_env): def register_reset_func(func) -> None: _VSDetectVSWhere.register_reset_func(func) -def msvs_manager(vswhere_env=None): + +def msvs_detect(vswhere_env=None): + vs_manager = _VSDetect.detect(vswhere_env) + return vs_manager + +def msvs_detect_env(env=None): + vswhere_env = Util.env_query(env, 'VSWHERE', subst=True) vs_manager = _VSDetect.detect(vswhere_env) return vs_manager diff --git a/SCons/Tool/MSCommon/MSVC/VSWhere.py b/SCons/Tool/MSCommon/MSVC/VSWhere.py index ffb2eefd17..93731c603e 100644 --- a/SCons/Tool/MSCommon/MSVC/VSWhere.py +++ b/SCons/Tool/MSCommon/MSVC/VSWhere.py @@ -189,7 +189,7 @@ def vswhere_get_executables(vswhere_env=None): vswhere_executables = [] - # env['VSWHERE'] path + # env['VSWHERE']=path if vswhere_env: vswhere_exec = _VSWhere.user_path(vswhere_env, "env['VSWHERE']") if vswhere_exec: @@ -205,6 +205,13 @@ def vswhere_get_executables(vswhere_env=None): return vswhere_executables +def vswhere_get_executables_env(env=None): + vswhere_env = Util.env_query(env, 'VSWHERE', subst=True) + vswhere_executables = vswhere_get_executables(vswhere_env) + return vswhere_executables + +# reset state + def reset() -> None: _VSWhere.reset() diff --git a/SCons/Tool/MSCommon/vc.py b/SCons/Tool/MSCommon/vc.py index a1c07f14a7..d0ee10154f 100644 --- a/SCons/Tool/MSCommon/vc.py +++ b/SCons/Tool/MSCommon/vc.py @@ -746,8 +746,7 @@ def msvc_find_vswhere(env=None): """ Find the location of vswhere """ # NB: this gets called from testsuite on non-Windows platforms. # Whether that makes sense or not, don't break it for those. - vswhere_env = env.subst('$VSWHERE') if env and 'VSWHERE' in env else None - vswhere_executables = MSVC.VSWhere.vswhere_get_executables(vswhere_env) + vswhere_executables = MSVC.VSWhere.vswhere_get_executables_env(env) if vswhere_executables: vswhere_path = vswhere_executables[0].path else: @@ -755,11 +754,6 @@ def msvc_find_vswhere(env=None): debug('vswhere_path=%s', vswhere_path) return vswhere_path -def msvs_manager(env=None): - vswhere_env = env.subst('$VSWHERE') if env and 'VSWHERE' in env else None - vs_manager = MSVC.VSDetect.msvs_manager(vswhere_env) - return vs_manager - def _find_msvc_instance(msvc_version, env=None): msvc_instance = None @@ -770,9 +764,9 @@ def _find_msvc_instance(msvc_version, env=None): debug("Unknown version of MSVC: %s", repr(msvc_version)) return msvc_instance, query_key - vs_manager = msvs_manager(env) + msvs_manager = MSVC.VSDetect.msvs_detect_env(env) - msvc_instances, query_key = vs_manager.query_msvc_instances(msvc_version=msvc_version) + msvc_instances, query_key = msvs_manager.query_msvc_instances(msvc_version=msvc_version) msvc_instance = msvc_instances[0] if msvc_instances else None @@ -1082,12 +1076,12 @@ def get_installed_msvc_instances(env=None): global _cache_installed_msvc_instances # the installed instance cache is cleared if new instances are discovered - vs_manager = msvs_manager(env) + msvs_manager = MSVC.VSDetect.msvs_detect_env(env) if _cache_installed_msvc_instances is not None: return _cache_installed_msvc_instances - installed_msvc_instances = vs_manager.get_installed_msvc_instances() + installed_msvc_instances = msvs_manager.get_installed_msvc_instances() _cache_installed_msvc_instances = installed_msvc_instances # debug("installed_msvc_instances=%s", _cache_installed_msvc_versions) @@ -1097,12 +1091,12 @@ def get_installed_vcs(env=None): global _cache_installed_msvc_versions # the installed version cache is cleared if new instances are discovered - vs_manager = msvs_manager(env) + msvs_manager = MSVC.VSDetect.msvs_detect_env(env) if _cache_installed_msvc_versions is not None: return _cache_installed_msvc_versions - installed_msvc_versions = vs_manager.get_installed_msvc_versions() + installed_msvc_versions = msvs_manager.get_installed_msvc_versions() _cache_installed_msvc_versions = installed_msvc_versions debug("installed_msvc_versions=%s", _cache_installed_msvc_versions) From 62a626b13f222d0e72633400f1a669e9d662cd52 Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Fri, 20 Oct 2023 05:16:18 -0400 Subject: [PATCH 24/24] Rework msvc options for running the test suite with preview-only msvc installations. Internal changes: * Remove "*" as a channel symbol. * MSVC options: * Use a single environment variable (SCONS_MSVC_OPTIONS). * Add an msvc options parser. * Always add scons options. * Update test/Help.py for msvc local options. * MSVC/MSVS detection: * Update configuration data for MSVS and update registry keys for MSVC/Config.py. * Add msvc exists guard in MSCommon/vcTests.py for sdk list tests in case sdk installed but msvc is not (preview testing). * Rework vs.py for msvs instances and temporarily comment-out existing accessor methods and replace with attributes. * SCons/Tool/msvsTests.py: * Rework DummyExists implementation to accommodate revised msvc/msvs detection. * Handle multiple versions returned for express versions. * test/Help.py * Detect if msvc location options should be present and adjust the expected output accordingly. --- SCons/Tool/MSCommon/MSVC/Config.py | 2 +- SCons/Tool/MSCommon/MSVC/Options.py | 239 ++++++++++++++----- SCons/Tool/MSCommon/MSVC/VSDetect.py | 82 +++++-- SCons/Tool/MSCommon/MSVC/VSWhere.py | 2 +- SCons/Tool/MSCommon/vcTests.py | 13 +- SCons/Tool/MSCommon/vs.py | 338 ++++++++++++++------------- SCons/Tool/msvsTests.py | 212 ++++++++++++++--- test/Help.py | 58 ++++- 8 files changed, 650 insertions(+), 296 deletions(-) diff --git a/SCons/Tool/MSCommon/MSVC/Config.py b/SCons/Tool/MSCommon/MSVC/Config.py index e978d3a134..72a623b984 100644 --- a/SCons/Tool/MSCommon/MSVC/Config.py +++ b/SCons/Tool/MSCommon/MSVC/Config.py @@ -338,7 +338,7 @@ for vs_channel_rank, vs_channel_suffix, vs_channel_symbols in ( (1, 'Rel', ['Release']), (2, 'Pre', ['Preview']), - (3, 'Any', ['Any', '*']) + (3, 'Any', ['Any']) ): vs_channel_id = vs_channel_symbols[0] diff --git a/SCons/Tool/MSCommon/MSVC/Options.py b/SCons/Tool/MSCommon/MSVC/Options.py index fed7111d7a..72f3fb5768 100644 --- a/SCons/Tool/MSCommon/MSVC/Options.py +++ b/SCons/Tool/MSCommon/MSVC/Options.py @@ -25,7 +25,10 @@ Command-line options for Microsoft Visual C/C++. """ +import argparse import os +import shlex + from collections import namedtuple import SCons.Script @@ -35,100 +38,208 @@ debug_extra, ) -from . import Config from . import Util +from .Exceptions import ( + MSVCInternalError, + MSVCArgumentError, +) + from . import Dispatcher Dispatcher.register_modulename(__name__) -CMDLINE_OPTIONS_FORCE = False -CMDLINE_OPTIONS_EVAR = 'SCONS_ENABLE_MSVC_OPTIONS' +MSVC_OPTIONS_EVAR = 'SCONS_MSVC_OPTIONS' -MSVS_CHANNEL_DEFAULT_EVAR = 'SCONS_MSVS_CHANNEL_DEFAULT' +class MSVCOptionsParser(argparse.ArgumentParser, Util.AutoInitialize): -class _Options(Util.AutoInitialize): + debug_extra = None - enabled = CMDLINE_OPTIONS_FORCE + def __init__(self, *, environ_var, environ_val): + super().__init__() + self.debug_extra = debug_extra(self.__class__) + self._environ_var = environ_var + self._environ_val = environ_val + self._environ_args = shlex.split(environ_val, posix=False) + self.prog = f'{self.__class__.__name__}' + debug('environ_args=%s', repr(self._environ_args), extra=self.debug_extra) - vswhere_opt = '--vswhere' - vswhere_dest = 'vswhere' + def error(self, message) -> None: + debug( + 'MSVCArgumentError: message=%s', + repr(message), extra=self.debug_extra + ) + errmsg = f'{self._environ_var} {message}\n {self._environ_var}={self._environ_val}' + raise MSVCArgumentError(errmsg) - vswhere_val = None - vswhere_src = vswhere_opt + def exit(self, status=0, message=None) -> None: + message = message.rstrip('\n') + debug( + 'MSVCInternalError: status=%d, message=%s', + status, repr(message), extra=self.debug_extra + ) + raise MSVCInternalError(message) - msvs_channel_opt = '--msvs-channel' - msvs_channel_dest = 'msvs_channel' + def parse_args(self) -> None: + args = super().parse_args(self._environ_args) + debug('args=%s', repr(args), extra=self.debug_extra) + return args - msvs_channel_val = None - msvs_channel_src = msvs_channel_opt +class _Options(Util.AutoInitialize): debug_extra = None + options_environ = None + options_enabled = False + + _Option = namedtuple('Option', [ + 'option', + 'dest', + 'default', + 'action', + 'help', + 'metavar', + 'type', + 'type_scons', + ]) + + msvs_channel_opt = _Option( + option='--msvs-channel', + dest='msvs_channel', + default=None, + type=str, + type_scons='string', + action='store', + help='Set default msvs CHANNEL [Release, Preview, Any].', + metavar='CHANNEL', + ) + + msvs_channel_val = msvs_channel_opt.default + + vswhere_opt = _Option( + option='--vswhere', + dest='vswhere', + default=None, + type=str, + type_scons='string', + action='store', + help='Add vswhere executable located at EXEPATH.', + metavar='EXEPATH', + ) + + vswhere_val = vswhere_opt.default + @classmethod - def _add_options(cls) -> None: - - if not cls.enabled: - val = os.environ.get(CMDLINE_OPTIONS_EVAR) - cls.enabled = bool(val in Config.BOOLEAN_SYMBOLS[True]) - - if cls.enabled: - - SCons.Script.AddOption( - cls.vswhere_opt, - nargs=1, - dest=cls.vswhere_dest, - default=None, - type="string", - action="store", - help='Add vswhere executable located at EXEPATH.', - metavar='EXEPATH', - ) - - SCons.Script.AddOption( - cls.msvs_channel_opt, - nargs=1, - dest=cls.msvs_channel_dest, - default=None, - type="string", - action="store", - help='Set default msvs CHANNEL [Release, Preview, Any].', - metavar='CHANNEL', - ) + def _options_enabled(cls) -> bool: + val = os.environ.get(MSVC_OPTIONS_EVAR) + if val: + val = val.strip() + + if val: + cls.options_environ = val + cls.options_enabled = True debug( - 'enabled=%s, vswhere=%s, msvs_channel=%s', - cls.enabled, repr(cls.vswhere_val), repr(cls.msvs_channel_val), - extra=cls.debug_extra + 'options_enabled=%s, options_environ=%s', + cls.options_enabled, repr(cls.options_environ), extra=cls.debug_extra ) + return cls.options_enabled + @classmethod - def _get_options(cls) -> None: + def _options_parser(cls) -> None: - try: - cls.vswhere_val = SCons.Script.GetOption(cls.vswhere_dest) - except AttributeError: - pass + parser = MSVCOptionsParser( + environ_var=MSVC_OPTIONS_EVAR, + environ_val=cls.options_environ, + ) + + parser.add_argument( + cls.msvs_channel_opt.option, + dest=cls.msvs_channel_opt.dest, + default=cls.msvs_channel_opt.default, + type=cls.msvs_channel_opt.type, + action=cls.msvs_channel_opt.action, + help=cls.msvs_channel_opt.help, + metavar=cls.msvs_channel_opt.metavar, + ) - try: - cls.msvs_channel_val = SCons.Script.GetOption(cls.msvs_channel_dest) - except AttributeError: - pass + parser.add_argument( + cls.vswhere_opt.option, + dest=cls.vswhere_opt.dest, + default=cls.vswhere_opt.default, + type=cls.vswhere_opt.type, + action=cls.vswhere_opt.action, + help=cls.vswhere_opt.help, + metavar=cls.vswhere_opt.metavar, + ) - if cls.msvs_channel_val is None: - cls.msvs_channel_val = os.environ.get(MSVS_CHANNEL_DEFAULT_EVAR) - if cls.msvs_channel_val is not None: - cls.msvs_channel_src = MSVS_CHANNEL_DEFAULT_EVAR + args = parser.parse_args() + + msvs_channel_val = getattr(args, cls.msvs_channel_opt.dest) + if msvs_channel_val: + cls.msvs_channel_val = msvs_channel_val + debug('msvs_channel=%s', repr(cls.msvs_channel_val), extra=cls.debug_extra) + + vswhere_val = getattr(args, cls.vswhere_opt.dest) + if vswhere_val: + cls.vswhere_val = vswhere_val + debug('vswhere=%s', repr(cls.vswhere_val), extra=cls.debug_extra) + + @classmethod + def _options_cmdline(cls) -> None: + + SCons.Script.AddOption( + cls.msvs_channel_opt.option, + dest=cls.msvs_channel_opt.dest, + default=cls.msvs_channel_opt.default, + type=cls.msvs_channel_opt.type_scons, + action=cls.msvs_channel_opt.action, + help=cls.msvs_channel_opt.help, + metavar=cls.msvs_channel_opt.metavar, + nargs=1, + ) + + SCons.Script.AddOption( + cls.vswhere_opt.option, + dest=cls.vswhere_opt.dest, + default=cls.vswhere_opt.default, + type=cls.vswhere_opt.type_scons, + action=cls.vswhere_opt.action, + help=cls.vswhere_opt.help, + metavar=cls.vswhere_opt.metavar, + nargs=1, + ) + + msvs_channel_val = SCons.Script.GetOption(cls.msvs_channel_opt.dest) + if msvs_channel_val: + cls.msvs_channel_val = msvs_channel_val + debug('msvs_channel=%s', repr(cls.msvs_channel_val), extra=cls.debug_extra) + + vswhere_val = SCons.Script.GetOption(cls.vswhere_opt.dest) + if vswhere_val: + cls.vswhere_val = vswhere_val + debug('vswhere=%s', repr(cls.vswhere_val), extra=cls.debug_extra) + + @classmethod + def _options_finalize(cls) -> None: + debug('msvs_channel=%s', repr(cls.msvs_channel_val), extra=cls.debug_extra) + debug('vswhere=%s', repr(cls.vswhere_val), extra=cls.debug_extra) @classmethod def _initialize(cls) -> None: + cls.debug_extra = debug_extra(cls) - cls._add_options() - cls._get_options() -def vswhere(): - return _Options.vswhere_val, _Options.vswhere_src + if cls._options_enabled(): + cls._options_parser() + + cls._options_cmdline() + cls._options_finalize() def msvs_channel(): - return _Options.msvs_channel_val, _Options.msvs_channel_src + return _Options.msvs_channel_val, _Options.msvs_channel_opt.option + +def vswhere(): + return _Options.vswhere_val, _Options.vswhere_opt.option diff --git a/SCons/Tool/MSCommon/MSVC/VSDetect.py b/SCons/Tool/MSCommon/MSVC/VSDetect.py index 2e631f7f52..8962b0c2f3 100644 --- a/SCons/Tool/MSCommon/MSVC/VSDetect.py +++ b/SCons/Tool/MSCommon/MSVC/VSDetect.py @@ -101,7 +101,7 @@ def path_exists(cls, pval): if rval == Config.UNDEFINED: rval = os.path.exists(pval) cls._path_exists[pval] = rval - debug('exists=%s, pval=%s', rval, repr(pval), extra=cls.debug_extra) + debug('exists=%s, pval=%s', repr(rval), repr(pval), extra=cls.debug_extra) return rval @@ -155,6 +155,7 @@ class _VSConfig: 'root', # relative path vc dir -> vs root 'vs_binary_cfg', # vs executable 'vs_batch_cfg', # vs batch file + 'vc_folder', # vc folder name ]) VCDetectConfigRegistry = namedtuple("VCDetectConfigRegistry", [ @@ -205,6 +206,7 @@ def factory( pathcomp=r'Common7\Tools', script='VsDevCmd.bat', ), + vc_folder='VC', ), vc_cfg=None, ), @@ -220,6 +222,7 @@ def factory( pathcomp=r'Common7\Tools', script='VsDevCmd.bat', ), + vc_folder='VC', ), vc_cfg=None, ), @@ -235,6 +238,7 @@ def factory( pathcomp=r'Common7\Tools', script='VsDevCmd.bat', ), + vc_folder='VC', ), vc_cfg=None, ), @@ -257,12 +261,16 @@ def factory( # VsDevCmd.bat and vsvars32.bat appear to be different script='vsvars32.bat', ), + vc_folder='VC', ), vc_cfg=VCDetectConfigRegistry( regkeys=[ - _regkey(key=r'Microsoft\VisualStudio\14.0\Setup\VC\ProductDir'), + _regkey(key=r'Microsoft\VisualStudio\SxS\VS7\14.0', is_vsroot=True), + _regkey(key=r'Microsoft\VisualStudio\14.0\Setup\VS\ProductDir', is_vsroot=True), _regkey(key=r'Microsoft\WDExpress\14.0\Setup\VS\ProductDir', is_vsroot=True), - _regkey(key=r'Microsoft\VCExpress\14.0\Setup\VC\ProductDir'), # not populated? + _regkey(key=r'Microsoft\VisualStudio\SxS\VC7\14.0'), + _regkey(key=r'Microsoft\VisualStudio\14.0\Setup\VC\ProductDir'), + _regkey(key=r'Microsoft\VCExpress\14.0\Setup\VC\ProductDir'), # key not defined ], ), ), @@ -279,9 +287,14 @@ def factory( # VsDevCmd.bat and vsvars32.bat appear to be different script='vsvars32.bat', ), + vc_folder='VC', ), vc_cfg=VCDetectConfigRegistry( regkeys=[ + _regkey(key=r'Microsoft\VisualStudio\SxS\VS7\12.0', is_vsroot=True), + _regkey(key=r'Microsoft\VisualStudio\12.0\Setup\VS\ProductDir', is_vsroot=True), + _regkey(key=r'Microsoft\WDExpress\12.0\Setup\VS\ProductDir', is_vsroot=True), + _regkey(key=r'Microsoft\VisualStudio\SxS\VC7\12.0'), _regkey(key=r'Microsoft\VisualStudio\12.0\Setup\VC\ProductDir'), _regkey(key=r'Microsoft\VCExpress\12.0\Setup\VC\ProductDir'), ] @@ -300,9 +313,14 @@ def factory( # VsDevCmd.bat and vsvars32.bat appear to be identical script='vsvars32.bat', ), + vc_folder='VC', ), vc_cfg=VCDetectConfigRegistry( regkeys=[ + _regkey(key=r'Microsoft\VisualStudio\SxS\VS7\11.0', is_vsroot=True), + _regkey(key=r'Microsoft\VisualStudio\11.0\Setup\VS\ProductDir', is_vsroot=True), + _regkey(key=r'Microsoft\WDExpress\11.0\Setup\VS\ProductDir', is_vsroot=True), + _regkey(key=r'Microsoft\VisualStudio\SxS\VC7\11.0'), _regkey(key=r'Microsoft\VisualStudio\11.0\Setup\VC\ProductDir'), _regkey(key=r'Microsoft\VCExpress\11.0\Setup\VC\ProductDir'), ] @@ -320,9 +338,14 @@ def factory( pathcomp=r'Common7\Tools', script='vsvars32.bat', ), + vc_folder='VC', ), vc_cfg=VCDetectConfigRegistry( regkeys=[ + _regkey(key=r'Microsoft\VisualStudio\SxS\VS7\10.0', is_vsroot=True), + _regkey(key=r'Microsoft\VisualStudio\10.0\Setup\VS\ProductDir', is_vsroot=True), + _regkey(key=r'Microsoft\VCExpress\10.0\Setup\VS\ProductDir', is_vsroot=True), + _regkey(key=r'Microsoft\VisualStudio\SxS\VC7\10.0'), _regkey(key=r'Microsoft\VisualStudio\10.0\Setup\VC\ProductDir'), _regkey(key=r'Microsoft\VCExpress\10.0\Setup\VC\ProductDir'), ] @@ -340,18 +363,23 @@ def factory( pathcomp=r'Common7\Tools', script='vsvars32.bat', ), + vc_folder='VC', ), vc_cfg=VCDetectConfigRegistry( regkeys=[ _regkey( hkroot=SCons.Util.HKEY_CURRENT_USER, - key=r'Microsoft\DevDiv\VCForPython\9.0\InstallDir', - is_vsroot=True, is_vcforpython=True, + key=r'Microsoft\DevDiv\VCForPython\9.0\InstallDir', is_vsroot=True, + is_vcforpython=True, ), _regkey( - key=r'Microsoft\DevDiv\VCForPython\9.0\InstallDir', - is_vsroot=True, is_vcforpython=True + key=r'Microsoft\DevDiv\VCForPython\9.0\InstallDir', is_vsroot=True, + is_vcforpython=True ), + _regkey(key=r'Microsoft\VisualStudio\SxS\VS7\9.0', is_vsroot=True), + _regkey(key=r'Microsoft\VisualStudio\9.0\Setup\VS\ProductDir', is_vsroot=True), + _regkey(key=r'Microsoft\VCExpress\9.0\Setup\VS\ProductDir', is_vsroot=True), + _regkey(key=r'Microsoft\VisualStudio\SxS\VC7\9.0'), _regkey(key=r'Microsoft\VisualStudio\9.0\Setup\VC\ProductDir'), _regkey(key=r'Microsoft\VCExpress\9.0\Setup\VC\ProductDir'), ] @@ -369,9 +397,14 @@ def factory( pathcomp=r'Common7\Tools', script='vsvars32.bat', ), + vc_folder='VC', ), vc_cfg=VCDetectConfigRegistry( regkeys=[ + _regkey(key=r'Microsoft\VisualStudio\SxS\VS7\8.0', is_vsroot=True), + _regkey(key=r'Microsoft\VisualStudio\8.0\Setup\VS\ProductDir', is_vsroot=True), + _regkey(key=r'Microsoft\VCExpress\8.0\Setup\VS\ProductDir', is_vsroot=True), + _regkey(key=r'Microsoft\VisualStudio\SxS\VC7\8.0'), _regkey(key=r'Microsoft\VisualStudio\8.0\Setup\VC\ProductDir'), _regkey(key=r'Microsoft\VCExpress\8.0\Setup\VC\ProductDir'), ] @@ -389,9 +422,13 @@ def factory( pathcomp=r'Common7\Tools', script='vsvars32.bat', ), + vc_folder='VC7', ), vc_cfg=VCDetectConfigRegistry( regkeys=[ + _regkey(key=r'Microsoft\VisualStudio\SxS\VS7\7.1', is_vsroot=True), + _regkey(key=r'Microsoft\VisualStudio\7.1\Setup\VS\ProductDir', is_vsroot=True), + _regkey(key=r'Microsoft\VisualStudio\SxS\VC7\7.1'), _regkey(key=r'Microsoft\VisualStudio\7.1\Setup\VC\ProductDir'), ] ), @@ -408,9 +445,11 @@ def factory( pathcomp=r'Common7\Tools', script='vsvars32.bat', ), + vc_folder='VC7', ), vc_cfg=VCDetectConfigRegistry( regkeys=[ + _regkey(key=r'Microsoft\VisualStudio\7.0\Setup\VS\ProductDir', is_vsroot=True), _regkey(key=r'Microsoft\VisualStudio\7.0\Setup\VC\ProductDir'), ] ), @@ -428,6 +467,7 @@ def factory( pathcomp=r'VC98\bin', script='vcvars32.bat', ), + vc_folder='VC98', ), vc_cfg=VCDetectConfigRegistry( regkeys=[ @@ -443,7 +483,7 @@ class _VSChannel(Util.AutoInitialize): # TODO(JCB): review reset - # priority: cmdline > user > initial + # priority: cmdline > environ > user > initial debug_extra = None @@ -538,7 +578,6 @@ def set_default_channel(cls, msvs_channel, source) -> bool: ) if not cls.vs_channel_cmdline: - # priority: cmdline > user > initial cls.vs_channel_def = cls.vs_channel_user debug( 'vs_channel=%s', @@ -563,7 +602,7 @@ def msvs_set_channel_default(msvs_channel) -> bool: string representing the msvs channel Case-insensitive values are: - Release, Rel, Preview, Pre, Any, * + Release, Rel, Preview, Pre, Any Returns: bool: True if the default msvs channel was accepted. @@ -1850,12 +1889,7 @@ def _vswhere_query_json_output(cls, vswhere_exe, vswhere_args): return vswhere_json @classmethod - def _msvc_resolve(cls, vc_dir, vs_product_def): - - detect_cfg = cls.DETECT_CONFIG[vs_product_def.vs_product] - - vs_cfg = detect_cfg.vs_cfg - vs_dir = os.path.normpath(os.path.join(vc_dir, vs_cfg.root)) + def _msvc_resolve(cls, vs_dir, vs_cfg): binaries_t, vs_script = _VSDetectCommon.msvs_detect(vs_dir, vs_cfg) @@ -1919,10 +1953,6 @@ def detect(cls, vswhere_env): # consider msvs root evaluated at this point cls.vs_dir_seen.add(vs_norm) - vc_dir = os.path.join(vs_dir, 'VC') - if not _VSUtil.path_exists(vc_dir): - continue - vs_major = vs_version.split('.')[0] if vs_major not in Config.MSVS_VERSION_MAJOR_MAP: debug('ignore vs_major: %s', vs_major, extra=cls.debug_extra) @@ -1930,6 +1960,12 @@ def detect(cls, vswhere_env): vs_product_def = Config.MSVS_VERSION_MAJOR_MAP[vs_major] + detect_cfg = cls.DETECT_CONFIG[vs_product_def.vs_product] + + vc_dir = os.path.join(vs_dir, detect_cfg.vs_cfg.vc_folder) + if not _VSUtil.path_exists(vc_dir): + continue + component_id = product_id.split('.')[-1] vs_component_def = Config.VSWHERE_COMPONENT_INTERNAL.get(component_id) if not vs_component_def: @@ -1942,7 +1978,7 @@ def detect(cls, vswhere_env): else: vs_channel_def = Config.MSVS_CHANNEL_RELEASE - vs_exec, vs_script = cls._msvc_resolve(vc_dir, vs_product_def) + vs_exec, vs_script = cls._msvc_resolve(vs_dir, detect_cfg.vs_cfg) vs_sequence_key = (vs_product_def, vs_channel_def, vs_component_def) @@ -2343,7 +2379,7 @@ def detect(cls): if regkey.is_vsroot: - vc_dir = os.path.join(vc_dir, 'VC') + vc_dir = os.path.join(vc_dir, config.vs_cfg.vc_folder) debug('convert vs dir to vc dir: %s', repr(vc_dir), extra=cls.debug_extra) if vc_dir in cls.vc_dir_seen: @@ -2351,7 +2387,7 @@ def detect(cls): cls.vc_dir_seen.add(vc_dir) if not os.path.exists(vc_dir): - debug('vc dir does not exist. (ignoring)', repr(vc_dir), extra=cls.debug_extra) + debug('vc dir does not exist: %s (ignoring)', repr(vc_dir), extra=cls.debug_extra) continue vc_norm = _VSUtil.normalize_path(vc_dir) diff --git a/SCons/Tool/MSCommon/MSVC/VSWhere.py b/SCons/Tool/MSCommon/MSVC/VSWhere.py index 93731c603e..3d90314209 100644 --- a/SCons/Tool/MSCommon/MSVC/VSWhere.py +++ b/SCons/Tool/MSCommon/MSVC/VSWhere.py @@ -43,7 +43,7 @@ from . import Dispatcher Dispatcher.register_modulename(__name__) -# priority: env > cmdline > (user, initial, user) +# priority: env > cmdline > environ > (user, initial, user) VSWHERE_EXE = 'vswhere.exe' diff --git a/SCons/Tool/MSCommon/vcTests.py b/SCons/Tool/MSCommon/vcTests.py index 315b684de1..c0923278b2 100644 --- a/SCons/Tool/MSCommon/vcTests.py +++ b/SCons/Tool/MSCommon/vcTests.py @@ -378,12 +378,13 @@ def test_valid_default_msvc(self) -> None: def test_valid_vcver(self) -> None: for symbol in MSCommon.vc._VCVER: version_def = MSCommon.msvc_version_components(symbol) - for msvc_uwp_app in (True, False): - sdk_list = MSCommon.vc.msvc_sdk_versions(version=symbol, msvc_uwp_app=msvc_uwp_app) - if Data.HAVE_MSVC and version_def.msvc_vernum >= 14.0: - self.assertTrue(sdk_list, "SDK list is empty for msvc version {}".format(repr(symbol))) - else: - self.assertFalse(sdk_list, "SDK list is not empty for msvc version {}".format(repr(symbol))) + if Data.HAVE_MSVC: + for msvc_uwp_app in (True, False): + sdk_list = MSCommon.vc.msvc_sdk_versions(version=symbol, msvc_uwp_app=msvc_uwp_app) + if version_def.msvc_vernum >= 14.0: + self.assertTrue(sdk_list, "SDK list is empty for msvc version {}".format(repr(symbol))) + else: + self.assertFalse(sdk_list, "SDK list is not empty for msvc version {}".format(repr(symbol))) def test_valid_vcver_toolsets(self) -> None: for symbol in MSCommon.vc._VCVER: diff --git a/SCons/Tool/MSCommon/vs.py b/SCons/Tool/MSCommon/vs.py index 6820ada089..695ad44923 100644 --- a/SCons/Tool/MSCommon/vs.py +++ b/SCons/Tool/MSCommon/vs.py @@ -33,6 +33,7 @@ from .common import ( debug, + debug_extra, get_output, is_win64, normalize_env, @@ -40,6 +41,7 @@ read_reg, ) +from . import MSVC class VisualStudio: """ @@ -47,97 +49,91 @@ class VisualStudio: Visual Studio. """ def __init__(self, version, **kw) -> None: + self.debug_extra = debug_extra(self.__class__) self.version = version - kw['vc_version'] = kw.get('vc_version', version) + #kw['vc_version'] = kw.get('vc_version', version) kw['sdk_version'] = kw.get('sdk_version', version) self.__dict__.update(kw) self._cache = {} - - def find_batch_file(self): - vs_dir = self.get_vs_dir() - if not vs_dir: - debug('no vs_dir') - return None - batch_file = os.path.join(vs_dir, self.batch_file_path) - batch_file = os.path.normpath(batch_file) - if not os.path.isfile(batch_file): - debug('%s not on file system', batch_file) - return None - return batch_file - - def find_vs_dir_by_vc(self, env): - msvc_instance = SCons.Tool.MSCommon.vc.find_msvc_instance(self.vc_version, env) - if not msvc_instance: - debug('no installed VC %s', self.vc_version) - return None - return os.path.abspath(os.path.join(msvc_instance.vc_dir, os.pardir)) - - def find_vs_dir_by_reg(self, env): - root = 'Software\\' - - if is_win64(): - root = root + 'Wow6432Node\\' - for key in self.hkeys: - if key=='use_dir': - return self.find_vs_dir_by_vc(env) - key = root + key - try: - comps = read_reg(key) - except OSError: - debug('no VS registry key %s', repr(key)) - else: - debug('found VS in registry: %s', comps) - return comps - return None - - def find_vs_dir(self, env): - """ Can use registry or location of VC to find vs dir - First try to find by registry, and if that fails find via VC dir - """ - - vs_dir = self.find_vs_dir_by_reg(env) - if not vs_dir: - vs_dir = self.find_vs_dir_by_vc(env) - debug('found VS in %s', str(vs_dir)) - return vs_dir - - def find_executable(self, env): - vs_dir = self.get_vs_dir(env) - if not vs_dir: - debug('no vs_dir (%s)', vs_dir) - return None - executable = os.path.join(vs_dir, self.executable_path) - executable = os.path.normpath(executable) - if not os.path.isfile(executable): - debug('%s not on file system', executable) - return None - return executable + self._msvs_instance = None + + def register_msvs_instance(self, msvs_instance): + self._msvs_instance = msvs_instance + + #def find_batch_file(self): + # vs_dir = self.get_vs_dir() + # if not vs_dir: + # debug('no vs_dir') + # return None + # batch_file = os.path.join(vs_dir, self.batch_file_path) + # batch_file = os.path.normpath(batch_file) + # if not os.path.isfile(batch_file): + # debug('%s not on file system', batch_file) + # return None + # return batch_file + + #def find_vs_dir_by_vc(self, env): + # msvc_instance = SCons.Tool.MSCommon.vc.find_msvc_instance(self.vc_version, env) + # if not msvc_instance: + # debug('no installed VC %s', self.vc_version) + # return None + # return os.path.abspath(os.path.join(msvc_instance.vc_dir, os.pardir)) + + #def find_vs_dir_by_reg(self, env): + # root = 'Software\\' + # + # if is_win64(): + # root = root + 'Wow6432Node\\' + # for key in self.hkeys: + # if key=='use_dir': + # return self.find_vs_dir_by_vc(env) + # key = root + key + # try: + # comps = read_reg(key) + # except OSError: + # debug('no VS registry key %s', repr(key)) + # else: + # debug('found VS in registry: %s', comps) + # return comps + # return None + + #def find_vs_dir(self, env): + # """ Can use registry or location of VC to find vs dir + # First try to find by registry, and if that fails find via VC dir + # """ + # + # vs_dir = self.find_vs_dir_by_reg(env) + # if not vs_dir: + # vs_dir = self.find_vs_dir_by_vc(env) + # debug('found VS in %s', str(vs_dir)) + # return vs_dir + + #def find_executable(self, env): + # vs_dir = self.get_vs_dir(env) + # if not vs_dir: + # debug('no vs_dir (%s)', vs_dir) + # return None + # executable = os.path.join(vs_dir, self.executable_path) + # executable = os.path.normpath(executable) + # if not os.path.isfile(executable): + # debug('%s not on file system', executable) + # return None + # return executable def get_batch_file(self): - try: - return self._cache['batch_file'] - except KeyError: - batch_file = self.find_batch_file() - self._cache['batch_file'] = batch_file - return batch_file + batch_file = self._msvs_instance.vs_script if self._msvs_instance else None + debug('batch_file=%s', repr(batch_file)) + return batch_file def get_executable(self, env=None): - try: - debug('using cache:%s', self._cache['executable']) - return self._cache['executable'] - except KeyError: - executable = self.find_executable(env) - self._cache['executable'] = executable - debug('not in cache:%s', executable) - return executable + executable = self._msvs_instance.vs_executable if self._msvs_instance else None + debug('executable=%s', repr(executable)) + return executable def get_vs_dir(self, env=None): - try: - return self._cache['vs_dir'] - except KeyError: - vs_dir = self.find_vs_dir(env) - self._cache['vs_dir'] = vs_dir - return vs_dir + vs_dir = self._msvs_instance.vs_dir if self._msvs_instance else None + debug('vs_dir=%s', repr(vs_dir)) + return vs_dir def get_supported_arch(self): try: @@ -202,213 +198,213 @@ def reset(self) -> None: SupportedVSList = [ # Visual Studio 2022 VisualStudio('14.3', - vc_version='14.3', + #vc_version='14.3', sdk_version='10.0A', - hkeys=[], + #hkeys=[], common_tools_var='VS170COMNTOOLS', - executable_path=r'Common7\IDE\devenv.com', + #executable_path=r'Common7\IDE\devenv.com', # should be a fallback, prefer use vswhere installationPath - batch_file_path=r'Common7\Tools\VsDevCmd.bat', + #batch_file_path=r'Common7\Tools\VsDevCmd.bat', supported_arch=['x86', 'amd64', "arm", 'arm64'], ), # Visual Studio 2019 VisualStudio('14.2', - vc_version='14.2', + #vc_version='14.2', sdk_version='10.0A', - hkeys=[], + #hkeys=[], common_tools_var='VS160COMNTOOLS', - executable_path=r'Common7\IDE\devenv.com', + #executable_path=r'Common7\IDE\devenv.com', # should be a fallback, prefer use vswhere installationPath - batch_file_path=r'Common7\Tools\VsDevCmd.bat', + #batch_file_path=r'Common7\Tools\VsDevCmd.bat', supported_arch=['x86', 'amd64', "arm", 'arm64'], ), # Visual Studio 2017 VisualStudio('14.1', - vc_version='14.1', + #vc_version='14.1', sdk_version='10.0A', - hkeys=[], + #hkeys=[], common_tools_var='VS150COMNTOOLS', - executable_path=r'Common7\IDE\devenv.com', + #executable_path=r'Common7\IDE\devenv.com', # should be a fallback, prefer use vswhere installationPath - batch_file_path=r'Common7\Tools\VsDevCmd.bat', + #batch_file_path=r'Common7\Tools\VsDevCmd.bat', supported_arch=['x86', 'amd64', "arm", 'arm64'], ), # Visual C++ 2017 Express Edition (for Desktop) VisualStudio('14.1Exp', - vc_version='14.1', + #vc_version='14.1', sdk_version='10.0A', - hkeys=[], + #hkeys=[], common_tools_var='VS150COMNTOOLS', - executable_path=r'Common7\IDE\WDExpress.exe', + #executable_path=r'Common7\IDE\WDExpress.exe', # should be a fallback, prefer use vswhere installationPath - batch_file_path=r'Common7\Tools\VsDevCmd.bat', + #batch_file_path=r'Common7\Tools\VsDevCmd.bat', supported_arch=['x86', 'amd64', "arm", 'arm64'], ), # Visual Studio 2015 VisualStudio('14.0', - vc_version='14.0', + #vc_version='14.0', sdk_version='10.0', - hkeys=[r'Microsoft\VisualStudio\14.0\Setup\VS\ProductDir'], + #hkeys=[r'Microsoft\VisualStudio\14.0\Setup\VS\ProductDir'], common_tools_var='VS140COMNTOOLS', - executable_path=r'Common7\IDE\devenv.com', - batch_file_path=r'Common7\Tools\vsvars32.bat', + #executable_path=r'Common7\IDE\devenv.com', + #batch_file_path=r'Common7\Tools\vsvars32.bat', supported_arch=['x86', 'amd64', "arm"], ), # Visual C++ 2015 Express Edition (for Desktop) VisualStudio('14.0Exp', - vc_version='14.0', + #vc_version='14.0', sdk_version='10.0A', - hkeys=[r'Microsoft\VisualStudio\14.0\Setup\VS\ProductDir'], + #hkeys=[r'Microsoft\VisualStudio\14.0\Setup\VS\ProductDir'], common_tools_var='VS140COMNTOOLS', - executable_path=r'Common7\IDE\WDExpress.exe', - batch_file_path=r'Common7\Tools\vsvars32.bat', + #executable_path=r'Common7\IDE\WDExpress.exe', + #batch_file_path=r'Common7\Tools\vsvars32.bat', supported_arch=['x86', 'amd64', "arm"], ), # Visual Studio 2013 VisualStudio('12.0', - vc_version='12.0', + #vc_version='12.0', sdk_version='8.1A', - hkeys=[r'Microsoft\VisualStudio\12.0\Setup\VS\ProductDir'], + #hkeys=[r'Microsoft\VisualStudio\12.0\Setup\VS\ProductDir'], common_tools_var='VS120COMNTOOLS', - executable_path=r'Common7\IDE\devenv.com', - batch_file_path=r'Common7\Tools\vsvars32.bat', + #executable_path=r'Common7\IDE\devenv.com', + #batch_file_path=r'Common7\Tools\vsvars32.bat', supported_arch=['x86', 'amd64'], ), # Visual C++ 2013 Express Edition (for Desktop) VisualStudio('12.0Exp', - vc_version='12.0', + #vc_version='12.0', sdk_version='8.1A', - hkeys=[r'Microsoft\VisualStudio\12.0\Setup\VS\ProductDir'], + #hkeys=[r'Microsoft\VisualStudio\12.0\Setup\VS\ProductDir'], common_tools_var='VS120COMNTOOLS', - executable_path=r'Common7\IDE\WDExpress.exe', - batch_file_path=r'Common7\Tools\vsvars32.bat', + #executable_path=r'Common7\IDE\WDExpress.exe', + #batch_file_path=r'Common7\Tools\vsvars32.bat', supported_arch=['x86', 'amd64'], ), # Visual Studio 2012 VisualStudio('11.0', sdk_version='8.0A', - hkeys=[r'Microsoft\VisualStudio\11.0\Setup\VS\ProductDir'], + #hkeys=[r'Microsoft\VisualStudio\11.0\Setup\VS\ProductDir'], common_tools_var='VS110COMNTOOLS', - executable_path=r'Common7\IDE\devenv.com', - batch_file_path=r'Common7\Tools\vsvars32.bat', + #executable_path=r'Common7\IDE\devenv.com', + #batch_file_path=r'Common7\Tools\vsvars32.bat', supported_arch=['x86', 'amd64'], ), # Visual C++ 2012 Express Edition (for Desktop) VisualStudio('11.0Exp', - vc_version='11.0', + #vc_version='11.0', sdk_version='8.0A', - hkeys=[r'Microsoft\VisualStudio\11.0\Setup\VS\ProductDir'], + #hkeys=[r'Microsoft\VisualStudio\11.0\Setup\VS\ProductDir'], common_tools_var='VS110COMNTOOLS', - executable_path=r'Common7\IDE\WDExpress.exe', - batch_file_path=r'Common7\Tools\vsvars32.bat', + #executable_path=r'Common7\IDE\WDExpress.exe', + #batch_file_path=r'Common7\Tools\vsvars32.bat', supported_arch=['x86', 'amd64'], ), # Visual Studio 2010 VisualStudio('10.0', sdk_version='7.0A', - hkeys=[r'Microsoft\VisualStudio\10.0\Setup\VS\ProductDir'], + #hkeys=[r'Microsoft\VisualStudio\10.0\Setup\VS\ProductDir'], common_tools_var='VS100COMNTOOLS', - executable_path=r'Common7\IDE\devenv.com', - batch_file_path=r'Common7\Tools\vsvars32.bat', + #executable_path=r'Common7\IDE\devenv.com', + #batch_file_path=r'Common7\Tools\vsvars32.bat', supported_arch=['x86', 'amd64'], ), # Visual C++ 2010 Express Edition VisualStudio('10.0Exp', - vc_version='10.0', + #vc_version='10.0', sdk_version='7.0A', - hkeys=[r'Microsoft\VCExpress\10.0\Setup\VS\ProductDir'], + #hkeys=[r'Microsoft\VCExpress\10.0\Setup\VS\ProductDir'], common_tools_var='VS100COMNTOOLS', - executable_path=r'Common7\IDE\VCExpress.exe', - batch_file_path=r'Common7\Tools\vsvars32.bat', + #executable_path=r'Common7\IDE\VCExpress.exe', + #batch_file_path=r'Common7\Tools\vsvars32.bat', supported_arch=['x86'], ), # Visual Studio 2008 VisualStudio('9.0', sdk_version='6.0A', - hkeys=[r'Microsoft\VisualStudio\9.0\Setup\VS\ProductDir'], + #hkeys=[r'Microsoft\VisualStudio\9.0\Setup\VS\ProductDir'], common_tools_var='VS90COMNTOOLS', - executable_path=r'Common7\IDE\devenv.com', - batch_file_path=r'Common7\Tools\vsvars32.bat', + #executable_path=r'Common7\IDE\devenv.com', + #batch_file_path=r'Common7\Tools\vsvars32.bat', supported_arch=['x86', 'amd64'], ), # Visual C++ 2008 Express Edition VisualStudio('9.0Exp', - vc_version='9.0', + #vc_version='9.0', sdk_version='6.0A', - hkeys=[r'Microsoft\VCExpress\9.0\Setup\VS\ProductDir'], + #hkeys=[r'Microsoft\VCExpress\9.0\Setup\VS\ProductDir'], common_tools_var='VS90COMNTOOLS', - executable_path=r'Common7\IDE\VCExpress.exe', - batch_file_path=r'Common7\Tools\vsvars32.bat', + #executable_path=r'Common7\IDE\VCExpress.exe', + #batch_file_path=r'Common7\Tools\vsvars32.bat', supported_arch=['x86'], ), # Visual Studio 2005 VisualStudio('8.0', sdk_version='6.0A', - hkeys=[r'Microsoft\VisualStudio\8.0\Setup\VS\ProductDir'], + #hkeys=[r'Microsoft\VisualStudio\8.0\Setup\VS\ProductDir'], common_tools_var='VS80COMNTOOLS', - executable_path=r'Common7\IDE\devenv.com', - batch_file_path=r'Common7\Tools\vsvars32.bat', - default_dirname='Microsoft Visual Studio 8', + #executable_path=r'Common7\IDE\devenv.com', + #batch_file_path=r'Common7\Tools\vsvars32.bat', + #default_dirname='Microsoft Visual Studio 8', supported_arch=['x86', 'amd64'], ), # Visual C++ 2005 Express Edition VisualStudio('8.0Exp', - vc_version='8.0Exp', + #vc_version='8.0Exp', sdk_version='6.0A', - hkeys=[r'Microsoft\VCExpress\8.0\Setup\VS\ProductDir'], + #hkeys=[r'Microsoft\VCExpress\8.0\Setup\VS\ProductDir'], common_tools_var='VS80COMNTOOLS', - executable_path=r'Common7\IDE\VCExpress.exe', - batch_file_path=r'Common7\Tools\vsvars32.bat', - default_dirname='Microsoft Visual Studio 8', + #executable_path=r'Common7\IDE\VCExpress.exe', + #batch_file_path=r'Common7\Tools\vsvars32.bat', + #default_dirname='Microsoft Visual Studio 8', supported_arch=['x86'], ), # Visual Studio .NET 2003 VisualStudio('7.1', sdk_version='6.0', - hkeys=[r'Microsoft\VisualStudio\7.1\Setup\VS\ProductDir'], + #hkeys=[r'Microsoft\VisualStudio\7.1\Setup\VS\ProductDir'], common_tools_var='VS71COMNTOOLS', - executable_path=r'Common7\IDE\devenv.com', - batch_file_path=r'Common7\Tools\vsvars32.bat', - default_dirname='Microsoft Visual Studio .NET 2003', + #executable_path=r'Common7\IDE\devenv.com', + #batch_file_path=r'Common7\Tools\vsvars32.bat', + #default_dirname='Microsoft Visual Studio .NET 2003', supported_arch=['x86'], ), # Visual Studio .NET VisualStudio('7.0', sdk_version='2003R2', - hkeys=[r'Microsoft\VisualStudio\7.0\Setup\VS\ProductDir'], + #hkeys=[r'Microsoft\VisualStudio\7.0\Setup\VS\ProductDir'], common_tools_var='VSCOMNTOOLS', - executable_path=r'Common7\IDE\devenv.com', - batch_file_path=r'Common7\Tools\vsvars32.bat', - default_dirname='Microsoft Visual Studio .NET', + #executable_path=r'Common7\IDE\devenv.com', + #batch_file_path=r'Common7\Tools\vsvars32.bat', + #default_dirname='Microsoft Visual Studio .NET', supported_arch=['x86'], ), # Visual Studio 6.0 VisualStudio('6.0', sdk_version='2003R1', - hkeys=[r'Microsoft\VisualStudio\6.0\Setup\Microsoft Visual Studio\ProductDir', - 'use_dir'], + #hkeys=[r'Microsoft\VisualStudio\6.0\Setup\Microsoft Visual Studio\ProductDir', + # 'use_dir'], common_tools_var='MSDevDir', - executable_path=r'Common\MSDev98\Bin\MSDEV.COM', - batch_file_path=r'Common7\Tools\vsvars32.bat', - default_dirname='Microsoft Visual Studio', + #executable_path=r'Common\MSDev98\Bin\MSDEV.COM', + #batch_file_path=r'Common7\Tools\vsvars32.bat', + #default_dirname='Microsoft Visual Studio', supported_arch=['x86'], ), ] @@ -430,18 +426,33 @@ def reset(self) -> None: def get_installed_visual_studios(env=None): global InstalledVSList global InstalledVSMap + # the cache is cleared if new instances are discovered + msvs_manager = MSVC.VSDetect.msvs_detect_env(env) if InstalledVSList is None: InstalledVSList = [] InstalledVSMap = {} for vs in SupportedVSList: debug('trying to find VS %s', vs.version) - if vs.get_executable(env): - debug('found VS %s', vs.version) - InstalledVSList.append(vs) - InstalledVSMap[vs.version] = vs + msvs_instances, _ = msvs_manager.query_msvs_instances(msvc_version=vs.version) + if not msvs_instances: + continue + msvs_instance = msvs_instances[0] + if not msvs_instance.vs_executable: + continue + msvc_instance = msvs_instance.msvc_instance + if not msvc_instance: + continue + vs.register_msvs_instance(msvs_instance) + debug('found VS %s (msvs_instance=%s)', repr(vs.version), repr(msvs_instance.id_str)) + InstalledVSList.append(vs) + InstalledVSMap[vs.version] = vs + #if vs.get_executable(env): + # debug('found VS %s', vs.version) + # InstalledVSList.append(vs) + # InstalledVSMap[vs.version] = vs return InstalledVSList -def reset_installed_visual_studios() -> None: +def _reset_installed_visual_studios() -> None: global InstalledVSList global InstalledVSMap InstalledVSList = None @@ -449,6 +460,11 @@ def reset_installed_visual_studios() -> None: for vs in SupportedVSList: vs.reset() +# register visual studios cache reset function with vs detection +MSVC.VSDetect.register_reset_func(_reset_installed_visual_studios) + +def reset_installed_visual_studios() -> None: + _reset_installed_visual_studios() # Need to clear installed VC's as well as they are used in finding # installed VS's SCons.Tool.MSCommon.vc.reset_installed_vcs() diff --git a/SCons/Tool/msvsTests.py b/SCons/Tool/msvsTests.py index e303ea2db9..b92ac62a88 100644 --- a/SCons/Tool/msvsTests.py +++ b/SCons/Tool/msvsTests.py @@ -359,6 +359,8 @@ "EnvironmentDirectory"="C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\Common7\\IDE\\" "VS7CommonDir"="C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\Common7\\" "VS7CommonBinDir"="" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\14.0\Setup\VC] +"ProductDir"="C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC" [HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\14.0\Setup\VS\BuildNumber] "1033"="14.0" [HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\14.0\Setup\VS\Community] @@ -576,8 +578,19 @@ def DummyQueryValue(key, value): # print "Query Value",key.name+"\\"+value,"=>",rv return rv +exists_maps = None + def DummyExists(path) -> bool: - return True + global exists_maps + rval = True + if exists_maps: + p = os.path.normcase(os.path.normpath(path)) + for exists_map in exists_maps: + if p.startswith(exists_map['prefix']) and p not in exists_map['paths']: + rval = False + break + # print("DummyExists", repr(path), rval) + return rval def DummyVsWhereExecutables(vswhere_env=None): # not testing versions with vswhere, so return empty list @@ -589,7 +602,9 @@ class msvsTestCase(unittest.TestCase): def setUp(self) -> None: debug("THIS TYPE :%s"%self) global registry + global exists_maps registry = self.registry + exists_maps = self.exists_maps from SCons.Tool.MSCommon.vs import reset_installed_visual_studios reset_installed_visual_studios() @@ -605,13 +620,13 @@ def test_get_default_version(self) -> None: env = DummyEnv() v1 = get_default_version(env) if v1: - assert env['MSVS_VERSION'] == self.default_version, \ - ("env['MSVS_VERSION'] != self.default_version", - env['MSVS_VERSION'],self.default_version) - assert env['MSVS']['VERSION'] == self.default_version, \ - ("env['MSVS']['VERSION'] != self.default_version", - env['MSVS']['VERSION'], self.default_version) - assert v1 == self.default_version, (self.default_version, v1) + assert env['MSVS_VERSION'] in self.default_versions, \ + ("env['MSVS_VERSION'] not in self.default_versions", + env['MSVS_VERSION'],self.default_versions) + assert env['MSVS']['VERSION'] in self.default_versions, \ + ("env['MSVS']['VERSION'] not in self.default_versions", + env['MSVS']['VERSION'], self.default_versions) + assert v1 in self.default_versions, (self.default_versions, v1) env = DummyEnv({'MSVS_VERSION':'7.0'}) v2 = get_default_version(env) @@ -638,8 +653,9 @@ def _TODO_test_merge_default_version(self) -> None: def test_query_versions(self) -> None: """Test retrieval of the list of visual studio versions""" v1 = query_versions() - assert not v1 or str(v1[0]) == self.highest_version, \ - (v1, self.highest_version) + vh = v1[:len(self.highest_versions)] if v1 else [] + assert not v1 or vh == self.highest_versions, \ + (v1, self.highest_versions) assert len(v1) == self.number_of_versions, v1 def test_config_generation(self) -> None: @@ -798,11 +814,25 @@ def subst(self, string, *args, **kwargs): except OSError: pass +def _exists_map(prefix, suffixes): + prefix = os.path.normpath(os.path.normcase(prefix)) + paths = [ + os.path.normpath(os.path.join(prefix, os.path.normcase(suffix))) + for suffix in suffixes + ] + exists_map = { + 'prefix': prefix, + 'paths': paths, + } + return exists_map + class msvs6aTestCase(msvsTestCase): """Test MSVS 6 Registry""" registry = DummyRegistry(regdata_6a + regdata_cv) default_version = '6.0' highest_version = '6.0' + default_versions = [default_version] + highest_versions = [highest_version] number_of_versions = 1 install_locs = { '6.0' : {'VSINSTALLDIR': 'C:\\Program Files\\Microsoft Visual Studio\\VC98', 'VCINSTALLDIR': 'C:\\Program Files\\Microsoft Visual Studio\\VC98\\Bin'}, @@ -812,12 +842,25 @@ class msvs6aTestCase(msvsTestCase): '8.0Exp' : {}, } default_install_loc = install_locs['6.0'] + exists_maps = [ + _exists_map( + r'C:\Program Files\Microsoft Visual Studio', + [ + r'VC98', + r'VC98\bin\vcvars32.bat', + r'VC98\bin\cl.exe', + r'Common\MSDev98\Bin\msdev.com', + ] + ) + ] class msvs6bTestCase(msvsTestCase): """Test Other MSVS 6 Registry""" registry = DummyRegistry(regdata_6b + regdata_cv) default_version = '6.0' highest_version = '6.0' + default_versions = [default_version] + highest_versions = [highest_version] number_of_versions = 1 install_locs = { '6.0' : {'VSINSTALLDIR': 'C:\\VS6\\VC98', 'VCINSTALLDIR': 'C:\\VS6\\VC98\\Bin'}, @@ -827,16 +870,28 @@ class msvs6bTestCase(msvsTestCase): '8.0Exp' : {}, } default_install_loc = install_locs['6.0'] + exists_maps = [ + _exists_map( + r'C:\VS6', + [ + r'VC98', + r'VC98\bin\vcvars32.bat', + r'VC98\bin\cl.exe', + r'Common\MSDev98\Bin\msdev.com', + ] + ) + ] -class msvs6and7TestCase(msvsTestCase): - """Test MSVS 6 & 7 Registry""" - registry = DummyRegistry(regdata_6b + regdata_7 + regdata_cv) +class msvs7TestCase(msvsTestCase): + """Test MSVS 7 Registry""" + registry = DummyRegistry(regdata_7 + regdata_cv) default_version = '7.0' highest_version = '7.0' - number_of_versions = 2 + default_versions = [default_version] + highest_versions = [highest_version] + number_of_versions = 1 install_locs = { - '6.0' : {'VSINSTALLDIR': 'C:\\VS6\\VC98', - 'VCINSTALLDIR': 'C:\\VS6\\VC98\\Bin'}, + '6.0' : {}, '7.0' : {'VSINSTALLDIR': 'C:\\Program Files\\Microsoft Visual Studio .NET\\Common7', 'VCINSTALLDIR': 'C:\\Program Files\\Microsoft Visual Studio .NET\\Common7\\Tools'}, '7.1' : {}, @@ -844,15 +899,30 @@ class msvs6and7TestCase(msvsTestCase): '8.0Exp' : {}, } default_install_loc = install_locs['7.0'] + exists_maps = [ + _exists_map( + r'C:\Program Files\Microsoft Visual Studio .NET', + [ + r'Vc7', + r'Vc7\bin\vcvars32.bat', + r'Vc7\bin\cl.exe', + r'Common7\IDE\devenv.com', + r'Common7\Tools\vsvars32.bat', + ] + ) + ] -class msvs7TestCase(msvsTestCase): - """Test MSVS 7 Registry""" - registry = DummyRegistry(regdata_7 + regdata_cv) +class msvs6and7TestCase(msvsTestCase): + """Test MSVS 6 & 7 Registry""" + registry = DummyRegistry(regdata_6b + regdata_7 + regdata_cv) default_version = '7.0' highest_version = '7.0' - number_of_versions = 1 + default_versions = [default_version] + highest_versions = [highest_version] + number_of_versions = 2 install_locs = { - '6.0' : {}, + '6.0' : {'VSINSTALLDIR': 'C:\\VS6\\VC98', + 'VCINSTALLDIR': 'C:\\VS6\\VC98\\Bin'}, '7.0' : {'VSINSTALLDIR': 'C:\\Program Files\\Microsoft Visual Studio .NET\\Common7', 'VCINSTALLDIR': 'C:\\Program Files\\Microsoft Visual Studio .NET\\Common7\\Tools'}, '7.1' : {}, @@ -860,12 +930,15 @@ class msvs7TestCase(msvsTestCase): '8.0Exp' : {}, } default_install_loc = install_locs['7.0'] + exists_maps = msvs6bTestCase.exists_maps + msvs7TestCase.exists_maps class msvs71TestCase(msvsTestCase): """Test MSVS 7.1 Registry""" registry = DummyRegistry(regdata_7_1 + regdata_cv) default_version = '7.1' highest_version = '7.1' + default_versions = [default_version] + highest_versions = [highest_version] number_of_versions = 1 install_locs = { '6.0' : {}, @@ -876,12 +949,26 @@ class msvs71TestCase(msvsTestCase): '8.0Exp' : {}, } default_install_loc = install_locs['7.1'] + exists_maps = [ + _exists_map( + r'C:\Program Files\Microsoft Visual Studio .NET 2003', + [ + r'Vc7', + r'Vc7\bin\vcvars32.bat', + r'Vc7\bin\cl.exe', + r'Common7\IDE\devenv.com', + r'Common7\Tools\vsvars32.bat', + ] + ) + ] -class msvs8ExpTestCase(msvsTestCase): # XXX: only one still not working +class msvs8ExpTestCase(msvsTestCase): """Test MSVS 8 Express Registry""" registry = DummyRegistry(regdata_8exp + regdata_cv) - default_version = '8.0' - highest_version = '8.0' + default_version = '8.0Exp' + highest_version = '8.0Exp' + default_versions = ['8.0', default_version] + highest_versions = ['8.0', highest_version] number_of_versions = 2 install_locs = { '6.0' : {}, @@ -892,12 +979,27 @@ class msvs8ExpTestCase(msvsTestCase): # XXX: only one still not working 'VCINSTALLDIR': 'C:\\Program Files\\Microsoft Visual Studio 8\\VC'}, } default_install_loc = install_locs['8.0Exp'] + exists_maps = [ + _exists_map( + r'C:\Program Files\Microsoft Visual Studio 8', + [ + r'VC', + r'VC\bin\cl.exe', + r'VC\bin\vcvars32.bat', + r'VC\vcvarsall.bat', + r'Common7\IDE\VCExpress.exe', + r'Common7\Tools\vsvars32.bat', + ] + ) + ] class msvs80TestCase(msvsTestCase): """Test MSVS 8 Registry""" registry = DummyRegistry(regdata_80 + regdata_cv) default_version = '8.0' highest_version = '8.0' + default_versions = [default_version] + highest_versions = [highest_version] number_of_versions = 1 install_locs = { '6.0' : {}, @@ -908,24 +1010,55 @@ class msvs80TestCase(msvsTestCase): '8.0Exp' : {}, } default_install_loc = install_locs['8.0'] + exists_maps = [ + _exists_map( + r'C:\Program Files\Microsoft Visual Studio 8', + [ + r'VC', + r'VC\bin\cl.exe', + r'VC\bin\vcvars32.bat', + r'VC\vcvarsall.bat', + r'Common7\IDE\devenv.com', + r'Common7\Tools\vsvars32.bat', + ] + ) + ] class msvs140TestCase(msvsTestCase): """Test MSVS 140 Registry""" registry = DummyRegistry(regdata_140 + regdata_cv) default_version = '14.0' highest_version = '14.0' - number_of_versions = 2 + default_versions = [default_version] + highest_versions = [highest_version] + number_of_versions = 1 install_locs = { - '14.0' : {'VSINSTALLDIR': 'C:\\Program Files\\Microsoft Visual Studio 14.0', - 'VCINSTALLDIR': 'C:\\Program Files\\Microsoft Visual Studio 14.0\\VC'}, + '14.0' : {'VSINSTALLDIR': 'C:\\Program Files (x86)\\Microsoft Visual Studio 14.0', + 'VCINSTALLDIR': 'C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC'}, } default_install_loc = install_locs['14.0'] + exists_maps = None + exists_maps = [ + _exists_map( + r'C:\Program Files (x86)\Microsoft Visual Studio 14.0', + [ + r'VC', + r'VC\bin\cl.exe', + r'VC\bin\vcvars32.bat', + r'VC\\vcvarsall.bat', + r'Common7\\IDE\\devenv.com', + r'Common7\\Tools\\vsvars32.bat', + ] + ) + ] class msvsEmptyTestCase(msvsTestCase): """Test Empty Registry""" registry = DummyRegistry(regdata_none) default_version = SupportedVSList[0].version highest_version = None + default_versions = [default_version] + highest_versions = None number_of_versions = 0 install_locs = { '6.0' : {}, @@ -934,7 +1067,8 @@ class msvsEmptyTestCase(msvsTestCase): '8.0' : {}, '8.0Exp' : {}, } - default_install_loc = install_locs['8.0Exp'] + default_install_loc = {} + exists_maps = None if __name__ == "__main__": @@ -957,17 +1091,23 @@ class msvsEmptyTestCase(msvsTestCase): exit_val = 0 test_classes = [ - msvs6aTestCase, - msvs6bTestCase, - msvs6and7TestCase, - msvs7TestCase, - msvs71TestCase, - msvs8ExpTestCase, - msvs80TestCase, - msvs140TestCase, msvsEmptyTestCase, ] + vs_channel = SCons.Tool.MSCommon.msvs_get_channel_default() + if vs_channel != 'Preview': + + test_classes += [ + msvs6aTestCase, + msvs6bTestCase, + msvs6and7TestCase, + msvs7TestCase, + msvs71TestCase, + msvs8ExpTestCase, + msvs80TestCase, + msvs140TestCase, + ] + for test_class in test_classes: # DEBUG # print("TEST: ", test_class.__doc__) diff --git a/test/Help.py b/test/Help.py index a090d42125..d2922e7b6e 100644 --- a/test/Help.py +++ b/test/Help.py @@ -23,6 +23,7 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +import re import TestSCons test = TestSCons.TestSCons() @@ -144,6 +145,8 @@ # Enhancement: keep_local flag saves the AddOption help, # but not SCons' own help. test.write('SConstruct', r""" +import sys + AddOption( '--debugging', dest='debugging', @@ -159,14 +162,54 @@ DefaultEnvironment(tools=[]) env = Environment() +print("") +if 'SCons.Tool.MSCommon.MSVC.Options' in sys.modules.keys(): + print("MSVC_OPTIONS") +print("") + Help(vars.GenerateHelpText(env), append=True, keep_local=True) """) -expect = """\ +test.run(arguments='-h') + +def process_output(test_output): + + head, control, tail = re.split(r'\n', test_output) + + output = head + tail + + expected_locals = [ + ('--debugging', 'Compile with debugging symbols'), + ] + + known_options = [ + local for local in [ + option.strip() for option in control.splitlines() + ] if local + ] + + if 'MSVC_OPTIONS' in known_options: + expected_locals.extend([ + ('--msvs-channel=CHANNEL', 'Set default msvs CHANNEL [Release, Preview, Any].'), + ('--vswhere=EXEPATH', 'Add vswhere executable located at EXEPATH.'), + ]) + + maxarglen = len(max(expected_locals, key=lambda item: len(item[0]))[0]) + + locals_str = '\n'.join([ + f' {arg:{maxarglen}} {help}' + for arg, help in expected_locals + ]) + + return output, locals_str + +output, local_options = process_output(test.stdout()) + +expect = f"""\ scons: Reading SConscript files ... scons: done reading SConscript files. Local Options: - --debugging Compile with debugging symbols +{local_options} buildmod: List of modules to build (all|none|comma-separated list of names) @@ -177,8 +220,15 @@ Use scons -H for help about SCons built-in command-line options. """ -test.run(arguments='-h', stdout=expect) - +if output != expect: + n = 80 + print('\nOutput:\n' + '=' * n) + print(output) + print('=' * n) + print('\nExpected:\n' + '=' * n) + print(expect) + print('=' * n) + test.fail_test() test.pass_test()