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/35] 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/35] 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/35] 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/35] 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/35] 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/35] 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/35] 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/35] 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/35] 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/35] 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/35] 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 ed18bdf70a2f8ff20f4debcc202684d7d4e737a4 Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Sun, 7 Jan 2024 09:27:02 -0500 Subject: [PATCH 12/35] Update CHANGES.txt and RELEASE.txt: move text items and fix typographical errors to prepare for merge with latest. --- CHANGES.txt | 28 +++++++++++++-------------- RELEASE.txt | 54 ++++++++++++++++++++++++++--------------------------- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 270861bf6e..e47c13e87a 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -42,12 +42,24 @@ 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). + - 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 + - VS2015 Express (14.0Exp) 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 + - VS2015 Express (14.0Exp) does not support the sdk version argument. VS2015 Express does not support the store argument for target architectures other than x86. Script argument validation now takes into account these restrictions. - VS2015 BuildTools (14.0) does not support the sdk version argument and does not @@ -60,11 +72,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 @@ -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 a79f24e2b6..6cfdb33e69 100644 --- a/RELEASE.txt +++ b/RELEASE.txt @@ -59,10 +59,6 @@ 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 @@ -86,6 +82,10 @@ CHANGED/ENHANCED EXISTING FUNCTIONALITY although the preferred usage remains to import from SCons.Util only. Any code that did the direct import will have to change to import from SCons.Util.sctypes. +- 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). FIXES ----- @@ -109,7 +109,23 @@ 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 +- 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. +- 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. +- MSVS: Add arm64 to the MSVS supported architectures list for VS2017 and later to be + consistent with the current documentation of MSVS_ARCH. +- FORTRAN: Fix gfortran tool initialization. Defaults to using binary named gfortran + as would be expected, and properly set's SHFORTRAN flags to include -fPIC + where previously it was only doing so for the other fortran versions (F77,..) +- MSCommon: Added more error handling while reading msvc config cache. + (Enabled/specified by SCONS_CACHE_MSVC_CONFIG). + The existing cache will be discarded if there's a decode error reading it. + It's possible there's a race condition creating this issue it in certain CI builds. +- Fixed: race condition in `Mkdir` which can happen when two `SConscript` + are processed simultaneously by two separate build commands. +- MSVC: VS2015 Express (14.0Exp) 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 @@ -122,43 +138,27 @@ 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 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. -- MSVS: Add arm64 to the MSVS supported architectures list for VS2017 and later to be - consistent with the current documentation of MSVS_ARCH. -- FORTRAN: Fix gfortran tool initialization. Defaults to using binary named gfortran - as would be expected, and properly set's SHFORTRAN flags to include -fPIC - where previously it was only doing so for the other fortran versions (F77,..) -- MSCommon: Added more error handling while reading msvc config cache. - (Enabled/specified by SCONS_CACHE_MSVC_CONFIG). - The existing cache will be discarded if there's a decode error reading it. - It's possible there's a race condition creating this issue it in certain CI builds. -- Fixed: race condition in `Mkdir` which can happen when two `SConscript` - are processed simultaneously by two separate build commands. 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). +- MSVC: VS2015 Express (14.0Exp) does not support the sdk version argument. VS2015 + Express does not support the store argument for target architectures other than + x86. Script argument validation now takes into account these restrictions. +- 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. PACKAGING --------- From d06ad9e5bd309ba75163e3c06df539f7378d2363 Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Sun, 7 Jan 2024 09:42:04 -0500 Subject: [PATCH 13/35] Merge branch 'master' into HEAD Manually resolve conflicts: * CHANGES.txt * RELEASE.txt * SCons/Tool/MSCommon/vc.py --- .github/workflows/experimental_tests.yml | 7 +- .github/workflows/framework_tests.yml | 4 +- .github/workflows/runtest.yml | 6 +- .github/workflows/scons-package.yml | 4 +- CHANGES.txt | 169 +++++--- RELEASE.txt | 203 +++------- ReleaseConfig | 2 +- SCons/Action.py | 228 +++++------ SCons/ActionTests.py | 54 ++- SCons/Conftest.py | 15 +- SCons/Debug.py | 3 + SCons/Defaults.py | 53 ++- SCons/Defaults.xml | 137 +++++-- SCons/Environment.py | 16 +- SCons/EnvironmentTests.py | 49 ++- SCons/PathList.py | 21 +- SCons/Platform/Platform.xml | 12 +- SCons/Platform/cygwin.py | 5 +- SCons/Platform/os2.py | 5 +- SCons/Platform/posix.py | 5 +- SCons/Platform/win32.py | 10 +- SCons/SConf.py | 4 +- SCons/SConfTests.py | 17 +- SCons/Script/Interactive.py | 2 +- SCons/Script/Main.py | 2 + SCons/Script/SConsOptions.py | 2 +- SCons/Script/SConscript.py | 9 +- SCons/Subst.py | 10 +- SCons/Tool/JavaCommon.py | 2 + SCons/Tool/MSCommon/MSVC/Config.py | 3 +- SCons/Tool/MSCommon/MSVC/Registry.py | 4 +- SCons/Tool/MSCommon/MSVC/Util.py | 87 ++++- SCons/Tool/MSCommon/MSVC/UtilTests.py | 103 ++++- SCons/Tool/MSCommon/MSVC/WinSDK.py | 4 +- SCons/Tool/MSCommon/common.py | 80 +++- SCons/Tool/MSCommon/sdk.py | 5 +- SCons/Tool/MSCommon/vc.py | 13 +- SCons/Tool/compilation_db.py | 23 +- SCons/Tool/cyglink.py | 25 +- SCons/Tool/dmd.py | 4 +- SCons/Tool/docbook/__init__.py | 4 +- SCons/Tool/gnulink.py | 3 + SCons/Tool/ldc.py | 8 +- SCons/Tool/link.py | 2 +- SCons/Tool/mingw.py | 2 +- SCons/Tool/msvc.xml | 67 +++- SCons/Tool/msvs.py | 6 +- SCons/Variables/ListVariable.py | 12 +- SCons/__init__.py | 10 +- SCons/cpp.py | 11 +- SConstruct | 2 +- doc/generated/builders.gen | 55 ++- .../examples/caching_ex-random_1.xml | 6 +- .../examples/commandline_EnumVariable_3.xml | 2 +- .../examples/commandline_Variables_Help_1.xml | 2 +- doc/generated/examples/output_ex1_1.xml | 2 +- doc/generated/examples/output_ex2_1.xml | 2 +- doc/generated/examples/output_ex2_2.xml | 2 +- .../examples/troubleshoot_Dump_1.xml | 1 + .../examples/troubleshoot_Dump_2.xml | 1 + .../examples/troubleshoot_explain1_3.xml | 2 +- doc/generated/functions.gen | 316 +++++++++------ doc/generated/tools.gen | 6 +- doc/generated/variables.gen | 361 ++++++++++++------ doc/generated/variables.mod | 2 + doc/man/scons.xml | 31 +- doc/sphinx/conf.py | 5 +- doc/user/README | 5 +- doc/user/SConstruct | 60 +-- doc/user/actions.xml | 35 +- doc/user/add-method.xml | 8 +- doc/user/alias.xml | 35 +- doc/user/ant.xml | 35 +- doc/user/build-install.xml | 4 +- doc/user/builders-built-in.xml | 35 +- doc/user/builders-commands.xml | 35 +- doc/user/builders-writing.xml | 35 +- doc/user/builders.xml | 33 +- doc/user/caching.xml | 4 +- doc/user/chtml.xsl | 26 +- doc/user/command-line.xml | 35 +- doc/user/depends.xml | 37 +- doc/user/environments.xml | 31 +- doc/user/epub.xsl | 26 +- doc/user/errors.xml | 33 +- doc/user/example.xml | 35 +- doc/user/external.xml | 33 +- doc/user/factories.xml | 37 +- doc/user/file-removal.xml | 35 +- doc/user/functions.xml | 33 +- doc/user/gettext.xml | 41 +- doc/user/hierarchy.xml | 6 +- doc/user/html.xsl | 26 +- doc/user/install.xml | 31 +- doc/user/java.xml | 14 +- doc/user/less-simple.xml | 5 +- doc/user/libraries.xml | 37 +- doc/user/main.xml | 29 +- doc/user/make.xml | 35 +- doc/user/mergeflags.xml | 35 +- doc/user/misc.xml | 31 +- doc/user/nodes.xml | 35 +- doc/user/output.xml | 4 +- doc/user/parse_flags_arg.xml | 31 +- doc/user/parseconfig.xml | 35 +- doc/user/parseflags.xml | 35 +- doc/user/pdf.xsl | 25 +- doc/user/preface.xml | 35 +- doc/user/python.xml | 35 +- doc/user/repositories.xml | 35 +- doc/user/run.xml | 33 +- doc/user/scanners.xml | 51 +-- doc/user/sconf.xml | 35 +- doc/user/scons_title.xsl | 72 ++-- doc/user/separate.xml | 4 +- doc/user/sideeffect.xml | 33 +- doc/user/simple.xml | 4 +- doc/user/tasks.xml | 39 +- doc/user/tools.xml | 33 +- doc/user/troubleshoot.xml | 31 +- doc/user/variables.xml | 33 +- doc/user/variants.xml | 7 +- setup.cfg | 4 - test/AS/as-live.py | 73 ++-- test/Configure/config-h.py | 2 +- test/Docbook/basic/man/image/refdb.xml | 6 +- test/LINK/VersionedLib-j2.py | 10 +- test/LINK/VersionedLib-subdir.py | 12 +- test/Libs/LIBLITERALPREFIX.py | 101 +++++ test/Libs/LIBPREFIXES.py | 23 +- test/Libs/LIBSUFFIXES.py | 33 +- test/Libs/Library.py | 6 +- test/MSVC/PCH-source.py | 2 +- test/MSVS/vs-10.0-exec.py | 2 + test/MSVS/vs-10.0Exp-exec.py | 2 + test/MSVS/vs-11.0-exec.py | 2 + test/MSVS/vs-11.0Exp-exec.py | 2 + test/MSVS/vs-14.0-exec.py | 2 + test/MSVS/vs-14.0Exp-exec.py | 10 +- test/MSVS/vs-14.1-exec.py | 2 + test/MSVS/vs-14.2-exec.py | 2 + test/MSVS/vs-14.3-exec.py | 2 + test/MSVS/vs-6.0-exec.py | 4 +- test/MSVS/vs-7.0-exec.py | 2 + test/MSVS/vs-7.1-exec.py | 2 + test/MSVS/vs-8.0-exec.py | 2 + test/MSVS/vs-8.0Exp-exec.py | 2 + test/MSVS/vs-9.0-exec.py | 2 + test/MSVS/vs-9.0Exp-exec.py | 2 + test/SWIG/SWIG.py | 9 +- test/TEX/generated_files.py | 11 +- test/TEX/variant_dir.py | 2 +- test/TEX/variant_dir_bibunit.py | 9 +- test/TEX/variant_dir_dup0.py | 9 +- test/TEX/variant_dir_style_dup0.py | 9 +- test/option/debug-sconscript.py | 72 ++++ test/packaging/ipkg.py | 4 +- test/packaging/option--package-type.py | 9 +- test/packaging/rpm/cleanup.py | 9 +- test/packaging/rpm/explicit-target.py | 9 +- test/packaging/rpm/internationalization.py | 11 +- test/packaging/rpm/multipackage.py | 9 +- test/packaging/rpm/package.py | 9 +- test/packaging/rpm/tagging.py | 9 +- test/rebuild-generated.py | 3 +- testing/framework/TestCmd.py | 27 +- testing/framework/TestCmdTests.py | 110 +++++- testing/framework/TestSCons.py | 2 +- testing/framework/TestUnit/taprunner.py | 2 +- 169 files changed, 2415 insertions(+), 2183 deletions(-) create mode 100644 test/Libs/LIBLITERALPREFIX.py create mode 100644 test/option/debug-sconscript.py diff --git a/.github/workflows/experimental_tests.yml b/.github/workflows/experimental_tests.yml index c272d51e82..639260a999 100644 --- a/.github/workflows/experimental_tests.yml +++ b/.github/workflows/experimental_tests.yml @@ -32,18 +32,19 @@ jobs: # Steps represent a sequence of tasks that will be executed as part of the job steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v2 + - uses: actions/checkout@v4.1.1 # experiment: maybe don't need this? # update: looks like we do: with this commented out, the build hung - name: Set up MinGW - uses: egor-tensin/setup-mingw@v2 + uses: egor-tensin/setup-mingw@v2.2.0 if: matrix.os == 'windows-2019' with: platform: x64 + static: 0 - name: Set up Python 3.11 ${{ matrix.os }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v5.0.0 with: python-version: '3.11' diff --git a/.github/workflows/framework_tests.yml b/.github/workflows/framework_tests.yml index 1c4754b7c3..680c8b97c0 100644 --- a/.github/workflows/framework_tests.yml +++ b/.github/workflows/framework_tests.yml @@ -29,10 +29,10 @@ jobs: steps: # Checkouut repository under $GITHUB_WORKSPACE - - uses: actions/checkout@v2 + - uses: actions/checkout@v4.1.1 - name: Set up Python 3.11 ${{ matrix.os }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v5.0.0 with: python-version: '3.11' diff --git a/.github/workflows/runtest.yml b/.github/workflows/runtest.yml index 02be7c1140..4f1b3f4f26 100644 --- a/.github/workflows/runtest.yml +++ b/.github/workflows/runtest.yml @@ -28,10 +28,10 @@ jobs: # Steps represent a sequence of tasks that will be executed as part of the job steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v2 + - uses: actions/checkout@v4.1.1 - name: Set up Python 3.10 ${{ matrix.os }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v5.0.0 with: python-version: '3.10' @@ -46,7 +46,7 @@ jobs: python runtest.py --all --time --jobs=2 - name: Archive Failed tests ${{ matrix.os }} - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3.1.3 with: name: ${{ matrix.os }}-failed-tests path: | diff --git a/.github/workflows/scons-package.yml b/.github/workflows/scons-package.yml index c0c4e523f3..6c36cf049b 100644 --- a/.github/workflows/scons-package.yml +++ b/.github/workflows/scons-package.yml @@ -14,10 +14,10 @@ jobs: runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4.1.1 - name: Set up Python 3.10 - uses: actions/setup-python@v2 + uses: actions/setup-python@v5.0.0 with: python-version: '3.10' diff --git a/CHANGES.txt b/CHANGES.txt index e47c13e87a..cc3aa392ef 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -9,56 +9,41 @@ NOTE: 4.3.0 now requires Python 3.6.0 and above. Python 3.5.x is no longer suppo RELEASE VERSION/DATE TO BE FILLED IN LATER - From Max Bachmann: - - Add missing directories to searched paths for mingw installs + From Ataf Fazledin Ahamed: + - Use of NotImplemented instead of NotImplementedError for special methods + of _ListVariable class From Joseph Brill: - - Fix issue #4312: the cached installed msvc list had an indirect dependency - on the target architecture in the environment dictionary. The first call - to construct the installed msvc list now forces the target architecture to be - undefined, constructs the installed msvc list, and then restores the original - target architecture. - Note: an indirect dependency on the VSWHERE construction variable in the - environment remains. - - Fix issue #4312: explicitly guard against an empty regular expression list - when msvc is not installed. - - When trying to find a valid msvc batch file, check that the compiler executable - (cl.exe) exists for VS6 to VS2015 to avoid executing the msvc batch file. Always - check that the compiler executable is found on the msvc script environment path - after running the msvc batch file. Only use the sdk batch files when all of the - msvc script host/target combinations have been exhausted and a valid script was - not found. - - Add ARM64 host configurations for windows and msvc. - Note: VS2013 and earlier has not been tested on ARM64. - - If necessary, automatically define VSCMD_SKIP_SENDTELEMETRY for VS2019 and later - on ARM64 hosts when using an arm32 build of python to prevent a powershell dll - not found error pop-up window. - - Fix an issue where test SConfTests.py would fail when mscommon debugging - was enabled. The mscommon debug filter class registered with the logging - 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. - - Fix an issue with an unhandled MissingConfiguration exception due to an msvc - 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). - - 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). + - Fix issue #2755: the msvs tool no longer writes the OS environment SCONS_HOME + value into the SCons environment when the SCONS_HOME variable already exists + in the SCons environment. Prior to this change, a valid user-defined SCons + environment value for SCONS_HOME would be overwritten with the OS environment + value of SCONS_HOME which could be None (i.e., undefined). + - Update the windows registry keys for detection of Visual Studio 2015 Express + ('14.0Exp'): the VS2015 registry key ('WDExpress') appears to be different + than the registry key ('VCExpress') for earlier Visual Studio express + versions. The registry key value is relative to the installation root rather + than the VC folder and requires additional path components during evaluation. + - Fix the vs-6.0-exec.py test script: the msvs generated project is 'foo.dsp' + and the command-line invocation of the Visual Studio development environment + program was attempting to build 'test.dsp'. The command-line invocation was + changed to build 'foo.dsp'. + - Update the msvs project generation test scripts: the msvs project execution + tests could produce a "false positive" test result when the test executable is + correctly built via the SConstruct env.Program() call and the command-line + invocation of the Visual Studio development environment program fails. The + test passes due to the existence of the test executable from the initial + build. The tests were modified to delete the test executable, object file, + and sconsign file prior to the command-line invocation of the VS development + binary. + - Method unlink_files was added to the TestCmd class that unlinks a list of + files from a specified directory. An attempt to unlink a file is made only + when the file exists; otherwise, the file is ignored. + - Fix issue #4320: add an optional argument list string to configure's CheckFunc + method so that the generated function argument list matches the function's + prototype when including a header file. - 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.0Exp) 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.0Exp) does not support the sdk version argument. VS2015 Express does not support the store argument for target architectures other than x86. Script argument validation now takes into account these restrictions. @@ -101,6 +86,76 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER 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 William Deegan: + - Fix sphinx config to handle SCons versions with post such as: 4.6.0.post1 + + From Michał Górny: + - Remove unecessary dependencies on pypi packages from setup.cfg + + From Sten Grüner: + - Fix of the --debug=sconscript option to return exist statements when using return + statement with stop flag enabled + + +RELEASE 4.6.0 - Sun, 19 Nov 2023 17:22:20 -0700 + + From Max Bachmann: + - Add missing directories to searched paths for mingw installs + + From Joseph Brill: + - Fix issue #4312: the cached installed msvc list had an indirect dependency + on the target architecture in the environment dictionary. The first call + to construct the installed msvc list now forces the target architecture to be + undefined, constructs the installed msvc list, and then restores the original + target architecture. + Note: an indirect dependency on the VSWHERE construction variable in the + environment remains. + - Fix issue #4312: explicitly guard against an empty regular expression list + when msvc is not installed. + - When trying to find a valid msvc batch file, check that the compiler executable + (cl.exe) exists for VS6 to VS2015 to avoid executing the msvc batch file. Always + check that the compiler executable is found on the msvc script environment path + after running the msvc batch file. Only use the sdk batch files when all of the + msvc script host/target combinations have been exhausted and a valid script was + not found. + - Add ARM64 host configurations for windows and msvc. + Note: VS2013 and earlier has not been tested on ARM64. + - If necessary, automatically define VSCMD_SKIP_SENDTELEMETRY for VS2019 and later + on ARM64 hosts when using an arm32 build of python to prevent a powershell dll + not found error pop-up window. + - Fix an issue where test SConfTests.py would fail when mscommon debugging + was enabled. The mscommon debug filter class registered with the logging + 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. + - Fix an issue with an unhandled MissingConfiguration exception due to an msvc + 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). + - The detection of the msvc compiler executable (cl.exe) has been modified: + * The host os environment path is no longer evaluated for the existence of the + msvc compiler executable when searching the detection dictionary. + * The 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., a msvc compiler executable already exists in the + user's ENV path prior to detection). + * The warning message issued when a msvc compiler executable is not found in the + detection dictionary was modified by adding the word "requested": + Old warning: "Could not find MSVC compiler 'cl'." + New warning: "Could not find requested MSVC compiler 'cl'.". + * An additonal sentence is appended to the warning message issued when an msvc + compiler executable is not found in the msvc detection dictionary and is found + in the user's ENV path prior to detection: + " A 'cl' was found on the scons ENV path which may be erroneous." From Vitaly Cheptsov: - Fix race condition in `Mkdir` which can happen when two `SConscript` @@ -113,11 +168,19 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER - Obsoleted YACCVCGFILESUFFIX, being replaced by YACC_GRAPH_FILE_SUFFIX. If YACC_GRAPH_FILE_SUFFIX is not set, it will respect YACCVCGFILESUFFIX. + From Sten Grüner + - The newly added --debug=sconscript option (new) will output notices when + entering an exiting each SConscript as they are processed. + From Philipp Maierhöfer: - Fix gfortran tool initialization. Defaults to using binary named gfortran as would be expected, and properly set's SHFORTRAN flags to include -fPIC where previously it was only doing so for the other fortran versions (F77,..) + From Jonathon Reinhart: + - Fix another instance of `int main()` in CheckLib() causing failures + when using -Wstrict-prototypes. + From Mats Wichmann - C scanner's dictifyCPPDEFINES routine did not understand the possible combinations of CPPDEFINES - not aware of a "name=value" string either @@ -238,11 +301,15 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER Python stdlib types module. - TeX tests: skip tests that use makeindex or epstopdf not installed, or if `kpsewhich glossaries.sty` fails. - - - From Jonathon Reinhart: - - Fix another instance of `int main()` in CheckLib() causing failures - when using -Wstrict-prototypes. + - Added a .note.GNU-stack section to the test assembler files to + avoid the GNU linker issuing warnings for its absence. + - Eliminate more http: references (mostly in comments/docstrings where + they really weren't harmful). A few links labeled dead with no alt. + - Add JDK 21 LTS support + - Add a LIBLITERALPREFIX variable which can be set to the linker's + prefix for considering a library argument unmodified (e.g. for the + GNU linker, the ':' in '-l:libfoo.a'). Fixes Github issue #3951. + - Update PCH builder docs with some usage notes. RELEASE 4.5.2 - Sun, 21 Mar 2023 14:08:29 -0700 diff --git a/RELEASE.txt b/RELEASE.txt index 6cfdb33e69..fb12a6feab 100644 --- a/RELEASE.txt +++ b/RELEASE.txt @@ -6,23 +6,20 @@ Past official release announcements appear at: ================================================================== -A new SCons release, 4.5.3, is now available on the SCons download page: +A new SCons release, 4.6.1, is now available on the SCons download page: https://scons.org/pages/download.html -Here is a summary of the changes since 4.5.2: +Here is a summary of the changes since 4.6.0: NEW FUNCTIONALITY ----------------- -- D compilers : added support for generation of .di interface files. - New variables DI_FILE_DIR, DI_FILE_DIR_PREFIX, DI_FILE_DIR_SUFFIX, - DI_FILE_SUFFIX. -- MSVC: If available, native arm64 tools will be used on arm64 hosts for VS2022. -- MSVC: If necessary, automatically define VSCMD_SKIP_SENDTELEMETRY for VS2019 and later - on arm64 hosts when using an arm (32-bit) build of python to prevent a powershell - error pop-up window (powershell dll not found). +- Method unlink_files was added to the TestCmd class that unlinks a list of files + from a specified directory. An attempt to unlink a file is made only when the + file exists; otherwise, the file is ignored. + DEPRECATED FUNCTIONALITY ------------------------ @@ -32,173 +29,83 @@ DEPRECATED FUNCTIONALITY CHANGED/ENHANCED EXISTING FUNCTIONALITY --------------------------------------- -- When debugging (--debug=pdb), the filenames SConstruct and SConscript - are now recognized when manipulating breakpoints. Previously, - only a full pathname to an sconscript file worked, as pdb requires - a .py extension to open a file that is not an absolute path. -- Three unused non-public methods of the Environment Base class - were dropped: get_src_sig_type, get_tgt_sig_type, _changed_source. - These were unused remnants of the previously removed SourceSignatures - and TargetSignatures features (dropped in 3.1.2). -- The --debug flag now has a 'json' option which will write information - generated by --debug={count, memory, time, action-timestamps} and about - the build. -- Obsoleted YACCVCGFILESUFFIX, it's being replaced by YACC_GRAPH_FILE_SUFFIX. - If YACC_GRAPH_FILE_SUFFIX is not set, it will respect YACCVCGFILESUFFIX. -- The yacc tool now understands the bison behavior of --header, --defines - and --graph being called without an option-argument as being synonyms - for -d (first two) and -g. -H also recognized as a synonym for -d. - Default value for $YACC_GRAPH_FILE_SUFFIX changed to '.gv' to match - current bison default (since bison 3.8). Set this variable to '.dot' - if using byacc. Fixes #4326 and #4327. -- MSVC: When trying to find a valid msvc batch file, the existence of the msvc compiler - executable is verified for VS6 to VS2015 to avoid executing the msvc batch file when - the host/target tool is known not to be present. Always check that the msvc compiler - executable is found on the msvc script environment path after running the msvc batch - file. This is predominately needed for recent versions of Visual Studio where the msvc - 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: 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: - when starting to build a help message, you can now retain help from AddOption - calls (options added for the project_, but omit help for SCons' own cmdline - options with "Help(newtext, append=True, local_only=True)". -- Calling SConscript() with a nonexistent file is now an error. - Previously this succeeded - prior to SCons 3.0, silently; since 3.0, with - a warning. Developers can still instruct such an SConscript() call not - to fail by being explicit: pass keyword argument "must_exist=False". - The "--warn=missing-sconscript" commandline option is no longer available - as the warning was part of the transitional phase. -- Add missing directories to searched paths for mingw installs -- SCons.Util.types renamed to to SCons.Util.sctypes to avoid any possible - confusion with the Python stdlib "types" module. Note that it was briefly - (for 4.5.x only) possible to import directly from SCons.Util.types, - although the preferred usage remains to import from SCons.Util only. - Any code that did the direct import will have to change to import from - SCons.Util.sctypes. +- Add an optional argument list string to configure's CheckFunc method so + that the generated function argument list matches the function's + prototype when including a header file. Fixes GH Issue #4320 - 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). + This was the behavior for Visual Studio 2008 (9.0) through Visual Studio 2015 + (14.0). This behavior was extended to Visual Studio 2017 (14.1) and Visual + Studio 2008 (8.0). FIXES ----- -- Fixed: when using the mingw tool, if an msys2 Python is used (os.sep - is '/' rather than the Windows default '\'), certain Configure checks - could fail due to the construction of the path to run the compiled check. -- C scanner's dictifyCPPDEFINES routine did not understand the possible - combinations of CPPDEFINES - not aware of a "name=value" string either - embedded in a sequence, or by itself. The conditional C scanner thus - did not always properly apply the defines. The regular C scanner does - not use these, so was not affected. [fixes #4193] -- MSVC: The installed msvc list is calculated once and cached. There was an indirect - dependency on the target architecture when determining if each version of msvc - was installed based on the initial invocation. It was possible that an msvc instance - would not be considered installed due to the tools for the requested target - architecture not being installed. The initial call to construct the installed msvc - list now uses an undefined target architecture to evaluate all potential host/target - combinations when evaluating if the msvc tools are installed for a given Visual Studio - installation. -- 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. -- 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. -- MSVS: Add arm64 to the MSVS supported architectures list for VS2017 and later to be - consistent with the current documentation of MSVS_ARCH. -- FORTRAN: Fix gfortran tool initialization. Defaults to using binary named gfortran - as would be expected, and properly set's SHFORTRAN flags to include -fPIC - where previously it was only doing so for the other fortran versions (F77,..) -- MSCommon: Added more error handling while reading msvc config cache. - (Enabled/specified by SCONS_CACHE_MSVC_CONFIG). - The existing cache will be discarded if there's a decode error reading it. - It's possible there's a race condition creating this issue it in certain CI builds. -- Fixed: race condition in `Mkdir` which can happen when two `SConscript` - are processed simultaneously by two separate build commands. -- MSVC: VS2015 Express (14.0Exp) 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. +- Fix of the --debug=sconscript option to return exist statements when using return + statement with stop flag enabled +- MSVS: prevent overwriting the SCons environment variable SCONS_HOME with the OS + environment value of SCONS_HOME in the msvs tool. +- MSVC: Fix the detection of Visual Studio 2015 Express ('14.0Exp') by adding a + registry key definition and updating the installation root-relative registry value + at runtime for the location of the VC folder. +- MSVS: Fix the msvs project generation test for MSVS 6.0 to use the correct name of + the generated project file. +- MSVS: Fix the msvs project generation test scripts so that "false positive" tests + results are not possible when the initial build is successful and the command-line + build of the project file fails. +- MSVC: Visual Studio 2010 (10.0) could be inadvertently detected due to an + sdk-only install of Windows SDK 7.1. An sdk-only install of Visual Studio + 2010 is ignored as the msvc batch files will fail. The installed files are + intended to be used in conjunction with the SDK batch file. Similar protection + was added for Visual Studio 2008 (9.0). +- MSVC: For Visual Studio 2005 (8.0) to Visual Studio 2015 (14.0), detection of + installed files was expanded to include the primary msvc batch file, dependent + msvc batch file, and compiler executable. In certain installations, the + dependent msvc batch file may not exist while the compiler executable does exists + resulting in a build failure. +- MSVC: Visual Studio 2008 (9.0) Visual C++ For Python was not detected when + installed using the ALLUSERS command-line option: + msiexec /i VCForPython27.msi ALLUSERS=1 + When installed for all users, Visual Studio 2008 (9.0) Visual C++ For Python is + now correctly detected. - MSVC: 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. + reflected in the installed vcs list. Now, when a user-specified vswhere + executable finds new msvc installations, the installed vcs list is reconstructed. 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.0Exp) does not support the sdk version argument. VS2015 - Express does not support the store argument for target architectures other than - x86. Script argument validation now takes into account these restrictions. -- 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 +- Use of NotImplemented instead of NotImplementedError for special methods + of _ListVariable class +- MSVC: Visual Studio 2015 Express (14.0Exp) does not support the sdk version + argument. Visual Studio 2015 Express does not support the store argument for + target architectures other than x86. Script argument validation now takes into account these restrictions. +- MSVC: Visual Studio 2015 BuildTools (14.0) does not support the sdk version + argument and does not support the store argument. Script argument validation now + takes into account these restrictions. PACKAGING --------- -- The build of scons now matches the help text displayed - the targets - listed there can all now be given as a target to build (except for - the two full source balls; the source tar.gz is currently generated directly - by the GitHub release process). +- Remove unecessary dependencies on pypi packages from setup.cfg DOCUMENTATION ------------- -- Aligned manpage signature for Alias function to match implementation - - if the previous *targets* parameter had been used as a keyword argument, - the results would be incorrect (does not apply to positional argument - usage, which had no problem). -- Changed the message about scons -H to clarify it shows built-in options only. -- Cachedir description updated. -- Updated the first two chapters on building with SCons in the User Guide. -- Clarify that exported variables are shared - if mutable, changes made in - an SConscript are visible everywhere that takes a refereence to that object. +- List any significant changes to the documentation (not individual + typo fixes, even if they're mentioned in src/CHANGES.txt to give + the contributor credit) DEVELOPMENT ----------- -- SCons test runner now uses pathlib to normalize and compare paths - to test files, which allows test lists, exclude lists, and tests on - the command line to "not care" about the OS convention for pathname - separators. -- Class ActionBase is now an abstract base class to more accurately - reflect its usage. Derived _ActionAction inherits the ABC, so it - now declares (actually raises NotImplementedError) two methods it - doesn't use so it can be instantiated by unittests and others. -- Added more type annotations to internal routines. -- Cleaned up dblite module (checker warnings, etc.). -- TeX tests: skip tests that use makeindex or epstopdf not installed, or - if `kpsewhich glossaries.sty` fails. +- Fix sphinx config to handle SCons versions with post such as: 4.6.0.post1 Thanks to the following contributors listed below for their contributions to this release. ========================================================================================== .. code-block:: text - git shortlog --no-merges -ns 4.5.2..HEAD + git shortlog --no-merges -ns 4.6.0..HEAD diff --git a/ReleaseConfig b/ReleaseConfig index 7cec710666..7ad834fb42 100755 --- a/ReleaseConfig +++ b/ReleaseConfig @@ -31,7 +31,7 @@ # If the release type is not 'final', the patchlevel is set to the # release date. This value is mandatory and must be present in this file. #version_tuple = (2, 2, 0, 'final', 0) -version_tuple = (4, 5, 3, 'a', 0) +version_tuple = (4, 6, 1, 'a', 0) # Python versions prior to unsupported_python_version cause a fatal error # when that version is used. Python versions prior to deprecate_python_version diff --git a/SCons/Action.py b/SCons/Action.py index e85e5e177b..0dd02cc287 100644 --- a/SCons/Action.py +++ b/SCons/Action.py @@ -108,16 +108,16 @@ import sys from abc import ABC, abstractmethod from collections import OrderedDict -from subprocess import DEVNULL -from typing import Union +from subprocess import DEVNULL, PIPE import SCons.Debug import SCons.Errors import SCons.Subst import SCons.Util -from SCons.Debug import logInstanceCreation # we use these a lot, so try to optimize them +from SCons.Debug import logInstanceCreation +from SCons.Subst import SUBST_SIG, SUBST_RAW from SCons.Util import is_String, is_List class _null: @@ -387,9 +387,10 @@ def _object_instance_content(obj): # print("Inst Methods :\n%s"%pp.pformat(methods)) def _actionAppend(act1, act2): - # This function knows how to slap two actions together. - # Mainly, it handles ListActions by concatenating into - # a single ListAction. + """Joins two actions together. + + Mainly, it handles ListActions by concatenating into a single ListAction. + """ a1 = Action(act1) a2 = Action(act2) if a1 is None: @@ -399,13 +400,12 @@ def _actionAppend(act1, act2): if isinstance(a1, ListAction): if isinstance(a2, ListAction): return ListAction(a1.list + a2.list) - else: - return ListAction(a1.list + [ a2 ]) - else: - if isinstance(a2, ListAction): - return ListAction([ a1 ] + a2.list) - else: - return ListAction([ a1, a2 ]) + return ListAction(a1.list + [ a2 ]) + + if isinstance(a2, ListAction): + return ListAction([ a1 ] + a2.list) + + return ListAction([ a1, a2 ]) def _do_create_keywords(args, kw): @@ -468,11 +468,7 @@ def _do_create_action(act, kw): return CommandAction(act, **kw) if callable(act): - try: - gen = kw['generator'] - del kw['generator'] - except KeyError: - gen = 0 + gen = kw.pop('generator', False) if gen: action_type = CommandGeneratorAction else: @@ -480,7 +476,7 @@ def _do_create_action(act, kw): return action_type(act, kw) # Catch a common error case with a nice message: - if isinstance(act, int) or isinstance(act, float): + if isinstance(act, (int, float)): raise TypeError("Don't know how to create an Action from a number (%s)"%act) # Else fail silently (???) return None @@ -504,10 +500,9 @@ def _do_create_list_action(act, kw) -> "ListAction": acts.append(aa) if not acts: return ListAction([]) - elif len(acts) == 1: + if len(acts) == 1: return acts[0] - else: - return ListAction(acts) + return ListAction(acts) def Action(act, *args, **kw): @@ -547,7 +542,7 @@ def no_batch_key(self, env, target, source): batch_key = no_batch_key - def genstring(self, target, source, env): + def genstring(self, target, source, env, executor=None) -> str: return str(self) @abstractmethod @@ -561,7 +556,7 @@ def get_implicit_deps(self, target, source, env, executor=None): def get_contents(self, target, source, env): result = self.get_presig(target, source, env) - if not isinstance(result,(bytes, bytearray)): + if not isinstance(result, (bytes, bytearray)): result = bytearray(result, 'utf-8') else: # Make a copy and put in bytearray, without this the contents returned by get_presig @@ -579,17 +574,15 @@ def get_contents(self, target, source, env): for v in vl: # do the subst this way to ignore $(...$) parts: if isinstance(result, bytearray): - result.extend(SCons.Util.to_bytes(env.subst_target_source('${'+v+'}', SCons.Subst.SUBST_SIG, target, source))) + result.extend(SCons.Util.to_bytes(env.subst_target_source('${'+v+'}', SUBST_SIG, target, source))) else: raise Exception("WE SHOULD NEVER GET HERE result should be bytearray not:%s"%type(result)) - # result.append(SCons.Util.to_bytes(env.subst_target_source('${'+v+'}', SCons.Subst.SUBST_SIG, target, source))) - + # result.append(SCons.Util.to_bytes(env.subst_target_source('${'+v+'}', SUBST_SIG, target, source))) - if isinstance(result, (bytes,bytearray)): + if isinstance(result, (bytes, bytearray)): return result - else: - raise Exception("WE SHOULD NEVER GET HERE - #2 result should be bytearray not:%s" % type(result)) - # return b''.join(result) + + raise Exception("WE SHOULD NEVER GET HERE - #2 result should be bytearray not:%s" % type(result)) def __add__(self, other): return _actionAppend(self, other) @@ -788,7 +781,7 @@ def get_default_ENV(env): return env['ENV'] except KeyError: if not default_ENV: - import SCons.Environment + import SCons.Environment # pylint: disable=import-outside-toplevel,redefined-outer-name # This is a hideously expensive way to get a default execution # environment. What it really should do is run the platform # setup to get the default ENV. Fortunately, it's incredibly @@ -815,42 +808,47 @@ def _resolve_shell_env(env, target, source): shell_gens = iter(shell_gen) except TypeError: raise SCons.Errors.UserError("SHELL_ENV_GENERATORS must be iteratable.") - else: - ENV = ENV.copy() - for generator in shell_gens: - ENV = generator(env, target, source, ENV) - if not isinstance(ENV, dict): - raise SCons.Errors.UserError(f"SHELL_ENV_GENERATORS function: {generator} must return a dict.") - return ENV + ENV = ENV.copy() + for generator in shell_gens: + ENV = generator(env, target, source, ENV) + if not isinstance(ENV, dict): + raise SCons.Errors.UserError(f"SHELL_ENV_GENERATORS function: {generator} must return a dict.") -def scons_subproc_run( - scons_env, *args, error: str = None, **kwargs -) -> subprocess.CompletedProcess: - """Run a command with arguments using an SCons execution environment. + return ENV - Does an underlyng call to :func:`subprocess.run` to run a command and - returns a :class:`subprocess.CompletedProcess` instance with the results. - Use when an external command needs to run in an "SCons context" - - that is, with a crafted execution environment, rather than the user's - existing environment, particularly when you need to collect output - back. Typical case: run a tool's "version" option to find out which - version you have installed. - If supplied, the ``env`` keyword argument passes an - execution environment to process into appropriate form before it is - supplied to :mod:`subprocess`; if omitted, *scons_env* is used to derive - a suitable default. The other keyword arguments are passed through, - except that SCons legacy ``error` keyword is remapped to - the subprocess ``check` keyword. The caller is responsible for - setting up the desired arguments for :func:`subprocess.run`. +def scons_subproc_run(scons_env, *args, **kwargs) -> subprocess.CompletedProcess: + """Run an external command using an SCons execution environment. + + SCons normally runs external build commands using :mod:`subprocess`, + but does not harvest any output from such commands. This function + is a thin wrapper around :func:`subprocess.run` allowing running + a command in an SCons context (i.e. uses an "execution environment" + rather than the user's existing environment), and provides the ability + to return any output in a :class:`subprocess.CompletedProcess` + instance (this must be selected by setting ``stdout`` and/or + ``stderr`` to ``PIPE``, or setting ``capture_output=True`` - see + Keyword Arguments). Typical use case is to run a tool's "version" + option to find out the installed version. + + If supplied, the ``env`` keyword argument provides an execution + environment to process into appropriate form before it is supplied + to :mod:`subprocess`; if omitted, *scons_env* is used to derive a + suitable default. The other keyword arguments are passed through, + except that the SCons legacy ``error`` keyword is remapped to the + subprocess ``check`` keyword; if both are omitted ``check=False`` + will be passed. The caller is responsible for setting up the desired + arguments for :func:`subprocess.run`. This function retains the legacy behavior of returning something - vaguely usable even in the face of complete failure: it synthesizes - a :class:`~subprocess.CompletedProcess` instance in this case. + vaguely usable even in the face of complete failure, unless + ``check=True`` (in which case an error is allowed to be raised): + it synthesizes a :class:`~subprocess.CompletedProcess` instance in + this case. A subset of interesting keyword arguments follows; see the Python - documentation of :mod:`subprocess` for a complete list. + documentation of :mod:`subprocess` for the complete list. Keyword Arguments: stdout: (and *stderr*, *stdin*) if set to :const:`subprocess.PIPE`. @@ -858,12 +856,11 @@ def scons_subproc_run( the subprocess; the default ``None`` does no redirection (i.e. output or errors may go to the console or log file, but is not captured); if set to :const:`subprocess.DEVNULL` - they are explicitly thrown away. ``capture_output`` is a + they are explicitly thrown away. ``capture_output=True`` is a synonym for setting both ``stdout`` and ``stderr`` to :const:`~subprocess.PIPE`. text: open *stdin*, *stdout*, *stderr* in text mode. Default - is binary mode. ``universal_newlines`` is a synonym - - note ``text`` is not understood before Python 3.7. + is binary mode. ``universal_newlines`` is a synonym. encoding: specifies an encoding. Changes to text mode. errors: specified error handling. Changes to text mode. input: a byte sequence to be passed to *stdin*, unless text @@ -877,54 +874,48 @@ def scons_subproc_run( .. versionadded:: 4.6 """ # Figure out the execution environment to use - ENV = kwargs.get('env', None) - if ENV is None: - ENV = get_default_ENV(scons_env) - kwargs['env'] = SCons.Util.sanitize_shell_env(ENV) - - # backwards-compat with _subproc: accept 'error', map it to - # ``check`` and remove, since it would not be recognized by run() - check = kwargs.get('check') - if check and error: + env = kwargs.get('env', None) + if env is None: + env = get_default_ENV(scons_env) + kwargs['env'] = SCons.Util.sanitize_shell_env(env) + + # Backwards-compat with _subproc: accept 'error', map to 'check', + # and remove, since subprocess.run does not recognize. + # 'error' isn't True/False, it takes a string value (see _subproc) + error = kwargs.get('error') + if error and 'check' in kwargs: raise ValueError('error and check arguments may not both be used.') - if check is None: - check = False # always supply some value, to pacify checkers (pylint). + check = kwargs.get('check', False) # always set a value for 'check' if error is not None: if error == 'raise': check = True del kwargs['error'] kwargs['check'] = check - # Ensure that the ENV values are all strings: - new_env = {} - for key, value in ENV.items(): - if is_List(value): - # If the value is a list, then we assume it is a path list, - # because that's a pretty common list-like value to stick - # in an environment variable: - value = SCons.Util.flatten_sequence(value) - new_env[key] = os.pathsep.join(str(v) for v in value) - else: - # If it's not a list type, "convert" it to str. This is - # harmless if it's already a string, but gets us the right - # thing for Dir and File instances and will produce something - # reasonable for just about everything else: - new_env[key] = str(value) - kwargs['env'] = new_env - - # Some tools/tests expect no failures for things like missing files - # unless raise/check is set. If set, let subprocess.run go ahead and - # kill things, else catch and construct a dummy CompletedProcess instance. + # TODO: Python version-compat stuff: remap/remove too-new args if needed + if 'text' in kwargs and sys.version_info[:3] < (3, 7): + kwargs['universal_newlines'] = kwargs.pop('text') + + if 'capture_output' in kwargs and sys.version_info[:3] < (3, 7): + capture_output = kwargs.pop('capture_output') + if capture_output: + kwargs['stdout'] = kwargs['stderr'] = PIPE + + # Most SCons tools/tests expect not to fail on things like missing files. + # check=True (or error="raise") means we're okay to take an exception; + # else we catch the likely exception and construct a dummy + # CompletedProcess instance. + # Note pylint can't see we always include 'check' in kwargs: suppress. if check: - cp = subprocess.run(*args, **kwargs) + cp = subprocess.run(*args, **kwargs) # pylint: disable=subprocess-run-check else: try: - cp = subprocess.run(*args, **kwargs) + cp = subprocess.run(*args, **kwargs) # pylint: disable=subprocess-run-check except OSError as exc: argline = ' '.join(*args) - cp = subprocess.CompletedProcess(argline, 1) - cp.stdout = "" - cp.stderr = "" + cp = subprocess.CompletedProcess( + args=argline, returncode=1, stdout="", stderr="" + ) return cp @@ -1044,7 +1035,6 @@ def strfunction(self, target, source, env, executor=None, overrides: bool=False) if self.cmdstr is None: return None if self.cmdstr is not _null: - from SCons.Subst import SUBST_RAW if executor: c = env.subst(self.cmdstr, SUBST_RAW, executor=executor, overrides=overrides) else: @@ -1077,12 +1067,11 @@ def execute(self, target, source, env, executor=None): spawn = env['SPAWN'] except KeyError: raise SCons.Errors.UserError('Missing SPAWN construction variable.') - else: - if is_String(spawn): - spawn = env.subst(spawn, raw=1, conv=lambda x: x) - escape = env.get('ESCAPE', lambda x: x) + if is_String(spawn): + spawn = env.subst(spawn, raw=1, conv=lambda x: x) + escape = env.get('ESCAPE', lambda x: x) ENV = _resolve_shell_env(env, target, source) # Ensure that the ENV values are all strings: @@ -1125,7 +1114,6 @@ def get_presig(self, target, source, env, executor=None): This strips $(-$) and everything in between the string, since those parts don't affect signatures. """ - from SCons.Subst import SUBST_SIG cmd = self.cmd_list if is_List(cmd): cmd = ' '.join(map(str, cmd)) @@ -1133,8 +1121,7 @@ def get_presig(self, target, source, env, executor=None): cmd = str(cmd) if executor: return env.subst_target_source(cmd, SUBST_SIG, executor=executor) - else: - return env.subst_target_source(cmd, SUBST_SIG, target, source) + return env.subst_target_source(cmd, SUBST_SIG, target, source) def get_implicit_deps(self, target, source, env, executor=None): """Return the implicit dependencies of this action's command line.""" @@ -1154,17 +1141,15 @@ def get_implicit_deps(self, target, source, env, executor=None): # An integer value greater than 1 specifies the number of entries # to scan. "all" means to scan all. return self._get_implicit_deps_heavyweight(target, source, env, executor, icd_int) - else: - # Everything else (usually 1 or True) means that we want - # lightweight dependency scanning. - return self._get_implicit_deps_lightweight(target, source, env, executor) + # Everything else (usually 1 or True) means that we want + # lightweight dependency scanning. + return self._get_implicit_deps_lightweight(target, source, env, executor) def _get_implicit_deps_lightweight(self, target, source, env, executor): """ Lightweight dependency scanning involves only scanning the first entry in an action string, even if it contains &&. """ - from SCons.Subst import SUBST_SIG if executor: cmd_list = env.subst_list(self.cmd_list, SUBST_SIG, executor=executor) else: @@ -1202,7 +1187,6 @@ def _get_implicit_deps_heavyweight(self, target, source, env, executor, # Avoid circular and duplicate dependencies by not providing source, # target, or executor to subst_list. This causes references to # $SOURCES, $TARGETS, and all related variables to disappear. - from SCons.Subst import SUBST_SIG cmd_list = env.subst_list(self.cmd_list, SUBST_SIG, conv=lambda x: x) res = [] @@ -1281,7 +1265,7 @@ def __str__(self) -> str: def batch_key(self, env, target, source): return self._generate(target, source, env, 1).batch_key(env, target, source) - def genstring(self, target, source, env, executor=None): + def genstring(self, target, source, env, executor=None) -> str: return self._generate(target, source, env, 1, executor).genstring(target, source, env) def __call__(self, target, source, env, exitstatfunc=_null, presub=_null, @@ -1409,7 +1393,6 @@ def strfunction(self, target, source, env, executor=None): if self.cmdstr is None: return None if self.cmdstr is not _null: - from SCons.Subst import SUBST_RAW if executor: c = env.subst(self.cmdstr, SUBST_RAW, executor=executor) else: @@ -1456,9 +1439,7 @@ def execute(self, target, source, env, executor=None): rsources = list(map(rfile, source)) try: result = self.execfunction(target=target, source=rsources, env=env) - except KeyboardInterrupt as e: - raise - except SystemExit as e: + except (KeyboardInterrupt, SystemExit): raise except Exception as e: result = e @@ -1466,8 +1447,8 @@ def execute(self, target, source, env, executor=None): if result: result = SCons.Errors.convert_to_BuildError(result, exc_info) - result.node=target - result.action=self + result.node = target + result.action = self try: result.command=self.strfunction(target, source, env, executor) except TypeError: @@ -1480,7 +1461,7 @@ def execute(self, target, source, env, executor=None): # some codes do not check the return value of Actions and I do # not have the time to modify them at this point. if (exc_info[1] and - not isinstance(exc_info[1],EnvironmentError)): + not isinstance(exc_info[1], EnvironmentError)): raise result return result @@ -1514,7 +1495,7 @@ def list_of_actions(x): self.varlist = () self.targets = '$TARGETS' - def genstring(self, target, source, env): + def genstring(self, target, source, env, executor=None) -> str: return '\n'.join([a.genstring(target, source, env) for a in self.list]) def __str__(self) -> str: @@ -1601,8 +1582,9 @@ def subst(self, s, target, source, env): # was called by using this hard-coded value as a special return. if s == '$__env__': return env - elif is_String(s): + if is_String(s): return env.subst(s, 1, target, source) + return self.parent.convert(s) def subst_args(self, target, source, env): diff --git a/SCons/ActionTests.py b/SCons/ActionTests.py index 7239f4f5ef..7d99a2560b 100644 --- a/SCons/ActionTests.py +++ b/SCons/ActionTests.py @@ -41,11 +41,13 @@ def __call__(self) -> None: import sys import types import unittest +from unittest import mock from subprocess import PIPE import SCons.Action import SCons.Environment import SCons.Errors +from SCons.Action import scons_subproc_run import TestCmd @@ -2329,7 +2331,7 @@ def test_code_contents(self) -> None: def test_uncaught_exception_bubbles(self): """Test that scons_subproc_run bubbles uncaught exceptions""" try: - cp = SCons.Action.scons_subproc_run(Environment(), None, stdout=PIPE) + cp = scons_subproc_run(Environment(), None, stdout=PIPE) except EnvironmentError: pass except Exception: @@ -2338,6 +2340,56 @@ def test_uncaught_exception_bubbles(self): raise Exception("expected a non-EnvironmentError exception") + + def mock_subprocess_run(*args, **kwargs): + """Replacement subprocess.run: return kwargs for checking.""" + kwargs.pop("env") # the value of env isn't interesting here + return kwargs + + @mock.patch("subprocess.run", mock_subprocess_run) + def test_scons_subproc_run(self): + """Test the argument remapping options.""" + # set phony Python versions to trigger the logic in scons_subproc_run: + # any version greater than 3.6, really + save_info, sys.version_info = sys.version_info, (3, 11, 1) + env = Environment() + self.assertEqual(scons_subproc_run(env), {"check": False}) + with self.subTest(): + self.assertEqual( + scons_subproc_run(env, error="raise"), + {"check": True} + ) + with self.subTest(): + self.assertEqual( + scons_subproc_run(env, capture_output=True), + {"capture_output": True, "check": False}, + ) + with self.subTest(): + self.assertEqual( + scons_subproc_run(env, text=True), + {"text": True, "check": False}, + ) + + # 3.6: + sys.version_info = (3, 6, 2) + with self.subTest(): + self.assertEqual( + scons_subproc_run(env, capture_output=True), + {"check": False, "stdout": PIPE, "stderr": PIPE}, + ) + with self.subTest(): + self.assertEqual( + scons_subproc_run(env, text=True), + {"check": False, "universal_newlines": True}, + ) + with self.subTest(): + self.assertEqual( + scons_subproc_run(env, universal_newlines=True), + {"universal_newlines": True, "check": False}, + ) + sys.version_info = save_info + + if __name__ == "__main__": unittest.main() diff --git a/SCons/Conftest.py b/SCons/Conftest.py index 6af5e78893..79ede99689 100644 --- a/SCons/Conftest.py +++ b/SCons/Conftest.py @@ -231,17 +231,22 @@ def _check_empty_program(context, comp, text, language, use_shared: bool = False return context.CompileProg(text, suffix) -def CheckFunc(context, function_name, header = None, language = None): +def CheckFunc(context, function_name, header = None, language = None, funcargs = None): """ Configure check for a function "function_name". "language" should be "C" or "C++" and is used to select the compiler. Default is "C". Optional "header" can be defined to define a function prototype, include a header file or anything else that comes before main(). + Optional "funcargs" can be defined to define an argument list for the + generated function invocation. Sets HAVE_function_name in context.havedict according to the result. Note that this uses the current value of compiler and linker flags, make sure $CFLAGS, $CPPFLAGS and $LIBS are set correctly. Returns an empty string for success, an error message for failure. + + .. versionchanged:: 4.7.0 + The ``funcargs`` parameter was added. """ # Remarks from autoconf: @@ -274,6 +279,9 @@ def CheckFunc(context, function_name, header = None, language = None): context.Display("Cannot check for %s(): %s\n" % (function_name, msg)) return msg + if not funcargs: + funcargs = '' + text = """ %(include)s #include @@ -287,14 +295,15 @@ def CheckFunc(context, function_name, header = None, language = None): #if defined (__stub_%(name)s) || defined (__stub___%(name)s) #error "%(name)s has a GNU stub, cannot check" #else - %(name)s(); + %(name)s(%(args)s); #endif return 0; } """ % { 'name': function_name, 'include': includetext, - 'hdr': header } + 'hdr': header, + 'args': funcargs} context.Display("Checking for %s function %s()... " % (lang, function_name)) ret = context.BuildProg(text, suffix) diff --git a/SCons/Debug.py b/SCons/Debug.py index 615eb4fe41..9397fdeda9 100644 --- a/SCons/Debug.py +++ b/SCons/Debug.py @@ -41,6 +41,9 @@ track_instances = False # List of currently tracked classes tracked_classes = {} +# Global variable that gets set to 'True' by the Main script +# when SConscript call tracing should be enabled. +sconscript_trace = False def logInstanceCreation(instance, name=None) -> None: if name is None: diff --git a/SCons/Defaults.py b/SCons/Defaults.py index cabadcc679..32e9da3f25 100644 --- a/SCons/Defaults.py +++ b/SCons/Defaults.py @@ -36,7 +36,7 @@ import stat import sys import time -from typing import List +from typing import List, Callable import SCons.Action import SCons.Builder @@ -455,16 +455,35 @@ def _concat_ixes(prefix, items_iter, suffix, env): return result -def _stripixes(prefix, itms, suffix, stripprefixes, stripsuffixes, env, c=None): +def _stripixes( + prefix: str, + items, + suffix: str, + stripprefixes: List[str], + stripsuffixes: List[str], + env, + literal_prefix: str = "", + c: Callable[[list], list] = None, +) -> list: + """Returns a list with text added to items after first stripping them. + + A companion to :func:`_concat_ixes`, used by tools (like the GNU + linker) that need to turn something like ``libfoo.a`` into ``-lfoo``. + *stripprefixes* and *stripsuffixes* are stripped from *items*. + Calls function *c* to postprocess the result. + + Args: + prefix: string to prepend to elements + items: string or iterable to transform + suffix: string to append to elements + stripprefixes: prefix string(s) to strip from elements + stripsuffixes: suffix string(s) to strip from elements + env: construction environment for variable interpolation + c: optional function to perform a transformation on the list. + The default is `None`, which will select :func:`_concat_ixes`. """ - This is a wrapper around _concat()/_concat_ixes() that checks for - the existence of prefixes or suffixes on list items and strips them - where it finds them. This is used by tools (like the GNU linker) - that need to turn something like 'libfoo.a' into '-lfoo'. - """ - - if not itms: - return itms + if not items: + return items if not callable(c): env_c = env['_concat'] @@ -480,8 +499,16 @@ def _stripixes(prefix, itms, suffix, stripprefixes, stripsuffixes, env, c=None): stripprefixes = list(map(env.subst, flatten(stripprefixes))) stripsuffixes = list(map(env.subst, flatten(stripsuffixes))) + # This is a little funky: if literal_prefix is the same as os.pathsep + # (e.g. both ':'), the normal conversion to a PathList will drop the + # literal_prefix prefix. Tell it not to split in that case, which *should* + # be okay because if we come through here, we're normally processing + # library names and won't have strings like "path:secondpath:thirdpath" + # which is why PathList() otherwise wants to split strings. + do_split = not literal_prefix == os.pathsep + stripped = [] - for l in SCons.PathList.PathList(itms).subst_path(env, None, None): + for l in SCons.PathList.PathList(items, do_split).subst_path(env, None, None): if isinstance(l, SCons.Node.FS.File): stripped.append(l) continue @@ -489,6 +516,10 @@ def _stripixes(prefix, itms, suffix, stripprefixes, stripsuffixes, env, c=None): if not is_String(l): l = str(l) + if literal_prefix and l.startswith(literal_prefix): + stripped.append(l) + continue + for stripprefix in stripprefixes: lsp = len(stripprefix) if l[:lsp] == stripprefix: diff --git a/SCons/Defaults.xml b/SCons/Defaults.xml index b892f2ef9f..f1ba3860d3 100644 --- a/SCons/Defaults.xml +++ b/SCons/Defaults.xml @@ -250,7 +250,7 @@ as the result will be non-portable and the directories will not be searched by the dependency scanner. &cv-CPPPATH; should be a list of path strings, or a single string, not a pathname list joined by -Python's os.sep. +Python's os.pathsep. @@ -473,7 +473,7 @@ when the &cv-link-_LIBDIRFLAGS; variable is automatically generated. An automatically-generated construction variable containing the linker command-line options for specifying libraries to be linked with the resulting target. -The value of &cv-link-_LIBFLAGS; is created +The value of &cv-_LIBFLAGS; is created by respectively prepending and appending &cv-link-LIBLINKPREFIX; and &cv-link-LIBLINKSUFFIX; to each filename in &cv-link-LIBS;. @@ -510,7 +510,7 @@ The list of directories that will be searched for libraries specified by the &cv-link-LIBS; &consvar;. &cv-LIBPATH; should be a list of path strings, or a single string, not a pathname list joined by -Python's os.sep. +Python's os.pathsep. +For each &Builder; call that causes linking with libraries, +&SCons; will add the libraries in the setting of &cv-LIBS; +in effect at that moment to the dependecy graph +as dependencies of the target being generated. + + + +The library list will transformed to command line +arguments through the automatically-generated +&cv-link-_LIBFLAGS; &consvar; which is constructed by respectively prepending and appending the values of the -&cv-LIBLINKPREFIX; and &cv-LIBLINKSUFFIX; &consvars; -to each library name in &cv-LIBS;. -Library name strings should not include a -path component, instead the compiler will be -directed to look for libraries in the paths -specified by &cv-link-LIBPATH;. +&cv-link-LIBLINKPREFIX; and &cv-link-LIBLINKSUFFIX; &consvars; +to each library name. -Any command lines you define that need -the &cv-LIBS; library list should -include &cv-_LIBFLAGS;: +Any command lines you define yourself that need +the libraries from &cv-LIBS; should include &cv-_LIBFLAGS; +(as well as &cv-link-_LIBDIRFLAGS;) +rather than &cv-LIBS;. +For example: env = Environment(LINKCOM="my_linker $_LIBDIRFLAGS $_LIBFLAGS -o $TARGET $SOURCE") + + + + -If you add a -File -object to the -&cv-LIBS; -list, the name of that file will be added to -&cv-_LIBFLAGS;, -and thus to the link line, as-is, without -&cv-LIBLINKPREFIX; -or -&cv-LIBLINKSUFFIX;. -For example: +If the linker supports command line syntax directing +that the argument specifying a library should be +searched for literally (without modification), +&cv-LIBLITERALPREFIX; can be set to that indicator. +For example, the GNU linker follows this rule: + +-l:foo searches the library path +for a filename called foo, +without converting it to +libfoo.so or +libfoo.a. + +If &cv-LIBLITERALPREFIX; is set, +&SCons; will not transform a string-valued entry in +&cv-link-LIBS; that starts with that string. +The entry will still be surrounded with +&cv-link-LIBLINKPREFIX; and &cv-link-LIBLINKSUFFIX; +on the command line. +This is useful, for example, +in directing that a static library +be used when both a static and dynamic library are available +and linker policy is to prefer dynamic libraries. +Compared to the example in &cv-link-LIBS;, - -env.Append(LIBS=File('/tmp/mylib.so')) +env.Append(LIBS=":libmylib.a") - -In all cases, scons will add dependencies from the executable program to -all the libraries in this list. +will let the linker select that specific (static) +library name if found in the library search path. +This differs from using a +File object +to specify the static library, +as the latter bypasses the library search path entirely. diff --git a/SCons/Environment.py b/SCons/Environment.py index 6327d86206..64d38b0768 100644 --- a/SCons/Environment.py +++ b/SCons/Environment.py @@ -37,6 +37,7 @@ import shlex from collections import UserDict, deque from subprocess import PIPE, DEVNULL +from typing import Optional import SCons.Action import SCons.Builder @@ -679,7 +680,7 @@ def gvars(self): def lvars(self): return {} - def subst(self, string, raw: int=0, target=None, source=None, conv=None, executor=None, overrides: bool=False): + def subst(self, string, raw: int=0, target=None, source=None, conv=None, executor=None, overrides: Optional[dict] = None): """Recursively interpolates construction variables from the Environment into the specified string, returning the expanded result. Construction variables are specified by a $ prefix @@ -705,9 +706,11 @@ def subst_kw(self, kw, raw: int=0, target=None, source=None): nkw[k] = v return nkw - def subst_list(self, string, raw: int=0, target=None, source=None, conv=None, executor=None, overrides: bool=False): - """Calls through to SCons.Subst.scons_subst_list(). See - the documentation for that function.""" + def subst_list(self, string, raw: int=0, target=None, source=None, conv=None, executor=None, overrides: Optional[dict] = None): + """Calls through to SCons.Subst.scons_subst_list(). + + See the documentation for that function. + """ gvars = self.gvars() lvars = self.lvars() lvars['__env__'] = self @@ -716,9 +719,10 @@ def subst_list(self, string, raw: int=0, target=None, source=None, conv=None, ex return SCons.Subst.scons_subst_list(string, self, raw, target, source, gvars, lvars, conv, overrides=overrides) def subst_path(self, path, target=None, source=None): - """Substitute a path list, turning EntryProxies into Nodes - and leaving Nodes (and other objects) as-is.""" + """Substitute a path list. + Turns EntryProxies into Nodes, leaving Nodes (and other objects) as-is. + """ if not is_List(path): path = [path] diff --git a/SCons/EnvironmentTests.py b/SCons/EnvironmentTests.py index 2f96679793..c40439ae07 100644 --- a/SCons/EnvironmentTests.py +++ b/SCons/EnvironmentTests.py @@ -1489,7 +1489,8 @@ def copy2(self, src, dst) -> None: SCons.CacheDir.CacheDir.copy_from_cache = save_copy_from_cache SCons.CacheDir.CacheDir.copy_to_cache = save_copy_to_cache - def test_concat(self) -> None: + # function is in Defaults.py, tested here to use TestEnvironment + def test__concat(self) -> None: """Test _concat()""" e1 = self.TestEnvironment(PRE='pre', SUF='suf', STR='a b', LIST=['a', 'b']) s = e1.subst @@ -1507,7 +1508,8 @@ def test_concat(self) -> None: assert x == '$( preasuf prebsuf $)', x - def test_concat_nested(self) -> None: + # function is in Defaults.py, tested here to use TestEnvironment + def test__concat_nested(self) -> None: """Test _concat() on a nested substitution strings.""" e = self.TestEnvironment(PRE='pre', SUF='suf', L1=['a', 'b'], @@ -1522,6 +1524,49 @@ def test_concat_nested(self) -> None: x = e.subst('$( ${_concat(PRE, L1, SUF, __env__)} $)') assert x == 'preasuf prebsuf precsuf predsuf precsuf predsuf', x + + # function is in Defaults.py, tested here to use TestEnvironment + def test__stripixes(self) -> None: + """Test _stripixes()""" + # LIBPREFIXES and LIBSUFFIXES are stripped, except if an entry + # begins with LIBLITERALPREFIX. Check this with and without that + # argument being passed, and whether or not LIBLITERALPREFIX is + # explicitly set. + e = self.TestEnvironment( + PRE='pre', + SUF='suf', + LIST=['xxx-a', 'b.yyy', 'zzxxx-c.yyy'], + LIBPREFIXES=['xxx-'], + LIBSUFFIXES=['.yyy'], + ) + + # e['LIBLITERALPREFIX'] = '' + with self.subTest(): + x = e.subst('$( ${_stripixes(PRE, LIST, SUF, LIBPREFIXES, LIBSUFFIXES,__env__, LIBLITERALPREFIX)} $)') + self.assertEqual('preasuf prebsuf prezzxxx-csuf', x) + + with self.subTest(): + x = e.subst('$( ${_stripixes(PRE, LIST, SUF, LIBPREFIXES, LIBSUFFIXES,__env__)} $)') + self.assertEqual('preasuf prebsuf prezzxxx-csuf', x) + + # add it to the env: + e['LIBLITERALPREFIX'] = 'zz' + + with self.subTest(): + x = e.subst('$( ${_stripixes(PRE, LIST, SUF, LIBPREFIXES, LIBSUFFIXES,__env__, LIBLITERALPREFIX)} $)') + self.assertEqual('preasuf prebsuf prezzxxx-c.yyysuf', x) + + with self.subTest(): + x = e.subst('$( ${_stripixes(PRE, LIST, SUF, LIBPREFIXES, LIBSUFFIXES,__env__)} $)') + self.assertEqual('preasuf prebsuf prezzxxx-csuf', x) + + # And special case: LIBLITERALPREFIX is the same as os.pathsep: + e['LIBLITERALPREFIX'] = os.pathsep + with self.subTest(): + x = e.subst('$( ${_stripixes(PRE, LIST, SUF, LIBPREFIXES, LIBSUFFIXES,__env__, LIBLITERALPREFIX)} $)') + self.assertEqual('preasuf prebsuf prezzxxx-csuf', x) + + def test_gvars(self) -> None: """Test the Environment gvars() method""" env = self.TestEnvironment(XXX = 'x', YYY = 'y', ZZZ = 'z') diff --git a/SCons/PathList.py b/SCons/PathList.py index dab8b2ce41..33ac7e58be 100644 --- a/SCons/PathList.py +++ b/SCons/PathList.py @@ -64,10 +64,9 @@ def node_conv(obj): return result class _PathList: - """ - An actual PathList object. - """ - def __init__(self, pathlist) -> None: + """An actual PathList object.""" + + def __init__(self, pathlist, split=True) -> None: """ Initializes a PathList object, canonicalizing the input and pre-processing it for quicker substitution later. @@ -94,7 +93,10 @@ def __init__(self, pathlist) -> None: over and over for each target. """ if SCons.Util.is_String(pathlist): - pathlist = pathlist.split(os.pathsep) + if split: + pathlist = pathlist.split(os.pathsep) + else: # no splitting, but still need a list + pathlist = [pathlist] elif not SCons.Util.is_Sequence(pathlist): pathlist = [pathlist] @@ -141,8 +143,7 @@ def subst_path(self, env, target, source): class PathListCache: - """ - A class to handle caching of PathList lookups. + """A class to handle caching of PathList lookups. This class gets instantiated once and then deleted from the namespace, so it's used as a Singleton (although we don't enforce that in the @@ -161,7 +162,7 @@ class PathListCache: The main type of duplication we're trying to catch will come from looking up the same path list from two different clones of the same construction environment. That is, given - + env2 = env1.Clone() both env1 and env2 will have the same CPPPATH value, and we can @@ -189,7 +190,7 @@ def _PathList_key(self, pathlist): return pathlist @SCons.Memoize.CountDictCall(_PathList_key) - def PathList(self, pathlist): + def PathList(self, pathlist, split=True): """ Returns the cached _PathList object for the specified pathlist, creating and caching a new object as necessary. @@ -206,7 +207,7 @@ def PathList(self, pathlist): except KeyError: pass - result = _PathList(pathlist) + result = _PathList(pathlist, split) memo_dict[pathlist] = result diff --git a/SCons/Platform/Platform.xml b/SCons/Platform/Platform.xml index c449fa50da..dc9ed795d4 100644 --- a/SCons/Platform/Platform.xml +++ b/SCons/Platform/Platform.xml @@ -49,7 +49,8 @@ to reflect the names of the libraries they create. -A list of all legal prefixes for library file names. +A list of all legal prefixes for library file names +on the current platform. When searching for library dependencies, SCons will look for files with these prefixes, the base library name, @@ -75,6 +76,7 @@ to reflect the names of the libraries they create. A list of all legal suffixes for library file names. +on the current platform. When searching for library dependencies, SCons will look for files with prefixes from the &cv-link-LIBPREFIXES; list, the base library name, @@ -129,7 +131,7 @@ else: platform argument to &f-link-Environment;). - Should be considered immutable. + Should be considered immutable. &cv-HOST_OS; is not currently used by &SCons;, but the option is reserved to do so in future @@ -177,7 +179,7 @@ else: and x86_64 for 64-bit hosts. - Should be considered immutable. + Should be considered immutable. &cv-HOST_ARCH; is not currently used by other platforms, but the option is reserved to do so in future @@ -305,7 +307,7 @@ an alternate command line so the invoked tool will make use of the contents of the temporary file. If you need to replace the default tempfile object, the callable should take into account the settings of -&cv-link-MAXLINELENGTH;, +&cv-link-MAXLINELENGTH;, &cv-link-TEMPFILEPREFIX;, &cv-link-TEMPFILESUFFIX;, &cv-link-TEMPFILEARGJOIN;, @@ -367,7 +369,7 @@ The directory to create the long-lines temporary file in. -The default argument escape function is +The default argument escape function is SCons.Subst.quote_spaces. If you need to apply extra operations on a command argument (to fix Windows slashes, normalize paths, etc.) diff --git a/SCons/Platform/cygwin.py b/SCons/Platform/cygwin.py index c62a668b37..2353763d7c 100644 --- a/SCons/Platform/cygwin.py +++ b/SCons/Platform/cygwin.py @@ -47,8 +47,9 @@ def generate(env) -> None: env['PROGSUFFIX'] = '.exe' env['SHLIBPREFIX'] = '' env['SHLIBSUFFIX'] = '.dll' - env['LIBPREFIXES'] = [ '$LIBPREFIX', '$SHLIBPREFIX', '$IMPLIBPREFIX' ] - env['LIBSUFFIXES'] = [ '$LIBSUFFIX', '$SHLIBSUFFIX', '$IMPLIBSUFFIX' ] + env['LIBPREFIXES'] = ['$LIBPREFIX', '$SHLIBPREFIX', '$IMPLIBPREFIX'] + env['LIBSUFFIXES'] = ['$LIBSUFFIX', '$SHLIBSUFFIX', '$IMPLIBSUFFIX'] + env['LIBLITERAPPREFIX'] = ':' env['TEMPFILE'] = TempFileMunge env['TEMPFILEPREFIX'] = '@' env['MAXLINELENGTH'] = 2048 diff --git a/SCons/Platform/os2.py b/SCons/Platform/os2.py index 7394aa8995..72bb034024 100644 --- a/SCons/Platform/os2.py +++ b/SCons/Platform/os2.py @@ -43,8 +43,9 @@ def generate(env) -> None: env['LIBSUFFIX'] = '.lib' env['SHLIBPREFIX'] = '' env['SHLIBSUFFIX'] = '.dll' - env['LIBPREFIXES'] = '$LIBPREFIX' - env['LIBSUFFIXES'] = [ '$LIBSUFFIX', '$SHLIBSUFFIX' ] + env['LIBPREFIXES'] = ['$LIBPREFIX'] + env['LIBSUFFIXES'] = ['$LIBSUFFIX', '$SHLIBSUFFIX'] + env['LIBLITERAPPREFIX'] = '' env['HOST_OS'] = 'os2' env['HOST_ARCH'] = win32.get_architecture().arch diff --git a/SCons/Platform/posix.py b/SCons/Platform/posix.py index 55b00b4db1..b655b77d51 100644 --- a/SCons/Platform/posix.py +++ b/SCons/Platform/posix.py @@ -93,8 +93,9 @@ def generate(env) -> None: env['LIBSUFFIX'] = '.a' env['SHLIBPREFIX'] = '$LIBPREFIX' env['SHLIBSUFFIX'] = '.so' - env['LIBPREFIXES'] = [ '$LIBPREFIX' ] - env['LIBSUFFIXES'] = [ '$LIBSUFFIX', '$SHLIBSUFFIX' ] + env['LIBPREFIXES'] = ['$LIBPREFIX'] + env['LIBSUFFIXES'] = ['$LIBSUFFIX', '$SHLIBSUFFIX'] + env['LIBLITERALPREFIX'] = '' env['HOST_OS'] = 'posix' env['HOST_ARCH'] = platform.machine() env['PSPAWN'] = pspawn diff --git a/SCons/Platform/win32.py b/SCons/Platform/win32.py index 160cb988e7..b145823616 100644 --- a/SCons/Platform/win32.py +++ b/SCons/Platform/win32.py @@ -81,9 +81,8 @@ def win_api_copyfile(src,dst) -> None: # This locked version of spawnve works around a Windows # MSVCRT bug, because its spawnve is not thread-safe. # Without this, python can randomly crash while using -jN. - # See the python bug at http://bugs.python.org/issue6476 - # and SCons issue at - # https://github.com/SCons/scons/issues/2449 + # See the python bug at https://github.com/python/cpython/issues/50725 + # and SCons issue at https://github.com/SCons/scons/issues/2449 def spawnve(mode, file, args, env): spawn_lock.acquire() try: @@ -421,8 +420,9 @@ def generate(env): env['LIBSUFFIX'] = '.lib' env['SHLIBPREFIX'] = '' env['SHLIBSUFFIX'] = '.dll' - env['LIBPREFIXES'] = [ '$LIBPREFIX' ] - env['LIBSUFFIXES'] = [ '$LIBSUFFIX' ] + env['LIBPREFIXES'] = ['$LIBPREFIX'] + env['LIBSUFFIXES'] = ['$LIBSUFFIX'] + env['LIBLITERALPREFIX'] = '' env['PSPAWN'] = piped_spawn env['SPAWN'] = spawn env['SHELL'] = cmd_interp diff --git a/SCons/SConf.py b/SCons/SConf.py index e522c8bb7a..53666e63ee 100644 --- a/SCons/SConf.py +++ b/SCons/SConf.py @@ -1002,8 +1002,8 @@ def SConf(*args, **kw): return SCons.Util.Null() -def CheckFunc(context, function_name, header = None, language = None) -> bool: - res = SCons.Conftest.CheckFunc(context, function_name, header = header, language = language) +def CheckFunc(context, function_name, header = None, language = None, funcargs = None) -> bool: + res = SCons.Conftest.CheckFunc(context, function_name, header = header, language = language, funcargs = funcargs) context.did_show_result = 1 return not res diff --git a/SCons/SConfTests.py b/SCons/SConfTests.py index 2903ba675e..08ef25e79d 100644 --- a/SCons/SConfTests.py +++ b/SCons/SConfTests.py @@ -671,11 +671,26 @@ def test_CheckFunc(self) -> None: log_file=self.test.workpath('config.log')) try: - # CheckFunc() + # look for function using default heading r = sconf.CheckFunc('strcpy') assert r, "did not find strcpy" + # no default heading, supply dummy signature r = sconf.CheckFunc('strcpy', '/* header */ char strcpy();') assert r, "did not find strcpy" + # ... supply complete signature, and function args + r = sconf.CheckFunc('strcpy', header='/* header */ char *strcpy(char *dest, char *src);', funcargs='"", ""') + # ... supply standard header for prototype, and function args + assert r, "did not find strcpy" + r = sconf.CheckFunc('strcpy', header='#include ', funcargs='"", ""') + # also try in C++ mode + cpp_header = """\ +#ifdef __cplusplus +extern "C" +#endif +char *strcpy(char *dest, char *src); +""" + r = sconf.CheckFunc('strcpy', header=cpp_header, funcargs='"", ""', language="C++") + assert r, "did not find strcpy" r = sconf.CheckFunc('hopefullynofunction') assert not r, "unexpectedly found hopefullynofunction" diff --git a/SCons/Script/Interactive.py b/SCons/Script/Interactive.py index aa390cb53a..ec58c0f79d 100644 --- a/SCons/Script/Interactive.py +++ b/SCons/Script/Interactive.py @@ -346,7 +346,7 @@ def do_shell(self, argv) -> None: argv = os.environ[self.shell_variable] try: # Per "[Python-Dev] subprocess insufficiently platform-independent?" - # http://mail.python.org/pipermail/python-dev/2008-August/081979.html "+ + # https://mail.python.org/pipermail/python-dev/2008-August/081979.html "+ # Doing the right thing with an argument list currently # requires different shell= values on Windows and Linux. p = subprocess.Popen(argv, shell=(sys.platform=='win32')) diff --git a/SCons/Script/Main.py b/SCons/Script/Main.py index 9735df2613..c29fb381c9 100644 --- a/SCons/Script/Main.py +++ b/SCons/Script/Main.py @@ -725,6 +725,8 @@ def _set_debug_values(options) -> None: SCons.Node.print_duplicate = True if "json" in debug_values: ENABLE_JSON = True + if "sconscript" in debug_values: + SCons.Debug.sconscript_trace = True def _create_path(plist): path = '.' diff --git a/SCons/Script/SConsOptions.py b/SCons/Script/SConsOptions.py index e799716c57..b74353eb9c 100644 --- a/SCons/Script/SConsOptions.py +++ b/SCons/Script/SConsOptions.py @@ -752,7 +752,7 @@ def opt_invalid_rm(group, value, msg): debug_options = ["count", "duplicate", "explain", "findlibs", "includes", "memoizer", "memory", "objects", "pdb", "prepare", "presub", "stacktrace", - "time", "action-timestamps", "json"] + "time", "action-timestamps", "json", "sconscript"] def opt_debug(option, opt, value__, parser, debug_options=debug_options, diff --git a/SCons/Script/SConscript.py b/SCons/Script/SConscript.py index 539fa75dc1..85070ab053 100644 --- a/SCons/Script/SConscript.py +++ b/SCons/Script/SConscript.py @@ -274,9 +274,16 @@ def _SConscript(fs, *files, **kw): scriptdata = _file_.read() scriptname = _file_.name _file_.close() + if SCons.Debug.sconscript_trace: + print("scons: Entering "+str(scriptname)) exec(compile(scriptdata, scriptname, 'exec'), call_stack[-1].globals) + if SCons.Debug.sconscript_trace: + print("scons: Exiting "+str(scriptname)) except SConscriptReturn: - pass + if SCons.Debug.sconscript_trace: + print("scons: Exiting "+str(scriptname)) + else: + pass finally: if Main.print_time: elapsed = time.perf_counter() - start_time diff --git a/SCons/Subst.py b/SCons/Subst.py index 4046ca6b53..b04ebe50cd 100644 --- a/SCons/Subst.py +++ b/SCons/Subst.py @@ -26,6 +26,7 @@ import collections import re from inspect import signature, Parameter +from typing import Optional import SCons.Errors from SCons.Util import is_String, is_Sequence @@ -448,11 +449,12 @@ def substitute(self, args, lvars): This serves as a wrapper for splitting up a string into separate tokens. """ + def sub_match(match): + return self.conv(self.expand(match.group(1), lvars)) + if is_String(args) and not isinstance(args, CmdStringHolder): args = str(args) # In case it's a UserString. try: - def sub_match(match): - return self.conv(self.expand(match.group(1), lvars)) result = _dollar_exps.sub(sub_match, args) except TypeError: # If the internal conversion routine doesn't return @@ -805,7 +807,7 @@ def _remove_list(list): _space_sep = re.compile(r'[\t ]+(?![^{]*})') -def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None, overrides: bool=False): +def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None, overrides: Optional[dict] = None): """Expand a string or list containing construction variable substitutions. @@ -887,7 +889,7 @@ def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={ return result -def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None,overrides: bool=False): +def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None, overrides: Optional[dict] = None): """Substitute construction variables in a string (or list or other object) and separate the arguments into a command list. diff --git a/SCons/Tool/JavaCommon.py b/SCons/Tool/JavaCommon.py index 0722f008cf..31695c21b2 100644 --- a/SCons/Tool/JavaCommon.py +++ b/SCons/Tool/JavaCommon.py @@ -124,6 +124,7 @@ def __init__(self, version=default_java_version) -> None: '18.0', '19.0', '20.0', + '21.0', ): msg = "Java version %s not supported" % version raise NotImplementedError(msg) @@ -251,6 +252,7 @@ def addAnonClass(self) -> None: '18.0', '19.0', '20.0', + '21.0', ): self.stackAnonClassBrackets.append(self.brackets) className = [] diff --git a/SCons/Tool/MSCommon/MSVC/Config.py b/SCons/Tool/MSCommon/MSVC/Config.py index 29d6f246f2..7c0f1fe6ff 100644 --- a/SCons/Tool/MSCommon/MSVC/Config.py +++ b/SCons/Tool/MSCommon/MSVC/Config.py @@ -29,8 +29,6 @@ namedtuple, ) -from . import Util - from .Exceptions import ( MSVCInternalError, ) @@ -319,6 +317,7 @@ def verify(): + from . import Util from .. import vc for msvc_version in vc._VCVER: if msvc_version not in MSVC_VERSION_SUFFIX: 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/Util.py b/SCons/Tool/MSCommon/MSVC/Util.py index d41ff7d9f6..44b112546b 100644 --- a/SCons/Tool/MSCommon/MSVC/Util.py +++ b/SCons/Tool/MSCommon/MSVC/Util.py @@ -26,16 +26,25 @@ """ import os +import pathlib import re from collections import ( namedtuple, ) +from ..common import debug + from . import Config # path utilities +# windows drive specification (e.g., 'C:') +_RE_DRIVESPEC = re.compile(r'^[A-Za-z][:]$', re.IGNORECASE) + +# windows path separators +_OS_PATH_SEPS = (os.path.sep, os.path.altsep) if os.path.altsep else (os.path.sep,) + def listdir_dirs(p): """ Return a list of tuples for each subdirectory of the given directory path. @@ -57,22 +66,92 @@ def listdir_dirs(p): dirs.append((dir_name, dir_path)) return dirs -def process_path(p): +def resolve_path(p, ignore_drivespec=True): """ - Normalize a system path + Make path absolute resolving any symlinks Args: p: str system path + ignore_drivespec: bool + ignore drive specifications when True Returns: - str: normalized system path + 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 for an unqualified file name + # on a mapped network drive in order to return a mapped drive letter + # path rather than a UNC path. + p = os.path.abspath(p) + try: + p = str(pathlib.Path(p).resolve()) + except OSError as e: + debug( + 'caught exception: path=%s, exception=%s(%s)', + repr(p), type(e).__name__, repr(str(e)) + ) + + return p + +def normalize_path( + p, + strip=True, + preserve_trailing=False, + expand=False, + realpath=True, + ignore_drivespec=True, +): + """ + 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 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 diff --git a/SCons/Tool/MSCommon/MSVC/UtilTests.py b/SCons/Tool/MSCommon/MSVC/UtilTests.py index 36e08f5eb1..86ea58d875 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 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_process_path(self) -> None: - func = Util.process_path - for p, expect in [ - (None, True), ('', True), - ('doesnotexist.xyz.abc', False), (Data.UTIL_PARENT_DIR, False), - ]: - rval = func(p) - self.assertTrue((p == rval) == expect, "{}({}): {}".format( + 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 + # 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/WinSDK.py b/SCons/Tool/MSCommon/MSVC/WinSDK.py index 39617b16cc..7115d505ee 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 diff --git a/SCons/Tool/MSCommon/common.py b/SCons/Tool/MSCommon/common.py index f8816c4f0c..2b8c67b213 100644 --- a/SCons/Tool/MSCommon/common.py +++ b/SCons/Tool/MSCommon/common.py @@ -75,32 +75,86 @@ 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, 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) + '', # 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_prefix = 'debug: ' log_handler = logging.StreamHandler(sys.stdout) else: + log_prefix = '' log_handler = logging.FileHandler(filename=LOGFILE) - log_formatter = logging.Formatter(log_format) + log_formatter = _CustomFormatter(log_prefix) 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 + + DEBUG_ENABLED = True + else: - def debug(x, *args): + def debug(x, *args, **kwargs): + return None + + 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', '') diff --git a/SCons/Tool/MSCommon/sdk.py b/SCons/Tool/MSCommon/sdk.py index 8499dc8a4b..c6600f3a5b 100644 --- a/SCons/Tool/MSCommon/sdk.py +++ b/SCons/Tool/MSCommon/sdk.py @@ -45,10 +45,9 @@ # seem to be any sane registry key, so the precise location is hardcoded. # # For versions below 2003R1, it seems the PSDK is included with Visual Studio? -# -# Also, per the following: -# http://benjamin.smedbergs.us/blog/tag/atl/ # VC++ Professional comes with the SDK, VC++ Express does not. +# +# Of course, all this changed again after Express was phased out (2005). # Location of the SDK (checked for 6.1 only) _CURINSTALLED_SDK_HKEY_ROOT = \ diff --git a/SCons/Tool/MSCommon/vc.py b/SCons/Tool/MSCommon/vc.py index a92ceb7cd8..5b94747e56 100644 --- a/SCons/Tool/MSCommon/vc.py +++ b/SCons/Tool/MSCommon/vc.py @@ -2190,7 +2190,7 @@ def msvc_setup_env(env): 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, [])) @@ -2201,16 +2201,15 @@ def msvc_setup_env(env): # 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): vcs = get_installed_vcs(env) diff --git a/SCons/Tool/compilation_db.py b/SCons/Tool/compilation_db.py index 14c6ef59c7..e17b5dc661 100644 --- a/SCons/Tool/compilation_db.py +++ b/SCons/Tool/compilation_db.py @@ -1,12 +1,5 @@ -""" -Implements the ability for SCons to emit a compilation database for the MongoDB project. See -http://clang.llvm.org/docs/JSONCompilationDatabase.html for details on what a compilation -database is, and why you might want one. The only user visible entry point here is -'env.CompilationDatabase'. This method takes an optional 'target' to name the file that -should hold the compilation database, otherwise, the file defaults to compile_commands.json, -which is the name that most clang tools search for by default. -""" - +# MIT License +# # Copyright 2020 MongoDB Inc. # # Permission is hereby granted, free of charge, to any person obtaining @@ -27,7 +20,17 @@ # 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. -# + +"""Compilation Database + +Implements the ability for SCons to emit a compilation database for a +project. See https://clang.llvm.org/docs/JSONCompilationDatabase.html +for details on what a compilation database is, and why you might want one. +The only user visible entry point here is ``env.CompilationDatabase``. +This method takes an optional *target* to name the file that should hold +the compilation database, otherwise, the file defaults to +``compile_commands.json``, the name that most clang tools search for by default. +""" import json import itertools diff --git a/SCons/Tool/cyglink.py b/SCons/Tool/cyglink.py index 80caf0b87a..6a5ca99cf3 100644 --- a/SCons/Tool/cyglink.py +++ b/SCons/Tool/cyglink.py @@ -1,6 +1,29 @@ +# 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. + """SCons.Tool.cyglink -Customization of gnulink for Cygwin (http://www.cygwin.com/) +Customization of gnulink for Cygwin (https://www.cygwin.com/) There normally shouldn't be any need to import this module directly. It will usually be imported through the generic SCons.Tool.Tool() diff --git a/SCons/Tool/dmd.py b/SCons/Tool/dmd.py index 13e9e7e446..2ac84d0cf2 100644 --- a/SCons/Tool/dmd.py +++ b/SCons/Tool/dmd.py @@ -24,7 +24,7 @@ """SCons.Tool.dmd Tool-specific initialization for the Digital Mars D compiler. -(http://digitalmars.com/d) +(https://digitalmars.com/d) Originally coded by Andy Friesen (andy@ikagames.com) 15 November 2003 @@ -138,7 +138,7 @@ def generate(env) -> None: env['DLIBLINKPREFIX'] = '' if env['PLATFORM'] == 'win32' else '-L-l' env['DLIBLINKSUFFIX'] = '.lib' if env['PLATFORM'] == 'win32' else '' - env['_DLIBFLAGS'] = '${_stripixes(DLIBLINKPREFIX, LIBS, DLIBLINKSUFFIX, LIBPREFIXES, LIBSUFFIXES, __env__)}' + env['_DLIBFLAGS'] = '${_stripixes(DLIBLINKPREFIX, LIBS, DLIBLINKSUFFIX, LIBPREFIXES, LIBSUFFIXES, __env__, LIBLITERALPREFIX)}' env['DLIBDIRPREFIX'] = '-L-L' env['DLIBDIRSUFFIX'] = '' diff --git a/SCons/Tool/docbook/__init__.py b/SCons/Tool/docbook/__init__.py index b682cdfa37..3adb3148bb 100644 --- a/SCons/Tool/docbook/__init__.py +++ b/SCons/Tool/docbook/__init__.py @@ -155,8 +155,8 @@ def __create_output_dir(base_dir) -> None: xsltproc_com_priority = ['xsltproc', 'saxon', 'saxon-xslt', 'xalan'] # TODO: Set minimum version of saxon-xslt to be 8.x (lower than this only supports xslt 1.0. -# see: http://saxon.sourceforge.net/saxon6.5.5/ -# see: http://saxon.sourceforge.net/ +# see: https://saxon.sourceforge.net/saxon6.5.5/ +# see: https://saxon.sourceforge.net/ xsltproc_com = {'xsltproc' : '$DOCBOOK_XSLTPROC $DOCBOOK_XSLTPROCFLAGS -o $TARGET $DOCBOOK_XSL $SOURCE', 'saxon' : '$DOCBOOK_XSLTPROC $DOCBOOK_XSLTPROCFLAGS -o $TARGET $DOCBOOK_XSL $SOURCE $DOCBOOK_XSLTPROCPARAMS', # Note if saxon-xslt is version 5.5 the proper arguments are: (swap order of docbook_xsl and source) diff --git a/SCons/Tool/gnulink.py b/SCons/Tool/gnulink.py index 159aa972cb..c636c11d23 100644 --- a/SCons/Tool/gnulink.py +++ b/SCons/Tool/gnulink.py @@ -52,6 +52,9 @@ def generate(env) -> None: env['RPATHSUFFIX'] = '' env['_RPATH'] = '${_concat(RPATHPREFIX, RPATH, RPATHSUFFIX, __env__)}' + env['LIBLITERALPREFIX'] = ':' + + def exists(env): # TODO: sync with link.smart_link() to choose a linker diff --git a/SCons/Tool/ldc.py b/SCons/Tool/ldc.py index bd0767e7f5..6ee022aa34 100644 --- a/SCons/Tool/ldc.py +++ b/SCons/Tool/ldc.py @@ -73,7 +73,7 @@ def generate(env) -> None: env['_DDEBUGFLAGS'] = '${_concat(DDEBUGPREFIX, DDEBUG, DDEBUGSUFFIX, __env__)}' env['_DI_FLAGS'] = "${DI_FILE_DIR and DI_FILE_DIR_PREFIX+DI_FILE_DIR+DI_FILE_DIR_SUFFFIX}" - + env['_DFLAGS'] = '${_concat(DFLAGPREFIX, DFLAGS, DFLAGSUFFIX, __env__)}' env['SHDC'] = '$DC' @@ -96,10 +96,10 @@ def generate(env) -> None: env['DFLAGPREFIX'] = '-' env['DFLAGSUFFIX'] = '' env['DFILESUFFIX'] = '.d' - + env['DI_FILE_DIR'] = '' env['DI_FILE_SUFFIX'] = '.di' - + env['DI_FILE_DIR_PREFIX'] = '-Hd=' env['DI_FILE_DIR_SUFFFIX'] = '' @@ -115,7 +115,7 @@ def generate(env) -> None: env['DLIBLINKPREFIX'] = '' if env['PLATFORM'] == 'win32' else '-L-l' env['DLIBLINKSUFFIX'] = '.lib' if env['PLATFORM'] == 'win32' else '' # env['_DLIBFLAGS'] = '${_concat(DLIBLINKPREFIX, LIBS, DLIBLINKSUFFIX, __env__, RDirs, TARGET, SOURCE)}' - env['_DLIBFLAGS'] = '${_stripixes(DLIBLINKPREFIX, LIBS, DLIBLINKSUFFIX, LIBPREFIXES, LIBSUFFIXES, __env__)}' + env['_DLIBFLAGS'] = '${_stripixes(DLIBLINKPREFIX, LIBS, DLIBLINKSUFFIX, LIBPREFIXES, LIBSUFFIXES, __env__, LIBLITERALPREFIX)}' env['DLIBDIRPREFIX'] = '-L-L' env['DLIBDIRSUFFIX'] = '' diff --git a/SCons/Tool/link.py b/SCons/Tool/link.py index e879ae8623..cd8b2f88b5 100644 --- a/SCons/Tool/link.py +++ b/SCons/Tool/link.py @@ -55,7 +55,7 @@ def generate(env) -> None: env['LINKCOM'] = '$LINK -o $TARGET $LINKFLAGS $__RPATH $SOURCES $_LIBDIRFLAGS $_LIBFLAGS' env['LIBDIRPREFIX'] = '-L' env['LIBDIRSUFFIX'] = '' - env['_LIBFLAGS'] = '${_stripixes(LIBLINKPREFIX, LIBS, LIBLINKSUFFIX, LIBPREFIXES, LIBSUFFIXES, __env__)}' + env['_LIBFLAGS'] = '${_stripixes(LIBLINKPREFIX, LIBS, LIBLINKSUFFIX, LIBPREFIXES, LIBSUFFIXES, __env__, LIBLITERALPREFIX)}' env['LIBLINKPREFIX'] = '-l' env['LIBLINKSUFFIX'] = '' diff --git a/SCons/Tool/mingw.py b/SCons/Tool/mingw.py index 0119444a71..824f20daaa 100644 --- a/SCons/Tool/mingw.py +++ b/SCons/Tool/mingw.py @@ -23,7 +23,7 @@ """SCons.Tool.gcc -Tool-specific initialization for MinGW (http://www.mingw.org/) +Tool-specific initialization for MinGW (https://www.mingw.org/) There normally shouldn't be any need to import this module directly. It will usually be imported through the generic SCons.Tool.Tool() diff --git a/SCons/Tool/msvc.xml b/SCons/Tool/msvc.xml index 8beebf9a2d..0772e5d3bc 100644 --- a/SCons/Tool/msvc.xml +++ b/SCons/Tool/msvc.xml @@ -94,10 +94,9 @@ Sets &consvars; for the Microsoft Visual C/C++ compiler. Builds a Microsoft Visual C++ precompiled header. Calling this builder -returns a list of two targets: the PCH as the first element, and the object -file as the second element. Normally the object file is ignored. -This builder is only -provided when Microsoft Visual C++ is being used as the compiler. +returns a list of two target nodes: the PCH as the first element, +and the object file as the second element. +Normally the object file is ignored. The &b-PCH; builder is generally used in conjunction with the &cv-link-PCH; &consvar; to force object files to use the precompiled header: @@ -106,6 +105,42 @@ the precompiled header: env['PCH'] = env.PCH('StdAfx.cpp')[0] + + + +This builder is specific to the PCH implementation +in Microsoft Visual C++. +Other compiler chains also implement precompiled header support, +but &b-PCH; does not work with them at this time. +As a result, the builder is only generated into the +construction environment when +Microsoft Visual C++ is being used as the compiler. + + +The builder only works correctly in a C++ project. +The Microsoft implementation distinguishes between +precompiled headers from C and C++. +Use of the builder will cause the PCH generation to happen with a flag +that tells cl.exe all of the +files are C++ files; if that PCH file is then supplied when +compiling a C source file, +cl.exe will fail the build with +a compatibility violation. + + +If possible, arrange the project so that a +C++ source file passed to the &b-PCH; builder +is not also included in the list of sources +to be otherwise compiled in the project. +&SCons; will correctly track that file in the dependency tree +as a result of the &b-PCH; call, +and (for MSVC 11.0 and greater) automatically add the +corresponding object file to the link line. +If the source list is automatically generated, +for example using the &f-link-Glob; function, +it may be necessary to remove that file from the list. + + @@ -154,7 +189,7 @@ when the &cv-link-PDB; &consvar; is set. -The Visual C++ compiler option that SCons uses by default +The Visual C++ compiler option that &SCons; uses by default to generate PDB information is . This works correctly with parallel () builds because it embeds the debug information in the intermediate object files, @@ -192,12 +227,12 @@ env['CCPDBFLAGS'] = '/Zi /Fd${TARGET}.pdb' When set to any true value, -specifies that SCons should batch +specifies that &SCons; should batch compilation of object files when calling the Microsoft Visual C/C++ compiler. All compilations of source files from the same source directory that generate target files in a same output directory -and were configured in SCons using the same &consenv; +and were configured in &SCons; using the same &consenv; will be built in a single call to the compiler. Only source files that have changed since their object files were built will be passed to each compiler invocation @@ -213,17 +248,19 @@ will be compiled separately. -The Microsoft Visual C++ precompiled header that will be used when compiling -object files. This variable is ignored by tools other than Microsoft Visual C++. +A node for the Microsoft Visual C++ precompiled header that will be +used when compiling object files. +This variable is ignored by tools other than Microsoft Visual C++. When this variable is -defined SCons will add options to the compiler command line to +defined, &SCons; will add options to the compiler command line to cause it to use the precompiled header, and will also set up the dependencies for the PCH file. -Example: +Examples: env['PCH'] = File('StdAfx.pch') +env['PCH'] = env.PCH('pch.cc')[0] @@ -242,7 +279,7 @@ builder to generated a precompiled header. The string displayed when generating a precompiled header. -If this is not set, then &cv-link-PCHCOM; (the command line) is displayed. +If not set, then &cv-link-PCHCOM; (the command line) is displayed. @@ -356,7 +393,7 @@ when the &cv-link-RCINCFLAGS; variable is expanded. Sets the preferred version of Microsoft Visual C/C++ to use. If the specified version is unavailable (not installed, or not discoverable), tool initialization will fail. -If &cv-MSVC_VERSION; is not set, SCons will (by default) select the +If &cv-MSVC_VERSION; is not set, &SCons; will (by default) select the latest version of Visual C/C++ installed on your system. @@ -809,10 +846,10 @@ Specify the location of vswhere.exe. It provides full information about installations of 2017 and later editions. With the argument, vswhere.exe can detect installations of the 2010 through 2015 editions with limited data returned. -If VSWHERE is set, SCons will use that location. +If VSWHERE is set, &SCons; will use that location. - Otherwise SCons will look in the following locations and set VSWHERE to the path of the first vswhere.exe + Otherwise &SCons; will look in the following locations and set VSWHERE to the path of the first vswhere.exe located. diff --git a/SCons/Tool/msvs.py b/SCons/Tool/msvs.py index 16e422dace..12974489a4 100644 --- a/SCons/Tool/msvs.py +++ b/SCons/Tool/msvs.py @@ -145,6 +145,8 @@ def msvs_parse_version(s): # the MSVS Project file invoke SCons the same way that scons.bat does, # which works regardless of how we were invoked. def getExecScriptMain(env, xml=None): + if 'SCONS_HOME' not in env: + env['SCONS_HOME'] = os.environ.get('SCONS_HOME') scons_home = env.get('SCONS_HOME') if not scons_home and 'SCONS_LIB_DIR' in os.environ: scons_home = os.environ['SCONS_LIB_DIR'] @@ -2109,7 +2111,9 @@ def generate(env) -> None: env['GET_MSVSSOLUTIONSUFFIX'] = GetMSVSSolutionSuffix env['MSVSPROJECTSUFFIX'] = '${GET_MSVSPROJECTSUFFIX}' env['MSVSSOLUTIONSUFFIX'] = '${GET_MSVSSOLUTIONSUFFIX}' - env['SCONS_HOME'] = os.environ.get('SCONS_HOME') + + if 'SCONS_HOME' not in env: + env['SCONS_HOME'] = os.environ.get('SCONS_HOME') def exists(env): return msvc_setup_env_tool(env, tool=tool_name) diff --git a/SCons/Variables/ListVariable.py b/SCons/Variables/ListVariable.py index bfa48f5ebe..a0640e6340 100644 --- a/SCons/Variables/ListVariable.py +++ b/SCons/Variables/ListVariable.py @@ -70,22 +70,22 @@ def __init__(self, initlist=None, allowedElems=None) -> None: self.allowedElems = sorted(allowedElems) def __cmp__(self, other): - raise NotImplementedError + return NotImplemented def __eq__(self, other): - raise NotImplementedError + return NotImplemented def __ge__(self, other): - raise NotImplementedError + return NotImplemented def __gt__(self, other): - raise NotImplementedError + return NotImplemented def __le__(self, other): - raise NotImplementedError + return NotImplemented def __lt__(self, other): - raise NotImplementedError + return NotImplemented def __str__(self) -> str: if not len(self): diff --git a/SCons/__init__.py b/SCons/__init__.py index f0f241de88..7a7d3ef794 100644 --- a/SCons/__init__.py +++ b/SCons/__init__.py @@ -1,9 +1,9 @@ -__version__="4.5.2" +__version__="4.6.1" __copyright__="Copyright (c) 2001 - 2023 The SCons Foundation" __developer__="bdbaddog" -__date__="Sun, 04 Jun 2023 15:36:48 -0700" +__date__="Sun, 19 Nov 2023 17:54:10 -0700" __buildsys__="M1Dog2021" -__revision__="b3744e8862927899e3d0ebcb41297f9b4c142c63" -__build__="b3744e8862927899e3d0ebcb41297f9b4c142c63" +__revision__="70bcde6f38478a85a552ee624baaa2beb7b2bb92" +__build__="70bcde6f38478a85a552ee624baaa2beb7b2bb92" # make sure compatibility is always in place -import SCons.compat # noqa \ No newline at end of file +import SCons.compat # noqa \ No newline at end of file diff --git a/SCons/cpp.py b/SCons/cpp.py index c9c9a70f06..97aba8cc34 100644 --- a/SCons/cpp.py +++ b/SCons/cpp.py @@ -561,8 +561,7 @@ def do_include(self, t) -> None: [('scons_current_file', self.current_file)] self.tuples[:] = new_tuples + self.tuples - # Date: Tue, 22 Nov 2005 20:26:09 -0500 - # From: Stefan Seefeld + # From: Stefan Seefeld (22 Nov 2005) # # By the way, #include_next is not the same as #include. The difference # being that #include_next starts its search in the path following the @@ -570,10 +569,12 @@ def do_include(self, t) -> None: # include paths are ['/foo', '/bar'], and you are looking at a header # '/foo/baz.h', it might issue an '#include_next ' which would # correctly resolve to '/bar/baz.h' (if that exists), but *not* see - # '/foo/baz.h' again. See http://www.delorie.com/gnu/docs/gcc/cpp_11.html - # for more reasoning. + # '/foo/baz.h' again. See + # https://gcc.gnu.org/onlinedocs/cpp/Wrapper-Headers.html for more notes. # - # I have no idea in what context 'import' might be used. + # I have no idea in what context #import might be used. + # Update: possibly these notes? + # https://github.com/MicrosoftDocs/cpp-docs/blob/main/docs/preprocessor/hash-import-directive-cpp.md # XXX is #include_next really the same as #include ? do_include_next = do_include diff --git a/SConstruct b/SConstruct index b414532704..98e1bd9462 100644 --- a/SConstruct +++ b/SConstruct @@ -36,7 +36,7 @@ copyright_years = strftime('2001 - %Y') # This gets inserted into the man pages to reflect the month of release. month_year = strftime('%B %Y') project = 'scons' -default_version = '4.5.3' +default_version = '4.6.1' copyright = f"Copyright (c) {copyright_years} The SCons Foundation" # We let the presence or absence of various utilities determine whether diff --git a/doc/generated/builders.gen b/doc/generated/builders.gen index deccae72a6..25254b2a36 100644 --- a/doc/generated/builders.gen +++ b/doc/generated/builders.gen @@ -27,10 +27,12 @@ Example: # builds foo.c -env.CFile(target = 'foo.c', source = 'foo.l') +env.CFile(target='foo.c', source='foo.l') + # builds bar.c -env.CFile(target = 'bar', source = 'bar.y') +env.CFile(target='bar', source='bar.y') + @@ -124,10 +126,12 @@ Example: # builds foo.cc -env.CXXFile(target = 'foo.cc', source = 'foo.ll') +env.CXXFile(target='foo.cc', source='foo.ll') + # builds bar.cc -env.CXXFile(target = 'bar', source = 'bar.yy') +env.CXXFile(target='bar', source='bar.yy') + @@ -1535,10 +1539,9 @@ the top directory of the project. Builds a Microsoft Visual C++ precompiled header. Calling this builder -returns a list of two targets: the PCH as the first element, and the object -file as the second element. Normally the object file is ignored. -This builder is only -provided when Microsoft Visual C++ is being used as the compiler. +returns a list of two target nodes: the PCH as the first element, +and the object file as the second element. +Normally the object file is ignored. The &b-PCH; builder is generally used in conjunction with the &cv-link-PCH; &consvar; to force object files to use the precompiled header: @@ -1547,6 +1550,42 @@ the precompiled header: env['PCH'] = env.PCH('StdAfx.cpp')[0] + + + +This builder is specific to the PCH implementation +in Microsoft Visual C++. +Other compiler chains also implement precompiled header support, +but &b-PCH; does not work with them at this time. +As a result, the builder is only generated into the +construction environment when +Microsoft Visual C++ is being used as the compiler. + + +The builder only works correctly in a C++ project. +The Microsoft implementation distinguishes between +precompiled headers from C and C++. +Use of the builder will cause the PCH generation to happen with a flag +that tells cl.exe all of the +files are C++ files; if that PCH file is then supplied when +compiling a C source file, +cl.exe will fail the build with +a compatibility violation. + + +If possible, arrange the project so that a +C++ source file passed to the &b-PCH; builder +is not also included in the list of sources +to be otherwise compiled in the project. +&SCons; will correctly track that file in the dependency tree +as a result of the &b-PCH; call, +and (for MSVC 11.0 and greater) automatically add the +corresponding object file to the link line. +If the source list is automatically generated, +for example using the &f-link-Glob; function, +it may be necessary to remove that file from the list. + + diff --git a/doc/generated/examples/caching_ex-random_1.xml b/doc/generated/examples/caching_ex-random_1.xml index 422f37d59d..940377d242 100644 --- a/doc/generated/examples/caching_ex-random_1.xml +++ b/doc/generated/examples/caching_ex-random_1.xml @@ -1,8 +1,8 @@ % scons -Q -cc -o f4.o -c f4.c cc -o f2.o -c f2.c -cc -o f1.o -c f1.c -cc -o f5.o -c f5.c cc -o f3.o -c f3.c +cc -o f5.o -c f5.c +cc -o f1.o -c f1.c +cc -o f4.o -c f4.c cc -o prog f1.o f2.o f3.o f4.o f5.o diff --git a/doc/generated/examples/commandline_EnumVariable_3.xml b/doc/generated/examples/commandline_EnumVariable_3.xml index 154c041d2a..6c03598287 100644 --- a/doc/generated/examples/commandline_EnumVariable_3.xml +++ b/doc/generated/examples/commandline_EnumVariable_3.xml @@ -4,5 +4,5 @@ COLOR: Set background color (red|green|blue) default: red actual: red -Use scons -H for help about command-line options. +Use scons -H for help about SCons built-in command-line options. diff --git a/doc/generated/examples/commandline_Variables_Help_1.xml b/doc/generated/examples/commandline_Variables_Help_1.xml index 1bbf2fb675..005c014a9a 100644 --- a/doc/generated/examples/commandline_Variables_Help_1.xml +++ b/doc/generated/examples/commandline_Variables_Help_1.xml @@ -4,5 +4,5 @@ RELEASE: Set to 1 to build for release default: 0 actual: 0 -Use scons -H for help about command-line options. +Use scons -H for help about SCons built-in command-line options. diff --git a/doc/generated/examples/output_ex1_1.xml b/doc/generated/examples/output_ex1_1.xml index 1558b0118b..5a3123d918 100644 --- a/doc/generated/examples/output_ex1_1.xml +++ b/doc/generated/examples/output_ex1_1.xml @@ -5,5 +5,5 @@ scons: done reading SConscript files. Type: 'scons program' to build the production program, 'scons debug' to build the debug version. -Use scons -H for help about command-line options. +Use scons -H for help about SCons built-in command-line options. diff --git a/doc/generated/examples/output_ex2_1.xml b/doc/generated/examples/output_ex2_1.xml index d832a522f2..b3b3fdb24f 100644 --- a/doc/generated/examples/output_ex2_1.xml +++ b/doc/generated/examples/output_ex2_1.xml @@ -6,5 +6,5 @@ Type: 'scons program' to build the production program. Type: 'scons windebug' to build the Windows debug version. -Use scons -H for help about command-line options. +Use scons -H for help about SCons built-in command-line options. diff --git a/doc/generated/examples/output_ex2_2.xml b/doc/generated/examples/output_ex2_2.xml index 4cbccc1345..528caddcab 100644 --- a/doc/generated/examples/output_ex2_2.xml +++ b/doc/generated/examples/output_ex2_2.xml @@ -4,5 +4,5 @@ scons: done reading SConscript files. Type: 'scons program' to build the production program. -Use scons -H for help about command-line options. +Use scons -H for help about SCons built-in command-line options. diff --git a/doc/generated/examples/troubleshoot_Dump_1.xml b/doc/generated/examples/troubleshoot_Dump_1.xml index 6a0fa822be..54d1288d2d 100644 --- a/doc/generated/examples/troubleshoot_Dump_1.xml +++ b/doc/generated/examples/troubleshoot_Dump_1.xml @@ -36,6 +36,7 @@ scons: Reading SConscript files ... 'IDLSUFFIXES': ['.idl', '.IDL'], 'INSTALL': <function copyFunc at 0x700000>, 'INSTALLVERSIONEDLIB': <function copyFuncVersionedLib at 0x700000>, + 'LIBLITERALPREFIX': '', 'LIBPREFIX': 'lib', 'LIBPREFIXES': ['$LIBPREFIX'], 'LIBSUFFIX': '.a', diff --git a/doc/generated/examples/troubleshoot_Dump_2.xml b/doc/generated/examples/troubleshoot_Dump_2.xml index c952602f4e..a29772a2b8 100644 --- a/doc/generated/examples/troubleshoot_Dump_2.xml +++ b/doc/generated/examples/troubleshoot_Dump_2.xml @@ -61,6 +61,7 @@ scons: Reading SConscript files ... 'INSTALL': <function copyFunc at 0x700000>, 'INSTALLVERSIONEDLIB': <function copyFuncVersionedLib at 0x700000>, 'LEXUNISTD': ['--nounistd'], + 'LIBLITERALPREFIX': '', 'LIBPREFIX': '', 'LIBPREFIXES': ['$LIBPREFIX'], 'LIBSUFFIX': '.lib', diff --git a/doc/generated/examples/troubleshoot_explain1_3.xml b/doc/generated/examples/troubleshoot_explain1_3.xml index a0aec9ff91..e658d89fd2 100644 --- a/doc/generated/examples/troubleshoot_explain1_3.xml +++ b/doc/generated/examples/troubleshoot_explain1_3.xml @@ -2,5 +2,5 @@ cp file.in file.oout scons: warning: Cannot find target file.out after building -File "/Users/bdbaddog/devel/scons/git/scons-2/scripts/scons.py", line 97, in <module> +File "/Users/bdbaddog/devel/scons/git/as_scons/scripts/scons.py", line 97, in <module> diff --git a/doc/generated/functions.gen b/doc/generated/functions.gen index 90d263da12..2068b55943 100644 --- a/doc/generated/functions.gen +++ b/doc/generated/functions.gen @@ -328,17 +328,32 @@ file into an object file. Alias(alias, [source, [action]]) env.Alias(alias, [source, [action]]) -Creates a phony target (or targets) that -can be used as references to zero or more other targets, -as specified by the optional source -parameter. -alias and -source +Creates an alias target that +can be used as a reference to zero or more other targets, +specified by the optional source parameter. +Aliases provide a way to give a shorter or more descriptive +name to specific targets, +and to group multiple targets under a single name. +The alias name, or an Alias Node object, +may be used as a dependency of any other target, +including another alias. + + + +alias and source may each be a string or Node object, or a list of strings or Node objects; if Nodes are used for alias they must be Alias nodes. +If source is omitted, +the alias is created but has no reference; +if selected for building this will result in a +Nothing to be done. message. +An empty alias can be used to define the alias +in a visible place in the project; +it can later be appended to in a subsidiary SConscript file +with the actual target(s) to refer to. The optional action parameter specifies an action or list of actions @@ -347,20 +362,18 @@ whenever the any of the alias targets are out-of-date. -Returns a list of Alias Node objects representing the alias(es), -which exist outside of any physical file system. +&f-Alias; can be called for an existing alias, +which appends the alias +and/or action arguments +to the existing lists for that alias. -The alias name, or an Alias Node object, -may be used as a dependency of any other target, -including another alias. -&f-Alias; -can be called multiple times for the same -alias to add additional targets to the alias, -or additional actions to the list for this alias. -Aliases are global even if set through -the construction environment method. +Returns a list of Alias Node objects representing the alias(es), +which exist outside of any physical file system. +The alias name space is separate from the name space for +tangible targets; to avoid confusion do not reuse +target names as alias names. @@ -794,15 +807,6 @@ of disables derived file caching. - -When specifying a -custom_class which should be a class type which is a subclass of -SCons.CacheDir.CacheDir, SCons will -internally invoke this class to use for performing caching operations. -This argument is optional and if left to default None, will use the -default SCons.CacheDir.CacheDir class. - - Calling the environment method &f-link-env-CacheDir; @@ -817,6 +821,18 @@ that do not set up environment-specific caching by calling &f-env-CacheDir;. + +Caching behavior can be configured by passing a specialized cache +class as the optional custom_class parameter. +This class must be a subclass of +SCons.CacheDir.CacheDir. +&SCons; will internally invoke the custom class for performing +caching operations. +If the parameter is omitted or set to +None, &SCons; will use the default +SCons.CacheDir.CacheDir class. + + When derived-file caching is being used and @@ -842,15 +858,14 @@ identified by its &buildsig;, for future use. The Retrieved `file' from cache messages are useful for human consumption, -but less so when comparing log files between +but less useful when comparing log files between &scons; runs which will show differences that are noisy and not actually significant. To disable, use the option. -With this option, &scons; -will print the action that would -have been used to build the file without -considering cache retrieval. +With this option, &scons; changes printing +to always show the action that would +have been used to build the file without caching. @@ -858,10 +873,10 @@ Derived-file caching may be disabled for any invocation of &scons; by giving the -command line option. -Cache updating may be disabled, leaving cache +command line option; +cache updating may be disabled, leaving cache fetching enabled, by giving the -. + option. @@ -871,7 +886,7 @@ option is used, &scons; will place a copy of all -derived files in the cache, +derived files into the cache, even if they already existed and were not built by this invocation. This is useful to populate a cache @@ -896,7 +911,7 @@ predict or prohibitively large. Note that (at this time) &SCons; provides no facilities for managing the derived-file cache. It is up to the developer -to arrange for cache pruning, expiry, etc. if needed. +to arrange for cache pruning, expiry, access control, etc. if needed. @@ -1447,26 +1462,39 @@ env.Default(hello) DefaultEnvironment([**kwargs]) -Instantiates and returns the default &consenv; object. -The &defenv; is used internally by SCons -in order to execute many of the global functions in this list +Instantiates and returns the global &consenv; object. +This environment is used internally by SCons +when it executes many of the global functions listed in this section (that is, those not called as methods of a specific &consenv;). +The &defenv; is a singleton: +the keyword arguments are used only on the first call; +on subsequent calls the already-constructed object is returned +and any keyword arguments are silently ignored. +The &defenv; can still be modified after instantiation +in the same way as any other &consenv;. +The &defenv; is independent: +modifying it has no effect on any other &consenv; +constructed by an &f-link-Environment; or &f-link-Clone; call. + + + It is not mandatory to call &f-DefaultEnvironment;: -the &defenv; will be instantiated automatically when the -build phase begins if the function has not been called, +the &defenv; is instantiated automatically when the +build phase begins if this function has not been called; however calling it explicitly gives the opportunity to affect and examine the contents of the &defenv;. +Instantiation happens even if no build instructions +appar to use it, as there are internal uses. +If there are no uses in the project &SConscript; files, +a small performance gain may be seen by calling +&f-DefaultEnvironment; with an empty tools list, +thus avoiding that part of the initialization cost. +This is mainly of interest in testing when &scons; is +launched repeatedly in a short time period: - -The &defenv; is a singleton, so the keyword -arguments affect it only on the first call, on subsequent -calls the already-constructed object is returned and -any keyword arguments are silently ignored. -The &defenv; can be modified after instantiation -in the same way as any &consenv;. -Modifying the &defenv; has no effect on the &consenv; -constructed by an &f-link-Environment; or &f-link-Clone; call. - + +DefaultEnvironment(tools=[]) + @@ -1805,25 +1833,45 @@ is used if no value is specified. Export([vars...], [key=value...]) env.Export([vars...], [key=value...]) -Exports variables from the current -SConscript file to a global collection where they can be -imported by other SConscript files. +Exports variables for sharing with other SConscript files. +The variables are added to a global collection where +they can be imported by other SConscript files. vars may be one or more -strings representing variable names to be exported. -If a string contains whitespace, it is split into -separate strings, as if multiple string arguments -had been given. A vars argument -may also be a dictionary, which can be used to map variables -to different names when exported. -Keyword arguments can be used to provide names and their values. +strings, or a list of strings. If any string +contains whitespace, it is split automatically +into individual strings. Each string must +match the name of a variable that is in scope +during evaluation of the current SConscript file, +or an exception is raised. + + + +A vars argument +may also be a dictionary or +individual keyword arguments; +in accordance with &Python; syntax rules, +keyword arguments must come after any +non-keyword arguments. +The dictionary/keyword form can be used +to map the local name of a variable to +a different name to be used for imports. +See the Examples for an illustration of the syntax. &f-Export; calls are cumulative. Specifying a previously -exported variable will overwrite the earlier value. +exported variable will replace the previous value in the collection. Both local variables and global variables can be exported. + +To use an exported variable, an SConscript must +call &f-link-Import; to bring it into its own scope. +Importing creates an additional reference to the object that +was originally exported, so if that object is mutable, +changes made will be visible to other users of that object. + + Examples: @@ -1850,10 +1898,10 @@ Export({"debug": env}) Note that the &f-link-SConscript; -function supports an &exports; -argument that allows exporting a variable or -set of variables to a specific SConscript file or files. -See the description below. +function also supports an &exports; +argument that allows exporting one or more variables +to the SConscript files invoked by that call (only). +See the description of that function for details. @@ -2657,28 +2705,41 @@ sources = Glob("*.cpp", exclude=["os_*_specific_*.cpp"]) \ - Help(text, append=False) - env.Help(text, append=False) + Help(text, append=False, local_only=False) + env.Help(text, append=False, local_only=False) -Specifies a local help message to be printed if the - -argument is given to -&scons;. -Subsequent calls to -&f-Help; -append text to the previously -defined local help text. +Adds text to the help message shown when +&scons; is called with the + or +argument. -For the first call to &f-Help; only, +On the first call to &f-Help;, if append is False -(the default) -any local help message generated through -&f-link-AddOption; calls is replaced. +(the default), any existing help text is discarded. +The default help text is the help for the &scons; +command itself plus help collected from any +project-local &f-link-AddOption; calls. +This is the help printed if &f-Help; has never been called. If append is True, text is appended to the existing help text. +If local_only is also True +(the default is False), +the project-local help from &f-AddOption; calls is preserved +in the help message but the &scons; command help is not. + + +Subsequent calls to +&f-Help; ignore the keyword arguments +append and +local_only +and always append to the existing help text. + + +Changed in 4.6.0: added local_only. + @@ -2724,23 +2785,31 @@ env.Ignore('bar', 'bar/foobar.obj') Import(vars...) env.Import(vars...) -Imports variables into the current SConscript file. +Imports variables into the scope of the current SConscript file. vars must be strings representing names of variables which have been previously exported either by the &f-link-Export; function or by the -&exports; argument to -&f-link-SConscript;. -Variables exported by -&f-SConscript; +&exports; argument to the +&f-link-SConscript; function. +Variables exported by the +&f-SConscript; call take precedence. Multiple variable names can be passed to &f-Import; -as separate arguments or as words in a space-separated string. +as separate arguments, as a list of strings, +or as words in a space-separated string. The wildcard "*" can be used to import all available variables. + +If the imported variable is mutable, +changes made locally will be reflected in the object the +variable is bound to. This allows subsidiary SConscript files +to contribute to building up, for example, a &consenv;. + + Examples: @@ -3623,12 +3692,12 @@ for a complete explanation of the arguments and behavior. - SConscript(scripts, [exports, variant_dir, duplicate, must_exist]) - env.SConscript(scripts, [exports, variant_dir, duplicate, must_exist]) + SConscript(scriptnames, [exports, variant_dir, duplicate, must_exist]) + env.SConscript(scriptnames, [exports, variant_dir, duplicate, must_exist]) SConscript(dirs=subdirs, [name=scriptname, exports, variant_dir, duplicate, must_exist]) env.SConscript(dirs=subdirs, [name=scriptname, exports, variant_dir, duplicate, must_exist]) -Executes one or more subsidiary SConscript (configuration) files. +Executes subsidiary SConscript (build configuration) file(s). There are two ways to call the &f-SConscript; function. @@ -3636,30 +3705,31 @@ There are two ways to call the The first calling style is to supply one or more SConscript file names -as the first (positional) argument. -A single script may be specified as a string; -multiple scripts must be specified as a list of strings -(either explicitly or as created by -a function like -&f-link-Split;). +as the first positional argument, +which can be a string or a list of strings. +If there is a second positional argument, +it is treated as if the +exports +keyword argument had been given (see below). Examples: SConscript('SConscript') # run SConscript in the current directory SConscript('src/SConscript') # run SConscript in the src directory SConscript(['src/SConscript', 'doc/SConscript']) +SConscript(Split('src/SConscript doc/SConscript')) config = SConscript('MyConfig.py') -The other calling style is to omit the positional argument naming -scripts and instead specify a list of directory names using the +The second calling style is to omit the positional argument naming +the script and instead specify directory names using the dirs keyword argument. +The value can be a string or list of strings. In this case, &scons; -will -execute a subsidiary configuration file named -&SConscript; +will execute a subsidiary configuration file named +&SConscript; (by default) in each of the specified directories. You may specify a name other than &SConscript; @@ -3679,22 +3749,29 @@ SConscript(dirs=['sub1', 'sub2'], name='MySConscript') The optional exports -keyword argument provides a string or list of strings representing -variable names, or a dictionary of named values, to export. -For the first calling style only, a second positional argument -will be interpreted as exports; the -second calling style must use the keyword argument form -for exports. +keyword argument specifies variables to make available +for use by the called SConscripts, +which are evaluated in an isolated context +and otherwise do not have access to local variables +from the calling SConscript. +The value may be a string or list of strings representing +variable names, or a dictionary mapping local names to +the names they can be imported by. +For the first (scriptnames) calling style, +a second positional argument will also be interpreted as +exports; +the second (directory) calling style accepts no +positional arguments and must use the keyword form. These variables are locally exported only to the called -SConscript file(s) -and do not affect the global pool of variables managed by the +SConscript file(s), and take precedence over any same-named +variables in the global pool managed by the &f-link-Export; function. The subsidiary SConscript files must use the &f-link-Import; -function to import the variables. +function to import the variables into their local scope. Examples: @@ -3798,15 +3875,18 @@ TODO??? SConscript('build/SConscript', src_dir='src') If the optional must_exist -is True, -causes an exception to be raised if a requested -SConscript file is not found. The current default is -False, -causing only a warning to be emitted, but this default is deprecated -(since 3.1). -For scripts which truly intend to be optional, transition to -explicitly supplying -must_exist=False to the &f-SConscript; call. +is True (the default), +an exception is raised if a requested +SConscript file is not found. +To allow missing scripts to be silently ignored +(the default behavior prior to &SCons; version 3.1), +pass +must_exist=False in the &f-SConscript; call. + + + +Changed in 4.6.0: must_exist +now defaults to True. diff --git a/doc/generated/tools.gen b/doc/generated/tools.gen index c3b4cfc51b..3e1b0dcf6c 100644 --- a/doc/generated/tools.gen +++ b/doc/generated/tools.gen @@ -1078,7 +1078,7 @@ Sets construction variables for the TeX formatter and typesetter. textfile -Set &consvars; for the &b-Textfile; and &b-Substfile; builders. +Set &consvars; for the &b-link-Textfile; and &b-link-Substfile; builders. Sets: &cv-link-FILE_ENCODING;, &cv-link-LINESEPARATOR;, &cv-link-SUBSTFILEPREFIX;, &cv-link-SUBSTFILESUFFIX;, &cv-link-TEXTFILEPREFIX;, &cv-link-TEXTFILESUFFIX;.Uses: &cv-link-SUBST_DICT;. @@ -1104,9 +1104,9 @@ provides &b-POTUpdate; builder to make PO yacc -Sets construction variables for the &yacc; parse generator. +Sets construction variables for the &yacc; parser generator. -Sets: &cv-link-YACC;, &cv-link-YACCCOM;, &cv-link-YACCFLAGS;, &cv-link-YACCHFILESUFFIX;, &cv-link-YACCHXXFILESUFFIX;, &cv-link-YACCVCGFILESUFFIX;.Uses: &cv-link-YACCCOMSTR;, &cv-link-YACCFLAGS;, &cv-link-YACC_GRAPH_FILE;, &cv-link-YACC_HEADER_FILE;. +Sets: &cv-link-YACC;, &cv-link-YACCCOM;, &cv-link-YACCFLAGS;, &cv-link-YACCHFILESUFFIX;, &cv-link-YACCHXXFILESUFFIX;, &cv-link-YACCVCGFILESUFFIX;, &cv-link-YACC_GRAPH_FILE_SUFFIX;.Uses: &cv-link-YACCCOMSTR;, &cv-link-YACCFLAGS;, &cv-link-YACC_GRAPH_FILE;, &cv-link-YACC_HEADER_FILE;. zip diff --git a/doc/generated/variables.gen b/doc/generated/variables.gen index 86395a70e1..621853d5f9 100644 --- a/doc/generated/variables.gen +++ b/doc/generated/variables.gen @@ -375,8 +375,8 @@ env['BUILDERS']['NewBuilder'] = bld The class type that SCons should use when instantiating a -new &f-link-CacheDir; for the given environment. It must be -a subclass of the SCons.CacheDir.CacheDir class. +new &f-link-CacheDir; in this &consenv;. Must be +a subclass of the SCons.CacheDir.CacheDir class. @@ -466,7 +466,7 @@ when the &cv-link-PDB; &consvar; is set. -The Visual C++ compiler option that SCons uses by default +The Visual C++ compiler option that &SCons; uses by default to generate PDB information is . This works correctly with parallel () builds because it embeds the debug information in the intermediate object files, @@ -895,23 +895,23 @@ to each directory in &cv-link-CPPPATH;. The list of directories that the C preprocessor will search for include directories. The C/C++ implicit dependency scanner will search these -directories for include files. +directories for include files. In general it's not advised to put include directory directives directly into &cv-link-CCFLAGS; or &cv-link-CXXFLAGS; as the result will be non-portable and the directories will not be searched by the dependency scanner. &cv-CPPPATH; should be a list of path strings, or a single string, not a pathname list joined by -Python's os.sep. +Python's os.pathsep. Note: directory names in &cv-CPPPATH; will be looked-up relative to the directory of the SConscript file -when they are used in a command. +when they are used in a command. To force &scons; -to look-up a directory relative to the root of the source tree use +to look-up a directory relative to the root of the source tree use the # prefix: @@ -1182,6 +1182,42 @@ General options that are passed to the D compiler. DFLAGSUFFIX. + + + + + DI_FILE_DIR + + +Path where .di files will be generated + + + + + + DI_FILE_DIR_PREFIX + + +Prefix to send the di path argument to compiler + + + + + + DI_FILE_DIR_SUFFFIX + + +Suffix to send the di path argument to compiler + + + + + + DI_FILE_SUFFIX + + +Suffix of d include files default is .di + @@ -2706,8 +2742,11 @@ target being built. FILE_ENCODING -File encoding used for files written by &b-link-Textfile; and &b-link-Substfile;. Set to "utf-8" by default. -Added in version 4.5.0. +File encoding used for files written by &b-link-Textfile; and &b-link-Substfile;. +Set to "utf-8" by default. + + +New in version 4.5.0. @@ -3183,7 +3222,7 @@ is -dNOPAUSE -dBATCH -sDEVICE=pdfwrite and x86_64 for 64-bit hosts. - Should be considered immutable. + Should be considered immutable. &cv-HOST_ARCH; is not currently used by other platforms, but the option is reserved to do so in future @@ -3201,7 +3240,7 @@ is -dNOPAUSE -dBATCH -sDEVICE=pdfwrite platform argument to &f-link-Environment;). - Should be considered immutable. + Should be considered immutable. &cv-HOST_OS; is not currently used by &SCons;, but the option is reserved to do so in future @@ -4204,7 +4243,7 @@ general information on specifying emitters. An automatically-generated construction variable containing the linker command-line options for specifying libraries to be linked with the resulting target. -The value of &cv-link-_LIBFLAGS; is created +The value of &cv-_LIBFLAGS; is created by respectively prepending and appending &cv-link-LIBLINKPREFIX; and &cv-link-LIBLINKSUFFIX; to each filename in &cv-link-LIBS;. @@ -4233,6 +4272,48 @@ This will be appended to each library in the &cv-link-LIBS; construction variable when the &cv-link-_LIBFLAGS; variable is automatically generated. + + + + + LIBLITERALPREFIX + + +If the linker supports command line syntax directing +that the argument specifying a library should be +searched for literally (without modification), +&cv-LIBLITERALPREFIX; can be set to that indicator. +For example, the GNU linker follows this rule: + +-l:foo searches the library path +for a filename called foo, +without converting it to +libfoo.so or +libfoo.a. + +If &cv-LIBLITERALPREFIX; is set, +&SCons; will not transform a string-valued entry in +&cv-link-LIBS; that starts with that string. +The entry will still be surrounded with +&cv-link-LIBLINKPREFIX; and &cv-link-LIBLINKSUFFIX; +on the command line. +This is useful, for example, +in directing that a static library +be used when both a static and dynamic library are available +and linker policy is to prefer dynamic libraries. +Compared to the example in &cv-link-LIBS;, + + +env.Append(LIBS=":libmylib.a") + + +will let the linker select that specific (static) +library name if found in the library search path. +This differs from using a +File object +to specify the static library, +as the latter bypasses the library search path entirely. + @@ -4244,7 +4325,7 @@ The list of directories that will be searched for libraries specified by the &cv-link-LIBS; &consvar;. &cv-LIBPATH; should be a list of path strings, or a single string, not a pathname list joined by -Python's os.sep. +Python's os.pathsep. +For each &Builder; call that causes linking with libraries, +&SCons; will add the libraries in the setting of &cv-LIBS; +in effect at that moment to the dependecy graph +as dependencies of the target being generated. + + +The library list will transformed to command line +arguments through the automatically-generated +&cv-link-_LIBFLAGS; &consvar; +which is constructed by +respectively prepending and appending the values of the +&cv-link-LIBLINKPREFIX; and &cv-link-LIBLINKSUFFIX; &consvars; +to each library name. + + + +Any command lines you define yourself that need +the libraries from &cv-LIBS; should include &cv-_LIBFLAGS; +(as well as &cv-link-_LIBDIRFLAGS;) +rather than &cv-LIBS;. +For example: + + + +env = Environment(LINKCOM="my_linker $_LIBDIRFLAGS $_LIBFLAGS -o $TARGET $SOURCE") + @@ -4413,6 +4523,7 @@ to reflect the names of the libraries they create. A list of all legal suffixes for library file names. +on the current platform. When searching for library dependencies, SCons will look for files with prefixes from the &cv-link-LIBPREFIXES; list, the base library name, @@ -4852,12 +4963,12 @@ and When set to any true value, -specifies that SCons should batch +specifies that &SCons; should batch compilation of object files when calling the Microsoft Visual C/C++ compiler. All compilations of source files from the same source directory that generate target files in a same output directory -and were configured in SCons using the same &consenv; +and were configured in &SCons; using the same &consenv; will be built in a single call to the compiler. Only source files that have changed since their object files were built will be passed to each compiler invocation @@ -5850,7 +5961,7 @@ The burden is on the user to ensure the requisite UWP libraries are installed. Sets the preferred version of Microsoft Visual C/C++ to use. If the specified version is unavailable (not installed, or not discoverable), tool initialization will fail. -If &cv-MSVC_VERSION; is not set, SCons will (by default) select the +If &cv-MSVC_VERSION; is not set, &SCons; will (by default) select the latest version of Visual C/C++ installed on your system. @@ -6774,17 +6885,19 @@ not the underlying project code itself. PCH -The Microsoft Visual C++ precompiled header that will be used when compiling -object files. This variable is ignored by tools other than Microsoft Visual C++. +A node for the Microsoft Visual C++ precompiled header that will be +used when compiling object files. +This variable is ignored by tools other than Microsoft Visual C++. When this variable is -defined SCons will add options to the compiler command line to +defined, &SCons; will add options to the compiler command line to cause it to use the precompiled header, and will also set up the dependencies for the PCH file. -Example: +Examples: env['PCH'] = File('StdAfx.pch') +env['PCH'] = env.PCH('pch.cc')[0] @@ -6805,7 +6918,7 @@ builder to generated a precompiled header. The string displayed when generating a precompiled header. -If this is not set, then &cv-link-PCHCOM; (the command line) is displayed. +If not set, then &cv-link-PCHCOM; (the command line) is displayed. @@ -9488,7 +9601,7 @@ an alternate command line so the invoked tool will make use of the contents of the temporary file. If you need to replace the default tempfile object, the callable should take into account the settings of -&cv-link-MAXLINELENGTH;, +&cv-link-MAXLINELENGTH;, &cv-link-TEMPFILEPREFIX;, &cv-link-TEMPFILESUFFIX;, &cv-link-TEMPFILEARGJOIN;, @@ -9503,7 +9616,7 @@ and TEMPFILEARGESCFUNC -The default argument escape function is +The default argument escape function is SCons.Subst.quote_spaces. If you need to apply extra operations on a command argument (to fix Windows slashes, normalize paths, etc.) @@ -9732,10 +9845,10 @@ Specify the location of vswhere.exe. It provides full information about installations of 2017 and later editions. With the argument, vswhere.exe can detect installations of the 2010 through 2015 editions with limited data returned. -If VSWHERE is set, SCons will use that location. +If VSWHERE is set, &SCons; will use that location. - Otherwise SCons will look in the following locations and set VSWHERE to the path of the first vswhere.exe + Otherwise &SCons; will look in the following locations and set VSWHERE to the path of the first vswhere.exe located. @@ -10146,7 +10259,9 @@ to disable debug package generation. To enable debug package generation, include this variable set either to None, or to a custom list that does not include the default line. -Added in version 3.1. + + +New in version 3.1. @@ -10502,6 +10617,30 @@ Will be emitted as a command-line option. Use this in preference to including in &cv-link-YACCFLAGS; directly. +New in version 4.4.0. + + + + + YACC_GRAPH_FILE_SUFFIX + + +Previously specified by &cv-link-YACCVCGFILESUFFIX;. + + +The suffix of the file +containing a graph of the grammar automaton +when the option +(or without an option-argument) +is used in &cv-link-YACCFLAGS;. +Note that setting this variable informs &SCons; +how to construct the graph filename for tracking purposes, +it does not affect the actual generated filename. +Various yacc tools have emitted various formats +at different times. +Set this to match what your parser generator produces. + +New in version 4.X.Y. @@ -10514,6 +10653,7 @@ Will be emitted as a command-line option. Use this in preference to including in &cv-link-YACCFLAGS; directly. +New in version 4.4.0. @@ -10554,12 +10694,19 @@ and adds those to the target list. -If a option is present, +If the option is present in &cv-YACCFLAGS; &scons; assumes that the call will also create a header file with the suffix defined by &cv-link-YACCHFILESUFFIX; if the yacc source file ends in a .y suffix, or a file with the suffix defined by &cv-link-YACCHXXFILESUFFIX; if the yacc source file ends in a .yy suffix. +The header will have the same base name as the requested target. +This is only correct if the executable is bison +(or win_bison). +If using Berkeley yacc (byacc), +y.tab.h is always written - +avoid the in this case and +use &cv-link-YACC_HEADER_FILE; instead. @@ -10576,22 +10723,31 @@ with the suffix .output. Also recognized are GNU &bison; options - and its deprecated synonym -, + +(and its deprecated synonym ), which is similar to -but the output filename is named by the option argument; -and , +but gives the option to explicitly name the output header file +through an option argument; +and , which is similar to -but the output filename is named by the option argument. +but gives the option to explicitly name the output graph file +through an option argument. +The file suffixes described for + and above +are not applied if these are used in the option=argument form. Note that files specified by and may not be properly handled -by &SCons; in all situations. Consider using -&cv-link-YACC_HEADER_FILE; and &cv-link-YACC_GRAPH_FILE; instead. +by &SCons; in all situations, and using those in &cv-YACCFLAGS; +should be considered legacy support only. +Consider using &cv-link-YACC_HEADER_FILE; +and &cv-link-YACC_GRAPH_FILE; instead +if the files need to be explicitly named +(new in version 4.4.0). @@ -10602,14 +10758,13 @@ by &SCons; in all situations. Consider using The suffix of the C header file generated by the parser generator -when the - -option is used. -Note that setting this variable does not cause -the parser generator to generate a header -file with the specified suffix, -it exists to allow you to specify -what suffix the parser generator will use of its own accord. +when the option +(or without an option-argument) +is used in &cv-link-YACCFLAGS;. +Note that setting this variable informs &SCons; +how to construct the header filename for tracking purposes, +it does not affect the actual generated filename. +Set this to match what your parser generator produces. The default value is .h. @@ -10622,22 +10777,14 @@ The default value is The suffix of the C++ header file generated by the parser generator -when the - -option is used. -Note that setting this variable does not cause -the parser generator to generate a header -file with the specified suffix, -it exists to allow you to specify -what suffix the parser generator will use of its own accord. -The default value is -.hpp, -except on Mac OS X, -where the default is -${TARGET.suffix}.h. -because the default &bison; parser generator just -appends .h -to the name of the generated C++ file. +when the option +(or without an option-argument) +is used in &cv-link-YACCFLAGS;. +Note that setting this variable informs &SCons; +how to construct the header filename for tracking purposes, +it does not affect the actual generated filename. +Set this to match what your parser generator produces. +The default value is .hpp. @@ -10646,18 +10793,14 @@ to the name of the generated C++ file. YACCVCGFILESUFFIX -The suffix of the file -containing the VCG grammar automaton definition -when the - -option is used. -Note that setting this variable does not cause -the parser generator to generate a VCG -file with the specified suffix, -it exists to allow you to specify -what suffix the parser generator will use of its own accord. -The default value is -.vcg. +Obsoleted. Use &cv-link-YACC_GRAPH_FILE_SUFFIX; instead. +The value is used only if &cv-YACC_GRAPH_FILE_SUFFIX; is not set. +The default value is .gv. + + +Changed in version 4.X.Y: deprecated. The default value +changed from .vcg (&bison; stopped generating +.vcg output with version 2.4, in 2006). diff --git a/doc/generated/variables.mod b/doc/generated/variables.mod index c3787ea8e6..3b3e81eb5a 100644 --- a/doc/generated/variables.mod +++ b/doc/generated/variables.mod @@ -286,6 +286,7 @@ THIS IS AN AUTOMATICALLY-GENERATED FILE. DO NOT EDIT. $_LIBFLAGS"> $LIBLINKPREFIX"> $LIBLINKSUFFIX"> +$LIBLITERALPREFIX"> $LIBPATH"> $LIBPREFIX"> $LIBPREFIXES"> @@ -967,6 +968,7 @@ THIS IS AN AUTOMATICALLY-GENERATED FILE. DO NOT EDIT. $_LIBFLAGS"> $LIBLINKPREFIX"> $LIBLINKSUFFIX"> +$LIBLITERALPREFIX"> $LIBPATH"> $LIBPREFIX"> $LIBPREFIXES"> diff --git a/doc/man/scons.xml b/doc/man/scons.xml index 6a888cbc9c..22f93f5f79 100644 --- a/doc/man/scons.xml +++ b/doc/man/scons.xml @@ -1117,6 +1117,12 @@ since multiple build commands and intervening SCons processing should take place in parallel.) + + + + sconscript + +Enables output indicating entering and exiting each SConscript file. @@ -3948,7 +3954,7 @@ Returns a boolean indicating success or failure. - context.CheckFunc(function_name, [header, language]) + context.CheckFunc(function_name, [header, language, funcargs]) Checks if function_name is usable in the context's local environment, using the compiler @@ -3976,17 +3982,18 @@ char function_name(void); -Note: if header is supplied, -it should not -include the standard header file that declares -function_name, -and it should include a -dummy prototype similar to the default case. -Compilers reject builds where a function call does -not match the declared prototype as happens -if the "real" header is included, -and modern compilers are now rejecting -implicit function declarations. +If header is supplied, it should not include +the standard header file that declares function_name and it +should include a dummy prototype similar to the default case. If +this is not possible, the optional funcargs argument can be used +to specify a string containing an argument list with the same number and type of +arguments as the prototype. The arguments can simply be constant values of the correct +type. Modern C/C++ compilers reject implicit function declarations and may also reject +function calls whose arguments are not type compatible with the prototype. + + + +Changed in version 4.7.0: added the funcargs. Returns a boolean indicating success or failure. diff --git a/doc/sphinx/conf.py b/doc/sphinx/conf.py index bf492f0063..8bfdc419e5 100644 --- a/doc/sphinx/conf.py +++ b/doc/sphinx/conf.py @@ -98,8 +98,9 @@ # The full version, including alpha/beta/rc tags: release = __version__ # The short X.Y version. -major, minor, _ = __version__.split('.') -version = '.'.join([major, minor]) +version_parts = __version__.split('.') +major, minor, patch = version_parts[0:3] +version = '.'.join([major, minor,patch]) # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/doc/user/README b/doc/user/README index f94632d942..1e0bd13aa3 100644 --- a/doc/user/README +++ b/doc/user/README @@ -1,11 +1,12 @@ -# __COPYRIGHT__ +# SPDX-FileCopyrightText: Copyright The SCons Foundation (https://scons.org) +# SPDX-License-Identifier: MIT When adding a new file, add it to main.xml and MANIFEST. To build the .xml files from the .in files: scons -D BUILDDOC=1 foo.xml To build the whole PDF doc from this dir, for testing: - scons -D ../../build/doc/PDF/scons-user.pdf + scons -D ../../build/doc/PDF/scons-user.pdf Writing examples: here's a simple template. diff --git a/doc/user/SConstruct b/doc/user/SConstruct index cfd85cf28e..b64482b776 100644 --- a/doc/user/SConstruct +++ b/doc/user/SConstruct @@ -1,57 +1,36 @@ +# SPDX-FileCopyrightText: Copyright The SCons Foundation (https://scons.org) +# SPDX-License-Identifier: MIT # # SConstruct file for building SCons documentation. # -# -# __COPYRIGHT__ -# -# 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. - import os -env = Environment(ENV={'PATH' : os.environ['PATH']}, - tools=['docbook','gs','zip'], - toolpath=['../../SCons/Tool'], - # DOCBOOK_XSLTPROCFLAGS="--stringparam fop.extensions 1", - DOCBOOK_DEFAULT_XSL_HTML='html.xsl', - DOCBOOK_DEFAULT_XSL_HTMLCHUNKED='chtml.xsl', - DOCBOOK_DEFAULT_XSL_PDF='pdf.xsl') +env = Environment( + ENV={'PATH': os.environ['PATH']}, + tools=['docbook', 'gs', 'zip'], + toolpath=['../../SCons/Tool'], + # DOCBOOK_XSLTPROCFLAGS="--stringparam fop.extensions 1", + DOCBOOK_DEFAULT_XSL_HTML='html.xsl', + DOCBOOK_DEFAULT_XSL_HTMLCHUNKED='chtml.xsl', + DOCBOOK_DEFAULT_XSL_PDF='pdf.xsl', +) has_pdf = False -if (env.WhereIs('fop') or - env.WhereIs('xep')): +if env.WhereIs('fop') or env.WhereIs('xep'): has_pdf = True # # UserGuide for SCons # env.DocbookXInclude('scons_xi.xml', 'main.xml') -env.DocbookXslt('scons_ex.xml', 'scons_xi.xml', - xsl='../xslt/xinclude_examples.xslt') +env.DocbookXslt('scons_ex.xml', 'scons_xi.xml', xsl='../xslt/xinclude_examples.xslt') env.DocbookXInclude('scons_exi.xml', 'scons_ex.xml') -env.DocbookXslt('scons_db.xml', 'scons_exi.xml', - xsl='../xslt/to_docbook.xslt') -env.DocbookHtml('index.html','scons_db.xml') +env.DocbookXslt('scons_db.xml', 'scons_exi.xml', xsl='../xslt/to_docbook.xslt') +env.DocbookHtml('index.html', 'scons_db.xml') env.DocbookHtmlChunked('index.html', 'scons_db.xml', base_dir='scons-user/') if has_pdf: - env.DocbookPdf('scons-user.pdf','scons_db.xml') + env.DocbookPdf('scons-user.pdf', 'scons_db.xml') has_gs = False if env.WhereIs('gs'): @@ -61,7 +40,10 @@ if env.WhereIs('gs'): # Create the EPUB format # if has_gs and has_pdf: - jpg = env.Gs('OEBPS/cover.jpg','scons-user.pdf', - GSFLAGS='-dNOPAUSE -dBATCH -sDEVICE=jpeg -dFirstPage=1 -dLastPage=1 -dJPEGQ=100 -r72x72 -q') + jpg = env.Gs( + 'OEBPS/cover.jpg', + 'scons-user.pdf', + GSFLAGS='-dNOPAUSE -dBATCH -sDEVICE=jpeg -dFirstPage=1 -dLastPage=1 -dJPEGQ=100 -r72x72 -q', + ) epub = env.DocbookEpub('scons-user.epub', 'scons_db.xml', xsl='epub.xsl') env.Depends(epub, jpg) diff --git a/doc/user/actions.xml b/doc/user/actions.xml index c980f9cf3f..d357eb6190 100644 --- a/doc/user/actions.xml +++ b/doc/user/actions.xml @@ -1,8 +1,14 @@ + + + %scons; - + %builders-mod; @@ -11,7 +17,7 @@ %tools-mod; %variables-mod; - + ]> &SCons; Actions - - %scons; - + %builders-mod; @@ -17,7 +17,7 @@ Copyright The SCons Foundation %tools-mod; %variables-mod; - + ]> + + + %scons; - + %builders-mod; @@ -11,7 +17,7 @@ %tools-mod; %variables-mod; - + ]> Alias Targets - - We've already seen how you can use the &Alias; diff --git a/doc/user/ant.xml b/doc/user/ant.xml index e829d0e855..17f44fc52d 100644 --- a/doc/user/ant.xml +++ b/doc/user/ant.xml @@ -1,8 +1,14 @@ + + + %scons; - + %builders-mod; @@ -11,7 +17,7 @@ %tools-mod; %variables-mod; - + ]> Converting From Ant - - XXX diff --git a/doc/user/build-install.xml b/doc/user/build-install.xml index 3f6982c968..f2b7241f3f 100644 --- a/doc/user/build-install.xml +++ b/doc/user/build-install.xml @@ -1,8 +1,8 @@ + + + + %scons; - + %builders-mod; @@ -11,7 +17,7 @@ %tools-mod; %variables-mod; - + ]> Built-In Builders - - &SCons; provides the ability to build a lot of different diff --git a/doc/user/builders-commands.xml b/doc/user/builders-commands.xml index 7d47daef5b..ba01e0c239 100644 --- a/doc/user/builders-commands.xml +++ b/doc/user/builders-commands.xml @@ -1,8 +1,14 @@ + + + %scons; - + %builders-mod; @@ -20,31 +26,6 @@ xsi:schemaLocation="http://www.scons.org/dbxsd/v1.0 http://www.scons.org/dbxsd/v1.0/scons.xsd"> Not Writing a Builder: the &Command; Builder - - + %scons; @@ -20,34 +26,7 @@ xsi:schemaLocation="http://www.scons.org/dbxsd/v1.0 http://www.scons.org/dbxsd/v1.0/scons.xsd"> Extending &SCons;: Writing Your Own Builders - - - + Although &SCons; provides many useful methods for building common software products diff --git a/doc/user/builders.xml b/doc/user/builders.xml index 9fd83d70b9..9383424b4a 100644 --- a/doc/user/builders.xml +++ b/doc/user/builders.xml @@ -1,8 +1,14 @@ + + + %scons; - + %builders-mod; @@ -19,31 +25,6 @@ xsi:schemaLocation="http://www.scons.org/dbxsd/v1.0 http://www.scons.org/dbxsd/v1.0/scons.xsd"> Builders - - This appendix contains descriptions of all of the diff --git a/doc/user/caching.xml b/doc/user/caching.xml index 8cca510ef7..b79cd6259b 100644 --- a/doc/user/caching.xml +++ b/doc/user/caching.xml @@ -1,8 +1,8 @@ + - + + + + %scons; @@ -20,34 +26,7 @@ xsi:schemaLocation="http://www.scons.org/dbxsd/v1.0 http://www.scons.org/dbxsd/v1.0/scons.xsd"> Controlling a Build From the Command Line - - - + &SCons; provides a number of ways for you as the writer of the &SConscript; files diff --git a/doc/user/depends.xml b/doc/user/depends.xml index 62b6d911d3..961edb2c9b 100644 --- a/doc/user/depends.xml +++ b/doc/user/depends.xml @@ -1,4 +1,10 @@ + + + %scons; @@ -20,34 +26,7 @@ xsi:schemaLocation="http://www.scons.org/dbxsd/v1.0 http://www.scons.org/dbxsd/v1.0/scons.xsd"> Dependencies - - - + So far we've seen how &SCons; handles one-time builds. But one of the main functions of a build tool like &SCons; @@ -125,7 +104,7 @@ int main() { printf("Hello, world!\n"); } - By default, &SCons; + By default, &SCons; uses a cryptographic hash of the file's contents, not the file's modification time, to decide whether a file has changed. diff --git a/doc/user/environments.xml b/doc/user/environments.xml index 7118f21bff..b4f86892c7 100644 --- a/doc/user/environments.xml +++ b/doc/user/environments.xml @@ -1,4 +1,10 @@ + + + %scons; @@ -20,31 +26,6 @@ xsi:schemaLocation="http://www.scons.org/dbxsd/v1.0 http://www.scons.org/dbxsd/v1.0/scons.xsd"> Environments - - + + + + %scons; - + %builders-mod; @@ -20,31 +26,6 @@ xsi:schemaLocation="http://www.scons.org/dbxsd/v1.0 http://www.scons.org/dbxsd/v1.0/scons.xsd"> Errors - - XXX diff --git a/doc/user/example.xml b/doc/user/example.xml index a4e3b99115..18783ded8e 100644 --- a/doc/user/example.xml +++ b/doc/user/example.xml @@ -1,8 +1,14 @@ + + + %scons; - + %builders-mod; @@ -11,7 +17,7 @@ %tools-mod; %variables-mod; - + ]> Complex &SCons; Example - - XXX diff --git a/doc/user/external.xml b/doc/user/external.xml index ceeece084d..46314c4cfe 100644 --- a/doc/user/external.xml +++ b/doc/user/external.xml @@ -1,4 +1,10 @@ + + + %scons; @@ -20,33 +26,6 @@ xsi:schemaLocation="http://www.scons.org/dbxsd/v1.0 http://www.scons.org/dbxsd/v1.0/scons.xsd"> Using SCons with other build tools - - Sometimes a project needs to interact with other projects diff --git a/doc/user/factories.xml b/doc/user/factories.xml index 362b6f0c88..905b959325 100644 --- a/doc/user/factories.xml +++ b/doc/user/factories.xml @@ -1,8 +1,14 @@ + + + %scons; - + %builders-mod; @@ -20,31 +26,6 @@ xsi:schemaLocation="http://www.scons.org/dbxsd/v1.0 http://www.scons.org/dbxsd/v1.0/scons.xsd"> Platform-Independent File System Manipulation - - &SCons; provides a number of platform-independent functions, @@ -183,7 +164,7 @@ touch $* The &Copy; factory has a third optional argument which controls how symlinks are copied. - + @@ -404,7 +385,7 @@ SConscript('S') For example, if we need to process a file in a temporary directory in which the processing tool - will create other files that we don't care about, + will create other files that we don't care about, you could use: diff --git a/doc/user/file-removal.xml b/doc/user/file-removal.xml index 6eb688d249..4f93b2066b 100644 --- a/doc/user/file-removal.xml +++ b/doc/user/file-removal.xml @@ -1,8 +1,14 @@ + + + %scons; - + %builders-mod; @@ -11,7 +17,7 @@ %tools-mod; %variables-mod; - + ]> Controlling Removal of Targets - - There are two occasions when &SCons; will, diff --git a/doc/user/functions.xml b/doc/user/functions.xml index 8044310306..9185f4adb8 100644 --- a/doc/user/functions.xml +++ b/doc/user/functions.xml @@ -1,8 +1,14 @@ + + + %scons; - + %builders-mod; @@ -19,31 +25,6 @@ xsi:schemaLocation="http://www.scons.org/dbxsd/v1.0 http://www.scons.org/dbxsd/v1.0/scons.xsd"> Functions and Environment Methods - - This appendix contains descriptions of all of the diff --git a/doc/user/gettext.xml b/doc/user/gettext.xml index 331ec33c81..b9ebbbda7c 100644 --- a/doc/user/gettext.xml +++ b/doc/user/gettext.xml @@ -1,8 +1,14 @@ + + + %scons; - + %builders-mod; @@ -11,7 +17,7 @@ %tools-mod; %variables-mod; - + ]> Internationalization and localization with gettext - - The &t-link-gettext; toolset supports internationalization and localization of SCons-based projects. Builders provided by &t-link-gettext; automatize @@ -76,7 +57,7 @@
Simple project - Let's start with a very simple project, the "Hello world" program + Let's start with a very simple project, the "Hello world" program for example @@ -145,7 +126,7 @@ int main(int argc, char* argv[]) translate the message at runtime. - + Now we shall instruct SCons how to generate and maintain translation files. For that, use the &b-link-Translate; builder and &b-link-MOFiles; builder. The first one takes source files, extracts internationalized @@ -158,7 +139,7 @@ int main(int argc, char* argv[]) called locale. - The completed + The completed SConstruct is as follows: diff --git a/doc/user/hierarchy.xml b/doc/user/hierarchy.xml index 17874d0349..c501e53cb0 100644 --- a/doc/user/hierarchy.xml +++ b/doc/user/hierarchy.xml @@ -1,8 +1,8 @@ + print for debugging, or write a Python function that wants to evaluate a path. - You can force &SCons; to evaluate a top-relative path + You can force &SCons; to evaluate a top-relative path and produce a string that can be used by &Python; code by creating a Node object from it: diff --git a/doc/user/html.xsl b/doc/user/html.xsl index fa3d915bf3..6def44b593 100644 --- a/doc/user/html.xsl +++ b/doc/user/html.xsl @@ -1,28 +1,10 @@ - + diff --git a/doc/user/install.xml b/doc/user/install.xml index d5ea4d8455..b1ae1f6cbd 100644 --- a/doc/user/install.xml +++ b/doc/user/install.xml @@ -1,4 +1,10 @@ + + + %scons; @@ -20,31 +26,6 @@ xsi:schemaLocation="http://www.scons.org/dbxsd/v1.0 http://www.scons.org/dbxsd/v1.0/scons.xsd"> Installing Files in Other Directories: the &Install; Builder - - Once a program is built, diff --git a/doc/user/java.xml b/doc/user/java.xml index 4f1beef5c0..1a51621ba3 100644 --- a/doc/user/java.xml +++ b/doc/user/java.xml @@ -1,4 +1,10 @@ + + + %scons; @@ -19,13 +25,7 @@ xsi:schemaLocation="http://www.scons.org/dbxsd/v1.0 http://www.scons.org/dbxsd/v1.0/scons.xsd"> Java Builds - - - + So far, we've been using examples of building C and C++ programs diff --git a/doc/user/less-simple.xml b/doc/user/less-simple.xml index 8a30cdfbfb..56d4f49f8a 100644 --- a/doc/user/less-simple.xml +++ b/doc/user/less-simple.xml @@ -1,9 +1,10 @@ + + %scons; diff --git a/doc/user/libraries.xml b/doc/user/libraries.xml index d7983c90ce..67e8a52a62 100644 --- a/doc/user/libraries.xml +++ b/doc/user/libraries.xml @@ -1,8 +1,14 @@ + + + %scons; - + %builders-mod; @@ -19,31 +25,6 @@ xsi:schemaLocation="http://www.scons.org/dbxsd/v1.0 http://www.scons.org/dbxsd/v1.0/scons.xsd"> Building and Linking with Libraries - - It's often useful to organize large software projects @@ -269,7 +250,7 @@ void f3() { printf("f3.c\n"); } You link libraries with a program by specifying the libraries in the &cv-link-LIBS; construction variable, and by specifying the directory in which - the library will be found in the + the library will be found in the &cv-link-LIBPATH; construction variable: diff --git a/doc/user/main.xml b/doc/user/main.xml index de549a8bcb..ba18279d1a 100644 --- a/doc/user/main.xml +++ b/doc/user/main.xml @@ -1,29 +1,8 @@ - The SCons Development Team - Released: Mon, 21 Mar 2023 12:25:39 -0400 + Released: Mon, 19 Nov 2023 17:52:53 -0700 2004 - 2023 diff --git a/doc/user/make.xml b/doc/user/make.xml index 7df8f6a35d..f7ca40e758 100644 --- a/doc/user/make.xml +++ b/doc/user/make.xml @@ -1,8 +1,14 @@ + + + %scons; - + %builders-mod; @@ -10,7 +16,7 @@ %tools-mod; - %variables-mod; + %variables-mod; ]> Converting From Make - - + %scons; @@ -19,34 +25,7 @@ xsi:schemaLocation="http://www.scons.org/dbxsd/v1.0 http://www.scons.org/dbxsd/v1.0/scons.xsd"> Merging Options into the Environment: the &MergeFlags; Function - - - + &SCons; &consenvs; have a &f-link-MergeFlags; method that merges values from a passed-in argument into the &consenv;. diff --git a/doc/user/misc.xml b/doc/user/misc.xml index b1e150786b..ef2e4ca1d6 100644 --- a/doc/user/misc.xml +++ b/doc/user/misc.xml @@ -1,4 +1,10 @@ + + + %scons; @@ -19,31 +25,6 @@ xsi:schemaLocation="http://www.scons.org/dbxsd/v1.0 http://www.scons.org/dbxsd/v1.0/scons.xsd"> Miscellaneous Functionality - - &SCons; supports a lot of additional functionality diff --git a/doc/user/nodes.xml b/doc/user/nodes.xml index 66b37cf28e..83514ec5f1 100644 --- a/doc/user/nodes.xml +++ b/doc/user/nodes.xml @@ -1,4 +1,10 @@ + + + %scons; @@ -19,34 +25,7 @@ xsi:schemaLocation="http://www.scons.org/dbxsd/v1.0 http://www.scons.org/dbxsd/v1.0/scons.xsd"> Node Objects - - - + Internally, &SCons; represents all of the files and directories it knows about as &Nodes;. diff --git a/doc/user/output.xml b/doc/user/output.xml index 7a63540516..ebcf06eef9 100644 --- a/doc/user/output.xml +++ b/doc/user/output.xml @@ -1,8 +1,8 @@ + + + + %scons; @@ -19,31 +25,6 @@ xsi:schemaLocation="http://www.scons.org/dbxsd/v1.0 http://www.scons.org/dbxsd/v1.0/scons.xsd"> Merging Options While Creating Environment: the <parameter>parse_flags</parameter> Parameter - - It is also possible to merge &consvar; values from arguments diff --git a/doc/user/parseconfig.xml b/doc/user/parseconfig.xml index fc9a889e36..c7fcdd63bd 100644 --- a/doc/user/parseconfig.xml +++ b/doc/user/parseconfig.xml @@ -1,4 +1,10 @@ + + + %scons; @@ -19,34 +25,7 @@ xsi:schemaLocation="http://www.scons.org/dbxsd/v1.0 http://www.scons.org/dbxsd/v1.0/scons.xsd"> Finding Installed Library Information: the &ParseConfig; Function - - - + Configuring the right options to build programs to work with libraries--especially shared libraries--that are available diff --git a/doc/user/parseflags.xml b/doc/user/parseflags.xml index a1ab7af8f2..a007f9b573 100644 --- a/doc/user/parseflags.xml +++ b/doc/user/parseflags.xml @@ -1,4 +1,10 @@ + + + %scons; @@ -19,34 +25,7 @@ xsi:schemaLocation="http://www.scons.org/dbxsd/v1.0 http://www.scons.org/dbxsd/v1.0/scons.xsd"> Separating Compile Arguments into their Variables: the &ParseFlags; Function - - - + &SCons; has a bewildering array of &consvars; for different types of options when building programs. diff --git a/doc/user/pdf.xsl b/doc/user/pdf.xsl index c975f0e809..a6281889e7 100644 --- a/doc/user/pdf.xsl +++ b/doc/user/pdf.xsl @@ -1,27 +1,8 @@ - + + + %scons; @@ -19,34 +25,7 @@ xsi:schemaLocation="http://www.scons.org/dbxsd/v1.0 http://www.scons.org/dbxsd/v1.0/scons.xsd"> Preface - - - + Thank you for taking the time to read about &SCons;. &SCons; is a modern diff --git a/doc/user/python.xml b/doc/user/python.xml index cac61a1841..dc10a7b484 100644 --- a/doc/user/python.xml +++ b/doc/user/python.xml @@ -1,8 +1,14 @@ + + + %scons; - + %builders-mod; @@ -20,31 +26,6 @@ xsi:schemaLocation="http://www.scons.org/dbxsd/v1.0 http://www.scons.org/dbxsd/v1.0/scons.xsd"> Python overview - - This section will provide a brief overview of @@ -155,7 +136,7 @@ while: statements look like break statements look like - + continue statements look like diff --git a/doc/user/repositories.xml b/doc/user/repositories.xml index 77d75d3f34..261ec0a05f 100644 --- a/doc/user/repositories.xml +++ b/doc/user/repositories.xml @@ -1,8 +1,14 @@ + + + %scons; - + %builders-mod; @@ -19,31 +25,6 @@ xsi:schemaLocation="http://www.scons.org/dbxsd/v1.0 http://www.scons.org/dbxsd/v1.0/scons.xsd"> Building From Code Repositories - - Often, a software project will have @@ -540,7 +521,7 @@ int f2() { printf("file2.c\n"); } - + (Note that this is safe even if the &SConstruct; file lists /usr/repository1 as a repository, because &SCons; will remove the current build directory diff --git a/doc/user/run.xml b/doc/user/run.xml index 3025afb84e..acedd4b293 100644 --- a/doc/user/run.xml +++ b/doc/user/run.xml @@ -1,8 +1,14 @@ + + + %scons; - + %builders-mod; @@ -19,31 +25,6 @@ xsi:schemaLocation="http://www.scons.org/dbxsd/v1.0 http://www.scons.org/dbxsd/v1.0/scons.xsd"> How to Run &SCons; - - + %scons; - + %builders-mod; @@ -19,33 +25,6 @@ xsi:schemaLocation="http://www.scons.org/dbxsd/v1.0 http://www.scons.org/dbxsd/v1.0/scons.xsd"> Extending &SCons;: Writing Your Own Scanners - - + %scons; @@ -19,34 +25,7 @@ xsi:schemaLocation="http://www.scons.org/dbxsd/v1.0 http://www.scons.org/dbxsd/v1.0/scons.xsd"> Multi-Platform Configuration (&Autoconf; Functionality) - - - + &SCons; has integrated support for build configuration similar in style to GNU &Autoconf;, but designed to be diff --git a/doc/user/scons_title.xsl b/doc/user/scons_title.xsl index 2eb1293e97..634cb2fee6 100644 --- a/doc/user/scons_title.xsl +++ b/doc/user/scons_title.xsl @@ -1,31 +1,13 @@ - - @@ -385,14 +367,14 @@ - + - + @@ -436,7 +418,7 @@ - + @@ -471,7 +453,7 @@ - + @@ -513,7 +495,7 @@ - + @@ -4702,7 +4684,7 @@ - + @@ -5585,7 +5567,7 @@ page-position="first"/> - @@ -5607,7 +5589,7 @@ page-position="first"/> - @@ -5619,7 +5601,7 @@ - + @@ -5627,7 +5609,7 @@ page-position="first"/> - @@ -5647,7 +5629,7 @@ page-position="first"/> - @@ -5672,7 +5654,7 @@ - @@ -5788,7 +5770,7 @@ - + @@ -5800,7 +5782,7 @@ - @@ -6003,22 +5985,22 @@ - - - - @@ -6042,7 +6024,7 @@ - @@ -6055,7 +6037,7 @@ - @@ -6063,7 +6045,7 @@ - diff --git a/doc/user/separate.xml b/doc/user/separate.xml index 0485a55e51..897a126e36 100644 --- a/doc/user/separate.xml +++ b/doc/user/separate.xml @@ -1,8 +1,8 @@ + + + + %scons; @@ -19,31 +25,6 @@ xsi:schemaLocation="http://www.scons.org/dbxsd/v1.0 http://www.scons.org/dbxsd/v1.0/scons.xsd"> Declaring Additional Outputs: the &f-SideEffect; Function - - Sometimes the way an action is defined causes effects on files @@ -157,7 +138,7 @@ f1 = env.Command( Unfortunately, the tool which sets up the &b-Program; builder for the MSVC compiler chain does not come prebuilt with an understanding of the details of the .ilk - example - that the target list would need to change + example - that the target list would need to change in the presence of that specific option flag. Unlike the trivial example above where we could simply tell the &Command; builder there were two targets of the action, modifying the diff --git a/doc/user/simple.xml b/doc/user/simple.xml index 6f27b4f13c..3fa8cea72d 100644 --- a/doc/user/simple.xml +++ b/doc/user/simple.xml @@ -1,8 +1,8 @@ + + + + %scons; - + %builders-mod; @@ -19,31 +25,6 @@ xsi:schemaLocation="http://www.scons.org/dbxsd/v1.0 http://www.scons.org/dbxsd/v1.0/scons.xsd"> Handling Common Tasks - - There is a common set of simple tasks that many build configurations rely on as they become more complex. Most build tools have special @@ -122,14 +103,14 @@ env.Append(CPPPATH = "#") env.Append(BUILDERS = {'Copy1' : Builder(action = 'cat < $SOURCE > $TARGET', suffix='.h', src_suffix='.bar')}) -env.Copy1('test.bar') # produces test.h from test.bar. +env.Copy1('test.bar') # produces test.h from test.bar. env.Program('app','main.cpp') # indirectly depends on test.bar ## Source file example env.Append(BUILDERS = {'Copy2' : Builder(action = 'cat < $SOURCE > $TARGET', suffix='.cpp', src_suffix='.bar2')}) -foo = env.Copy2('foo.bar2') # produces foo.cpp from foo.bar2. +foo = env.Copy2('foo.bar2') # produces foo.cpp from foo.bar2. env.Program('app2',['main2.cpp'] + foo) # compiles main2.cpp and foo.cpp into app2. @@ -163,7 +144,7 @@ produces this: scons -Q - + diff --git a/doc/user/tools.xml b/doc/user/tools.xml index 5aa88ea002..ac46bab582 100644 --- a/doc/user/tools.xml +++ b/doc/user/tools.xml @@ -1,8 +1,14 @@ + + + %scons; - + %builders-mod; @@ -19,31 +25,6 @@ xsi:schemaLocation="http://www.scons.org/dbxsd/v1.0 http://www.scons.org/dbxsd/v1.0/scons.xsd"> Tools - - This appendix contains descriptions of all of the diff --git a/doc/user/troubleshoot.xml b/doc/user/troubleshoot.xml index 24020abb5e..7a13b64da7 100644 --- a/doc/user/troubleshoot.xml +++ b/doc/user/troubleshoot.xml @@ -1,4 +1,10 @@ + + + %scons; @@ -19,31 +25,6 @@ xsi:schemaLocation="http://www.scons.org/dbxsd/v1.0 http://www.scons.org/dbxsd/v1.0/scons.xsd"> Troubleshooting - - The experience of configuring any diff --git a/doc/user/variables.xml b/doc/user/variables.xml index 0677450b74..27e3323a55 100644 --- a/doc/user/variables.xml +++ b/doc/user/variables.xml @@ -1,8 +1,14 @@ + + + %scons; - + %builders-mod; @@ -19,31 +25,6 @@ xsi:schemaLocation="http://www.scons.org/dbxsd/v1.0 http://www.scons.org/dbxsd/v1.0/scons.xsd"> Construction Variables - - This appendix contains descriptions of all of the diff --git a/doc/user/variants.xml b/doc/user/variants.xml index 5096745e35..498d82c775 100644 --- a/doc/user/variants.xml +++ b/doc/user/variants.xml @@ -1,14 +1,15 @@ - %scons; - + %builders-mod; diff --git a/setup.cfg b/setup.cfg index b6d7a5e567..40e24e2985 100644 --- a/setup.cfg +++ b/setup.cfg @@ -45,10 +45,6 @@ classifiers = [options] zip_safe = False python_requires = >=3.6 -install_requires = setuptools -setup_requires = - setuptools - build include_package_data = True packages = find: diff --git a/test/AS/as-live.py b/test/AS/as-live.py index 676b5375ee..4a21fcf6f0 100644 --- a/test/AS/as-live.py +++ b/test/AS/as-live.py @@ -1,6 +1,8 @@ #!/usr/bin/env python # -# __COPYRIGHT__ +# MIT License +# +# Copyright The SCons Foundation # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the @@ -20,9 +22,6 @@ # 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. -# - -__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" """ Verify correct use of the live 'as' assembler. @@ -37,24 +36,22 @@ test = TestSCons.TestSCons() - - if not test.detect('AS', 'as'): test.skip_test("as not found; skipping test\n") x86 = (sys.platform == 'win32' or sys.platform.find('linux') != -1) - if not x86: test.skip_test("skipping as test on non-x86 platform '%s'\n" % sys.platform) namelbl = "name" -testccc = """ccc = aaa.Clone(CPPPATH=['.']) -ccc.Program(target = 'ccc', source = ['ccc.S', 'ccc_main.c']) +testccc = """\ +ccc = aaa.Clone(CPPPATH=['.']) +ccc.Program(target='ccc', source=['ccc.S', 'ccc_main.c']) """ if sys.platform == "win32": namelbl = "_name" testccc = "" - + test.write("wrapper.py", """\ import subprocess import sys @@ -66,30 +63,34 @@ test.write('SConstruct', """\ aaa = Environment() -aaa.Program(target = 'aaa', source = ['aaa.s', 'aaa_main.c']) -bbb = aaa.Clone(AS = r'%(_python_)s wrapper.py ' + WhereIs('as')) -bbb.Program(target = 'bbb', source = ['bbb.s', 'bbb_main.c']) +aaa.Program(target='aaa', source=['aaa.s', 'aaa_main.c']) +bbb = aaa.Clone(AS=r'%(_python_)s wrapper.py ' + WhereIs('as')) +bbb.Program(target='bbb', source=['bbb.s', 'bbb_main.c']) %(testccc)s """ % locals()) -test.write('aaa.s', -""" .file "aaa.s" -.data -.align 4 -.globl %(namelbl)s +test.write('aaa.s', """ + .file "aaa.s" + .data + .align 4 + .globl %(namelbl)s %(namelbl)s: - .ascii "aaa.s" - .byte 0 + .ascii "aaa.s" + .byte 0 + .ident "handcrafted test assembly" + .section .note.GNU-stack,"",@progbits """ % locals()) test.write('bbb.s', """\ -.file "bbb.s" -.data -.align 4 -.globl %(namelbl)s + .file "bbb.s" + .data + .align 4 + .globl %(namelbl)s %(namelbl)s: - .ascii "bbb.s" - .byte 0 + .ascii "bbb.s" + .byte 0 + .ident "handcrafted test assembly" + .section .note.GNU-stack,"",@progbits """ % locals()) test.write('ccc.h', """\ @@ -98,13 +99,15 @@ test.write('ccc.S', """\ #include -.file STRING -.data -.align 4 -.globl name + .file STRING + .data + .align 4 + .globl name name: - .ascii STRING - .byte 0 + .ascii STRING + .byte 0 + .ident "handcrafted test assembly" + .section .note.GNU-stack,"",@progbits """) test.write('aaa_main.c', r""" @@ -171,13 +174,13 @@ if sys.platform != "win32": test.run(program = test.workpath('ccc'), stdout = "ccc_main.c ccc.S\n") - + test.must_match('wrapper.out', "wrapper.py: bbb.s\n") - + test.write("ccc.h", """\ #define STRING "ccc.S 2" """) - + test.run() test.run(program = test.workpath('ccc'), stdout = "ccc_main.c ccc.S 2\n") diff --git a/test/Configure/config-h.py b/test/Configure/config-h.py index e5df6258f1..aca18e42ac 100644 --- a/test/Configure/config-h.py +++ b/test/Configure/config-h.py @@ -41,7 +41,7 @@ import os env.AppendENVPath('PATH', os.environ['PATH']) conf = Configure(env, config_h = 'config.h') -r1 = conf.CheckFunc('printf') +r1 = conf.CheckFunc('printf', header='#include ', funcargs='""') r2 = conf.CheckFunc('noFunctionCall') r3 = conf.CheckFunc('memmove') r4 = conf.CheckType('int') diff --git a/test/Docbook/basic/man/image/refdb.xml b/test/Docbook/basic/man/image/refdb.xml index de5f94e351..502798d6bf 100644 --- a/test/Docbook/basic/man/image/refdb.xml +++ b/test/Docbook/basic/man/image/refdb.xml @@ -65,13 +65,13 @@ See also RefDB (7), refdbd (1) - refdbctl (1). + refdbctl (1). RefDB manual (local copy) <prefix>/share/doc/refdb-<version>/refdb-manual/index.html - RefDB manual (web) <http://refdb.sourceforge.net/manual/index.html> + RefDB manual (web) <https://refdb.sourceforge.net/manual/index.html> - RefDB on the web <http://refdb.sourceforge.net/> + RefDB on the web <https://refdb.sourceforge.net/> diff --git a/test/LINK/VersionedLib-j2.py b/test/LINK/VersionedLib-j2.py index 0cde91c56d..8f84a1a18e 100644 --- a/test/LINK/VersionedLib-j2.py +++ b/test/LINK/VersionedLib-j2.py @@ -1,6 +1,8 @@ #!/usr/bin/env python # -# __COPYRIGHT__ +# MIT License +# +# Copyright The SCons Foundation # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the @@ -20,14 +22,12 @@ # 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. -# - -__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" """ Ensure that SharedLibrary builder works with SHLIBVERSION and -j2. This is regression test for: -http://article.gmane.org/gmane.comp.programming.tools.scons.user/27049 +https://article.gmane.org/gmane.comp.programming.tools.scons.user/27049 +(historical - dead link) """ import TestSCons diff --git a/test/LINK/VersionedLib-subdir.py b/test/LINK/VersionedLib-subdir.py index 66fef63adb..53ae5d0c9c 100644 --- a/test/LINK/VersionedLib-subdir.py +++ b/test/LINK/VersionedLib-subdir.py @@ -1,6 +1,8 @@ #!/usr/bin/env python # -# __COPYRIGHT__ +# MIT License +# +# Copyright The SCons Foundation # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the @@ -20,16 +22,14 @@ # 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. -# - -__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" """ Ensure that SharedLibrary builder with SHLIBVERSION='0.1.2' can build its target -in a subdirectory containing .so.0.1.2 in name. +in a subdirectory containing .so.0.1.2 in name. This is regression test for issue mentioned in: -http://thread.gmane.org/gmane.comp.programming.tools.scons.user/27081 +https://thread.gmane.org/gmane.comp.programming.tools.scons.user/27081 +(historical - dead link) """ import TestSCons diff --git a/test/Libs/LIBLITERALPREFIX.py b/test/Libs/LIBLITERALPREFIX.py new file mode 100644 index 0000000000..ebd32b6ae5 --- /dev/null +++ b/test/Libs/LIBLITERALPREFIX.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python +# +# MIT License +# +# Copyright The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +""" +Test LIBLITERALPREFIX behavior. + +Build a static library and shared library of the same name, +and make sure the requested syntax selects the static one. +Depends on a live compiler to build libraries and executable, +and actually runs the executable. +""" + +import os +import sys + +import TestSCons +from TestSCons import lib_, _lib + +test = TestSCons.TestSCons() + +if sys.platform == 'win32': + import SCons.Tool.MSCommon as msc + if msc.msvc_exists(): + test.skip_test("Functionality not available for msvc, skipping test.\n") + +test.write('SConstruct', """\ +LIBLITERALPREFIX = ":" +env = Environment(LIBLITERALPREFIX=LIBLITERALPREFIX, LIBPATH=".") +lib = env.Library(target='foo', source='foo.c') +shlib = env.SharedLibrary(target='foo', source='shfoo.c') +env.Program(target='prog', source=['prog.c'], LIBS=f"{LIBLITERALPREFIX}libfoo.a") +""") + +test.write('shfoo.c', r""" +#include + +void +foo(void) +{ + printf("shared libfoo\n"); +} +""") + +test.write('foo.c', r""" +#include + +void +foo(void) +{ + printf("static libfoo\n"); +} +""") + +test.write('prog.c', r""" +#include + +void foo(void); +int +main(int argc, char *argv[]) +{ + argv[argc++] = "--"; + foo(); + printf("prog.c\n"); + return 0; +} +""") + +test.run(arguments='.', stderr=TestSCons.noisy_ar, match=TestSCons.match_re_dotall) +test.fail_test(not os.path.exists(test.workpath(f"{lib_}foo{_lib}"))) + +test.run(program=test.workpath('prog'), stdout="static libfoo\nprog.c\n") + +test.pass_test() + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/test/Libs/LIBPREFIXES.py b/test/Libs/LIBPREFIXES.py index e496ca4baa..2bf3c9e205 100644 --- a/test/Libs/LIBPREFIXES.py +++ b/test/Libs/LIBPREFIXES.py @@ -23,6 +23,13 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +""" +Test LIBPREFIXES. + +Depends on a live compiler to build library and executable, +and actually runs the executable. +""" + import os import sys @@ -36,11 +43,10 @@ test = TestSCons.TestSCons() -test.write('SConstruct', """ -env = Environment(LIBPREFIX = 'xxx-', - LIBPREFIXES = ['xxx-']) -lib = env.Library(target = 'foo', source = 'foo.c') -env.Program(target = 'prog', source = ['prog.c', lib]) +test.write('SConstruct', """\ +env = Environment(LIBPREFIX='xxx-', LIBPREFIXES=['xxx-']) +lib = env.Library(target='foo', source='foo.c') +env.Program(target='prog', source=['prog.c', lib]) """) test.write('foo.c', r""" @@ -67,13 +73,10 @@ } """) -test.run(arguments = '.', - stderr=TestSCons.noisy_ar, - match=TestSCons.match_re_dotall) - +test.run(arguments='.', stderr=TestSCons.noisy_ar, match=TestSCons.match_re_dotall) test.fail_test(not os.path.exists(test.workpath('xxx-foo' + _lib))) -test.run(program = test.workpath('prog'), stdout = "foo.c\nprog.c\n") +test.run(program=test.workpath('prog'), stdout="foo.c\nprog.c\n") test.pass_test() diff --git a/test/Libs/LIBSUFFIXES.py b/test/Libs/LIBSUFFIXES.py index 13baeab9ff..06e4506ac4 100644 --- a/test/Libs/LIBSUFFIXES.py +++ b/test/Libs/LIBSUFFIXES.py @@ -1,6 +1,8 @@ #!/usr/bin/env python # -# __COPYRIGHT__ +# MIT License +# +# Copyright The SCons Foundation # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the @@ -20,29 +22,31 @@ # 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. -# -__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" +""" +Test LIBSUFFIXES. + +Depends on a live compiler to build library and executable, +and actually runs the executable. +""" import os import sys + import TestSCons +from TestSCons import lib_ if sys.platform == 'win32': - lib_ = '' import SCons.Tool.MSCommon as msc if not msc.msvc_exists(): lib_ = 'lib' -else: - lib_ = 'lib' test = TestSCons.TestSCons() -test.write('SConstruct', """ -env = Environment(LIBSUFFIX = '.xxx', - LIBSUFFIXES = ['.xxx']) -lib = env.Library(target = 'foo', source = 'foo.c') -env.Program(target = 'prog', source = ['prog.c', lib]) +test.write('SConstruct', """\ +env = Environment(LIBSUFFIX='.xxx', LIBSUFFIXES=['.xxx']) +lib = env.Library(target='foo', source='foo.c') +env.Program(target='prog', source=['prog.c', lib]) """) test.write('foo.c', r""" @@ -69,13 +73,10 @@ } """) -test.run(arguments = '.', - stderr=TestSCons.noisy_ar, - match=TestSCons.match_re_dotall) - +test.run(arguments='.', stderr=TestSCons.noisy_ar, match=TestSCons.match_re_dotall) test.fail_test(not os.path.exists(test.workpath(lib_ + 'foo.xxx'))) -test.run(program = test.workpath('prog'), stdout = "foo.c\nprog.c\n") +test.run(program=test.workpath('prog'), stdout="foo.c\nprog.c\n") test.pass_test() diff --git a/test/Libs/Library.py b/test/Libs/Library.py index 50fe68bf7d..04ba37dff1 100644 --- a/test/Libs/Library.py +++ b/test/Libs/Library.py @@ -1,6 +1,8 @@ #!/usr/bin/env python # -# __COPYRIGHT__ +# MIT License +# +# Copyright The SCons Foundation # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the @@ -20,9 +22,7 @@ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# -__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import TestSCons diff --git a/test/MSVC/PCH-source.py b/test/MSVC/PCH-source.py index 75f6245c8c..84a39bd78b 100644 --- a/test/MSVC/PCH-source.py +++ b/test/MSVC/PCH-source.py @@ -27,7 +27,7 @@ Test use of pre-compiled headers when the source .cpp file shows up in both the env.PCH() and the env.Program() source list. -Issue 2505: http://github.com/SCons/scons/issues/2505 +Issue 2505: https://github.com/SCons/scons/issues/2505 """ import TestSCons diff --git a/test/MSVS/vs-10.0-exec.py b/test/MSVS/vs-10.0-exec.py index df13454a06..cf140a26eb 100644 --- a/test/MSVS/vs-10.0-exec.py +++ b/test/MSVS/vs-10.0-exec.py @@ -90,6 +90,8 @@ test.run(chdir='sub dir', arguments='.') +test.unlink_files('sub dir', ['foo.exe', 'foo.obj', '.sconsign.dblite']) + test.vcproj_sys_path(test.workpath('sub dir', 'foo.vcxproj')) import SCons.Platform.win32 diff --git a/test/MSVS/vs-10.0Exp-exec.py b/test/MSVS/vs-10.0Exp-exec.py index 1c3b56185a..87ddf29cea 100644 --- a/test/MSVS/vs-10.0Exp-exec.py +++ b/test/MSVS/vs-10.0Exp-exec.py @@ -91,6 +91,8 @@ test.run(chdir='sub dir', arguments='.') +test.unlink_files('sub dir', ['foo.exe', 'foo.obj', '.sconsign.dblite']) + test.vcproj_sys_path(test.workpath('sub dir', 'foo.vcxproj')) import SCons.Platform.win32 diff --git a/test/MSVS/vs-11.0-exec.py b/test/MSVS/vs-11.0-exec.py index 82868418a5..27dd79460e 100644 --- a/test/MSVS/vs-11.0-exec.py +++ b/test/MSVS/vs-11.0-exec.py @@ -91,6 +91,8 @@ test.run(chdir='sub dir', arguments='.') +test.unlink_files('sub dir', ['foo.exe', 'foo.obj', '.sconsign.dblite']) + test.vcproj_sys_path(test.workpath('sub dir', 'foo.vcxproj')) import SCons.Platform.win32 diff --git a/test/MSVS/vs-11.0Exp-exec.py b/test/MSVS/vs-11.0Exp-exec.py index e7cffada28..385a50dfcf 100644 --- a/test/MSVS/vs-11.0Exp-exec.py +++ b/test/MSVS/vs-11.0Exp-exec.py @@ -91,6 +91,8 @@ test.run(chdir='sub dir', arguments='.') +test.unlink_files('sub dir', ['foo.exe', 'foo.obj', '.sconsign.dblite']) + test.vcproj_sys_path(test.workpath('sub dir', 'foo.vcxproj')) import SCons.Platform.win32 diff --git a/test/MSVS/vs-14.0-exec.py b/test/MSVS/vs-14.0-exec.py index 7e919ba830..96e3389692 100644 --- a/test/MSVS/vs-14.0-exec.py +++ b/test/MSVS/vs-14.0-exec.py @@ -92,6 +92,8 @@ test.run(chdir='sub dir', arguments='.') +test.unlink_files('sub dir', ['foo.exe', 'foo.obj', '.sconsign.dblite']) + test.vcproj_sys_path(test.workpath('sub dir', 'foo.vcxproj')) import SCons.Platform.win32 diff --git a/test/MSVS/vs-14.0Exp-exec.py b/test/MSVS/vs-14.0Exp-exec.py index 5d2a5859cf..d27d4d5ed4 100644 --- a/test/MSVS/vs-14.0Exp-exec.py +++ b/test/MSVS/vs-14.0Exp-exec.py @@ -55,10 +55,14 @@ test.run(arguments = '-n -q -Q -f -', stdin = """\ env = Environment(tools = ['msvc'], MSVS_VERSION='%(msvs_version)s') -sconsEnv = repr(env['ENV']) -print("os.environ.update(" + sconsEnv + ")") +if env.WhereIs('cl'): + print("os.environ.update(%%s)" %% repr(env['ENV'])) """ % locals()) +if test.stdout() == "": + msg = "Visual Studio %s missing cl.exe; skipping test.\n" % msvs_version + test.skip_test(msg) + exec(test.stdout()) @@ -88,6 +92,8 @@ test.run(chdir='sub dir', arguments='.') +test.unlink_files('sub dir', ['foo.exe', 'foo.obj', '.sconsign.dblite']) + test.vcproj_sys_path(test.workpath('sub dir', 'foo.vcxproj')) import SCons.Platform.win32 diff --git a/test/MSVS/vs-14.1-exec.py b/test/MSVS/vs-14.1-exec.py index 2accaafab6..0a9250923f 100644 --- a/test/MSVS/vs-14.1-exec.py +++ b/test/MSVS/vs-14.1-exec.py @@ -92,6 +92,8 @@ test.run(chdir='sub dir', arguments='.') +test.unlink_files('sub dir', ['foo.exe', 'foo.obj', '.sconsign.dblite']) + test.vcproj_sys_path(test.workpath('sub dir', 'foo.vcxproj')) import SCons.Platform.win32 diff --git a/test/MSVS/vs-14.2-exec.py b/test/MSVS/vs-14.2-exec.py index 5a3079d075..a95982b85c 100644 --- a/test/MSVS/vs-14.2-exec.py +++ b/test/MSVS/vs-14.2-exec.py @@ -92,6 +92,8 @@ test.run(chdir='sub dir', arguments='.') +test.unlink_files('sub dir', ['foo.exe', 'foo.obj', '.sconsign.dblite']) + test.vcproj_sys_path(test.workpath('sub dir', 'foo.vcxproj')) import SCons.Platform.win32 diff --git a/test/MSVS/vs-14.3-exec.py b/test/MSVS/vs-14.3-exec.py index 840b4324d5..ef9dc33de5 100644 --- a/test/MSVS/vs-14.3-exec.py +++ b/test/MSVS/vs-14.3-exec.py @@ -92,6 +92,8 @@ test.run(chdir='sub dir', arguments='.') +test.unlink_files('sub dir', ['foo.exe', 'foo.obj', '.sconsign.dblite']) + test.vcproj_sys_path(test.workpath('sub dir', 'foo.vcxproj')) import SCons.Platform.win32 diff --git a/test/MSVS/vs-6.0-exec.py b/test/MSVS/vs-6.0-exec.py index ab708726fd..7cffb48373 100644 --- a/test/MSVS/vs-6.0-exec.py +++ b/test/MSVS/vs-6.0-exec.py @@ -90,9 +90,11 @@ test.run(chdir='sub dir', arguments='.') +test.unlink_files('sub dir', ['foo.exe', 'foo.obj', '.sconsign.dblite']) + test.run(chdir='sub dir', program=[test.get_msvs_executable(msvs_version)], - arguments=['Test.dsp', '/MAKE', 'foo - Win32 Release']) + arguments=['foo.dsp', '/MAKE', 'foo - Win32 Release']) test.run(program=test.workpath('sub dir', 'foo'), stdout="foo.c\n") diff --git a/test/MSVS/vs-7.0-exec.py b/test/MSVS/vs-7.0-exec.py index 3c41aa56a3..7e81d4e70c 100644 --- a/test/MSVS/vs-7.0-exec.py +++ b/test/MSVS/vs-7.0-exec.py @@ -90,6 +90,8 @@ test.run(chdir='sub dir', arguments='.') +test.unlink_files('sub dir', ['foo.exe', 'foo.obj', '.sconsign.dblite']) + test.vcproj_sys_path(test.workpath('sub dir', 'foo.vcproj')) test.run(chdir='sub dir', diff --git a/test/MSVS/vs-7.1-exec.py b/test/MSVS/vs-7.1-exec.py index f66b92d793..e7f4203f79 100644 --- a/test/MSVS/vs-7.1-exec.py +++ b/test/MSVS/vs-7.1-exec.py @@ -90,6 +90,8 @@ test.run(chdir='sub dir', arguments='.') +test.unlink_files('sub dir', ['foo.exe', 'foo.obj', '.sconsign.dblite']) + test.vcproj_sys_path(test.workpath('sub dir', 'foo.vcproj')) test.run(chdir='sub dir', diff --git a/test/MSVS/vs-8.0-exec.py b/test/MSVS/vs-8.0-exec.py index 91e99dd5a0..3a3de05295 100644 --- a/test/MSVS/vs-8.0-exec.py +++ b/test/MSVS/vs-8.0-exec.py @@ -91,6 +91,8 @@ test.run(chdir='sub dir', arguments='.') +test.unlink_files('sub dir', ['foo.exe', 'foo.obj', '.sconsign.dblite']) + test.vcproj_sys_path(test.workpath('sub dir', 'foo.vcproj')) import SCons.Platform.win32 diff --git a/test/MSVS/vs-8.0Exp-exec.py b/test/MSVS/vs-8.0Exp-exec.py index 77cae646e1..a3a83ada8b 100644 --- a/test/MSVS/vs-8.0Exp-exec.py +++ b/test/MSVS/vs-8.0Exp-exec.py @@ -91,6 +91,8 @@ test.run(chdir='sub dir', arguments='.') +test.unlink_files('sub dir', ['foo.exe', 'foo.obj', '.sconsign.dblite']) + test.vcproj_sys_path(test.workpath('sub dir', 'foo.vcproj')) import SCons.Platform.win32 diff --git a/test/MSVS/vs-9.0-exec.py b/test/MSVS/vs-9.0-exec.py index 26a115df34..e112f7a20f 100644 --- a/test/MSVS/vs-9.0-exec.py +++ b/test/MSVS/vs-9.0-exec.py @@ -91,6 +91,8 @@ test.run(chdir='sub dir', arguments='.') +test.unlink_files('sub dir', ['foo.exe', 'foo.obj', '.sconsign.dblite']) + test.vcproj_sys_path(test.workpath('sub dir', 'foo.vcproj')) import SCons.Platform.win32 diff --git a/test/MSVS/vs-9.0Exp-exec.py b/test/MSVS/vs-9.0Exp-exec.py index 0c274ba58b..cc95ba9715 100644 --- a/test/MSVS/vs-9.0Exp-exec.py +++ b/test/MSVS/vs-9.0Exp-exec.py @@ -91,6 +91,8 @@ test.run(chdir='sub dir', arguments='.') +test.unlink_files('sub dir', ['foo.exe', 'foo.obj', '.sconsign.dblite']) + test.vcproj_sys_path(test.workpath('sub dir', 'foo.vcproj')) import SCons.Platform.win32 diff --git a/test/SWIG/SWIG.py b/test/SWIG/SWIG.py index 625b5f1be8..5858952241 100644 --- a/test/SWIG/SWIG.py +++ b/test/SWIG/SWIG.py @@ -1,6 +1,8 @@ #!/usr/bin/env python # -# __COPYRIGHT__ +# MIT License +# +# Copyright The SCons Foundation # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the @@ -20,9 +22,6 @@ # 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. -# - -__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" """ Verify that the swig tool generates file names that we expect. @@ -55,7 +54,7 @@ print("") print("Configured options: +pcre") print("") - print("Please see http://www.swig.org for reporting bugs " + print("Please see https://www.swig.org for reporting bugs " "and further information") sys.exit(0) diff --git a/test/TEX/generated_files.py b/test/TEX/generated_files.py index 418d99b7cd..f5d0aaa365 100644 --- a/test/TEX/generated_files.py +++ b/test/TEX/generated_files.py @@ -1,6 +1,8 @@ #!/usr/bin/env python # -# __COPYRIGHT__ +# MIT License +# +# Copyright The SCons Foundation # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the @@ -20,9 +22,6 @@ # 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. -# - -__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" r""" Test creation of a Tex document with generated tex files @@ -87,7 +86,7 @@ month = {March}, publisher = {Morgan Kaufmann}, title = {The Art of Multiprocessor Programming}, - url = {http://www.worldcat.org/isbn/0123705916}, + url = {https://www.worldcat.org/isbn/0123705916}, year = {2008} } @@ -106,7 +105,7 @@ keywords = {books, model\_checking}, publisher = {The MIT Press}, title = {Principles of Model Checking}, - url = {http://www.worldcat.org/isbn/026202649X}, + url = {https://www.worldcat.org/isbn/026202649X}, year = {2008} } diff --git a/test/TEX/variant_dir.py b/test/TEX/variant_dir.py index 4280f5986e..1db3768937 100644 --- a/test/TEX/variant_dir.py +++ b/test/TEX/variant_dir.py @@ -160,7 +160,7 @@ test.write(['docs', 'test.bib'], """\ %% This BibTeX bibliography file was created using BibDesk. -%% http://bibdesk.sourceforge.net/ +%% https://bibdesk.sourceforge.net/ %% Created for Rob Managan at 2006-11-15 12:53:16 -0800 diff --git a/test/TEX/variant_dir_bibunit.py b/test/TEX/variant_dir_bibunit.py index 2a669ba524..0d286300ac 100644 --- a/test/TEX/variant_dir_bibunit.py +++ b/test/TEX/variant_dir_bibunit.py @@ -1,6 +1,8 @@ #!/usr/bin/env python # -# __COPYRIGHT__ +# MIT License +# +# Copyright The SCons Foundation # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the @@ -20,9 +22,6 @@ # 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. -# - -__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" r""" Test creation of a fully-featured TeX document (with bibunit @@ -151,7 +150,7 @@ test.write(['src', 'units.bib'], """\ %% This BibTeX bibliography file was created using BibDesk. -%% http://bibdesk.sourceforge.net/ +%% https://bibdesk.sourceforge.net/ @techreport{gnu:1998, Author = {A. N. Author}, diff --git a/test/TEX/variant_dir_dup0.py b/test/TEX/variant_dir_dup0.py index ea6b51e098..cc2e0f635b 100644 --- a/test/TEX/variant_dir_dup0.py +++ b/test/TEX/variant_dir_dup0.py @@ -1,6 +1,8 @@ #!/usr/bin/env python # -# __COPYRIGHT__ +# MIT License +# +# Copyright The SCons Foundation # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the @@ -20,9 +22,6 @@ # 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. -# - -__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" r""" Test creation of a fully-featured TeX document (with bibliography @@ -160,7 +159,7 @@ test.write(['docs', 'test.bib'], """\ %% This BibTeX bibliography file was created using BibDesk. -%% http://bibdesk.sourceforge.net/ +%% https://bibdesk.sourceforge.net/ %% Saved with string encoding Western (ASCII) diff --git a/test/TEX/variant_dir_style_dup0.py b/test/TEX/variant_dir_style_dup0.py index 430b7924bf..c12e7bb631 100644 --- a/test/TEX/variant_dir_style_dup0.py +++ b/test/TEX/variant_dir_style_dup0.py @@ -1,6 +1,8 @@ #!/usr/bin/env python # -# __COPYRIGHT__ +# MIT License +# +# Copyright The SCons Foundation # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the @@ -20,9 +22,6 @@ # 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. -# - -__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" r""" Test creation of a fully-featured TeX document (with bibliography @@ -87,7 +86,7 @@ test.write(['docs', 'test.bib'], r""" % This BibTeX bibliography file was created using BibDesk. -% http://bibdesk.sourceforge.net/ +% https://bibdesk.sourceforge.net/ @techreport{AnAuthor:2006fk, Author = {A. N. Author}, diff --git a/test/option/debug-sconscript.py b/test/option/debug-sconscript.py new file mode 100644 index 0000000000..337a2c4e72 --- /dev/null +++ b/test/option/debug-sconscript.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python +# +# MIT License +# +# Copyright The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +""" +Test the --debug=sconscript option +""" + +import os +import TestSCons + +test = TestSCons.TestSCons() + +test.write('SConstruct', """\ +print("SConstruct") +""") + +wpath = test.workpath() + +test.run(arguments=".") +unexpect = [ + 'scons: Entering %s%sSConstruct' % (wpath, os.sep), + 'scons: Exiting %s%sSConstruct' % (wpath, os.sep) +] +test.must_not_contain_any_line(test.stdout(), unexpect) + +test.run(arguments="--debug=sconscript .") +expect = [ + 'scons: Entering %s%sSConstruct' % (wpath, os.sep), + 'scons: Exiting %s%sSConstruct' % (wpath, os.sep) +] +test.must_contain_all_lines(test.stdout(), expect) + +# Ensure that reutrns with stop are handled properly + +test.write('SConstruct', """\ +foo = "bar" +Return("foo", stop=True) +print("SConstruct") +""") + +test.run(arguments="--debug=sconscript .") +test.must_contain_all_lines(test.stdout(), expect) + +test.pass_test() + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/test/packaging/ipkg.py b/test/packaging/ipkg.py index 99c31e534b..2c7ebcf825 100644 --- a/test/packaging/ipkg.py +++ b/test/packaging/ipkg.py @@ -65,7 +65,7 @@ When you modify this example, be sure to change the Package, Version, Maintainer, Depends, and Description fields.''', - SOURCE_URL = 'http://gnu.org/foo-0.0.tar.gz', + SOURCE_URL = 'https://gnu.org/foo-0.0.tar.gz', X_IPK_SECTION = 'extras', X_IPK_PRIORITY = 'optional', ARCHITECTURE = 'arm', @@ -102,7 +102,7 @@ Package: foo Priority: optional Section: extras -Source: http://gnu.org/foo-0.0.tar.gz +Source: https://gnu.org/foo-0.0.tar.gz Version: 0.0 Architecture: arm Maintainer: Familiar User diff --git a/test/packaging/option--package-type.py b/test/packaging/option--package-type.py index 715dc96b8e..089c05d570 100644 --- a/test/packaging/option--package-type.py +++ b/test/packaging/option--package-type.py @@ -1,6 +1,8 @@ #!/usr/bin/env python # -# __COPYRIGHT__ +# MIT License +# +# Copyright The SCons Foundation # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the @@ -20,9 +22,6 @@ # 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. -# - -__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" """ Test the --package-type option. @@ -65,7 +64,7 @@ X_RPM_INSTALL = r'%(_python_)s %(scons)s --install-sandbox="$RPM_BUILD_ROOT" "$RPM_BUILD_ROOT"', DESCRIPTION = 'this should be really long', source = [ prog ], - SOURCE_URL = 'http://foo.org/foo-1.2.3.tar.gz', + SOURCE_URL = 'https://foo.org/foo-1.2.3.tar.gz', ARCHITECTURE = 'noarch' ) """ % locals()) diff --git a/test/packaging/rpm/cleanup.py b/test/packaging/rpm/cleanup.py index 7483750b95..178a13d8da 100644 --- a/test/packaging/rpm/cleanup.py +++ b/test/packaging/rpm/cleanup.py @@ -1,6 +1,8 @@ #!/usr/bin/env python # -# __COPYRIGHT__ +# MIT License +# +# Copyright The SCons Foundation # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the @@ -20,9 +22,6 @@ # 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. -# - -__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" """ Assert that files created by the RPM packager will be removed by 'scons -c'. @@ -71,7 +70,7 @@ X_RPM_INSTALL = r'%(_python_)s %(scons)s --install-sandbox="$RPM_BUILD_ROOT" "$RPM_BUILD_ROOT"', DESCRIPTION = 'this should be really really long', source = [ prog ], - SOURCE_URL = 'http://foo.org/foo-1.2.3.tar.gz' + SOURCE_URL = 'https://foo.org/foo-1.2.3.tar.gz' ) env.Alias( 'install', prog ) diff --git a/test/packaging/rpm/explicit-target.py b/test/packaging/rpm/explicit-target.py index 66f5e4a289..95684ca55c 100644 --- a/test/packaging/rpm/explicit-target.py +++ b/test/packaging/rpm/explicit-target.py @@ -1,6 +1,8 @@ #!/usr/bin/env python # -# __COPYRIGHT__ +# MIT License +# +# Copyright The SCons Foundation # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the @@ -20,9 +22,6 @@ # 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. -# - -__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" """ Test the ability to create a rpm package from a explicit target name. @@ -72,7 +71,7 @@ DESCRIPTION = 'this should be really really long', source = [ prog ], target = "my_rpm_package.rpm", - SOURCE_URL = 'http://foo.org/foo-1.2.3.tar.gz' + SOURCE_URL = 'https://foo.org/foo-1.2.3.tar.gz' ) """ % locals()) diff --git a/test/packaging/rpm/internationalization.py b/test/packaging/rpm/internationalization.py index 612360708d..28f0b450be 100644 --- a/test/packaging/rpm/internationalization.py +++ b/test/packaging/rpm/internationalization.py @@ -1,6 +1,8 @@ #!/usr/bin/env python # -# __COPYRIGHT__ +# MIT License +# +# Copyright The SCons Foundation # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the @@ -20,9 +22,6 @@ # 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. -# - -__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" """ Test the ability to handle internationalized package and file meta-data. @@ -78,7 +77,7 @@ DESCRIPTION_de = 'das sollte wirklich lang sein', DESCRIPTION_fr = 'ceci devrait être vraiment long', source = [ prog ], - SOURCE_URL = 'http://foo.org/foo-1.2.3.tar.gz' + SOURCE_URL = 'https://foo.org/foo-1.2.3.tar.gz' ) env.Alias ( 'install', prog ) @@ -156,7 +155,7 @@ DESCRIPTION_de = 'das sollte wirklich lang sein', DESCRIPTION_fr = 'ceci devrait être vraiment long', source = [ prog, man_pages ], - SOURCE_URL = 'http://foo.org/foo-1.2.3.tar.gz', + SOURCE_URL = 'https://foo.org/foo-1.2.3.tar.gz', ) env.Alias ( 'install', [ prog, man_pages ] ) diff --git a/test/packaging/rpm/multipackage.py b/test/packaging/rpm/multipackage.py index 4a29a4087d..99a94f2c0f 100644 --- a/test/packaging/rpm/multipackage.py +++ b/test/packaging/rpm/multipackage.py @@ -1,6 +1,8 @@ #!/usr/bin/env python # -# __COPYRIGHT__ +# MIT License +# +# Copyright The SCons Foundation # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the @@ -20,9 +22,6 @@ # 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. -# - -__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" """ Test the ability to create more than rpm file with different package root @@ -70,7 +69,7 @@ X_RPM_INSTALL = r'%(_python_)s %(scons)s --tree=all --install-sandbox="$RPM_BUILD_ROOT" "$RPM_BUILD_ROOT"', DESCRIPTION = 'this should be really really long', source = [ prog ], - SOURCE_URL = 'http://foo.org/foo-1.2.3.tar.gz' + SOURCE_URL = 'https://foo.org/foo-1.2.3.tar.gz' ) env.Package( NAME = 'foo2', diff --git a/test/packaging/rpm/package.py b/test/packaging/rpm/package.py index 0ef5fad07d..04a8ff0aa9 100644 --- a/test/packaging/rpm/package.py +++ b/test/packaging/rpm/package.py @@ -1,6 +1,8 @@ #!/usr/bin/env python # -# __COPYRIGHT__ +# MIT License +# +# Copyright The SCons Foundation # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the @@ -20,9 +22,6 @@ # 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. -# - -__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" """ Test the ability to create a really simple rpm package. @@ -69,7 +68,7 @@ X_RPM_INSTALL = r'%(_python_)s %(scons)s --tree=all --install-sandbox="$RPM_BUILD_ROOT" "$RPM_BUILD_ROOT"', DESCRIPTION = 'this should be really really long', source = [ prog ], - SOURCE_URL = 'http://foo.org/foo-1.2.3.tar.gz' + SOURCE_URL = 'https://foo.org/foo-1.2.3.tar.gz' ) env.Alias( 'install', prog ) diff --git a/test/packaging/rpm/tagging.py b/test/packaging/rpm/tagging.py index f02a51b053..a888a37cca 100644 --- a/test/packaging/rpm/tagging.py +++ b/test/packaging/rpm/tagging.py @@ -1,6 +1,8 @@ #!/usr/bin/env python # -# __COPYRIGHT__ +# MIT License +# +# Copyright The SCons Foundation # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the @@ -20,9 +22,6 @@ # 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. -# - -__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" """ Test the ability to add file tags @@ -75,7 +74,7 @@ X_RPM_INSTALL = r'%(_python_)s %(scons)s --install-sandbox="$RPM_BUILD_ROOT" "$RPM_BUILD_ROOT"', DESCRIPTION = 'this should be really really long', source = [ prog_install ], - SOURCE_URL = 'http://foo.org/foo-1.2.3.tar.gz' + SOURCE_URL = 'https://foo.org/foo-1.2.3.tar.gz' ) """ % locals()) diff --git a/test/rebuild-generated.py b/test/rebuild-generated.py index 91b4e1e7c3..1b4292f878 100644 --- a/test/rebuild-generated.py +++ b/test/rebuild-generated.py @@ -26,7 +26,8 @@ """ Test case for the bug report: "[ 1088979 ] Unnecessary rebuild with generated header file" -(). +(). +(historical - dead link) Unnecessary rebuild with generated header file Scons rebuilds some nodes when invoked twice. The diff --git a/testing/framework/TestCmd.py b/testing/framework/TestCmd.py index 48238a023a..98094c875e 100644 --- a/testing/framework/TestCmd.py +++ b/testing/framework/TestCmd.py @@ -802,7 +802,7 @@ def where_is(file, path=None, pathext=None): # From Josiah Carlson, # ASPN : Python Cookbook : Module to allow Asynchronous subprocess use on Windows and Posix platforms -# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/440554 +# https://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/440554 if sys.platform == 'win32': # and subprocess.mswindows: try: @@ -1872,6 +1872,31 @@ def unlink(self, file) -> None: file = self.canonicalize(file) os.unlink(file) + def unlink_files(self, dirpath, files): + """Unlinks a list of file names from the specified directory. + + The directory path may be a list, in which case the elements are + concatenated with the os.path.join() method. + + A file name may be a list, in which case the elements are + concatenated with the os.path.join() method. + + The directory path and file name are concatenated with the + os.path.join() method. The resulting file path is assumed to be + under the temporary working directory unless it is an absolute path + name. An attempt to unlink the resulting file is made only when the + file exists otherwise the file path is ignored. + """ + if is_List(dirpath): + dirpath = os.path.join(*dirpath) + for file in files: + if is_List(file): + file = os.path.join(*file) + filepath = os.path.join(dirpath, file) + filepath = self.canonicalize(filepath) + if os.path.exists(filepath): + self.unlink(filepath) + def verbose_set(self, verbose) -> None: """Sets the verbose level.""" self.verbose = verbose diff --git a/testing/framework/TestCmdTests.py b/testing/framework/TestCmdTests.py index dc752ba4e8..1331f9db4d 100644 --- a/testing/framework/TestCmdTests.py +++ b/testing/framework/TestCmdTests.py @@ -1601,10 +1601,10 @@ def test_rmdir(self): try: test.rmdir(['no', 'such', 'dir']) - except EnvironmentError: + except FileNotFoundError: pass else: - raise Exception("did not catch expected SConsEnvironmentError") + raise Exception("did not catch expected FileNotFoundError") test.subdir(['sub'], ['sub', 'dir'], @@ -1616,19 +1616,19 @@ def test_rmdir(self): try: test.rmdir(['sub']) - except EnvironmentError: + except OSError: pass else: - raise Exception("did not catch expected SConsEnvironmentError") + raise Exception("did not catch expected OSError") assert os.path.isdir(s_d_o), f"{s_d_o} is gone?" try: test.rmdir(['sub']) - except EnvironmentError: + except OSError: pass else: - raise Exception("did not catch expected SConsEnvironmentError") + raise Exception("did not catch expected OSError") assert os.path.isdir(s_d_o), f"{s_d_o} is gone?" @@ -1647,7 +1647,6 @@ def test_rmdir(self): assert not os.path.exists(s), f"{s} exists?" - class run_TestCase(TestCmdTestCase): def test_run(self) -> None: """Test run()""" @@ -2994,6 +2993,103 @@ def test_unlink(self): os.chmod(wdir_file5, 0o600) +class unlink_files_TestCase(TestCmdTestCase): + def test_unlink_files(self): + """Test unlink_files()""" + test = TestCmd.TestCmd(workdir = '', subdir = 'foo') + wdir_file1 = os.path.join(test.workdir, 'file1') + wdir_file2 = os.path.join(test.workdir, 'file2') + wdir_foo_file3a = os.path.join(test.workdir, 'foo', 'file3a') + wdir_foo_file3b = os.path.join(test.workdir, 'foo', 'file3b') + wdir_foo_file3c = os.path.join(test.workdir, 'foo', 'file3c') + wdir_foo_file3d = os.path.join(test.workdir, 'foo', 'file3d') + wdir_foo_file4a = os.path.join(test.workdir, 'foo', 'file4a') + wdir_foo_file4b = os.path.join(test.workdir, 'foo', 'file4b') + wdir_foo_file4c = os.path.join(test.workdir, 'foo', 'file4c') + wdir_foo_file4d = os.path.join(test.workdir, 'foo', 'file4d') + wdir_file5 = os.path.join(test.workdir, 'file5') + + with open(wdir_file1, 'w') as f: + f.write("") + with open(wdir_file2, 'w') as f: + f.write("") + with open(wdir_foo_file3a, 'w') as f: + f.write("") + with open(wdir_foo_file3b, 'w') as f: + f.write("") + with open(wdir_foo_file3c, 'w') as f: + f.write("") + with open(wdir_foo_file3d, 'w') as f: + f.write("") + with open(wdir_foo_file4a, 'w') as f: + f.write("") + with open(wdir_foo_file4b, 'w') as f: + f.write("") + with open(wdir_foo_file4c, 'w') as f: + f.write("") + with open(wdir_foo_file4d, 'w') as f: + f.write("") + with open(wdir_file5, 'w') as f: + f.write("") + + test.unlink_files('', [ + 'no_file_a', + 'no_file_b', + ]) + + test.unlink_files('', [ + 'file1', + 'file2', + ]) + assert not os.path.exists(wdir_file1) + assert not os.path.exists(wdir_file2) + + test.unlink_files('foo', [ + 'file3a', + 'file3b', + ]) + assert not os.path.exists(wdir_foo_file3a) + assert not os.path.exists(wdir_foo_file3b) + + test.unlink_files(['foo'], [ + 'file3c', + 'file3d', + ]) + assert not os.path.exists(wdir_foo_file3c) + assert not os.path.exists(wdir_foo_file3d) + + test.unlink_files('', [ + ['foo', 'file4a'], + ['foo', 'file4b'], + ]) + assert not os.path.exists(wdir_foo_file4a) + assert not os.path.exists(wdir_foo_file4b) + + test.unlink_files([''], [ + ['foo', 'file4c'], + ['foo', 'file4d'], + ]) + assert not os.path.exists(wdir_foo_file4c) + assert not os.path.exists(wdir_foo_file4d) + + # Make it so we can't unlink file5. + # For UNIX, remove write permission from the dir and the file. + # For Windows, open the file. + os.chmod(test.workdir, 0o500) + os.chmod(wdir_file5, 0o400) + with open(wdir_file5, 'r'): + try: + try: + test.unlink_files('', ['file5']) + except OSError: # expect "Permission denied" + pass + except: + raise + finally: + os.chmod(test.workdir, 0o700) + os.chmod(wdir_file5, 0o600) + + class touch_TestCase(TestCmdTestCase): def test_touch(self) -> None: """Test touch()""" diff --git a/testing/framework/TestSCons.py b/testing/framework/TestSCons.py index 39a4245dbc..b6e34905cc 100644 --- a/testing/framework/TestSCons.py +++ b/testing/framework/TestSCons.py @@ -55,7 +55,7 @@ # here provides some independent verification that what we packaged # conforms to what we expect. -default_version = '4.5.3ayyyymmdd' +default_version = '4.7.0ayyyymmdd' # TODO: these need to be hand-edited when there are changes python_version_unsupported = (3, 6, 0) diff --git a/testing/framework/TestUnit/taprunner.py b/testing/framework/TestUnit/taprunner.py index 6f8cb00510..b52c762a82 100644 --- a/testing/framework/TestUnit/taprunner.py +++ b/testing/framework/TestUnit/taprunner.py @@ -1,6 +1,6 @@ """ Format unittest results in Test Anything Protocol (TAP). -http://testanything.org/tap-version-13-specification.html +https://testanything.org/tap-version-13-specification.html Public domain work by: anatoly techtonik From caa65390a5e16603386578eddbe2cf377d61ef8f Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Sun, 7 Jan 2024 09:57:59 -0500 Subject: [PATCH 14/35] Add additional registry keys for 12.0Exp and 11.0Exp detection (VS roots). --- SCons/Tool/MSCommon/vc.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/SCons/Tool/MSCommon/vc.py b/SCons/Tool/MSCommon/vc.py index 5b94747e56..d28b6af3c4 100644 --- a/SCons/Tool/MSCommon/vc.py +++ b/SCons/Tool/MSCommon/vc.py @@ -788,21 +788,25 @@ 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? - (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\14.0\Setup\VC\ProductDir')], # kind detection + (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\WDExpress\14.0\Setup\VS\ProductDir'), + (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\14.0\Setup\VC\ProductDir'), + (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\14.0\Setup\VC\ProductDir') + ], '12.0': [ (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\12.0\Setup\VC\ProductDir'), ], '12.0Exp': [ + (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\WDExpress\12.0\Setup\VS\ProductDir'), (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\12.0\Setup\VC\ProductDir'), ], '11.0': [ (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\11.0\Setup\VC\ProductDir'), ], '11.0Exp': [ + (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\WDExpress\11.0\Setup\VS\ProductDir'), (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\11.0\Setup\VC\ProductDir'), ], '10.0': [ @@ -1415,8 +1419,8 @@ def find_vc_pdir_registry(msvc_version): # 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 + elif msvc_version in ('14.0Exp', '12.0Exp', '11.0Exp') and key.lower().endswith('\\setup\\vs\\productdir'): + # Visual Studio 2015/2013/2012 Express is VS productdir (root) not VC productdir is_vsroot = True if is_vsroot: From bc1ee034f818b7f454c6c78bb3b7842e08ac361b Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Sun, 7 Jan 2024 10:09:21 -0500 Subject: [PATCH 15/35] Remove unused cache dictionary. --- SCons/Tool/MSCommon/MSVC/Kind.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/SCons/Tool/MSCommon/MSVC/Kind.py b/SCons/Tool/MSCommon/MSVC/Kind.py index 8b254a23b4..d91e6a29fb 100644 --- a/SCons/Tool/MSCommon/MSVC/Kind.py +++ b/SCons/Tool/MSCommon/MSVC/Kind.py @@ -656,7 +656,6 @@ def msvc_version_uwp_is_supported(msvc_version, target_arch=None, env=None): # reset cache def reset() -> None: - global _cache_installed_vcs global _cache_vcver_kind_map global _cache_pdir_vswhere_kind global _cache_pdir_registry_kind @@ -664,7 +663,6 @@ def reset() -> None: debug('') - _cache_installed_vcs = None _cache_vcver_kind_map = {} _cache_pdir_vswhere_kind = {} _cache_pdir_registry_kind = {} From 5038ac5e11edc5704b647de7a06af1200012f3e7 Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Sun, 7 Jan 2024 10:22:50 -0500 Subject: [PATCH 16/35] Add additional vswhere locations and remove vswhere command-line stub. Add vswhere roots for: * winget * scoop --- SCons/Tool/MSCommon/vc.py | 34 +++------------------------------- 1 file changed, 3 insertions(+), 31 deletions(-) diff --git a/SCons/Tool/MSCommon/vc.py b/SCons/Tool/MSCommon/vc.py index d28b6af3c4..51c83e80d5 100644 --- a/SCons/Tool/MSCommon/vc.py +++ b/SCons/Tool/MSCommon/vc.py @@ -56,8 +56,6 @@ import SCons.Warnings from SCons.Tool import find_program_path -# import SCons.Script - from . import common from .common import CONFIG_CACHE, debug from .sdk import get_installed_sdks @@ -920,6 +918,9 @@ def msvc_version_to_maj_min(msvc_version): 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"), ]] # normalize user-specified vswhere paths @@ -964,27 +965,6 @@ def _vswhere_user_path(pval): return vswhere_path -# normalized user-specified command-line vswhere path - -# TODO: stub for command-line specification of vswhere -_vswhere_path_cmdline = None - -def _msvc_cmdline_vswhere(): - global _vswhere_path_cmdline - - if _vswhere_path_cmdline == UNDEFINED: - - vswhere_path = None - vswhere_user = None - - 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 = [ @@ -1018,10 +998,6 @@ 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 = _msvc_cmdline_vswhere() - if vswhere_path: - return - vswhere_path = None for pf in VSWHERE_PATHS: if os.path.exists(pf): @@ -1078,10 +1054,6 @@ 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) - vswhere_default = _msvc_default_vswhere() if vswhere_default and vswhere_default not in _VSWhere.seen_vswhere: vswhere_paths.append(vswhere_default) From 7859a407b5c4f6e6ff89b7da63cd258b0bec1930 Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Sun, 7 Jan 2024 10:46:26 -0500 Subject: [PATCH 17/35] Replace removed process_path function call with normalize_path. --- SCons/Tool/MSCommon/vc.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SCons/Tool/MSCommon/vc.py b/SCons/Tool/MSCommon/vc.py index 51c83e80d5..c464715413 100644 --- a/SCons/Tool/MSCommon/vc.py +++ b/SCons/Tool/MSCommon/vc.py @@ -946,7 +946,7 @@ def _vswhere_user_path(pval): else: - # vswhere_norm = MSVC.Util.process_path(pval) + # TODO: vswhere_norm = MSVC.Util.normalize_path(pval) vswhere_norm = os.path.normcase(os.path.normpath(pval)) tail = os.path.split(vswhere_norm)[-1] @@ -968,7 +968,7 @@ def _vswhere_user_path(pval): # normalized default vswhere path _vswhere_paths_processed = [ - MSVC.Util.process_path(pval) + MSVC.Util.normalize_path(pval) for pval in VSWHERE_PATHS if os.path.exists(pval) ] @@ -1156,7 +1156,7 @@ def _update_vswhere_msvc_map(env): if not os.path.exists(vc_path): continue - vc_root = MSVC.Util.process_path(vc_path) + vc_root = MSVC.Util.normalize_path(vc_path) if vc_root in _VSWhere.seen_root: continue _VSWhere.seen_root.add(vc_root) From 981d74aa31021e46aa752fd0a66f47a9e203e983 Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Thu, 29 Feb 2024 07:41:08 -0500 Subject: [PATCH 18/35] Test suite update and bug fixes. Changes: * Bug fix for not found toolset version in vcTests.py * Re-work sdk list tests in vcTests.py when run on machine that may not have sdks installed (no msvc or older msvc). Potential future problem lurking as the gap between supported sdk versions grows with future releases. * Update the tolerance for precompiled header "fast enough" in msvc.py. This test, more than others, has a tendency to fail when run in a virtual machine on Windows. * Explicity run "foo.exe" instead of "foo" in vs-14.3-exec.py. A recent change in VS2022 appears to create a "foo" folder which causes the test to fail. --- SCons/Tool/MSCommon/vcTests.py | 25 ++++++++++++++++++++----- test/MSVC/msvc.py | 2 +- test/MSVS/vs-14.3-exec.py | 2 +- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/SCons/Tool/MSCommon/vcTests.py b/SCons/Tool/MSCommon/vcTests.py index 8c22ba37e9..da360b3717 100644 --- a/SCons/Tool/MSCommon/vcTests.py +++ b/SCons/Tool/MSCommon/vcTests.py @@ -215,7 +215,14 @@ def runTest(self) -> None: class Data: - HAVE_MSVC = True if MSCommon.vc.msvc_default_version() else False + DEFAULT_VERSION = MSCommon.vc.msvc_default_version() + + if DEFAULT_VERSION: + HAVE_MSVC = True + DEFAULT_VERSION_DEF = MSCommon.msvc_version_components(DEFAULT_VERSION) + else: + HAVE_MSVC = False + DEFAULT_VERSION_DEF = None INSTALLED_VCS_COMPONENTS = MSCommon.vc.get_installed_vcs_components() @@ -230,11 +237,11 @@ def _msvc_toolset_notfound_list(cls, toolset_seen, toolset_list): if len(comps) != 3: continue # full versions only + ival = int(comps[-1]) nloop = 0 while nloop < 10: - ival = int(comps[-1]) if ival == 0: - ival = 1000000 + ival = 100000 ival -= 1 version = '{}.{}.{:05d}'.format(comps[0], comps[1], ival) if version not in toolset_seen: @@ -310,10 +317,18 @@ def test_valid_vcver(self) -> None: version_def = MSCommon.msvc_version_components(symbol) for msvc_uwp_app in (True, False): sdk_list = MSCommon.vc.msvc_sdk_versions(version=symbol, msvc_uwp_app=msvc_uwp_app) - if Data.HAVE_MSVC and version_def.msvc_vernum >= 14.0: + if version_def.msvc_vernum < 14.0: + # version < VS2015 + # does not accept sdk version argument + self.assertFalse(sdk_list, "SDK list is not empty for msvc version {}".format(repr(symbol))) + elif Data.HAVE_MSVC and Data.DEFAULT_VERSION_DEF.msvc_vernum >= 14.0: + # version >= VS2015 and default_version >= VS2015 + # no guarantee test is valid: compatible sdks may not be installed as version gap grows self.assertTrue(sdk_list, "SDK list is empty for msvc version {}".format(repr(symbol))) else: - self.assertFalse(sdk_list, "SDK list is not empty for msvc version {}".format(repr(symbol))) + # version >= VS2015 and default_version < VS2015 + # skip test: version is not installed; compatible windows sdks may not be installed + pass def test_valid_vcver_toolsets(self) -> None: for symbol in MSCommon.vc._VCVER: diff --git a/test/MSVC/msvc.py b/test/MSVC/msvc.py index 6bc36759a2..8d018f82bf 100644 --- a/test/MSVC/msvc.py +++ b/test/MSVC/msvc.py @@ -116,7 +116,7 @@ # TODO: Reevaluate if having this part of the test makes sense any longer # using precompiled headers should be faster -limit = slow*0.90 +limit = slow if fast >= limit: print("Using precompiled headers was not fast enough:") print("slow.obj: %.3fs" % slow) diff --git a/test/MSVS/vs-14.3-exec.py b/test/MSVS/vs-14.3-exec.py index ef9dc33de5..dc4dc7e0e7 100644 --- a/test/MSVS/vs-14.3-exec.py +++ b/test/MSVS/vs-14.3-exec.py @@ -104,7 +104,7 @@ program=[test.get_msvs_executable(msvs_version)], arguments=['foo.sln', '/build', 'Release']) -test.run(program=test.workpath('sub dir', 'foo'), stdout="foo.c\n") +test.run(program=test.workpath('sub dir', 'foo.exe'), stdout="foo.c\n") test.validate_msvs_file(test.workpath('sub dir', 'foo.vcxproj.user')) From ea5baf8bae87b0d7900ea717a4ac5bdd477359f9 Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Sun, 3 Mar 2024 07:52:23 -0500 Subject: [PATCH 19/35] Clear VisualStudio internal cache and installed visual studios data structures when vswhere finds new msvc roots. --- CHANGES.txt | 17 +++++++++-------- RELEASE.txt | 12 +++++++----- SCons/Tool/MSCommon/vc.py | 25 +++++++++++++++++-------- SCons/Tool/MSCommon/vs.py | 38 +++++++++++++++++++++++++++++++++++--- 4 files changed, 68 insertions(+), 24 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index f11e002e87..a55f9ed323 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -80,13 +80,14 @@ 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. - - 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. - + - Previously, the installed vcs and visual studios lists were 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 and visual studios lists. Now, when a + user-specified vswhere executable finds new msvc installations, internal runtime + caches are cleared and the installed vcs and visual studios lists are reconstructed + on their next retrieval. + From Thaddeus Crews: - Implemented SCons.Util.sctyping as a safe means of hinting complex types. Currently only implemented for `Executor` as a proof-of-concept. @@ -1404,7 +1405,7 @@ RELEASE 3.1.2 - Mon, 17 Dec 2019 02:06:27 +0000 in the case where a child process is spawned while a Python action has a file open. Original author: Ryan Beasley. - Added memoization support for calls to Environment.Value() in order to - improve performance of repeated calls. + improve performance of repeated calls. From Jason Kenny diff --git a/RELEASE.txt b/RELEASE.txt index 99aa0fa1df..7a8706d1e9 100644 --- a/RELEASE.txt +++ b/RELEASE.txt @@ -70,11 +70,13 @@ FIXES msiexec /i VCForPython27.msi ALLUSERS=1 When installed for all users, Visual Studio 2008 (9.0) Visual C++ For Python is now correctly detected. -- MSVC: 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. +- MSVC: The installed vcs and visual studios lists were constructed and cached + during their initial invocations. If a vswhere executable was specified via the + construction variable VSWHERE and found additional msvc installations, the new + installations were not reflected in the cached installed vcs and visual studios + lists. Now, when a user-specified vswhere executable finds new msvc installations, + the installed vcs and visual studios lists are cleared and reconstructed on their + next retrieval. - On Windows platform, when collecting command output (Configure checks), make sure decoding of bytes doesn't fail. - Documentation indicated that both Pseudo() and env.Pseudo() were usable, diff --git a/SCons/Tool/MSCommon/vc.py b/SCons/Tool/MSCommon/vc.py index c464715413..7857a7a41f 100644 --- a/SCons/Tool/MSCommon/vc.py +++ b/SCons/Tool/MSCommon/vc.py @@ -1008,7 +1008,8 @@ def msvc_find_vswhere(): class _VSWhere: - reset_funcs = [] + reset_funcs_list = [] + reset_funcs_seen = set() @classmethod def reset(cls): @@ -1036,15 +1037,20 @@ def msvc_instances_default_order(a, b): @classmethod def register_reset_func(cls, func): - cls.reset_funcs.append(func) + if func and func not in cls.reset_funcs_seen: + cls.reset_funcs_seen.add(func) + cls.reset_funcs_list.append(func) @classmethod def call_reset_funcs(cls): - for func in cls.reset_funcs: + for func in cls.reset_funcs_list: func() _VSWhere.reset() +def vswhere_register_reset_func(func): + _VSWhere.register_reset_func(func) + def _filter_vswhere_paths(env): vswhere_paths = [] @@ -1117,7 +1123,7 @@ def _vswhere_query_json_output(vswhere_exe, vswhere_args): 'vc_component_suffix', ]) -def _update_vswhere_msvc_map(env): +def _vswhere_update_msvc_map(env): vswhere_paths = _filter_vswhere_paths(env) if not vswhere_paths: @@ -1241,6 +1247,9 @@ def _update_vswhere_msvc_map(env): return _VSWhere.msvc_map +def vswhere_update_msvc(env): + _vswhere_update_msvc_map(env) + _cache_pdir_vswhere_queries = {} def _reset_pdir_vswhere_queries(): @@ -1267,7 +1276,7 @@ def find_vc_pdir_vswhere(msvc_version, env=None): """ global _cache_pdir_vswhere_queries - msvc_map = _update_vswhere_msvc_map(env) + msvc_map = _vswhere_update_msvc_map(env) if not msvc_map: return None @@ -1751,9 +1760,9 @@ def _check_files_exist_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) + debug('') + # the installed vcs cache is cleared if new vc roots are discovered + _vswhere_update_msvc_map(env) if __INSTALLED_VCS_RUN is not None: return __INSTALLED_VCS_RUN diff --git a/SCons/Tool/MSCommon/vs.py b/SCons/Tool/MSCommon/vs.py index ef4f13cdfb..afe2f743ef 100644 --- a/SCons/Tool/MSCommon/vs.py +++ b/SCons/Tool/MSCommon/vs.py @@ -28,7 +28,6 @@ import os import SCons.Errors -import SCons.Tool.MSCommon.vc import SCons.Util from .common import ( @@ -40,6 +39,14 @@ read_reg, ) +from .vc import ( + find_vc_pdir, + get_msvc_version_numeric, + reset_installed_vcs, + vswhere_register_reset_func, + vswhere_update_msvc, +) + class VisualStudio: """ @@ -48,6 +55,7 @@ class VisualStudio: """ def __init__(self, version, **kw) -> None: self.version = version + self.vernum = float(get_msvc_version_numeric(version)) kw['vc_version'] = kw.get('vc_version', version) kw['sdk_version'] = kw.get('sdk_version', version) self.__dict__.update(kw) @@ -66,7 +74,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(self.vc_version, env) + dir = find_vc_pdir(self.vc_version, env) if not dir: debug('no installed VC %s', self.vc_version) return None @@ -411,8 +419,12 @@ def reset(self) -> None: ), ] +SupportedVSWhereList = [] SupportedVSMap = {} for vs in SupportedVSList: + if vs.vernum >= 14.1: + # VS2017 and later detected via vswhere.exe + SupportedVSWhereList.append(vs) SupportedVSMap[vs.version] = vs @@ -428,6 +440,12 @@ def reset(self) -> None: def get_installed_visual_studios(env=None): global InstalledVSList global InstalledVSMap + debug('') + # Calling vswhere_update_msvc may cause installed caches to be cleared. + # The installed vcs and visual studios are cleared when new vc roots + # are discovered which causes the InstalledVSList and InstalledVSMap + # variables to be set to None. + vswhere_update_msvc(env) if InstalledVSList is None: InstalledVSList = [] InstalledVSMap = {} @@ -442,6 +460,7 @@ def get_installed_visual_studios(env=None): def reset_installed_visual_studios() -> None: global InstalledVSList global InstalledVSMap + debug('') InstalledVSList = None InstalledVSMap = None for vs in SupportedVSList: @@ -449,7 +468,20 @@ def reset_installed_visual_studios() -> None: # Need to clear installed VC's as well as they are used in finding # installed VS's - SCons.Tool.MSCommon.vc.reset_installed_vcs() + reset_installed_vcs() + +def _reset_installed_vswhere_visual_studios(): + # vswhere found new roots: reset VS2017 and later + global InstalledVSList + global InstalledVSMap + debug('') + InstalledVSList = None + InstalledVSMap = None + for vs in SupportedVSWhereList: + vs.reset() + +# register vswhere callback function +vswhere_register_reset_func(_reset_installed_vswhere_visual_studios) # We may be asked to update multiple construction environments with From 1fab5957fc547c6ce6475e29613e892dd73afb6e Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Tue, 12 Mar 2024 07:45:07 -0400 Subject: [PATCH 20/35] Detection changes for VS IDE editions and VS2008 (develop and vcforpython). Changes: * An express installation of the IDE binary is used when no other IDE edition is detected. * A full development edition (e.g., Professional) of VS2008 is elected before a Visual C++ For Python edition. * Update detection order in README.rst and remove hard tabs. * Minor fixes lingering from original VSWHERE implementation where all call chains were not updated when the environment argument was added. --- CHANGES.txt | 7 ++- RELEASE.txt | 7 ++- SCons/Tool/MSCommon/README.rst | 72 +++++++++++++++--------------- SCons/Tool/MSCommon/sdk.py | 2 +- SCons/Tool/MSCommon/vc.py | 46 +++++++++---------- SCons/Tool/MSCommon/vs.py | 33 ++++++++++---- SCons/Tool/msvsTests.py | 4 +- test/MSVC/query_vcbat.py | 2 +- testing/framework/TestSConsMSVS.py | 6 +-- 9 files changed, 102 insertions(+), 77 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 359211bc34..6d79040e71 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -43,7 +43,9 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER method so that the generated function argument list matches the function's prototype when including a header file. - For msvc version specifications without an 'Exp' suffix, an express installation - is used when no other edition is detected for the msvc version. + is used when no other edition is detected for the msvc version. Similarly, an + express installation of the IDE binary is used when no other IDE edition is + detected. - VS2015 Express (14.0Exp) does not support the sdk version argument. VS2015 Express does not support the store argument for target architectures other than x86. Script argument validation now takes into account these restrictions. @@ -72,6 +74,9 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER (i.e., msiexec /i VCForPython27.msi ALLUSERS=1), the registry keys are written to HKEY_LOCAL_MACHINE rather than HKEY_CURRENT_USER. An entry was added to query the Visual C++ For Python keys in HKLM following the HKCU query, if necessary. + - For VS2008, a full development edition (e.g., Professional) is now selected before + a Visual C++ For Python edition. Prior to this change, Visual C++ For Python was + selected before a full development edition when both editions are installed. - The registry detection of VS2015 (14.0), and earlier, is now cached at runtime and is only evaluated once for each msvc version. - The vswhere detection of VS2017 (14.1), and later, is now cached at runtime and is diff --git a/RELEASE.txt b/RELEASE.txt index 7a8706d1e9..65114e6c33 100644 --- a/RELEASE.txt +++ b/RELEASE.txt @@ -38,7 +38,8 @@ CHANGED/ENHANCED EXISTING FUNCTIONALITY installation is used when no other edition is detected for the msvc version. This was the behavior for Visual Studio 2008 (9.0) through Visual Studio 2015 (14.0). This behavior was extended to Visual Studio 2017 (14.1) and Visual - Studio 2008 (8.0). + Studio 2008 (8.0). An express installation of the IDE binary is used when no + other IDE edition is detected. FIXES ----- @@ -70,6 +71,10 @@ FIXES msiexec /i VCForPython27.msi ALLUSERS=1 When installed for all users, Visual Studio 2008 (9.0) Visual C++ For Python is now correctly detected. +- MSVC: For Visual Studio 2008 (9.0), a full development edition (e.g., Professional) + is now selected before a Visual C++ For Python edition. Prior to this change, + Visual C++ For Python was selected before a full development edition when both + editions are installed. - MSVC: The installed vcs and visual studios lists were constructed and cached during their initial invocations. If a vswhere executable was specified via the construction variable VSWHERE and found additional msvc installations, the new diff --git a/SCons/Tool/MSCommon/README.rst b/SCons/Tool/MSCommon/README.rst index bc57ca43b0..4a7acc59b4 100644 --- a/SCons/Tool/MSCommon/README.rst +++ b/SCons/Tool/MSCommon/README.rst @@ -36,7 +36,7 @@ installation is used only when no other installation is detected. ======= ======= ======================================================== Product VCVer Priority ======= ======= ======================================================== -VS2022 14.3 Enterprise, Professional, Community, BuildTools +VS2022 14.3 Enterprise, Professional, Community, BuildTools ------- ------- -------------------------------------------------------- VS2019 14.2 Enterprise, Professional, Community, BuildTools ------- ------- -------------------------------------------------------- @@ -44,15 +44,15 @@ VS2017 14.1 Enterprise, Professional, Community, BuildTools, Express ------- ------- -------------------------------------------------------- VS2017 14.1Exp Express ------- ------- -------------------------------------------------------- -VS2015 14.0 [Develop, BuildTools, CmdLine], Express +VS2015 14.0 [Develop, BuildTools, CmdLine], Express ------- ------- -------------------------------------------------------- VS2015 14.0Exp Express ------- ------- -------------------------------------------------------- -VS2013 12.0 Develop, Express +VS2013 12.0 Develop, Express ------- ------- -------------------------------------------------------- VS2013 12.0Exp Express ------- ------- -------------------------------------------------------- -VS2012 11.0 Develop, Express +VS2012 11.0 Develop, Express ------- ------- -------------------------------------------------------- VS2012 11.0Exp Express ------- ------- -------------------------------------------------------- @@ -60,7 +60,7 @@ VS2010 10.0 Develop, Express ------- ------- -------------------------------------------------------- VS2010 10.0Exp Express ------- ------- -------------------------------------------------------- -VS2008 9.0 VCForPython, Develop, Express +VS2008 9.0 Develop, VCForPython, Express ------- ------- -------------------------------------------------------- VS2008 9.0Exp Express ------- ------- -------------------------------------------------------- @@ -105,20 +105,20 @@ 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 + 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_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 + :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 -------------- @@ -136,19 +136,19 @@ architecture. The generated ``store`` library paths include directories that do 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% + :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% + :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 ^^^^^^^^^^^^^^^^^^^^^^^ @@ -158,19 +158,19 @@ architecture. The generated ``store`` library paths include directories that do 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% + :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% + :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 diff --git a/SCons/Tool/MSCommon/sdk.py b/SCons/Tool/MSCommon/sdk.py index c6600f3a5b..a06dfacfc6 100644 --- a/SCons/Tool/MSCommon/sdk.py +++ b/SCons/Tool/MSCommon/sdk.py @@ -372,7 +372,7 @@ def mssdk_setup_env(env): return msvs_version = env.subst(msvs_version) from . import vs - msvs = vs.get_vs_by_version(msvs_version) + msvs = vs.get_vs_by_version(msvs_version, env) debug('mssdk_setup_env:msvs is :%s', msvs) if not msvs: debug('mssdk_setup_env: no VS version detected, bailingout:%s', msvs) diff --git a/SCons/Tool/MSCommon/vc.py b/SCons/Tool/MSCommon/vc.py index 7857a7a41f..fcc1ba80c7 100644 --- a/SCons/Tool/MSCommon/vc.py +++ b/SCons/Tool/MSCommon/vc.py @@ -103,7 +103,7 @@ class BatchFileExecutionError(VisualCException): # MSVC 9.0 preferred query order: # True: VCForPython, VisualStudio # False: VisualStudio, VCForPython -_VC90_Prefer_VCForPython = True +_VC90_Prefer_VCForPython = False # Dict to 'canonalize' the arch _ARCH_TO_CANONICAL = { @@ -923,6 +923,21 @@ def msvc_version_to_maj_min(msvc_version): os.path.expandvars(r"%SCOOP%\shims"), ]] +def msvc_find_vswhere(): + """ Find the location of vswhere """ + # For bug 3333: support default location of vswhere for both + # 64 and 32 bit windows installs. + # For bug 3542: also accommodate not being on C: drive. + # NB: this gets called from testsuite on non-Windows platforms. + # Whether that makes sense or not, don't break it for those. + vswhere_path = None + for pf in VSWHERE_PATHS: + if os.path.exists(pf): + vswhere_path = pf + break + + return vswhere_path + # normalize user-specified vswhere paths _cache_user_vswhere_paths = {} @@ -965,13 +980,7 @@ def _vswhere_user_path(pval): return vswhere_path -# normalized default vswhere path - -_vswhere_paths_processed = [ - MSVC.Util.normalize_path(pval) - for pval in VSWHERE_PATHS - if os.path.exists(pval) -] +# normalize default vswhere path _vswhere_path_default = UNDEFINED @@ -981,6 +990,12 @@ def _msvc_default_vswhere(): if _vswhere_path_default == UNDEFINED: + _vswhere_paths_processed = [ + MSVC.Util.normalize_path(pval) + for pval in VSWHERE_PATHS + if os.path.exists(pval) + ] + if _vswhere_paths_processed: vswhere_path = _vswhere_paths_processed[0] else: @@ -991,21 +1006,6 @@ def _msvc_default_vswhere(): return _vswhere_path_default -def msvc_find_vswhere(): - """ Find the location of vswhere """ - # For bug 3333: support default location of vswhere for both - # 64 and 32 bit windows installs. - # For bug 3542: also accommodate not being on C: drive. - # NB: this gets called from testsuite on non-Windows platforms. - # Whether that makes sense or not, don't break it for those. - vswhere_path = None - for pf in VSWHERE_PATHS: - if os.path.exists(pf): - vswhere_path = pf - break - - return vswhere_path - class _VSWhere: reset_funcs_list = [] diff --git a/SCons/Tool/MSCommon/vs.py b/SCons/Tool/MSCommon/vs.py index afe2f743ef..d40ac426ff 100644 --- a/SCons/Tool/MSCommon/vs.py +++ b/SCons/Tool/MSCommon/vs.py @@ -48,6 +48,12 @@ ) +# Visual Studio express version policy when unqualified version is not installed: +# True: use express version for unqualified version (e.g., use 12.0Exp for 12.0) +# False: do not use express version for unqualified version +_VSEXPRESS_USE_VERSTR = True + + class VisualStudio: """ An abstract base class for trying to find installed versions of @@ -55,7 +61,9 @@ class VisualStudio: """ def __init__(self, version, **kw) -> None: self.version = version - self.vernum = float(get_msvc_version_numeric(version)) + self.verstr = get_msvc_version_numeric(version) + self.vernum = float(self.verstr) + self.is_express = True if self.verstr != self.version else False kw['vc_version'] = kw.get('vc_version', version) kw['sdk_version'] = kw.get('sdk_version', version) self.__dict__.update(kw) @@ -454,9 +462,17 @@ def get_installed_visual_studios(env=None): if vs.get_executable(env): debug('found VS %s', vs.version) InstalledVSList.append(vs) + if vs.is_express and vs.verstr not in InstalledVSMap: + if _VSEXPRESS_USE_VERSTR: + InstalledVSMap[vs.verstr] = vs InstalledVSMap[vs.version] = vs return InstalledVSList +def _get_installed_vss(env=None): + get_installed_visual_studios(env) + versions = list(InstalledVSMap.keys()) + return versions + def reset_installed_visual_studios() -> None: global InstalledVSList global InstalledVSMap @@ -519,7 +535,7 @@ def _reset_installed_vswhere_visual_studios(): def msvs_exists(env=None) -> bool: return len(get_installed_visual_studios(env)) > 0 -def get_vs_by_version(msvs): +def get_vs_by_version(msvs, env=None): global InstalledVSMap global SupportedVSMap @@ -527,7 +543,7 @@ def get_vs_by_version(msvs): if msvs not in SupportedVSMap: msg = "Visual Studio version %s is not supported" % repr(msvs) raise SCons.Errors.UserError(msg) - get_installed_visual_studios() + get_installed_visual_studios(env) vs = InstalledVSMap.get(msvs) debug('InstalledVSMap:%s', InstalledVSMap) debug('found vs:%s', vs) @@ -556,7 +572,7 @@ def get_default_version(env): """ if 'MSVS' not in env or not SCons.Util.is_Dict(env['MSVS']): # get all versions, and remember them for speed later - versions = [vs.version for vs in get_installed_visual_studios()] + versions = _get_installed_vss(env) env['MSVS'] = {'VERSIONS' : versions} else: versions = env['MSVS'].get('VERSIONS', []) @@ -602,7 +618,7 @@ def merge_default_version(env) -> None: # TODO: refers to versions and arch which aren't defined; called nowhere. Drop? def msvs_setup_env(env) -> None: - msvs = get_vs_by_version(version) + msvs = get_vs_by_version(version, env) if msvs is None: return batfilename = msvs.get_batch_file() @@ -614,7 +630,7 @@ def msvs_setup_env(env) -> None: vars = ('LIB', 'LIBPATH', 'PATH', 'INCLUDE') - msvs_list = get_installed_visual_studios() + msvs_list = get_installed_visual_studios(env) vscommonvarnames = [vs.common_tools_var for vs in msvs_list] save_ENV = env['ENV'] nenv = normalize_env(env['ENV'], @@ -629,11 +645,10 @@ def msvs_setup_env(env) -> None: for k, v in vars.items(): env.PrependENVPath(k, v, delete_existing=1) -def query_versions(): +def query_versions(env=None): """Query the system to get available versions of VS. A version is considered when a batfile is found.""" - msvs_list = get_installed_visual_studios() - versions = [msvs.version for msvs in msvs_list] + versions = _get_installed_vss(env) return versions # Local Variables: diff --git a/SCons/Tool/msvsTests.py b/SCons/Tool/msvsTests.py index 1266055494..63d91ce2fa 100644 --- a/SCons/Tool/msvsTests.py +++ b/SCons/Tool/msvsTests.py @@ -635,9 +635,9 @@ def _TODO_test_merge_default_version(self) -> None: """Test the merge_default_version() function""" pass - def test_query_versions(self) -> None: + def test_query_versions(self, env=None) -> None: """Test retrieval of the list of visual studio versions""" - v1 = query_versions() + v1 = query_versions(env) assert not v1 or str(v1[0]) == self.highest_version, \ (v1, self.highest_version) assert len(v1) == self.number_of_versions, v1 diff --git a/test/MSVC/query_vcbat.py b/test/MSVC/query_vcbat.py index 6e30706b89..28bbd02dc3 100644 --- a/test/MSVC/query_vcbat.py +++ b/test/MSVC/query_vcbat.py @@ -51,7 +51,7 @@ # print k, v #MergeMSVSBatFile(env, 9.0) #print(env['ENV']['PATH']) -print(query_versions()) +print(query_versions(env=None)) """) test.run(stderr=None) diff --git a/testing/framework/TestSConsMSVS.py b/testing/framework/TestSConsMSVS.py index 8c34372b85..91aa329d66 100644 --- a/testing/framework/TestSConsMSVS.py +++ b/testing/framework/TestSConsMSVS.py @@ -680,7 +680,7 @@ def msvs_versions(self): import SCons import SCons.Tool.MSCommon print("self.scons_version =%%s"%%repr(SCons.__%s__)) -print("self._msvs_versions =%%s"%%str(SCons.Tool.MSCommon.query_versions())) +print("self._msvs_versions =%%s"%%str(SCons.Tool.MSCommon.query_versions(env=None))) """ % 'version' self.run(arguments = '-n -q -Q -f -', stdin = input) @@ -738,13 +738,13 @@ def msvs_substitute(self, input, msvs_ver, result = result.replace('\n', sln_sccinfo) return result - def get_msvs_executable(self, version): + def get_msvs_executable(self, version, env=None): """Returns a full path to the executable (MSDEV or devenv) for the specified version of Visual Studio. """ from SCons.Tool.MSCommon import get_vs_by_version - msvs = get_vs_by_version(version) + msvs = get_vs_by_version(version, env) if not msvs: return None return msvs.get_executable() From 3b95dd6919dec1a13e26e7bef62a02157108c350 Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Sat, 23 Mar 2024 10:21:47 -0400 Subject: [PATCH 21/35] Refactor vswhere executable support and detection. vswhere changes: * Split the current vswhere paths list into two lists: * the visual studio installer locations, * the package manager locations. * Add additional user vswhere priority groups: * before the vs installer locations [high priority], * after the vs installer locations [default priority], and * after the package manager locations [low priority]. * Adjust tests for vswhere path list changes accordingly. * Add vswhere location functions to: * register user vswhere executable paths by priority, * get the currently configured vswhere executable location, * freeze the vswhere executable location used for detection. * Assign the environment VSWHERE construction variable in Tool/MSCommon/vc.py. * Remove the assignment of the environment VSWHERE construction variable in Tool/msvc.py * Update the test suite function signature for finding the msvc product directory with vswhere. * Freeze the vswhere executable in the following functions before the cache is evaluated which will raise an exception if the vswhere executable has changed since the initial detection (if necessary): * MSCommon.vc.get_installed_visual_studios, * MSCommon.vc.find_vc_pdir, * MSCommon.vc.msvc_setup_env, * MSCommon.vs.get_installed_visual_studios --- CHANGES.txt | 17 +- RELEASE.txt | 17 +- SCons/Tool/MSCommon/MSVC/Util.py | 9 + SCons/Tool/MSCommon/__init__.py | 5 + SCons/Tool/MSCommon/vc.py | 770 ++++++++++++------ SCons/Tool/MSCommon/vcTests.py | 16 +- SCons/Tool/MSCommon/vs.py | 23 +- SCons/Tool/msvc.py | 3 - SCons/Tool/msvsTests.py | 9 +- test/MSVC/VSWHERE-fixture/SConstruct | 25 +- test/MSVC/VSWHERE.py | 10 + 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 +- 17 files changed, 608 insertions(+), 320 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 6d79040e71..9733e1ec55 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -85,13 +85,16 @@ 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. - - Previously, the installed vcs and visual studios lists were 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 and visual studios lists. Now, when a - user-specified vswhere executable finds new msvc installations, internal runtime - caches are cleared and the installed vcs and visual studios lists are reconstructed - on their next retrieval. + - The vswhere executable is frozen upon initial detection. Specifying a different + vswhere executable via the construction variable VSWHERE after the initial + detection now results in an exception. Multiple bugs in the implementation of + specifying a vswhere executable via the construction variable VSWHERE have been + fixed. Previously, when a user specified vswhere executable detects new msvc + installations after the initial detection, the internal msvc installation cache + and the default msvc version based on the initial detection are no longer valid. + For example, when no vswhere executable is found for the initial detection + and then later an environment is constructed with a user specified vswhere + executable that detects new msvc installations. From Thaddeus Crews: - Implemented SCons.Util.sctyping as a safe means of hinting complex types. Currently diff --git a/RELEASE.txt b/RELEASE.txt index 65114e6c33..5ca5d06fdb 100644 --- a/RELEASE.txt +++ b/RELEASE.txt @@ -75,13 +75,16 @@ FIXES is now selected before a Visual C++ For Python edition. Prior to this change, Visual C++ For Python was selected before a full development edition when both editions are installed. -- MSVC: The installed vcs and visual studios lists were constructed and cached - during their initial invocations. If a vswhere executable was specified via the - construction variable VSWHERE and found additional msvc installations, the new - installations were not reflected in the cached installed vcs and visual studios - lists. Now, when a user-specified vswhere executable finds new msvc installations, - the installed vcs and visual studios lists are cleared and reconstructed on their - next retrieval. +- The vswhere executable is frozen upon initial detection. Specifying a different + vswhere executable via the construction variable VSWHERE after the initial + detection now results in an exception. Multiple bugs in the implementation of + specifying a vswhere executable via the construction variable VSWHERE have been + fixed. Previously, when a user specified vswhere executable detects new msvc + installations after the initial detection, the internal msvc installation cache + and the default msvc version based on the initial detection are no longer valid. + For example, when no vswhere executable is found for the initial detection + and then later an environment is constructed with a user specified vswhere + executable that detects new msvc installations. - On Windows platform, when collecting command output (Configure checks), make sure decoding of bytes doesn't fail. - Documentation indicated that both Pseudo() and env.Pseudo() were usable, diff --git a/SCons/Tool/MSCommon/MSVC/Util.py b/SCons/Tool/MSCommon/MSVC/Util.py index 44b112546b..f6178e2886 100644 --- a/SCons/Tool/MSCommon/MSVC/Util.py +++ b/SCons/Tool/MSCommon/MSVC/Util.py @@ -37,6 +37,15 @@ from . import Config + +# call _initialize method upon class definition completion + +class AutoInitialize: + def __init_subclass__(cls, **kwargs): + super().__init_subclass__(**kwargs) + if hasattr(cls, '_initialize') and callable(getattr(cls, '_initialize', None)): + cls._initialize() + # path utilities # windows drive specification (e.g., 'C:') diff --git a/SCons/Tool/MSCommon/__init__.py b/SCons/Tool/MSCommon/__init__.py index c3078ac630..4d7b8bcb3a 100644 --- a/SCons/Tool/MSCommon/__init__.py +++ b/SCons/Tool/MSCommon/__init__.py @@ -45,6 +45,9 @@ msvc_toolset_versions, msvc_toolset_versions_spectre, msvc_query_version_toolset, + vswhere_register_executable, + vswhere_get_executable, + vswhere_freeze_executable, ) from SCons.Tool.MSCommon.vs import ( # noqa: F401 @@ -78,7 +81,9 @@ MSVCUnsupportedHostArch, MSVCUnsupportedTargetArch, MSVCScriptNotFound, + MSVCUseScriptError, MSVCUseSettingsError, + VSWhereUserError, ) from .MSVC.Util import ( # noqa: F401 diff --git a/SCons/Tool/MSCommon/vc.py b/SCons/Tool/MSCommon/vc.py index fcc1ba80c7..126d2588ce 100644 --- a/SCons/Tool/MSCommon/vc.py +++ b/SCons/Tool/MSCommon/vc.py @@ -51,13 +51,17 @@ ) import json from functools import cmp_to_key +import enum import SCons.Util import SCons.Warnings from SCons.Tool import find_program_path from . import common -from .common import CONFIG_CACHE, debug +from .common import ( + CONFIG_CACHE, + debug, +) from .sdk import get_installed_sdks from . import MSVC @@ -81,9 +85,15 @@ class MSVCUnsupportedTargetArch(VisualCException): class MSVCScriptNotFound(MSVCUserError): pass +class MSVCUseScriptError(MSVCUserError): + pass + class MSVCUseSettingsError(MSVCUserError): pass +class VSWhereUserError(MSVCUserError): + pass + # internal exceptions class UnsupportedVersion(VisualCException): @@ -914,113 +924,330 @@ def msvc_version_to_maj_min(msvc_version): raise ValueError("Unrecognized version %s (%s)" % (msvc_version,msvc_version_numeric)) from None -VSWHERE_PATHS = [os.path.join(p,'vswhere.exe') for p in [ +_VSWHERE_EXEGROUP_MSVS = [os.path.join(p, _VSWHERE_EXE) for p in [ + # For bug 3333: support default location of vswhere for both + # 64 and 32 bit windows installs. + # For bug 3542: also accommodate not being on C: drive. os.path.expandvars(r"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer"), os.path.expandvars(r"%ProgramFiles%\Microsoft Visual Studio\Installer"), +]] + +_VSWHERE_EXEGROUP_PKGMGR = [os.path.join(p, _VSWHERE_EXE) for p in [ os.path.expandvars(r"%ChocolateyInstall%\bin"), os.path.expandvars(r"%LOCALAPPDATA%\Microsoft\WinGet\Links"), os.path.expanduser(r"~\scoop\shims"), os.path.expandvars(r"%SCOOP%\shims"), ]] -def msvc_find_vswhere(): - """ Find the location of vswhere """ - # For bug 3333: support default location of vswhere for both - # 64 and 32 bit windows installs. - # For bug 3542: also accommodate not being on C: drive. - # NB: this gets called from testsuite on non-Windows platforms. - # Whether that makes sense or not, don't break it for those. - vswhere_path = None - for pf in VSWHERE_PATHS: - if os.path.exists(pf): - vswhere_path = pf - break +_VSWhereBinary = namedtuple('_VSWhereBinary', [ + 'vswhere_exe', + 'vswhere_norm', +]) - return vswhere_path +class VSWhereBinary(_VSWhereBinary, MSVC.Util.AutoInitialize): + + _UNDEFINED_VSWHERE_BINARY = None -# normalize user-specified vswhere paths + _cache_vswhere_paths = {} -_cache_user_vswhere_paths = {} + @classmethod + def _initialize(cls): + vswhere_binary = cls( + vswhere_exe=None, + vswhere_norm=None, + ) + cls._UNDEFINED_VSWHERE_BINARY = vswhere_binary + for symbol in (None, ''): + cls._cache_vswhere_paths[symbol] = vswhere_binary -def _vswhere_user_path(pval): - global _cache_user_vswhere_path + @classmethod + def factory(cls, vswhere_exe): - rval = _cache_user_vswhere_paths.get(pval, UNDEFINED) - if rval != UNDEFINED: - debug('vswhere_path=%s', rval) - return rval + if not vswhere_exe: + return cls._UNDEFINED_VSWHERE_BINARY - vswhere_path = None - if pval: + vswhere_binary = cls._cache_vswhere_paths.get(vswhere_exe) + if vswhere_binary: + return vswhere_binary - if not os.path.exists(pval): + vswhere_norm = MSVC.Util.normalize_path(vswhere_exe) - warn_msg = f'vswhere executable path not found: {pval!r}' - SCons.Warnings.warn(MSVC.Warnings.VSWherePathWarning, warn_msg) - debug(warn_msg) + vswhere_binary = cls._cache_vswhere_paths.get(vswhere_norm) + if vswhere_binary: + return vswhere_binary - else: + vswhere_binary = cls( + vswhere_exe=vswhere_exe, + vswhere_norm=vswhere_exe, + ) - # TODO: vswhere_norm = MSVC.Util.normalize_path(pval) - vswhere_norm = os.path.normcase(os.path.normpath(pval)) + cls._cache_vswhere_paths[vswhere_exe] = vswhere_binary + cls._cache_vswhere_paths[vswhere_norm] = vswhere_binary - tail = os.path.split(vswhere_norm)[-1] - if tail != _VSWHERE_EXE: + return vswhere_binary - 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) +class _VSWhereExecutable(MSVC.Util.AutoInitialize): - else: + debug_extra = None - vswhere_path = vswhere_norm - debug('vswhere_path=%s', vswhere_path) + class _VSWhereUserPriority(enum.IntEnum): + HIGH = 0 # before msvs locations + DEFAULT = enum.auto() # after msvs locations and before pkgmgr locations + LOW = enum.auto() # after pkgmgr locations - _cache_user_vswhere_paths[pval] = vswhere_path + priority_default = _VSWhereUserPriority.DEFAULT + priority_map = {} + priority_symbols = [] + priority_indmap = {} - return vswhere_path + for e in _VSWhereUserPriority: + for symbol in (e.name.lower(), e.name.lower().capitalize(), e.name): + priority_map[symbol] = e.value + priority_symbols.append(symbol) + priority_indmap[e.value] = e.name + + UNDEFINED_VSWHERE_BINARY = VSWhereBinary.factory(None) + + @classmethod + def reset(cls): + + cls._vswhere_exegroups = None + cls._vswhere_exegroups_user = None + cls._vswhere_exegroup_msvs = None + cls._vswhere_exegroup_pkgmgr = None + + cls.vswhere_frozen_flag = False + cls.vswhere_frozen_binary = None + + @classmethod + def _initialize(cls): + cls.debug_extra = common.debug_extra(cls) + cls.reset() + + @classmethod + def _vswhere_exegroup_binaries(cls, vswhere_exe_list, label): + vswhere_binaries = [] + for vswhere_exe in vswhere_exe_list: + if not os.path.exists(vswhere_exe): + continue + vswhere_binary = VSWhereBinary.factory(vswhere_exe) + vswhere_binaries.append(vswhere_binary) + debug( + "insert exegroup=%s, vswhere_binary=%s", + label, vswhere_binary, extra=cls.debug_extra + ) + return vswhere_binaries + + @classmethod + def _get_vswhere_exegroups(cls): + + if cls._vswhere_exegroups is None: + + cls._vswhere_exegroup_msvs = cls._vswhere_exegroup_binaries(_VSWHERE_EXEGROUP_MSVS, 'MSVS') + cls._vswhere_exegroup_pkgmgr = cls._vswhere_exegroup_binaries(_VSWHERE_EXEGROUP_PKGMGR, 'PKGMGR') + + cls._vswhere_exegroups_user = [ + [] for e in cls._VSWhereUserPriority + ] -# normalize default vswhere path + vswhere_exegroups = [ + cls._vswhere_exegroups_user[cls._VSWhereUserPriority.HIGH], + cls._vswhere_exegroup_msvs, + cls._vswhere_exegroups_user[cls._VSWhereUserPriority.DEFAULT], + cls._vswhere_exegroup_pkgmgr, + cls._vswhere_exegroups_user[cls._VSWhereUserPriority.LOW], + ] -_vswhere_path_default = UNDEFINED + cls._vswhere_exegroups = vswhere_exegroups -def _msvc_default_vswhere(): - global _vswhere_paths_processed - global _vswhere_path_default + return cls._vswhere_exegroups - if _vswhere_path_default == UNDEFINED: + @classmethod + def _get_vswhere_exegroups_user(cls): + + if cls._vswhere_exegroups_user is None: + cls._get_vswhere_exegroups() - _vswhere_paths_processed = [ - MSVC.Util.normalize_path(pval) - for pval in VSWHERE_PATHS - if os.path.exists(pval) - ] + return cls._vswhere_exegroups_user + + @classmethod + def _vswhere_exegroup_binary(cls, exegroup): + # first vswhere binary in group or UNDEFINED_VSWHERE_BINARY + vswhere_binary = exegroup[0] if exegroup else cls.UNDEFINED_VSWHERE_BINARY + return vswhere_binary - if _vswhere_paths_processed: - vswhere_path = _vswhere_paths_processed[0] + @classmethod + def _vswhere_current_binary(cls): + # first vswhere binary in priority order or UNDEFINED_VSWHERE_BINARY + vswhere_binary = cls.UNDEFINED_VSWHERE_BINARY + vswhere_exegroups = cls._get_vswhere_exegroups() + for exegroup in vswhere_exegroups: + binary = cls._vswhere_exegroup_binary(exegroup) + if binary == cls.UNDEFINED_VSWHERE_BINARY: + continue + vswhere_binary = binary + break + return vswhere_binary + + @classmethod + def _vswhere_all_executables(cls): + # unique vswhere executables in priority order + vswhere_exe_list = [] + vswhere_norm_set = set() + vswhere_exegroups = cls._get_vswhere_exegroups() + for exegroup in vswhere_exegroups: + for vswhere_binary in exegroup: + if vswhere_binary.vswhere_norm in vswhere_norm_set: + continue + vswhere_norm_set.add(vswhere_binary.vswhere_norm) + vswhere_exe_list.append(vswhere_binary.vswhere_exe) + return vswhere_exe_list + + @classmethod + def freeze_vswhere_binary(cls): + if not cls.vswhere_frozen_flag: + cls.vswhere_frozen_flag = True + cls.vswhere_frozen_binary = cls._vswhere_current_binary() + debug("freeze=%s", cls.vswhere_frozen_binary, extra=cls.debug_extra) + return cls.vswhere_frozen_binary + + @classmethod + def freeze_vswhere_executable(cls): + vswhere_binary = cls.freeze_vswhere_binary() + vswhere_exe = vswhere_binary.vswhere_exe + return vswhere_exe + + @classmethod + def get_vswhere_executable(cls): + if cls.vswhere_frozen_flag: + vswhere_binary = cls.vswhere_frozen_binary else: - vswhere_path = None + vswhere_binary = cls._vswhere_current_binary() + vswhere_exe = vswhere_binary.vswhere_exe + debug("vswhere_exe=%s", repr(vswhere_exe), extra=cls.debug_extra) + return vswhere_exe + + @classmethod + def _vswhere_priority_group(cls, priority): + if not priority: + group = cls.priority_default + else: + group = cls.priority_map.get(priority) + if group is None: + msg = f'Value specified for vswhere executable priority is not supported: {priority!r}:\n' \ + f' Valid values are: {cls.priority_symbols}' + debug(f'VSWhereUserError: {msg}', extra=cls.debug_extra) + raise VSWhereUserError(msg) + return group + + @classmethod + def register_vswhere_executable(cls, vswhere_exe, priority=None) -> bool: - _vswhere_path_default = vswhere_path - debug('vswhere_path=%s', vswhere_path) + vswhere_binary = cls.UNDEFINED_VSWHERE_BINARY - return _vswhere_path_default + if not vswhere_exe: + # ignore: None or empty + return vswhere_binary -class _VSWhere: + if not os.path.exists(vswhere_exe): + msg = f'Specified vswhere executable not found: {vswhere_exe!r}.' + debug(f'VSWhereUserError: {msg}', extra=cls.debug_extra) + raise VSWhereUserError(msg) - reset_funcs_list = [] - reset_funcs_seen = set() + group = cls._vswhere_priority_group(priority) + + vswhere_binary = VSWhereBinary.factory(vswhere_exe) + + if cls.vswhere_frozen_flag: + + if vswhere_binary.vswhere_norm == cls.vswhere_frozen_binary.vswhere_norm: + # ignore: user executable == frozen executable + return vswhere_binary + + msg = 'A different vswhere execuable cannot be requested after initial detetection:\n' \ + f' initial vswhere executable: {cls.vswhere_frozen_binary.vswhere_exe!r}\n' \ + f' request vswhere executable: {vswhere_binary.vswhere_exe!r}' + + debug(f'VSWhereUserError: {msg}', extra=cls.debug_extra) + raise VSWhereUserError(msg) + + vswhere_exegroups_user = cls._get_vswhere_exegroups_user() + + exegroup = vswhere_exegroups_user[group] + group_binary = cls._vswhere_exegroup_binary(exegroup) + + if vswhere_binary.vswhere_norm == group_binary.vswhere_norm: + # ignore: user executable == exegroup[0] executable + return vswhere_binary + + exegroup.insert(0, vswhere_binary) + debug( + "insert exegroup=user[%s], vswhere_binary=%s", + cls.priority_indmap[group], vswhere_binary, extra=cls.debug_extra + ) + + return vswhere_binary @classmethod - def reset(cls): + def vswhere_freeze_executable(cls, vswhere_exe): + vswhere_binary = cls.register_vswhere_executable(vswhere_exe, priority='high') + frozen_binary = cls.freeze_vswhere_binary() + return frozen_binary, vswhere_binary - cls.seen_vswhere = set() - cls.seen_root = set() + @classmethod + def vswhere_freeze_env(cls, env): + + if env is None: + # no environment, no VSWHERE + vswhere_exe = None + write_vswhere = False + elif 'VSWHERE' not in env or not env['VSWHERE']: + # environment, VSWHERE undefined + vswhere_exe = None + write_vswhere = True + else: + # environment, VSWHERE defined + vswhere_exe = env.subst('$VSWHERE') + write_vswhere = False - cls.vswhere_executables = [] + frozen_binary, vswhere_binary = cls.vswhere_freeze_executable(vswhere_exe) - cls.msvc_instances = [] - cls.msvc_map = {} + if write_vswhere and frozen_binary.vswhere_norm != vswhere_binary.vswhere_norm: + env['VSWHERE'] = frozen_binary.vswhere_exe + debug( + "env['VSWHERE']=%s", + repr(frozen_binary.vswhere_exe), extra=cls.debug_extra + ) + + return frozen_binary, vswhere_binary + +# external use +vswhere_register_executable = _VSWhereExecutable.register_vswhere_executable +vswhere_get_executable = _VSWhereExecutable.get_vswhere_executable +vswhere_freeze_executable = _VSWhereExecutable.freeze_vswhere_executable + +# internal use +vswhere_freeze_env = _VSWhereExecutable.vswhere_freeze_env + +def msvc_find_vswhere(): + """ 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_path = _VSWhereExecutable.get_vswhere_executable() + return vswhere_path + +_MSVCInstance = namedtuple('_MSVCInstance', [ + 'vc_path', + 'vc_version', + 'vc_version_numeric', + 'vc_version_scons', + 'vc_release', + 'vc_component_id', + 'vc_component_rank', + 'vc_component_suffix', +]) + +class MSVCInstance(_MSVCInstance): @staticmethod def msvc_instances_default_order(a, b): @@ -1035,237 +1262,231 @@ def msvc_instances_default_order(a, b): return 1 if a.vc_component_rank < b.vc_component_rank else -1 return 0 - @classmethod - def register_reset_func(cls, func): - if func and func not in cls.reset_funcs_seen: - cls.reset_funcs_seen.add(func) - cls.reset_funcs_list.append(func) +class _VSWhere(MSVC.Util.AutoInitialize): + + debug_extra = None @classmethod - def call_reset_funcs(cls): - for func in cls.reset_funcs_list: - func() + def reset(cls): -_VSWhere.reset() + cls.seen_vswhere = set() + cls.seen_root = set() -def vswhere_register_reset_func(func): - _VSWhere.register_reset_func(func) + cls.vswhere_executables = [] -def _filter_vswhere_paths(env): + cls.msvc_instances = [] + cls.msvc_map = {} - vswhere_paths = [] + @classmethod + def _initialize(cls): + cls.debug_extra = common.debug_extra(cls) + cls.reset() - 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) + @classmethod + def _new_roots_discovered(cls): + # TODO(JCB) should not happen due to freezing vswhere.exe + # need sanity check? + pass - vswhere_default = _msvc_default_vswhere() - if vswhere_default and vswhere_default not in _VSWhere.seen_vswhere: - vswhere_paths.append(vswhere_default) + @classmethod + def _filter_vswhere_paths(cls, vswhere_exe): - debug('vswhere_paths=%s', vswhere_paths) - return vswhere_paths + vswhere_paths = [] -def _vswhere_query_json_output(vswhere_exe, vswhere_args): + if vswhere_exe and vswhere_exe not in cls.seen_vswhere: + vswhere_paths.append(vswhere_exe) - vswhere_json = None + return vswhere_paths - once = True - while once: - once = False - # using break for single exit (unless exception) + @classmethod + def _vswhere_query_json_output(cls, vswhere_exe, vswhere_args): - vswhere_cmd = [vswhere_exe] + vswhere_args + ['-format', 'json', '-utf8'] - debug("running: %s", vswhere_cmd) + vswhere_json = None - 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 + once = True + while once: + once = False + # using break for single exit (unless exception) - if not cp.stdout: - debug("no vswhere information returned") - break + vswhere_cmd = [vswhere_exe] + vswhere_args + ['-format', 'json', '-utf8'] + debug("running: %s", vswhere_cmd, extra=cls.debug_extra) - vswhere_output = cp.stdout.decode('utf8', errors='replace') - if not vswhere_output: - debug("no vswhere information output") - break + try: + cp = subprocess.run(vswhere_cmd, stdout=PIPE, stderr=PIPE, check=True) + except OSError as e: + errmsg = str(e) + debug("%s: %s", type(e).__name__, errmsg, extra=cls.debug_extra) + break + except Exception as e: + errmsg = str(e) + debug("%s: %s", type(e).__name__, errmsg, extra=cls.debug_extra) + raise - try: - vswhere_output_json = json.loads(vswhere_output) - except json.decoder.JSONDecodeError: - debug("json decode exception loading vswhere output") - break + if not cp.stdout: + debug("no vswhere information returned", extra=cls.debug_extra) + break - vswhere_json = vswhere_output_json - break + vswhere_output = cp.stdout.decode('utf8', errors='replace') + if not vswhere_output: + debug("no vswhere information output", extra=cls.debug_extra) + break - debug('vswhere_json=%s, vswhere_exe=%s', bool(vswhere_json), repr(vswhere_exe)) + try: + vswhere_output_json = json.loads(vswhere_output) + except json.decoder.JSONDecodeError: + debug("json decode exception loading vswhere output", extra=cls.debug_extra) + break - return vswhere_json + vswhere_json = vswhere_output_json + break -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( + 'vswhere_json=%s, vswhere_exe=%s', + bool(vswhere_json), repr(vswhere_exe), extra=cls.debug_extra + ) -def _vswhere_update_msvc_map(env): + return vswhere_json - 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 + @classmethod + def vswhere_update_msvc_map(cls, vswhere_exe): - n_instances = len(_VSWhere.msvc_instances) + frozen_binary, vswhere_binary = _VSWhereExecutable.vswhere_freeze_executable(vswhere_exe) - for vswhere_exe in vswhere_paths: + vswhere_exe = frozen_binary.vswhere_exe - if vswhere_exe in _VSWhere.seen_vswhere: - continue + vswhere_paths = cls._filter_vswhere_paths(vswhere_exe) + if not vswhere_paths: + return cls.msvc_map - _VSWhere.seen_vswhere.add(vswhere_exe) - _VSWhere.vswhere_executables.append(vswhere_exe) + n_instances = len(cls.msvc_instances) - debug('vswhere_exe=%s', repr(vswhere_exe)) + for vswhere_exe in vswhere_paths: - vswhere_json = _vswhere_query_json_output( - vswhere_exe, - ['-all', '-products', '*'] - ) + if vswhere_exe in cls.seen_vswhere: + continue - if not vswhere_json: - continue + cls.seen_vswhere.add(vswhere_exe) + cls.vswhere_executables.append(vswhere_exe) - for instance in vswhere_json: + debug('vswhere_exe=%s', repr(vswhere_exe), extra=cls.debug_extra) - #print(json.dumps(instance, indent=4, sort_keys=True)) + vswhere_json = cls._vswhere_query_json_output( + vswhere_exe, + ['-all', '-products', '*'] + ) - installation_path = instance.get('installationPath') - if not installation_path or not os.path.exists(installation_path): + if not vswhere_json: continue - vc_path = os.path.join(installation_path, 'VC') - if not os.path.exists(vc_path): - continue + for instance in vswhere_json: - vc_root = MSVC.Util.normalize_path(vc_path) - if vc_root in _VSWhere.seen_root: - continue - _VSWhere.seen_root.add(vc_root) + #print(json.dumps(instance, indent=4, sort_keys=True)) - installation_version = instance.get('installationVersion') - if not installation_version: - continue + installation_path = instance.get('installationPath') + if not installation_path or not os.path.exists(installation_path): + 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_path = os.path.join(installation_path, 'VC') + if not os.path.exists(vc_path): + continue - vc_version = _VSWHERE_VSMAJOR_TO_VCVERSION[vs_major] + vc_root = MSVC.Util.normalize_path(vc_path) + if vc_root in cls.seen_root: + continue + cls.seen_root.add(vc_root) - product_id = instance.get('productId') - if not product_id: - continue + installation_version = instance.get('installationVersion') + if not installation_version: + continue - component_id = product_id.split('.')[-1] - if component_id not in _VSWHERE_COMPONENTID_CANDIDATES: - debug('ignore component_id: %s', component_id) - continue + vs_major = installation_version.split('.')[0] + if not vs_major in _VSWHERE_VSMAJOR_TO_VCVERSION: + debug('ignore vs_major: %s', vs_major, extra=cls.debug_extra) + continue - 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}') + vc_version = _VSWHERE_VSMAJOR_TO_VCVERSION[vs_major] - scons_suffix = _VSWHERE_COMPONENTID_SCONS_SUFFIX[component_id] + product_id = instance.get('productId') + if not product_id: + continue - 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, - ) + component_id = product_id.split('.')[-1] + if component_id not in _VSWHERE_COMPONENTID_CANDIDATES: + debug('ignore component_id: %s', component_id, extra=cls.debug_extra) + continue - _VSWhere.msvc_instances.append(msvc_instance) + 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}') - new_roots = bool(len(_VSWhere.msvc_instances) > n_instances) - if new_roots: + scons_suffix = _VSWHERE_COMPONENTID_SCONS_SUFFIX[component_id] - _VSWhere.msvc_instances = sorted( - _VSWhere.msvc_instances, - key=cmp_to_key(_VSWhere.msvc_instances_default_order) - ) + if scons_suffix: + vc_version_scons = vc_version + scons_suffix + else: + vc_version_scons = vc_version + + is_prerelease = True if instance.get('isPrerelease', False) else False + is_release = False if is_prerelease else True + + msvc_instance = MSVCInstance( + vc_path = vc_path, + vc_version = vc_version, + vc_version_numeric = float(vc_version), + vc_version_scons = vc_version_scons, + vc_release = is_release, + vc_component_id = component_id, + vc_component_rank = component_rank, + vc_component_suffix = component_suffix, + ) - _VSWhere.msvc_map = {} + cls.msvc_instances.append(msvc_instance) - for msvc_instance in _VSWhere.msvc_instances: + new_roots = bool(len(cls.msvc_instances) > n_instances) + if new_roots: - 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) + cls.msvc_instances = sorted( + cls.msvc_instances, + key=cmp_to_key(MSVCInstance.msvc_instances_default_order) ) - key = (msvc_instance.vc_version_scons, msvc_instance.vc_release) - _VSWhere.msvc_map.setdefault(key,[]).append(msvc_instance) + cls.msvc_map = {} - if msvc_instance.vc_version_scons == msvc_instance.vc_version: - continue + for msvc_instance in cls.msvc_instances: - key = (msvc_instance.vc_version, msvc_instance.vc_release) - _VSWhere.msvc_map.setdefault(key,[]).append(msvc_instance) + debug( + 'msvc instance: msvc_version=%s, is_release=%s, component_id=%s, vc_path=%s', + repr(msvc_instance.vc_version_scons), msvc_instance.vc_release, + repr(msvc_instance.vc_component_id), repr(msvc_instance.vc_path), + extra=cls.debug_extra + ) - _VSWhere.call_reset_funcs() + key = (msvc_instance.vc_version_scons, msvc_instance.vc_release) + cls.msvc_map.setdefault(key,[]).append(msvc_instance) - debug('new_roots=%s, msvc_instances=%s', new_roots, len(_VSWhere.msvc_instances)) + if msvc_instance.vc_version_scons == msvc_instance.vc_version: + continue - return _VSWhere.msvc_map + key = (msvc_instance.vc_version, msvc_instance.vc_release) + cls.msvc_map.setdefault(key,[]).append(msvc_instance) -def vswhere_update_msvc(env): - _vswhere_update_msvc_map(env) + cls._new_roots_discovered() -_cache_pdir_vswhere_queries = {} + debug( + 'new_roots=%s, msvc_instances=%s', + new_roots, len(cls.msvc_instances), extra=cls.debug_extra + ) -def _reset_pdir_vswhere_queries(): - global _cache_pdir_vswhere_queries - _cache_pdir_vswhere_queries = {} - debug('reset _cache_pdir_vswhere_queries') + return cls.msvc_map -# register pdir vswhere cache reset function with vswhere state manager -_VSWhere.register_reset_func(_reset_pdir_vswhere_queries) +_cache_pdir_vswhere_queries = {} -def find_vc_pdir_vswhere(msvc_version, env=None): +def _find_vc_pdir_vswhere(msvc_version, vswhere_exe): """ Find the MSVC product directory using the vswhere program. Args: msvc_version: MSVC version to search for - env: optional to look up VSWHERE variable + vswhere_exe: vswhere executable or None Returns: MSVC install dir or None @@ -1276,7 +1497,7 @@ def find_vc_pdir_vswhere(msvc_version, env=None): """ global _cache_pdir_vswhere_queries - msvc_map = _vswhere_update_msvc_map(env) + msvc_map = _VSWhere.vswhere_update_msvc_map(vswhere_exe) if not msvc_map: return None @@ -1335,7 +1556,7 @@ def find_vc_pdir_vswhere(msvc_version, env=None): _cache_pdir_registry_queries = {} -def find_vc_pdir_registry(msvc_version): +def _find_vc_pdir_registry(msvc_version): """ Find the MSVC product directory using the registry. Args: @@ -1436,17 +1657,15 @@ def find_vc_pdir_registry(msvc_version): return pdir -def find_vc_pdir(msvc_version, env=None): +def _find_vc_pdir(msvc_version, vswhere_exe): """Find the MSVC product directory for the given version. - 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 + + vswhere_exe: str + vswhere executable or None Returns: str: Path found in registry, or None @@ -1460,14 +1679,14 @@ def find_vc_pdir(msvc_version, env=None): if msvc_version in _VSWHERE_SUPPORTED_VCVER: - pdir = find_vc_pdir_vswhere(msvc_version, env) + pdir = _find_vc_pdir_vswhere(msvc_version, vswhere_exe) if pdir: debug('VC found: %s, dir=%s', repr(msvc_version), repr(pdir)) return pdir elif msvc_version in _VCVER_TO_PRODUCT_DIR: - pdir = find_vc_pdir_registry(msvc_version) + pdir = _find_vc_pdir_registry(msvc_version) if pdir: debug('VC found: %s, dir=%s', repr(msvc_version), repr(pdir)) return pdir @@ -1480,16 +1699,40 @@ def find_vc_pdir(msvc_version, env=None): debug('no VC found for version %s', repr(msvc_version)) return None +def find_vc_pdir(msvc_version, env=None): + """Find the MSVC product directory for the given version. + + Args: + msvc_version: str + msvc version (major.minor, e.g. 10.0) + env: + optional to look up VSWHERE variable + + Returns: + str: Path found in registry, or None + + Raises: + UnsupportedVersion: if the version is not known by this file. + + UnsupportedVersion inherits from VisualCException. + + """ + + frozen_binary, _ = _VSWhereExecutable.vswhere_freeze_env(env) + + pdir = _find_vc_pdir(msvc_version, frozen_binary.vswhere_exe) + debug('pdir=%s', repr(pdir)) + + return pdir + # register find_vc_pdir function with Kind routines # in case of unknown msvc_version (just in case) MSVC.Kind.register_msvc_version_pdir_func(find_vc_pdir) def _reset_vc_pdir(): debug('reset pdir caches') - global _cache_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 = {} @@ -1593,9 +1836,6 @@ def _reset_installed_vcs(): 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) @@ -1760,9 +2000,7 @@ def _check_files_exist_in_vc_dir(env, vc_dir, msvc_version) -> bool: def get_installed_vcs(env=None): global __INSTALLED_VCS_RUN - debug('') - # the installed vcs cache is cleared if new vc roots are discovered - _vswhere_update_msvc_map(env) + _VSWhereExecutable.vswhere_freeze_env(env) if __INSTALLED_VCS_RUN is not None: return __INSTALLED_VCS_RUN @@ -1788,8 +2026,8 @@ def get_installed_vcs(env=None): debug('no compiler found %s', ver) else: debug('return None for ver %s', ver) - except (MSVCUnsupportedTargetArch, MSVCUnsupportedHostArch): - # Allow this exception to propagate further as it should cause + except (MSVCUnsupportedTargetArch, MSVCUnsupportedHostArch, VSWhereUserError): + # Allow these exceptions to propagate further as it should cause # SCons to exit with an error code raise except VisualCException as e: @@ -1807,6 +2045,7 @@ def reset_installed_vcs() -> None: """Make it try again to find VC. This is just for the tests.""" _reset_installed_vcs() _reset_vc_pdir() + _VSWhereExecutable.reset() _VSWhere.reset() MSVC._reset() @@ -2132,6 +2371,9 @@ def get_use_script_use_settings(env): def msvc_setup_env(env): debug('called') + + _VSWhereExecutable.vswhere_freeze_env(env) + version = get_default_version(env) if version is None: if not msvc_setup_env_user(env): @@ -2268,8 +2510,11 @@ def msvc_sdk_versions(version=None, msvc_uwp_app: bool=False): rval = MSVC.WinSDK.get_msvc_sdk_version_list(version, msvc_uwp_app) return rval -def msvc_toolset_versions(msvc_version=None, full: bool=True, sxs: bool=False): - debug('msvc_version=%s, full=%s, sxs=%s', repr(msvc_version), repr(full), repr(sxs)) +def msvc_toolset_versions(msvc_version=None, full: bool=True, sxs: bool=False, vswhere_exe=None): + debug( + 'msvc_version=%s, full=%s, sxs=%s, vswhere_exe=%s', + repr(msvc_version), repr(full), repr(sxs), repr(vswhere_exe) + ) rval = [] @@ -2284,7 +2529,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(msvc_version) + vc_dir = _find_vc_pdir(msvc_version, vswhere_exe) if not vc_dir: debug('VC folder not found for version %s', repr(msvc_version)) return rval @@ -2292,8 +2537,8 @@ def msvc_toolset_versions(msvc_version=None, full: bool=True, sxs: bool=False): rval = MSVC.ScriptArguments._msvc_toolset_versions_internal(msvc_version, vc_dir, full=full, sxs=sxs) return rval -def msvc_toolset_versions_spectre(msvc_version=None): - debug('msvc_version=%s', repr(msvc_version)) +def msvc_toolset_versions_spectre(msvc_version=None, vswhere_exe=None): + debug('msvc_version=%s, vswhere_exe=%s', repr(msvc_version), repr(vswhere_exe)) rval = [] @@ -2308,7 +2553,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(msvc_version) + vc_dir = _find_vc_pdir(msvc_version, vswhere_exe) if not vc_dir: debug('VC folder not found for version %s', repr(msvc_version)) return rval @@ -2316,7 +2561,7 @@ def msvc_toolset_versions_spectre(msvc_version=None): rval = MSVC.ScriptArguments._msvc_toolset_versions_spectre_internal(msvc_version, vc_dir) return rval -def msvc_query_version_toolset(version=None, prefer_newest: bool=True): +def msvc_query_version_toolset(version=None, prefer_newest: bool=True, vswhere_exe=None): """ Returns an msvc version and a toolset version given a version specification. @@ -2353,6 +2598,9 @@ def msvc_query_version_toolset(version=None, prefer_newest: bool=True): the native Visual Studio instance is not detected, prefer newer Visual Studio instances. + vswhere_exe: str + A vswhere executable location or None. + Returns: tuple: A tuple containing the msvc version and the msvc toolset version. The msvc toolset version may be None. @@ -2427,7 +2675,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(query_msvc_version) + vc_dir = _find_vc_pdir(query_msvc_version, vswhere_exe) if not vc_dir: continue diff --git a/SCons/Tool/MSCommon/vcTests.py b/SCons/Tool/MSCommon/vcTests.py index da360b3717..3b30a570a0 100644 --- a/SCons/Tool/MSCommon/vcTests.py +++ b/SCons/Tool/MSCommon/vcTests.py @@ -57,12 +57,22 @@ def testDefaults(self) -> None: Verify that msvc_find_vswhere() find's files in the specified paths """ # import pdb; pdb.set_trace() - vswhere_dirs = [os.path.splitdrive(p)[1] for p in SCons.Tool.MSCommon.vc.VSWHERE_PATHS] + test_all_dirs = [] + base_dir = test.workpath('fake_vswhere') + + vswhere_dirs = [os.path.splitdrive(p)[1] for p in SCons.Tool.MSCommon.vc._VSWHERE_EXEGROUP_MSVS] + test_vswhere_dirs = [os.path.join(base_dir,d[1:]) for d in vswhere_dirs] + SCons.Tool.MSCommon.vc._VSWHERE_EXEGROUP_MSVS = test_vswhere_dirs + test_all_dirs += test_vswhere_dirs + + vswhere_dirs = [os.path.splitdrive(p)[1] for p in SCons.Tool.MSCommon.vc._VSWHERE_EXEGROUP_PKGMGR] test_vswhere_dirs = [os.path.join(base_dir,d[1:]) for d in vswhere_dirs] + SCons.Tool.MSCommon.vc._VSWHERE_EXEGROUP_PKGMGR = test_vswhere_dirs + test_all_dirs += test_vswhere_dirs - SCons.Tool.MSCommon.vc.VSWHERE_PATHS = test_vswhere_dirs - for vsw in test_vswhere_dirs: + for vsw in test_all_dirs: + SCons.Tool.MSCommon.vc._VSWhereExecutable.reset() VswhereTestCase._createVSWhere(vsw) find_path = SCons.Tool.MSCommon.vc.msvc_find_vswhere() self.assertTrue(vsw == find_path, "Didn't find vswhere in %s found in %s" % (vsw, find_path)) diff --git a/SCons/Tool/MSCommon/vs.py b/SCons/Tool/MSCommon/vs.py index d40ac426ff..1f14376251 100644 --- a/SCons/Tool/MSCommon/vs.py +++ b/SCons/Tool/MSCommon/vs.py @@ -43,8 +43,7 @@ find_vc_pdir, get_msvc_version_numeric, reset_installed_vcs, - vswhere_register_reset_func, - vswhere_update_msvc, + vswhere_freeze_env, ) @@ -448,12 +447,7 @@ def reset(self) -> None: def get_installed_visual_studios(env=None): global InstalledVSList global InstalledVSMap - debug('') - # Calling vswhere_update_msvc may cause installed caches to be cleared. - # The installed vcs and visual studios are cleared when new vc roots - # are discovered which causes the InstalledVSList and InstalledVSMap - # variables to be set to None. - vswhere_update_msvc(env) + vswhere_freeze_env(env) if InstalledVSList is None: InstalledVSList = [] InstalledVSMap = {} @@ -486,19 +480,6 @@ def reset_installed_visual_studios() -> None: # installed VS's reset_installed_vcs() -def _reset_installed_vswhere_visual_studios(): - # vswhere found new roots: reset VS2017 and later - global InstalledVSList - global InstalledVSMap - debug('') - InstalledVSList = None - InstalledVSMap = None - for vs in SupportedVSWhereList: - vs.reset() - -# register vswhere callback function -vswhere_register_reset_func(_reset_installed_vswhere_visual_studios) - # We may be asked to update multiple construction environments with # SDK information. When doing this, we check on-disk for whether diff --git a/SCons/Tool/msvc.py b/SCons/Tool/msvc.py index 6afa171c97..b823752b50 100644 --- a/SCons/Tool/msvc.py +++ b/SCons/Tool/msvc.py @@ -296,9 +296,6 @@ def generate(env) -> None: # without it for lex generation env["LEXUNISTD"] = SCons.Util.CLVar("--nounistd") - # Get user specified vswhere location or locate. - env['VSWHERE'] = env.get('VSWHERE', msvc_find_vswhere()) - # Set-up ms tools paths msvc_setup_env_once(env, tool=tool_name) diff --git a/SCons/Tool/msvsTests.py b/SCons/Tool/msvsTests.py index 63d91ce2fa..7893cafdcc 100644 --- a/SCons/Tool/msvsTests.py +++ b/SCons/Tool/msvsTests.py @@ -420,6 +420,11 @@ def get(self, name, value=None): def Dir(self, name): return self.fs.Dir(name) + def subst(self, key): + if key[0] == '$': + key = key[1:] + return self[key] + class RegKey: """key class for storing an 'open' registry key""" @@ -579,7 +584,7 @@ def DummyQueryValue(key, value): def DummyExists(path) -> bool: return True -def DummyVsWhere(msvc_version, env): +def DummyVsWhere(msvc_version, vswhere_exe): # not testing versions with vswhere, so return none return None @@ -947,7 +952,7 @@ class msvsEmptyTestCase(msvsTestCase): SCons.Util.RegEnumKey = DummyEnumKey SCons.Util.RegEnumValue = DummyEnumValue SCons.Util.RegQueryValueEx = DummyQueryValue - SCons.Tool.MSCommon.vc.find_vc_pdir_vswhere = DummyVsWhere + SCons.Tool.MSCommon.vc._find_vc_pdir_vswhere = DummyVsWhere os.path.exists = DummyExists # make sure all files exist :-) os.path.isfile = DummyExists # make sure all files are files :-) diff --git a/test/MSVC/VSWHERE-fixture/SConstruct b/test/MSVC/VSWHERE-fixture/SConstruct index 74eea18c16..0346f802e0 100644 --- a/test/MSVC/VSWHERE-fixture/SConstruct +++ b/test/MSVC/VSWHERE-fixture/SConstruct @@ -5,12 +5,16 @@ import os import os.path -from SCons.Tool.MSCommon.vc import VSWHERE_PATHS +import SCons.Tool.MSCommon as mscommon +import SCons.Tool.MSCommon.vc as vc # Dump out expected paths -for vw_path in VSWHERE_PATHS: - print("VSWHERE_PATH=%s" % vw_path) - +for exegroup in ( + vc._VSWHERE_EXEGROUP_MSVS, + vc._VSWHERE_EXEGROUP_PKGMGR +): + for vw_path in exegroup: + print("VSWHERE_PATH=%s" % vw_path) # Allow normal detection logic to find vswhere.exe DefaultEnvironment(tools=[]) @@ -21,6 +25,19 @@ print("VSWHERE-detect=%s" % env1['VSWHERE']) v_local = os.path.join(os.getcwd(), 'vswhere.exe') Execute(Copy(os.path.join(os.getcwd(), 'vswhere.exe'), env1['VSWHERE'])) +# Reset vswhere executable manager +# A vswhere.exe not equal to the vswhere.exe for initial detection is an error +vc._VSWhereExecutable.reset() + # With VSWHERE set to copied vswhere.exe (see above), find vswhere.exe env = Environment(VSWHERE=v_local) print("VSWHERE-env=%s" % env['VSWHERE']) + +# Reset vswhere executable manager +# A vswhere.exe not equal to the vswhere.exe for initial detection is an error +vc._VSWhereExecutable.reset() + +# With VSWHERE set to copied vswhere.exe (see above), find vswhere.exe +mscommon.vswhere_register_executable(v_local, priority='high') +env = Environment() +print("VSWHERE-util=%s" % env['VSWHERE']) diff --git a/test/MSVC/VSWHERE.py b/test/MSVC/VSWHERE.py index e50e42a26e..b38ae5f2da 100644 --- a/test/MSVC/VSWHERE.py +++ b/test/MSVC/VSWHERE.py @@ -62,11 +62,14 @@ detected_path = l.strip().split('=')[-1] elif 'VSWHERE-env' in l: env_path = l.strip().split('=')[-1] + elif 'VSWHERE-util' in l: + util_path = l.strip().split('=')[-1] # Debug code # print("VPP:%s" % default_locations) # print("V-D:%s" % detected_path) # print("V-E:%s" % env_path) +# print("V-U:%s" % util_path) test.fail_test( @@ -87,6 +90,12 @@ message='VSWHERE not\n\t%s\n\t but\n\t%s' % (expected_env_path, env_path), ) +expected_util_path = os.path.join(test.workdir, 'vswhere.exe') +test.fail_test( + util_path != expected_env_path, + message='VSWHERE not\n\t%s\n\t but\n\t%s' % (expected_util_path, util_path), +) + test.pass_test() # here for reference, unused @@ -99,6 +108,7 @@ VSWHERE-detect=C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe Copy("C:\Users\Bill\AppData\Local\Temp\testcmd.11256.1ae1_as5\vswhere.exe", "C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe") VSWHERE-env=C:\Users\Bill\AppData\Local\Temp\testcmd.11256.1ae1_as5\vswhere.exe +VSWHERE-util=C:\Users\Bill\AppData\Local\Temp\testcmd.11256.1ae1_as5\vswhere.exe scons: done reading SConscript files. scons: Building targets ... scons: `.' is up to date. diff --git a/test/fixture/no_msvc/no_msvcs_sconstruct.py b/test/fixture/no_msvc/no_msvcs_sconstruct.py index 1aed9ecc1c..8dba65acba 100644 --- a/test/fixture/no_msvc/no_msvcs_sconstruct.py +++ b/test/fixture/no_msvc/no_msvcs_sconstruct.py @@ -7,7 +7,7 @@ DefaultEnvironment(tools=[]) -def DummyVsWhere(msvc_version, env): +def DummyVsWhere(msvc_version, vswhere_exe): # not testing versions with vswhere, so return none return None @@ -16,6 +16,6 @@ def DummyVsWhere(msvc_version, env): (SCons.Util.HKEY_LOCAL_MACHINE, r'') ] -SCons.Tool.MSCommon.vc.find_vc_pdir_vswhere = DummyVsWhere +SCons.Tool.MSCommon.vc._find_vc_pdir_vswhere = DummyVsWhere env = SCons.Environment.Environment() print('MSVC_VERSION=' + str(env.get('MSVC_VERSION'))) diff --git a/test/fixture/no_msvc/no_msvcs_sconstruct_msvc_query_toolset_version.py b/test/fixture/no_msvc/no_msvcs_sconstruct_msvc_query_toolset_version.py index fc5558b54a..9fe9440321 100644 --- a/test/fixture/no_msvc/no_msvcs_sconstruct_msvc_query_toolset_version.py +++ b/test/fixture/no_msvc/no_msvcs_sconstruct_msvc_query_toolset_version.py @@ -7,7 +7,7 @@ DefaultEnvironment(tools=[]) -def DummyVsWhere(msvc_version, env): +def DummyVsWhere(msvc_version, vswhere_exe): # not testing versions with vswhere, so return none return None @@ -16,6 +16,6 @@ def DummyVsWhere(msvc_version, env): (SCons.Util.HKEY_LOCAL_MACHINE, r'') ] -SCons.Tool.MSCommon.vc.find_vc_pdir_vswhere = DummyVsWhere +SCons.Tool.MSCommon.vc._find_vc_pdir_vswhere = DummyVsWhere msvc_version, msvc_toolset_version = SCons.Tool.MSCommon.msvc_query_version_toolset() print(f'msvc_version={msvc_version!r}, msvc_toolset_version={msvc_toolset_version!r}') diff --git a/test/fixture/no_msvc/no_msvcs_sconstruct_msvc_sdk_versions.py b/test/fixture/no_msvc/no_msvcs_sconstruct_msvc_sdk_versions.py index 6e7562d390..1880b8bf7a 100644 --- a/test/fixture/no_msvc/no_msvcs_sconstruct_msvc_sdk_versions.py +++ b/test/fixture/no_msvc/no_msvcs_sconstruct_msvc_sdk_versions.py @@ -7,7 +7,7 @@ DefaultEnvironment(tools=[]) -def DummyVsWhere(msvc_version, env): +def DummyVsWhere(msvc_version, vswhere_exe): # not testing versions with vswhere, so return none return None @@ -16,6 +16,6 @@ def DummyVsWhere(msvc_version, env): (SCons.Util.HKEY_LOCAL_MACHINE, r'') ] -SCons.Tool.MSCommon.vc.find_vc_pdir_vswhere = DummyVsWhere +SCons.Tool.MSCommon.vc._find_vc_pdir_vswhere = DummyVsWhere sdk_version_list = SCons.Tool.MSCommon.msvc_sdk_versions() print('sdk_version_list=' + repr(sdk_version_list)) diff --git a/test/fixture/no_msvc/no_msvcs_sconstruct_msvc_toolset_versions.py b/test/fixture/no_msvc/no_msvcs_sconstruct_msvc_toolset_versions.py index c2208cd393..35a45c3b62 100644 --- a/test/fixture/no_msvc/no_msvcs_sconstruct_msvc_toolset_versions.py +++ b/test/fixture/no_msvc/no_msvcs_sconstruct_msvc_toolset_versions.py @@ -7,7 +7,7 @@ DefaultEnvironment(tools=[]) -def DummyVsWhere(msvc_version, env): +def DummyVsWhere(msvc_version, vswhere_exe): # not testing versions with vswhere, so return none return None @@ -16,6 +16,6 @@ def DummyVsWhere(msvc_version, env): (SCons.Util.HKEY_LOCAL_MACHINE, r'') ] -SCons.Tool.MSCommon.vc.find_vc_pdir_vswhere = DummyVsWhere +SCons.Tool.MSCommon.vc._find_vc_pdir_vswhere = DummyVsWhere toolset_version_list = SCons.Tool.MSCommon.msvc_toolset_versions() print('toolset_version_list=' + repr(toolset_version_list)) diff --git a/test/fixture/no_msvc/no_msvcs_sconstruct_tools.py b/test/fixture/no_msvc/no_msvcs_sconstruct_tools.py index 2f2f07aedc..baac01bb2c 100644 --- a/test/fixture/no_msvc/no_msvcs_sconstruct_tools.py +++ b/test/fixture/no_msvc/no_msvcs_sconstruct_tools.py @@ -7,7 +7,7 @@ DefaultEnvironment(tools=[]) -def DummyVsWhere(msvc_version, env): +def DummyVsWhere(msvc_version, vswhere_exe): # not testing versions with vswhere, so return none return None @@ -16,6 +16,6 @@ def DummyVsWhere(msvc_version, env): (SCons.Util.HKEY_LOCAL_MACHINE, r'') ] -SCons.Tool.MSCommon.vc.find_vc_pdir_vswhere = DummyVsWhere +SCons.Tool.MSCommon.vc._find_vc_pdir_vswhere = DummyVsWhere env = SCons.Environment.Environment(tools=['myignoredefaultmsvctool']) diff --git a/test/fixture/no_msvc/no_msvcs_sconstruct_version.py b/test/fixture/no_msvc/no_msvcs_sconstruct_version.py index bd81688e35..3bf15d35e9 100644 --- a/test/fixture/no_msvc/no_msvcs_sconstruct_version.py +++ b/test/fixture/no_msvc/no_msvcs_sconstruct_version.py @@ -7,7 +7,7 @@ DefaultEnvironment(tools=[]) -def DummyVsWhere(msvc_version, env): +def DummyVsWhere(msvc_version, vswhere_exe): # not testing versions with vswhere, so return none return None @@ -16,6 +16,6 @@ def DummyVsWhere(msvc_version, env): (SCons.Util.HKEY_LOCAL_MACHINE, r'') ] -SCons.Tool.MSCommon.vc.find_vc_pdir_vswhere = DummyVsWhere +SCons.Tool.MSCommon.vc._find_vc_pdir_vswhere = DummyVsWhere SCons.Tool.MSCommon.msvc_set_notfound_policy('error') env = SCons.Environment.Environment(MSVC_VERSION='14.3') From 7dd8caac555cc97ae46e245be1801b4ffd2e8b8a Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Sat, 23 Mar 2024 10:57:31 -0400 Subject: [PATCH 22/35] Remove orphaned warning and update CHANGES.txt --- CHANGES.txt | 9 +++------ SCons/Tool/MSCommon/MSVC/Warnings.py | 3 --- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index dca9d509de..e4f6a92ba1 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -47,12 +47,6 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER selected before a full development edition when both editions are installed. - The registry detection of VS2015 (14.0), and earlier, is now cached at runtime and is only evaluated once for each msvc version. - - The vswhere 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 vswhere executable is frozen upon initial detection. Specifying a different vswhere executable via the construction variable VSWHERE after the initial detection now results in an exception. Multiple bugs in the implementation of @@ -63,6 +57,9 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER For example, when no vswhere executable is found for the initial detection and then later an environment is constructed with a user specified vswhere executable that detects new msvc installations. + - The vswhere detection of VS2017 (14.1), and later, is now cached at runtime and is + only evaluated once using a single vswhere invocation for all installations. + Previously, the vswhere executable was invoked for each supported msvc version. From Mats Wichmann: diff --git a/SCons/Tool/MSCommon/MSVC/Warnings.py b/SCons/Tool/MSCommon/MSVC/Warnings.py index 902de299a6..cab5145a99 100644 --- a/SCons/Tool/MSCommon/MSVC/Warnings.py +++ b/SCons/Tool/MSCommon/MSVC/Warnings.py @@ -30,9 +30,6 @@ class VisualCWarning(SCons.Warnings.WarningOnByDefault): pass -class VSWherePathWarning(VisualCWarning): - pass - class MSVCScriptExecutionWarning(VisualCWarning): pass From 9714abc7704897e782770011dae14aa2c5add38a Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Sun, 24 Mar 2024 07:53:42 -0400 Subject: [PATCH 23/35] Update documentation. --- CHANGES.txt | 5 +++- RELEASE.txt | 8 +++++++ SCons/Tool/msvc.xml | 57 ++++++++++++++++++++++++++------------------- 3 files changed, 45 insertions(+), 25 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index e4f6a92ba1..99e1938954 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -58,8 +58,11 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER and then later an environment is constructed with a user specified vswhere executable that detects new msvc installations. - The vswhere detection of VS2017 (14.1), and later, is now cached at runtime and is - only evaluated once using a single vswhere invocation for all installations. + only evaluated once using a single vswhere invocation for all msvc versions. Previously, the vswhere executable was invoked for each supported msvc version. + - The vswhere executable locations for the WinGet and Scoop package managers were + added to the default vswhere executable search list after the Chocolatey + installation location. From Mats Wichmann: diff --git a/RELEASE.txt b/RELEASE.txt index e549ec76d2..4a10bf7d27 100644 --- a/RELEASE.txt +++ b/RELEASE.txt @@ -35,6 +35,10 @@ CHANGED/ENHANCED EXISTING FUNCTIONALITY (14.0). This behavior was extended to Visual Studio 2017 (14.1) and Visual Studio 2008 (8.0). An express installation of the IDE binary is used when no other IDE edition is detected. +- The vswhere executable locations for the WinGet and Scoop package managers were + added to the default vswhere executable search list after the Chocolatey + installation location. + FIXES ----- @@ -79,6 +83,10 @@ IMPROVEMENTS - MSVC: Visual Studio 2015 BuildTools (14.0) does not support the sdk version argument and does not support the store argument. Script argument validation now takes into account these restrictions. +- MSVC: The registry detection of VS2015 (14.0), and earlier, is now cached at runtime + and is only evaluated once for each msvc version. +- MSVC: The vswhere detection of VS2017 (14.1), and later, is now cached at runtime and + is only evaluated once using a single vswhere invocation for all msvc versions. PACKAGING --------- diff --git a/SCons/Tool/msvc.xml b/SCons/Tool/msvc.xml index e6cbca6841..2b8f8a4f13 100644 --- a/SCons/Tool/msvc.xml +++ b/SCons/Tool/msvc.xml @@ -400,24 +400,25 @@ latest version of &MSVC; installed on your system. The valid values for &cv-MSVC_VERSION; represent major versions of the compiler, except that versions ending in Exp -refer to "Express" or "Express for Desktop" Visual Studio editions, -which require distinct entries because they use a different -filesystem layout and have feature limitations compared to -the full version. +refer to "Express" or "Express for Desktop" Visual Studio editions. Values that do not look like a valid compiler version string are not supported. -To have the desired effect, &cv-MSVC_VERSION; must be set by the -time compiler discovery takes place. -If the default tools list -or an explicit tools list including &t-link-msvc; is used, -discovery takes place as the &consenv; is created, -so passing it as an argument in the the &f-link-Environment; call +To have the desired effect, &cv-MSVC_VERSION; must be set before an msvc &f-link-Tool; +(e.g., &t-link-msvc;) or an msvc-dependent &f-link-Tool; (e.g., &t-link-midl;) is loaded +into the &consenv;. + + + +If the default tools list or an explicit tools list is used that includes an +msvc &f-link-Tool; (e.g., &t-link-msvc;) or an msvc-dependent &f-link-Tool; (e.g., &t-link-midl;); +the &cv-MSVC_VERSION; is resolved when the &consenv; is created. +In this case, passing &cv-MSVC_VERSION; as an argument in the &f-link-Environment; call is the effective solution. -Otherwise, &cv-MSVC_VERSION; must be set before the first msvc tool is -loaded into the environment. +Otherwise, &cv-MSVC_VERSION; must be set before the first msvc &f-link-Tool; or +msvc-dependent &f-link-Tool; is loaded into the environment. See the manpage section "Construction Environments" for an example. @@ -475,10 +476,10 @@ Visual Studio "14.1Exp" - 14.1 - 1910 + 14.1 or 14.1x + 191x Visual Studio 2017 Express - 15.0 + 15.x "14.0" @@ -864,34 +865,42 @@ Specify the location of vswhere.exe. The vswhere.exe executable is distributed with Microsoft Visual Studio and Build - Tools since the 2017 edition, but is also available standalone. + Tools since the 2017 edition, but is also available as a standalone installation. It provides full information about installations of 2017 and later editions. With the argument, vswhere.exe can detect installations of the 2010 through 2015 editions with limited data returned. -If VSWHERE is set, &SCons; will use that location. - Otherwise &SCons; will look in the following locations and set VSWHERE to the path of the first vswhere.exe -located. + If &cv-VSWHERE; is set to a vswhere.exe location, &SCons; will use that location. + When &cv-VSWHERE; is undefined, &SCons; will look in the following locations and set &cv-VSWHERE; to the path + of the first vswhere.exe located: %ProgramFiles(x86)%\Microsoft Visual Studio\Installer %ProgramFiles%\Microsoft Visual Studio\Installer %ChocolateyInstall%\bin +%LOCALAPPDATA%\Microsoft\WinGet\Links +~\scoop\shims +%SCOOP%\shims - Note that VSWHERE must be set at the same time or prior to any of &t-link-msvc;, &t-link-msvs; , and/or &t-link-mslink; &f-link-Tool; being initialized. + Note that &cv-VSWHERE; must be set prior to the initial &MSVC; compiler discovery. + For example, &cv-VSWHERE; must be set at the same time or before the first msvc &f-link-Tool; + (e.g., &t-link-msvc;) or msvc-dependent &f-link-Tool; (e.g., &t-link-midl;) is initialized. + - Either set it as follows + + Either set it as follows: env = Environment(VSWHERE='c:/my/path/to/vswhere') -or if your &consenv; is created specifying an empty tools list -(or a list of tools which omits all of default, msvs, msvc, and mslink), -and also before &f-link-env-Tool; is called to ininitialize any of those tools: +Or, if your &consenv; is created specifying: (a) an empty tools list, or (b) +a list of tools which omits all of default, msvc (e.g., &t-link-msvc;), and +msvc-dependent tools (e.g., &t-link-midl;); and before &f-link-env-Tool; +is called to initialize any of those tools: env = Environment(tools=[]) From 1dacd31dd618640cc7cdb003114bd05f4149cfa3 Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Sun, 24 Mar 2024 16:20:59 -0400 Subject: [PATCH 24/35] Rework vswhere executable filter/seen handling. --- SCons/Tool/MSCommon/MSVC/Kind.py | 1 - SCons/Tool/MSCommon/vc.py | 150 ++++++++++++++----------------- SCons/Tool/MSCommon/vs.py | 4 - 3 files changed, 69 insertions(+), 86 deletions(-) diff --git a/SCons/Tool/MSCommon/MSVC/Kind.py b/SCons/Tool/MSCommon/MSVC/Kind.py index d91e6a29fb..47d4a25d9f 100644 --- a/SCons/Tool/MSCommon/MSVC/Kind.py +++ b/SCons/Tool/MSCommon/MSVC/Kind.py @@ -177,7 +177,6 @@ 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 diff --git a/SCons/Tool/MSCommon/vc.py b/SCons/Tool/MSCommon/vc.py index 126d2588ce..e5ca7d8e06 100644 --- a/SCons/Tool/MSCommon/vc.py +++ b/SCons/Tool/MSCommon/vc.py @@ -1272,8 +1272,6 @@ def reset(cls): cls.seen_vswhere = set() cls.seen_root = set() - cls.vswhere_executables = [] - cls.msvc_instances = [] cls.msvc_map = {} @@ -1283,20 +1281,20 @@ def _initialize(cls): cls.reset() @classmethod - def _new_roots_discovered(cls): - # TODO(JCB) should not happen due to freezing vswhere.exe - # need sanity check? - pass + def _filter_vswhere_binary(cls, vswhere_binary): - @classmethod - def _filter_vswhere_paths(cls, vswhere_exe): + vswhere_norm = None - vswhere_paths = [] + if vswhere_binary.vswhere_norm not in cls.seen_vswhere: + cls.seen_vswhere.add(vswhere_binary.vswhere_norm) + vswhere_norm = vswhere_binary.vswhere_norm - if vswhere_exe and vswhere_exe not in cls.seen_vswhere: - vswhere_paths.append(vswhere_exe) + return vswhere_norm - return vswhere_paths + @classmethod + def _new_roots_discovered(cls): + if len(cls.seen_vswhere) > 1: + raise MSVCInternalError(f'vswhere discovered new msvc installations after initial detection') @classmethod def _vswhere_query_json_output(cls, vswhere_exe, vswhere_args): @@ -1352,95 +1350,85 @@ def vswhere_update_msvc_map(cls, vswhere_exe): frozen_binary, vswhere_binary = _VSWhereExecutable.vswhere_freeze_executable(vswhere_exe) - vswhere_exe = frozen_binary.vswhere_exe - - vswhere_paths = cls._filter_vswhere_paths(vswhere_exe) - if not vswhere_paths: + vswhere_norm = cls._filter_vswhere_binary(frozen_binary) + if not vswhere_norm: return cls.msvc_map - n_instances = len(cls.msvc_instances) + debug('vswhere_norm=%s', repr(vswhere_norm), extra=cls.debug_extra) - for vswhere_exe in vswhere_paths: + vswhere_json = cls._vswhere_query_json_output( + vswhere_norm, + ['-all', '-products', '*'] + ) - if vswhere_exe in cls.seen_vswhere: - continue + if not vswhere_json: + return cls.msvc_map - cls.seen_vswhere.add(vswhere_exe) - cls.vswhere_executables.append(vswhere_exe) + n_instances = len(cls.msvc_instances) - debug('vswhere_exe=%s', repr(vswhere_exe), extra=cls.debug_extra) + for instance in vswhere_json: - vswhere_json = cls._vswhere_query_json_output( - vswhere_exe, - ['-all', '-products', '*'] - ) + #print(json.dumps(instance, indent=4, sort_keys=True)) - if not vswhere_json: + installation_path = instance.get('installationPath') + if not installation_path or not os.path.exists(installation_path): 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_path = os.path.join(installation_path, 'VC') - if not os.path.exists(vc_path): - continue + vc_path = os.path.join(installation_path, 'VC') + if not os.path.exists(vc_path): + continue - vc_root = MSVC.Util.normalize_path(vc_path) - if vc_root in cls.seen_root: - continue - cls.seen_root.add(vc_root) + vc_root = MSVC.Util.normalize_path(vc_path) + if vc_root in cls.seen_root: + continue + cls.seen_root.add(vc_root) - installation_version = instance.get('installationVersion') - if not installation_version: - continue + installation_version = instance.get('installationVersion') + if not installation_version: + continue - vs_major = installation_version.split('.')[0] - if not vs_major in _VSWHERE_VSMAJOR_TO_VCVERSION: - debug('ignore vs_major: %s', vs_major, extra=cls.debug_extra) - continue + vs_major = installation_version.split('.')[0] + if not vs_major in _VSWHERE_VSMAJOR_TO_VCVERSION: + debug('ignore vs_major: %s', vs_major, extra=cls.debug_extra) + continue - vc_version = _VSWHERE_VSMAJOR_TO_VCVERSION[vs_major] + vc_version = _VSWHERE_VSMAJOR_TO_VCVERSION[vs_major] - product_id = instance.get('productId') - if not product_id: - continue + product_id = instance.get('productId') + if not product_id: + continue - component_id = product_id.split('.')[-1] - if component_id not in _VSWHERE_COMPONENTID_CANDIDATES: - debug('ignore component_id: %s', component_id, extra=cls.debug_extra) - continue + component_id = product_id.split('.')[-1] + if component_id not in _VSWHERE_COMPONENTID_CANDIDATES: + debug('ignore component_id: %s', component_id, extra=cls.debug_extra) + continue - component_rank = _VSWHERE_COMPONENTID_RANKING.get(component_id,0) - if component_rank == 0: - raise MSVCInternalError(f'unknown component_rank for component_id: {component_id!r}') + 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] + scons_suffix = _VSWHERE_COMPONENTID_SCONS_SUFFIX[component_id] - if scons_suffix: - vc_version_scons = vc_version + scons_suffix - else: - vc_version_scons = vc_version - - is_prerelease = True if instance.get('isPrerelease', False) else False - is_release = False if is_prerelease else True - - msvc_instance = MSVCInstance( - vc_path = vc_path, - vc_version = vc_version, - vc_version_numeric = float(vc_version), - vc_version_scons = vc_version_scons, - vc_release = is_release, - vc_component_id = component_id, - vc_component_rank = component_rank, - vc_component_suffix = component_suffix, - ) + if scons_suffix: + vc_version_scons = vc_version + scons_suffix + else: + vc_version_scons = vc_version + + is_prerelease = True if instance.get('isPrerelease', False) else False + is_release = False if is_prerelease else True + + msvc_instance = MSVCInstance( + vc_path = vc_path, + vc_version = vc_version, + vc_version_numeric = float(vc_version), + vc_version_scons = vc_version_scons, + vc_release = is_release, + vc_component_id = component_id, + vc_component_rank = component_rank, + vc_component_suffix = component_suffix, + ) - cls.msvc_instances.append(msvc_instance) + cls.msvc_instances.append(msvc_instance) new_roots = bool(len(cls.msvc_instances) > n_instances) if new_roots: diff --git a/SCons/Tool/MSCommon/vs.py b/SCons/Tool/MSCommon/vs.py index 1f14376251..34fb670b5f 100644 --- a/SCons/Tool/MSCommon/vs.py +++ b/SCons/Tool/MSCommon/vs.py @@ -426,12 +426,8 @@ def reset(self) -> None: ), ] -SupportedVSWhereList = [] SupportedVSMap = {} for vs in SupportedVSList: - if vs.vernum >= 14.1: - # VS2017 and later detected via vswhere.exe - SupportedVSWhereList.append(vs) SupportedVSMap[vs.version] = vs From 41308408334e4865ba8db77f96e964d8d5bad271 Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Mon, 25 Mar 2024 06:50:54 -0400 Subject: [PATCH 25/35] Update MSCommon/README.rst formatting. --- SCons/Tool/MSCommon/README.rst | 225 +++++++++++++++------------------ 1 file changed, 99 insertions(+), 126 deletions(-) diff --git a/SCons/Tool/MSCommon/README.rst b/SCons/Tool/MSCommon/README.rst index 4a7acc59b4..4ff8d7f913 100644 --- a/SCons/Tool/MSCommon/README.rst +++ b/SCons/Tool/MSCommon/README.rst @@ -33,55 +33,37 @@ MSVC Detection Priority For msvc version specifications without an 'Exp' suffix, an express installation is used only when no other installation is detected. -======= ======= ======================================================== -Product VCVer Priority -======= ======= ======================================================== -VS2022 14.3 Enterprise, Professional, Community, BuildTools -------- ------- -------------------------------------------------------- -VS2019 14.2 Enterprise, Professional, Community, BuildTools -------- ------- -------------------------------------------------------- -VS2017 14.1 Enterprise, Professional, Community, BuildTools, Express -------- ------- -------------------------------------------------------- -VS2017 14.1Exp Express -------- ------- -------------------------------------------------------- -VS2015 14.0 [Develop, BuildTools, CmdLine], Express -------- ------- -------------------------------------------------------- -VS2015 14.0Exp Express -------- ------- -------------------------------------------------------- -VS2013 12.0 Develop, Express -------- ------- -------------------------------------------------------- -VS2013 12.0Exp Express -------- ------- -------------------------------------------------------- -VS2012 11.0 Develop, Express -------- ------- -------------------------------------------------------- -VS2012 11.0Exp Express -------- ------- -------------------------------------------------------- -VS2010 10.0 Develop, Express -------- ------- -------------------------------------------------------- -VS2010 10.0Exp Express -------- ------- -------------------------------------------------------- -VS2008 9.0 Develop, VCForPython, Express -------- ------- -------------------------------------------------------- -VS2008 9.0Exp Express -------- ------- -------------------------------------------------------- -VS2005 8.0 Develop, Express -------- ------- -------------------------------------------------------- -VS2005 8.0Exp Express -------- ------- -------------------------------------------------------- -VS2003 7.1 Develop -------- ------- -------------------------------------------------------- -VS2002 7.0 Develop -------- ------- -------------------------------------------------------- -VS6.0 6.0 Develop -======= ======= ======================================================== +======= ======= ======================================================== +Product VCVer Priority +======= ======= ======================================================== +VS2022 14.3 Enterprise, Professional, Community, BuildTools +VS2019 14.2 Enterprise, Professional, Community, BuildTools +VS2017 14.1 Enterprise, Professional, Community, BuildTools, Express +VS2017 14.1Exp Express +VS2015 14.0 [Develop, BuildTools, CmdLine], Express +VS2015 14.0Exp Express +VS2013 12.0 Develop, Express +VS2013 12.0Exp Express +VS2012 11.0 Develop, Express +VS2012 11.0Exp Express +VS2010 10.0 Develop, Express +VS2010 10.0Exp Express +VS2008 9.0 Develop, VCForPython, Express +VS2008 9.0Exp Express +VS2005 8.0 Develop, Express +VS2005 8.0Exp Express +VS2003 7.1 Develop +VS2002 7.0 Develop +VS6.0 6.0 Develop +======= ======= ======================================================== Legend: Develop - devenv.com (or msdev.com) is detected. + devenv.com or msdev.com is detected. Express - WDExpress.exe (or VCExpress.exe) is detected. + WDExpress.exe or VCExpress.exe is detected. BuildTools [VS2015] The vcvarsall batch file dispatches to the buildtools batch file. @@ -104,7 +86,9 @@ 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 @@ -135,7 +119,9 @@ As installed, VS2015 Express does not support the ``store`` argument for the ``a 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% ... @@ -143,7 +129,9 @@ The store library paths appear in two places in the ``vcvarsx86_amd64`` batch fi @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% ... @@ -157,7 +145,9 @@ As installed, VS2015 Express does not support the ``store`` argument for the ``a 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% ... @@ -165,7 +155,9 @@ The store library paths appear in two places in the ``vcvarsx86_arm`` batch file @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% ... @@ -208,6 +200,7 @@ returns the msvc version and the msvc toolset version for the corresponding vers This is a proxy for using the toolset version for selection until that functionality can be added. Example usage: + :: for version in [ @@ -240,6 +233,7 @@ Example usage: print('{}Query: {} version={}, prefer_newest={}'.format(newline, msg, version, prefer_newest)) Example output fragment + :: Build: _build003 {'MSVC_VERSION': '14.3', 'MSVC_TOOLSET_VERSION': '14.29.30133'} @@ -267,6 +261,7 @@ added to the batch file argument list. This is intended to make the cache more updates that may change the default toolset version and/or the default SDK version. Example usage: + :: @echo Enabling scons cache ... @@ -289,6 +284,7 @@ Enabling warnings to be produced for detected msvc batch file errors may provide for build failures. Refer to the documentation for details. Change the default policy: + :: from SCons.Tool.MSCommon import msvc_set_scripterror_policy @@ -296,6 +292,7 @@ Change the default policy: msvc_set_scripterror_policy('Warning') Specify the policy per-environment: + :: env = Environment(MSVC_VERSION='14.3', MSVC_SPECTRE_LIBS=True, MSVC_SCRIPTERROR_POLICY='Warning') @@ -321,6 +318,7 @@ On occasion, the raw vswhere output may prove useful especially if there are sus detection of installed msvc instances. Windows command-line sample invocations: + :: @rem 64-Bit Windows @@ -338,17 +336,14 @@ 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 [1]_ X [2]_ -======= ======= ====== ======= ======= +======== ======= ======= ======== ======= +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. @@ -356,17 +351,14 @@ VS2015 X [1]_ X [2]_ Supported MSVC batch file arguments in SCons: -======== ====================================== =================================================== -Argument Construction Variable Script Argument Equivalent -======== ====================================== =================================================== -UWP ``MSVC_UWP_APP=True`` ``MSVC_SCRIPT_ARGS='store'`` --------- -------------------------------------- --------------------------------------------------- -SDK ``MSVC_SDK_VERSION='10.0.20348.0'`` ``MSVC_SCRIPT_ARGS='10.0.20348.0'`` --------- -------------------------------------- --------------------------------------------------- -Toolset ``MSVC_TOOLSET_VERSION='14.31.31103'`` ``MSVC_SCRIPT_ARGS='-vcvars_ver=14.31.31103'`` --------- -------------------------------------- --------------------------------------------------- -Spectre ``MSVC_SPECTRE_LIBS=True`` ``MSVC_SCRIPT_ARGS='-vcvars_spectre_libs=spectre'`` -======== ====================================== =================================================== +======== ====================================== =================================================== +Argument Construction Variable Script Argument Equivalent +======== ====================================== =================================================== +UWP ``MSVC_UWP_APP=True`` ``MSVC_SCRIPT_ARGS='store'`` +SDK ``MSVC_SDK_VERSION='10.0.20348.0'`` ``MSVC_SCRIPT_ARGS='10.0.20348.0'`` +Toolset ``MSVC_TOOLSET_VERSION='14.31.31103'`` ``MSVC_SCRIPT_ARGS='-vcvars_ver=14.31.31103'`` +Spectre ``MSVC_SPECTRE_LIBS=True`` ``MSVC_SCRIPT_ARGS='-vcvars_spectre_libs=spectre'`` +======== ====================================== =================================================== **MSVC_SCRIPT_ARGS contents are not validated. Utilizing script arguments that have construction variable equivalents is discouraged and may lead to difficult to diagnose build errors.** @@ -398,6 +390,7 @@ that the msvc batch files would return. When using ``MSVC_SCRIPT_ARGS``, the toolset specification should be omitted entirely. Local installation and summary test results: + :: VS2022\VC\Auxiliary\Build\Microsoft.VCToolsVersion.v143.default.txt @@ -407,6 +400,7 @@ Local installation and summary test results: 14.32.31326 Toolset version summary: + :: 14.31.31103 Environment() @@ -422,6 +416,7 @@ Toolset version summary: 14.32.31326 Environment(MSVC_SCRIPT_ARGS=['-vcvars_ver=14.32']) VS2022\\Common7\\Tools\\vsdevcmd\\ext\\vcvars.bat usage fragment: + :: @echo -vcvars_ver=version : Version of VC++ Toolset to select @@ -443,6 +438,7 @@ VS2022\\Common7\\Tools\\vsdevcmd\\ext\\vcvars.bat usage fragment: @echo SxS toolset to [VSInstallDir]\VC\MSVC\Tools\ directory. VS2022 batch file fragment to determine the default toolset version: + :: @REM Add MSVC @@ -469,13 +465,12 @@ Visual Studio Version Notes SDK Versions ------------ -==== ================= -SDK Format -==== ================= -10.0 10.0.XXXXX.Y [*]_ ----- ----------------- -8.1 8.1 -==== ================= +==== ================= +SDK Format +==== ================= +10.0 10.0.XXXXX.Y [*]_ +8.1 8.1 +==== ================= .. [*] The Windows 10 SDK version number is 10.0.20348.0 and earlier. @@ -484,64 +479,42 @@ SDK Format BuildTools Versions ------------------- -========== ===== ===== ======== -BuildTools VCVER CLVER MSVCRT -========== ===== ===== ======== -v143 14.3 19.3 140/ucrt ----------- ----- ----- -------- -v142 14.2 19.2 140/ucrt ----------- ----- ----- -------- -v141 14.1 19.1 140/ucrt ----------- ----- ----- -------- -v140 14.0 19.0 140/ucrt ----------- ----- ----- -------- -v120 12.0 18.0 120 ----------- ----- ----- -------- -v110 11.0 17.0 110 ----------- ----- ----- -------- -v100 10.0 16.0 100 ----------- ----- ----- -------- -v90 9.0 15.0 90 ----------- ----- ----- -------- -v80 8.0 14.0 80 ----------- ----- ----- -------- -v71 7.1 13.1 71 ----------- ----- ----- -------- -v70 7.0 13.0 70 ----------- ----- ----- -------- -v60 6.0 12.0 60 -========== ===== ===== ======== +========== ===== ===== ======== +BuildTools VCVER CLVER MSVCRT +========== ===== ===== ======== +v143 14.3 19.3 140/ucrt +v142 14.2 19.2 140/ucrt +v141 14.1 19.1 140/ucrt +v140 14.0 19.0 140/ucrt +v120 12.0 18.0 120 +v110 11.0 17.0 110 +v100 10.0 16.0 100 +v90 9.0 15.0 90 +v80 8.0 14.0 80 +v71 7.1 13.1 71 +v70 7.0 13.0 70 +v60 6.0 12.0 60 +========== ===== ===== ======== Product Versions ---------------- -======== ===== ========= ====================== -Product VSVER SDK BuildTools -======== ===== ========= ====================== -2022 17.0 10.0, 8.1 v143, v142, v141, v140 --------- ----- --------- ---------------------- -2019 16.0 10.0, 8.1 v142, v141, v140 --------- ----- --------- ---------------------- -2017 15.0 10.0, 8.1 v141, v140 --------- ----- --------- ---------------------- -2015 14.0 10.0, 8.1 v140 --------- ----- --------- ---------------------- -2013 12.0 v120 --------- ----- --------- ---------------------- -2012 11.0 v110 --------- ----- --------- ---------------------- -2010 10.0 v100 --------- ----- --------- ---------------------- -2008 9.0 v90 --------- ----- --------- ---------------------- -2005 8.0 v80 --------- ----- --------- ---------------------- -2003.NET 7.1 v71 --------- ----- --------- ---------------------- -2002.NET 7.0 v70 --------- ----- --------- ---------------------- -6.0 6.0 v60 -======== ===== ========= ====================== +========= ====== ========== ====================== +Product VSVER SDK BuildTools +========= ====== ========== ====================== +2022 17.0 10.0, 8.1 v143, v142, v141, v140 +2019 16.0 10.0, 8.1 v142, v141, v140 +2017 15.0 10.0, 8.1 v141, v140 +2015 14.0 10.0, 8.1 v140 +2013 12.0 v120 +2012 11.0 v110 +2010 10.0 v100 +2008 9.0 v90 +2005 8.0 v80 +2003.NET 7.1 v71 +2002.NET 7.0 v70 +6.0 6.0 v60 +========= ====== ========== ====================== SCons Implementation Notes From f747386f833aca843201e9241ba0f83c5ee24b46 Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Mon, 25 Mar 2024 08:15:56 -0400 Subject: [PATCH 26/35] Convert simple tables to grid tables in MSCommon/README.rst --- SCons/Tool/MSCommon/README.rst | 202 ++++++++++++++++++++------------- 1 file changed, 125 insertions(+), 77 deletions(-) diff --git a/SCons/Tool/MSCommon/README.rst b/SCons/Tool/MSCommon/README.rst index 4ff8d7f913..bcac466759 100644 --- a/SCons/Tool/MSCommon/README.rst +++ b/SCons/Tool/MSCommon/README.rst @@ -33,29 +33,47 @@ MSVC Detection Priority For msvc version specifications without an 'Exp' suffix, an express installation is used only when no other installation is detected. -======= ======= ======================================================== -Product VCVer Priority -======= ======= ======================================================== -VS2022 14.3 Enterprise, Professional, Community, BuildTools -VS2019 14.2 Enterprise, Professional, Community, BuildTools -VS2017 14.1 Enterprise, Professional, Community, BuildTools, Express -VS2017 14.1Exp Express -VS2015 14.0 [Develop, BuildTools, CmdLine], Express -VS2015 14.0Exp Express -VS2013 12.0 Develop, Express -VS2013 12.0Exp Express -VS2012 11.0 Develop, Express -VS2012 11.0Exp Express -VS2010 10.0 Develop, Express -VS2010 10.0Exp Express -VS2008 9.0 Develop, VCForPython, Express -VS2008 9.0Exp Express -VS2005 8.0 Develop, Express -VS2005 8.0Exp Express -VS2003 7.1 Develop -VS2002 7.0 Develop -VS6.0 6.0 Develop -======= ======= ======================================================== ++---------+---------+----------------------------------------------------------+ +| Product | VCVer | Priority | ++=========+=========+==========================================================+ +| VS2022 | 14.3 | Enterprise, Professional, Community, BuildTools | ++---------+---------+----------------------------------------------------------+ +| VS2019 | 14.2 | Enterprise, Professional, Community, BuildTools | ++---------+---------+----------------------------------------------------------+ +| VS2017 | 14.1 | Enterprise, Professional, Community, BuildTools, Express | ++---------+---------+----------------------------------------------------------+ +| VS2017 | 14.1Exp | Express | ++---------+---------+----------------------------------------------------------+ +| VS2015 | 14.0 | [Develop, BuildTools, CmdLine], Express | ++---------+---------+----------------------------------------------------------+ +| VS2015 | 14.0Exp | Express | ++---------+---------+----------------------------------------------------------+ +| VS2013 | 12.0 | Develop, Express | ++---------+---------+----------------------------------------------------------+ +| VS2013 | 12.0Exp | Express | ++---------+---------+----------------------------------------------------------+ +| VS2012 | 11.0 | Develop, Express | ++---------+---------+----------------------------------------------------------+ +| VS2012 | 11.0Exp | Express | ++---------+---------+----------------------------------------------------------+ +| VS2010 | 10.0 | Develop, Express | ++---------+---------+----------------------------------------------------------+ +| VS2010 | 10.0Exp | Express | ++---------+---------+----------------------------------------------------------+ +| VS2008 | 9.0 | Develop, VCForPython, Express | ++---------+---------+----------------------------------------------------------+ +| VS2008 | 9.0Exp | Express | ++---------+---------+----------------------------------------------------------+ +| VS2005 | 8.0 | Develop, Express | ++---------+---------+----------------------------------------------------------+ +| VS2005 | 8.0Exp | Express | ++---------+---------+----------------------------------------------------------+ +| VS2003 | 7.1 | Develop | ++---------+---------+----------------------------------------------------------+ +| VS2002 | 7.0 | Develop | ++---------+---------+----------------------------------------------------------+ +| VS6.0 | 6.0 | Develop | ++---------+---------+----------------------------------------------------------+ Legend: @@ -336,14 +354,18 @@ 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 [1]_ X [2]_ -======== ======= ======= ======== ======= ++---------+---------+--------+---------+---------+ +| 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. @@ -351,14 +373,17 @@ VS2015 X [1]_ X [2]_ Supported MSVC batch file arguments in SCons: -======== ====================================== =================================================== -Argument Construction Variable Script Argument Equivalent -======== ====================================== =================================================== -UWP ``MSVC_UWP_APP=True`` ``MSVC_SCRIPT_ARGS='store'`` -SDK ``MSVC_SDK_VERSION='10.0.20348.0'`` ``MSVC_SCRIPT_ARGS='10.0.20348.0'`` -Toolset ``MSVC_TOOLSET_VERSION='14.31.31103'`` ``MSVC_SCRIPT_ARGS='-vcvars_ver=14.31.31103'`` -Spectre ``MSVC_SPECTRE_LIBS=True`` ``MSVC_SCRIPT_ARGS='-vcvars_spectre_libs=spectre'`` -======== ====================================== =================================================== ++----------+----------------------------------------+-----------------------------------------------------+ +| Argument | Construction Variable | Script Argument Equivalent | ++==========+========================================+=====================================================+ +| UWP | ``MSVC_UWP_APP=True`` | ``MSVC_SCRIPT_ARGS='store'`` | ++----------+----------------------------------------+-----------------------------------------------------+ +| SDK | ``MSVC_SDK_VERSION='10.0.20348.0'`` | ``MSVC_SCRIPT_ARGS='10.0.20348.0'`` | ++----------+----------------------------------------+-----------------------------------------------------+ +| Toolset | ``MSVC_TOOLSET_VERSION='14.31.31103'`` | ``MSVC_SCRIPT_ARGS='-vcvars_ver=14.31.31103'`` | ++----------+----------------------------------------+-----------------------------------------------------+ +| Spectre | ``MSVC_SPECTRE_LIBS=True`` | ``MSVC_SCRIPT_ARGS='-vcvars_spectre_libs=spectre'`` | ++----------+----------------------------------------+-----------------------------------------------------+ **MSVC_SCRIPT_ARGS contents are not validated. Utilizing script arguments that have construction variable equivalents is discouraged and may lead to difficult to diagnose build errors.** @@ -465,12 +490,13 @@ Visual Studio Version Notes SDK Versions ------------ -==== ================= -SDK Format -==== ================= -10.0 10.0.XXXXX.Y [*]_ -8.1 8.1 -==== ================= ++------+-------------------+ +| SDK | Format | ++======+===================+ +| 10.0 | 10.0.XXXXX.Y [*]_ | ++------+-------------------+ +| 8.1 | 8.1 | ++------+-------------------+ .. [*] The Windows 10 SDK version number is 10.0.20348.0 and earlier. @@ -479,42 +505,64 @@ SDK Format BuildTools Versions ------------------- -========== ===== ===== ======== -BuildTools VCVER CLVER MSVCRT -========== ===== ===== ======== -v143 14.3 19.3 140/ucrt -v142 14.2 19.2 140/ucrt -v141 14.1 19.1 140/ucrt -v140 14.0 19.0 140/ucrt -v120 12.0 18.0 120 -v110 11.0 17.0 110 -v100 10.0 16.0 100 -v90 9.0 15.0 90 -v80 8.0 14.0 80 -v71 7.1 13.1 71 -v70 7.0 13.0 70 -v60 6.0 12.0 60 -========== ===== ===== ======== ++------------+-------+-------+----------+ +| BuildTools | VCVER | CLVER | MSVCRT | ++============+=======+=======+==========+ +| v143 | 14.3 | 19.3 | 140/ucrt | ++------------+-------+-------+----------+ +| v142 | 14.2 | 19.2 | 140/ucrt | ++------------+-------+-------+----------+ +| v141 | 14.1 | 19.1 | 140/ucrt | ++------------+-------+-------+----------+ +| v140 | 14.0 | 19.0 | 140/ucrt | ++------------+-------+-------+----------+ +| v120 | 12.0 | 18.0 | 120 | ++------------+-------+-------+----------+ +| v110 | 11.0 | 17.0 | 110 | ++------------+-------+-------+----------+ +| v100 | 10.0 | 16.0 | 100 | ++------------+-------+-------+----------+ +| v90 | 9.0 | 15.0 | 90 | ++------------+-------+-------+----------+ +| v80 | 8.0 | 14.0 | 80 | ++------------+-------+-------+----------+ +| v71 | 7.1 | 13.1 | 71 | ++------------+-------+-------+----------+ +| v70 | 7.0 | 13.0 | 70 | ++------------+-------+-------+----------+ +| v60 | 6.0 | 12.0 | 60 | ++------------+-------+-------+----------+ Product Versions ---------------- -========= ====== ========== ====================== -Product VSVER SDK BuildTools -========= ====== ========== ====================== -2022 17.0 10.0, 8.1 v143, v142, v141, v140 -2019 16.0 10.0, 8.1 v142, v141, v140 -2017 15.0 10.0, 8.1 v141, v140 -2015 14.0 10.0, 8.1 v140 -2013 12.0 v120 -2012 11.0 v110 -2010 10.0 v100 -2008 9.0 v90 -2005 8.0 v80 -2003.NET 7.1 v71 -2002.NET 7.0 v70 -6.0 6.0 v60 -========= ====== ========== ====================== ++----------+-------+-----------+------------------------+ +| Product | VSVER | SDK | BuildTools | ++==========+=======+===========+========================+ +| 2022 | 17.0 | 10.0, 8.1 | v143, v142, v141, v140 | ++----------+-------+-----------+------------------------+ +| 2019 | 16.0 | 10.0, 8.1 | v142, v141, v140 | ++----------+-------+-----------+------------------------+ +| 2017 | 15.0 | 10.0, 8.1 | v141, v140 | ++----------+-------+-----------+------------------------+ +| 2015 | 14.0 | 10.0, 8.1 | v140 | ++----------+-------+-----------+------------------------+ +| 2013 | 12.0 | | v120 | ++----------+-------+-----------+------------------------+ +| 2012 | 11.0 | | v110 | ++----------+-------+-----------+------------------------+ +| 2010 | 10.0 | | v100 | ++----------+-------+-----------+------------------------+ +| 2008 | 9.0 | | v90 | ++----------+-------+-----------+------------------------+ +| 2005 | 8.0 | | v80 | ++----------+-------+-----------+------------------------+ +| 2003.NET | 7.1 | | v71 | ++----------+-------+-----------+------------------------+ +| 2002.NET | 7.0 | | v70 | ++----------+-------+-----------+------------------------+ +| 6.0 | 6.0 | | v60 | ++----------+-------+-----------+------------------------+ SCons Implementation Notes From 15206e4b5ca1cfeb49c9c00e23033bfac42759e6 Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Mon, 25 Mar 2024 08:37:18 -0400 Subject: [PATCH 27/35] Change rst footnotes to numbered lists (github rendering places at end of document). --- SCons/Tool/MSCommon/README.rst | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/SCons/Tool/MSCommon/README.rst b/SCons/Tool/MSCommon/README.rst index bcac466759..28511494bc 100644 --- a/SCons/Tool/MSCommon/README.rst +++ b/SCons/Tool/MSCommon/README.rst @@ -363,13 +363,14 @@ Supported MSVC batch file arguments by product: +---------+---------+--------+---------+---------+ | VS2017 | X | X | X | X | +---------+---------+--------+---------+---------+ -| VS2015 | X [1]_ | X [2]_ | | | +| VS2015 | X [1] | X [2] | | | +---------+---------+--------+---------+---------+ +Notes: -.. [1] The BuildTools edition does not support the ``store`` argument. The Express edition - supports the ``store`` argument for the ``x86`` target only. -.. [2] The ``sdk version`` argument is not supported in the BuildTools and Express editions. +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: @@ -493,14 +494,16 @@ SDK Versions +------+-------------------+ | SDK | Format | +======+===================+ -| 10.0 | 10.0.XXXXX.Y [*]_ | +| 10.0 | 10.0.XXXXX.Y [1] | +------+-------------------+ | 8.1 | 8.1 | +------+-------------------+ -.. [*] The Windows 10 SDK version number is 10.0.20348.0 and earlier. +Notes: - The Windows 11 SDK version number is 10.0.22000.194 and later. +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 56e1769b4d9e29d004d52c824f7d91dcb119812f Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Tue, 26 Mar 2024 13:53:35 -0400 Subject: [PATCH 28/35] Update vswhere function api and update MSCommon/README.rst Changes: * add vswhere_exe argument to msvc_quert_version_toolset function in MSCommon/README.rst * fix: pass normalized path for vswhere_norm in vswhere binary named tuple constructor * add freeze argument for vswhere_register_executable --- SCons/Tool/MSCommon/README.rst | 12 +++++++----- SCons/Tool/MSCommon/vc.py | 33 +++++++++++++++++++++++++++------ 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/SCons/Tool/MSCommon/README.rst b/SCons/Tool/MSCommon/README.rst index 28511494bc..5a72885c00 100644 --- a/SCons/Tool/MSCommon/README.rst +++ b/SCons/Tool/MSCommon/README.rst @@ -207,13 +207,15 @@ The following issues are known to exist: Experimental Features ===================== -msvc_query_version_toolset(version=None, prefer_newest=True) ------------------------------------------------------------- +msvc_query_version_toolset(version=None, prefer_newest=True, vswhere_exe=None) +------------------------------------------------------------------------------ The experimental function ``msvc_query_version_toolset`` was added to ``MSCommon/vc.py`` -and is available via the ``SCons.Tool.MSCommon`` namespace. This function takes a version -specification or a toolset version specification and a product preference as arguments and -returns the msvc version and the msvc toolset version for the corresponding version specification. +and is available via the ``SCons.Tool.MSCommon`` namespace. + +This function takes a version specification or a toolset version specification, an optional product +preference, and an optional vswhere executable location as arguments and returns the msvc version and +the msvc toolset version for the corresponding version specification. This is a proxy for using the toolset version for selection until that functionality can be added. diff --git a/SCons/Tool/MSCommon/vc.py b/SCons/Tool/MSCommon/vc.py index e5ca7d8e06..0c80363f3a 100644 --- a/SCons/Tool/MSCommon/vc.py +++ b/SCons/Tool/MSCommon/vc.py @@ -978,7 +978,7 @@ def factory(cls, vswhere_exe): vswhere_binary = cls( vswhere_exe=vswhere_exe, - vswhere_norm=vswhere_exe, + vswhere_norm=vswhere_norm, ) cls._cache_vswhere_paths[vswhere_exe] = vswhere_binary @@ -1103,6 +1103,11 @@ def _vswhere_all_executables(cls): vswhere_exe_list.append(vswhere_binary.vswhere_exe) return vswhere_exe_list + @classmethod + def is_frozen(cls) -> bool: + rval = bool(cls.vswhere_frozen_flag) + return rval + @classmethod def freeze_vswhere_binary(cls): if not cls.vswhere_frozen_flag: @@ -1141,7 +1146,7 @@ def _vswhere_priority_group(cls, priority): return group @classmethod - def register_vswhere_executable(cls, vswhere_exe, priority=None) -> bool: + def register_vswhere_executable(cls, vswhere_exe, priority=None): vswhere_binary = cls.UNDEFINED_VSWHERE_BINARY @@ -1222,11 +1227,27 @@ def vswhere_freeze_env(cls, env): return frozen_binary, vswhere_binary # external use -vswhere_register_executable = _VSWhereExecutable.register_vswhere_executable -vswhere_get_executable = _VSWhereExecutable.get_vswhere_executable -vswhere_freeze_executable = _VSWhereExecutable.freeze_vswhere_executable -# internal use +def vswhere_register_executable(vswhere_exe, priority=None, freeze=False): + debug('register vswhere_exe=%s, priority=%s, freeze=%s', repr(vswhere_exe), repr(priority), repr(freeze)) + _VSWhereExecutable.register_vswhere_executable(vswhere_exe, priority=priority) + if freeze: + _VSWhereExecutable.freeze_vswhere_executable() + rval = _VSWhereExecutable.get_vswhere_executable() + debug('current vswhere_exe=%s, is_frozen=%s', repr(rval), _VSWhereExecutable.is_frozen()) + return vswhere_exe + +def vswhere_get_executable(): + debug('') + vswhere_exe = _VSWhereExecutable.get_vswhere_executable() + return vswhere_exe + +def vswhere_freeze_executable(): + debug('') + vswhere_exe = _VSWhereExecutable.freeze_vswhere_executable() + return vswhere_exe + +# internal use only vswhere_freeze_env = _VSWhereExecutable.vswhere_freeze_env def msvc_find_vswhere(): From e8a541f1948857fc51b7ffc538d8c5b062703200 Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Tue, 21 May 2024 05:32:16 -0400 Subject: [PATCH 29/35] Simplify check for VSWHERE undefined, None, or empty. --- SCons/Tool/MSCommon/vc.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SCons/Tool/MSCommon/vc.py b/SCons/Tool/MSCommon/vc.py index 0c80363f3a..8a2fd8e026 100644 --- a/SCons/Tool/MSCommon/vc.py +++ b/SCons/Tool/MSCommon/vc.py @@ -1203,11 +1203,11 @@ def vswhere_freeze_executable(cls, vswhere_exe): def vswhere_freeze_env(cls, env): if env is None: - # no environment, no VSWHERE + # no environment, VSWHERE undefined vswhere_exe = None write_vswhere = False - elif 'VSWHERE' not in env or not env['VSWHERE']: - # environment, VSWHERE undefined + elif not env.get('VSWHERE'): + # environment, VSWHERE undefined/none/empty vswhere_exe = None write_vswhere = True else: From 40e6f1d24682cab5bf76df375ebd16551d0942be Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Mon, 27 May 2024 09:38:49 -0400 Subject: [PATCH 30/35] Fix issue #4543: Add support for msvc toolset versions 14.4x for msvc buildtools v143. Changes: * Add msvc build series data structure. * Change msvc build tools data structure to contain list of build series. * Update script argument validation and tests. --- CHANGES.txt | 3 + RELEASE.txt | 2 + SCons/Tool/MSCommon/MSVC/Config.py | 135 ++++++++++++----- SCons/Tool/MSCommon/MSVC/ScriptArguments.py | 135 ++++++++++------- .../MSCommon/MSVC/ScriptArgumentsTests.py | 25 +-- SCons/Tool/MSCommon/MSVC/Util.py | 15 +- SCons/Tool/MSCommon/README.rst | 142 +++++++++++------- SCons/Tool/MSCommon/vc.py | 2 +- test/MSVC/MSVC_SDK_VERSION.py | 4 +- test/MSVC/MSVC_TOOLSET_VERSION.py | 59 ++++++-- 10 files changed, 347 insertions(+), 175 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 51dbf28e53..82663a7e6e 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -63,6 +63,9 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER - The vswhere executable locations for the WinGet and Scoop package managers were added to the default vswhere executable search list after the Chocolatey installation location. + - Fix issue #4543: add support for msvc toolset versions 14.4X installed as the + latest msvc toolset versions for msvc buildtools v143. The v143 msvc buildtools + may contain msvc toolset versions from 14.30 to 14.4X. From Thaddeus Crews: - GetSConsVersion() to grab the latest SCons version without needing to diff --git a/RELEASE.txt b/RELEASE.txt index 7d9f82574f..f4c9e8581e 100644 --- a/RELEASE.txt +++ b/RELEASE.txt @@ -90,6 +90,8 @@ FIXES For example, when no vswhere executable is found for the initial detection and then later an environment is constructed with a user specified vswhere executable that detects new msvc installations. +- MSVC: Visual Studio 2022 v143 BuildTools now supports msvc toolset versions from + 14.30 to 14.4X. Fixes Issue #4543. IMPROVEMENTS ------------ diff --git a/SCons/Tool/MSCommon/MSVC/Config.py b/SCons/Tool/MSCommon/MSVC/Config.py index 7c0f1fe6ff..a04db4904e 100644 --- a/SCons/Tool/MSCommon/MSVC/Config.py +++ b/SCons/Tool/MSCommon/MSVC/Config.py @@ -118,15 +118,67 @@ for vc_runtime_alias in vc_runtime_alias_list: MSVC_RUNTIME_EXTERNAL[vc_runtime_alias] = vc_runtime_def -MSVC_BUILDTOOLS_DEFINITION = namedtuple('MSVCBuildtools', [ - 'vc_buildtools', - 'vc_buildtools_numeric', +MSVC_BUILDSERIES_DEFINITION = namedtuple('MSVCBuildSeries', [ + 'vc_buildseries', + 'vc_buildseries_numeric', 'vc_version', 'vc_version_numeric', 'cl_version', 'cl_version_numeric', +]) + +MSVC_BUILDSERIES_DEFINITION_LIST = [] + +MSVC_BUILDSERIES_INTERNAL = {} +MSVC_BUILDSERIES_EXTERNAL = {} + +VC_BUILDTOOLS_MAP = {} + +VC_VERSION_MAP = {} +CL_VERSION_MAP = {} + +for (vc_buildseries, vc_version, cl_version) in [ + ('144', '14.4', '19.4'), + ('143', '14.3', '19.3'), + ('142', '14.2', '19.2'), + ('141', '14.1', '19.1'), + ('140', '14.0', '19.0'), + ('120', '12.0', '18.0'), + ('110', '11.0', '17.0'), + ('100', '10.0', '16.0'), + ('90', '9.0', '15.0'), + ('80', '8.0', '14.0'), + ('71', '7.1', '13.1'), + ('70', '7.0', '13.0'), + ('60', '6.0', '12.0'), +]: + + vc_buildseries_def = MSVC_BUILDSERIES_DEFINITION( + vc_buildseries=vc_buildseries, + vc_buildseries_numeric=int(vc_buildseries), + vc_version=vc_version, + vc_version_numeric=float(vc_version), + cl_version=cl_version, + cl_version_numeric=float(cl_version), + ) + + MSVC_BUILDSERIES_DEFINITION_LIST.append(vc_buildseries_def) + + MSVC_BUILDSERIES_INTERNAL[vc_buildseries] = vc_buildseries_def + MSVC_BUILDSERIES_EXTERNAL[vc_buildseries] = vc_buildseries_def + MSVC_BUILDSERIES_EXTERNAL[vc_version] = vc_buildseries_def + + VC_VERSION_MAP[vc_version] = vc_buildseries_def + CL_VERSION_MAP[cl_version] = vc_buildseries_def + +MSVC_BUILDTOOLS_DEFINITION = namedtuple('MSVCBuildtools', [ + 'vc_buildtools', + 'vc_buildtools_numeric', + 'vc_buildseries_list', 'vc_runtime_def', 'vc_istoolset', + 'msvc_version', + 'msvc_version_numeric', ]) MSVC_BUILDTOOLS_DEFINITION_LIST = [] @@ -134,34 +186,44 @@ MSVC_BUILDTOOLS_INTERNAL = {} MSVC_BUILDTOOLS_EXTERNAL = {} -VC_VERSION_MAP = {} - -for vc_buildtools, vc_version, cl_version, vc_runtime, vc_istoolset in [ - ('v143', '14.3', '19.3', '140', True), - ('v142', '14.2', '19.2', '140', True), - ('v141', '14.1', '19.1', '140', True), - ('v140', '14.0', '19.0', '140', True), - ('v120', '12.0', '18.0', '120', False), - ('v110', '11.0', '17.0', '110', False), - ('v100', '10.0', '16.0', '100', False), - ('v90', '9.0', '15.0', '90', False), - ('v80', '8.0', '14.0', '80', False), - ('v71', '7.1', '13.1', '71', False), - ('v70', '7.0', '13.0', '70', False), - ('v60', '6.0', '12.0', '60', False), +MSVC_VERSION_NEWEST = None +MSVC_VERSION_NEWEST_NUMERIC = 0.0 + +for vc_buildtools, vc_buildseries_list, vc_runtime, vc_istoolset in [ + ('v143', ['144', '143'], '140', True), + ('v142', ['142'], '140', True), + ('v141', ['141'], '140', True), + ('v140', ['140'], '140', True), + ('v120', ['120'], '120', False), + ('v110', ['110'], '110', False), + ('v100', ['100'], '100', False), + ('v90', ['90'], '90', False), + ('v80', ['80'], '80', False), + ('v71', ['71'], '71', False), + ('v70', ['70'], '70', False), + ('v60', ['60'], '60', False), ]: vc_runtime_def = MSVC_RUNTIME_INTERNAL[vc_runtime] + vc_buildseries_list = tuple( + MSVC_BUILDSERIES_INTERNAL[vc_buildseries] + for vc_buildseries in vc_buildseries_list + ) + + vc_buildtools_numstr = vc_buildtools[1:] + + msvc_version = vc_buildtools_numstr[:-1] + '.' + vc_buildtools_numstr[-1] + msvc_version_numeric = float(msvc_version) + vc_buildtools_def = MSVC_BUILDTOOLS_DEFINITION( vc_buildtools = vc_buildtools, vc_buildtools_numeric = int(vc_buildtools[1:]), - vc_version = vc_version, - vc_version_numeric = float(vc_version), - cl_version = cl_version, - cl_version_numeric = float(cl_version), + vc_buildseries_list = vc_buildseries_list, vc_runtime_def = vc_runtime_def, vc_istoolset = vc_istoolset, + msvc_version = msvc_version, + msvc_version_numeric = msvc_version_numeric, ) MSVC_BUILDTOOLS_DEFINITION_LIST.append(vc_buildtools_def) @@ -170,7 +232,12 @@ MSVC_BUILDTOOLS_EXTERNAL[vc_buildtools] = vc_buildtools_def MSVC_BUILDTOOLS_EXTERNAL[vc_version] = vc_buildtools_def - VC_VERSION_MAP[vc_version] = vc_buildtools_def + for vc_buildseries_def in vc_buildseries_list: + VC_BUILDTOOLS_MAP[vc_buildseries_def.vc_buildseries] = vc_buildtools_def + + if vc_buildtools_def.msvc_version_numeric > MSVC_VERSION_NEWEST_NUMERIC: + MSVC_VERSION_NEWEST_NUMERIC = vc_buildtools_def.msvc_version_numeric + MSVC_VERSION_NEWEST = vc_buildtools_def.msvc_version MSVS_VERSION_INTERNAL = {} MSVS_VERSION_EXTERNAL = {} @@ -181,8 +248,6 @@ MSVS_VERSION_MAJOR_MAP = {} -CL_VERSION_MAP = {} - MSVC_SDK_VERSIONS = set() VISUALSTUDIO_DEFINITION = namedtuple('VisualStudioDefinition', [ @@ -247,15 +312,15 @@ vc_buildtools_def.vc_runtime_def.vc_runtime_vsdef_list.append(vs_def) - vc_version = vc_buildtools_def.vc_version + msvc_version = vc_buildtools_def.msvc_version MSVS_VERSION_INTERNAL[vs_product] = vs_def MSVS_VERSION_EXTERNAL[vs_product] = vs_def MSVS_VERSION_EXTERNAL[vs_version] = vs_def - MSVC_VERSION_INTERNAL[vc_version] = vs_def + MSVC_VERSION_INTERNAL[msvc_version] = vs_def MSVC_VERSION_EXTERNAL[vs_product] = vs_def - MSVC_VERSION_EXTERNAL[vc_version] = vs_def + MSVC_VERSION_EXTERNAL[msvc_version] = vs_def MSVC_VERSION_EXTERNAL[vc_buildtools_def.vc_buildtools] = vs_def if vs_product in VS_PRODUCT_ALIAS: @@ -264,14 +329,12 @@ MSVS_VERSION_EXTERNAL[vs_product_alias] = vs_def MSVC_VERSION_EXTERNAL[vs_product_alias] = vs_def - MSVC_VERSION_SUFFIX[vc_version] = vs_def + MSVC_VERSION_SUFFIX[msvc_version] = vs_def if vs_express: - MSVC_VERSION_SUFFIX[vc_version + 'Exp'] = vs_def + MSVC_VERSION_SUFFIX[msvc_version + 'Exp'] = vs_def MSVS_VERSION_MAJOR_MAP[vs_version_major] = vs_def - CL_VERSION_MAP[vc_buildtools_def.cl_version] = vs_def - if vc_sdk: MSVC_SDK_VERSIONS.update(vc_sdk) @@ -292,7 +355,7 @@ for vs_def in VISUALSTUDIO_DEFINITION_LIST: if not vs_def.vc_buildtools_def.vc_istoolset: continue - version_key = vs_def.vc_buildtools_def.vc_version + version_key = vs_def.vc_buildtools_def.msvc_version MSVC_VERSION_TOOLSET_DEFAULTS_MAP[version_key] = [version_key] MSVC_VERSION_TOOLSET_SEARCH_MAP[version_key] = [] if vs_def.vs_express: @@ -305,11 +368,11 @@ for vs_def in VISUALSTUDIO_DEFINITION_LIST: if not vs_def.vc_buildtools_def.vc_istoolset: continue - version_key = vs_def.vc_buildtools_def.vc_version + version_key = vs_def.vc_buildtools_def.msvc_version for vc_buildtools in vs_def.vc_buildtools_all: toolset_buildtools_def = MSVC_BUILDTOOLS_INTERNAL[vc_buildtools] - toolset_vs_def = MSVC_VERSION_INTERNAL[toolset_buildtools_def.vc_version] - buildtools_key = toolset_buildtools_def.vc_version + toolset_vs_def = MSVC_VERSION_INTERNAL[toolset_buildtools_def.msvc_version] + buildtools_key = toolset_buildtools_def.msvc_version MSVC_VERSION_TOOLSET_SEARCH_MAP[buildtools_key].extend(MSVC_VERSION_TOOLSET_DEFAULTS_MAP[version_key]) # convert string version set to string version list ranked in descending order diff --git a/SCons/Tool/MSCommon/MSVC/ScriptArguments.py b/SCons/Tool/MSCommon/MSVC/ScriptArguments.py index c689d7a0b5..76ad8717b0 100644 --- a/SCons/Tool/MSCommon/MSVC/ScriptArguments.py +++ b/SCons/Tool/MSCommon/MSVC/ScriptArguments.py @@ -173,6 +173,12 @@ class SortOrder(enum.IntEnum): 'vs_def', ]) +TOOLSET_VERSION_ARGS_DEFINITION = namedtuple('ToolsetVersionArgsDefinition', [ + 'version', # full version (e.g., '14.1Exp', '14.32.31326') + 'vc_buildtools_def', + 'is_user', +]) + def _msvc_version(version): verstr = Util.get_msvc_version_prefix(version) @@ -185,14 +191,17 @@ def _msvc_version(version): return version_args -def _toolset_version(version): +def _toolset_version(version, is_user=False): - verstr = Util.get_msvc_version_prefix(version) - vs_def = Config.MSVC_VERSION_INTERNAL[verstr] + vc_series = Util.get_msvc_version_prefix(version) - version_args = MSVC_VERSION_ARGS_DEFINITION( + vc_buildseries_def = Config.MSVC_BUILDSERIES_EXTERNAL[vc_series] + vc_buildtools_def = Config.VC_BUILDTOOLS_MAP[vc_buildseries_def.vc_buildseries] + + version_args = TOOLSET_VERSION_ARGS_DEFINITION( version = version, - vs_def = vs_def, + vc_buildtools_def = vc_buildtools_def, + is_user = is_user, ) return version_args @@ -208,14 +217,14 @@ 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.vs_def.vc_buildtools_def.msvc_version_numeric < VS2015.vc_buildtools_def.msvc_version_numeric: debug( 'invalid: msvc version constraint: %s < %s VS2015', - repr(msvc.vs_def.vc_buildtools_def.vc_version_numeric), - repr(VS2015.vc_buildtools_def.vc_version_numeric) + repr(msvc.vs_def.vc_buildtools_def.msvc_version_numeric), + repr(VS2015.vc_buildtools_def.msvc_version_numeric) ) err_msg = "MSVC_UWP_APP ({}) constraint violation: MSVC_VERSION {} < {} VS2015".format( - repr(uwp_app), repr(msvc.version), repr(VS2015.vc_buildtools_def.vc_version) + repr(uwp_app), repr(msvc.version), repr(VS2015.vc_buildtools_def.msvc_version) ) raise MSVCArgumentError(err_msg) @@ -270,14 +279,14 @@ def _user_script_argument_uwp(env, uwp, user_argstr) -> bool: def _msvc_script_argument_sdk_constraints(msvc, sdk_version, env): - if msvc.vs_def.vc_buildtools_def.vc_version_numeric < VS2015.vc_buildtools_def.vc_version_numeric: + if msvc.vs_def.vc_buildtools_def.msvc_version_numeric < VS2015.vc_buildtools_def.msvc_version_numeric: debug( 'invalid: msvc_version constraint: %s < %s VS2015', - repr(msvc.vs_def.vc_buildtools_def.vc_version_numeric), - repr(VS2015.vc_buildtools_def.vc_version_numeric) + repr(msvc.vs_def.vc_buildtools_def.msvc_version_numeric), + repr(VS2015.vc_buildtools_def.msvc_version_numeric) ) err_msg = "MSVC_SDK_VERSION ({}) constraint violation: MSVC_VERSION {} < {} VS2015".format( - repr(sdk_version), repr(msvc.version), repr(VS2015.vc_buildtools_def.vc_version) + repr(sdk_version), repr(msvc.version), repr(VS2015.vc_buildtools_def.msvc_version) ) return err_msg @@ -303,23 +312,23 @@ def _msvc_script_argument_sdk_platform_constraints(msvc, toolset, sdk_version, p if sdk_version == '8.1' and platform_def.is_uwp: - vs_def = toolset.vs_def if toolset else msvc.vs_def + vc_buildtools_def = toolset.vc_buildtools_def if toolset else msvc.vs_def.vc_buildtools_def - if vs_def.vc_buildtools_def.vc_version_numeric > VS2015.vc_buildtools_def.vc_version_numeric: + if vc_buildtools_def.msvc_version_numeric > VS2015.vc_buildtools_def.msvc_version_numeric: debug( 'invalid: uwp/store SDK 8.1 msvc_version constraint: %s > %s VS2015', - repr(vs_def.vc_buildtools_def.vc_version_numeric), - repr(VS2015.vc_buildtools_def.vc_version_numeric) + repr(vc_buildtools_def.msvc_version_numeric), + repr(VS2015.vc_buildtools_def.msvc_version_numeric) ) - if toolset and toolset.vs_def != msvc.vs_def: - err_msg = "MSVC_SDK_VERSION ({}) and platform type ({}) constraint violation: toolset version {} > {} VS2015".format( + if toolset and toolset.is_user: + err_msg = "MSVC_SDK_VERSION ({}) and platform type ({}) constraint violation: toolset {} MSVC_VERSION {} > {} VS2015".format( repr(sdk_version), repr(platform_def.vc_platform), - repr(toolset.version), repr(VS2015.vc_buildtools_def.vc_version) + repr(toolset.version), repr(msvc.version), repr(VS2015.vc_buildtools_def.msvc_version) ) else: err_msg = "MSVC_SDK_VERSION ({}) and platform type ({}) constraint violation: MSVC_VERSION {} > {} VS2015".format( repr(sdk_version), repr(platform_def.vc_platform), - repr(msvc.version), repr(VS2015.vc_buildtools_def.vc_version) + repr(msvc.version), repr(VS2015.vc_buildtools_def.msvc_version) ) return err_msg @@ -359,7 +368,7 @@ def _msvc_script_argument_sdk(env, msvc, toolset, platform_def, arglist): def _msvc_script_default_sdk(env, msvc, platform_def, arglist, force_sdk: bool=False): - if msvc.vs_def.vc_buildtools_def.vc_version_numeric < VS2015.vc_buildtools_def.vc_version_numeric: + if msvc.vs_def.vc_buildtools_def.msvc_version_numeric < VS2015.vc_buildtools_def.msvc_version_numeric: return None if not Kind.msvc_version_sdk_version_is_supported(msvc.version, env): @@ -444,10 +453,11 @@ def _msvc_sxs_toolset_folder(msvc, sxs_folder): if Util.is_toolset_sxs(sxs_folder): return sxs_folder, sxs_folder - key = (msvc.vs_def.vc_buildtools_def.vc_version, sxs_folder) - if key in _msvc_sxs_bugfix_folder: - sxs_version = _msvc_sxs_bugfix_folder[key] - return sxs_folder, sxs_version + for vc_buildseries_def in msvc.vs_def.vc_buildtools_def.vc_buildseries_list: + key = (vc_buildseries_def.vc_version, sxs_folder) + sxs_version = _msvc_sxs_bugfix_folder.get(key) + if sxs_version: + return sxs_folder, sxs_version debug('sxs folder: ignore version=%s', repr(sxs_folder)) return None, None @@ -603,49 +613,60 @@ def _msvc_version_toolset_vcvars(msvc, vc_dir, toolset_version): def _msvc_script_argument_toolset_constraints(msvc, toolset_version): - if msvc.vs_def.vc_buildtools_def.vc_version_numeric < VS2017.vc_buildtools_def.vc_version_numeric: + if msvc.vs_def.vc_buildtools_def.msvc_version_numeric < VS2017.vc_buildtools_def.msvc_version_numeric: debug( 'invalid: msvc version constraint: %s < %s VS2017', - repr(msvc.vs_def.vc_buildtools_def.vc_version_numeric), - repr(VS2017.vc_buildtools_def.vc_version_numeric) + repr(msvc.vs_def.vc_buildtools_def.msvc_version_numeric), + repr(VS2017.vc_buildtools_def.msvc_version_numeric) ) err_msg = "MSVC_TOOLSET_VERSION ({}) constraint violation: MSVC_VERSION {} < {} VS2017".format( - repr(toolset_version), repr(msvc.version), repr(VS2017.vc_buildtools_def.vc_version) + repr(toolset_version), repr(msvc.version), repr(VS2017.vc_buildtools_def.msvc_version) ) return err_msg - toolset_verstr = Util.get_msvc_version_prefix(toolset_version) + toolset_series = Util.get_msvc_version_prefix(toolset_version) - if not toolset_verstr: + if not toolset_series: debug('invalid: msvc version: toolset_version=%s', repr(toolset_version)) err_msg = 'MSVC_TOOLSET_VERSION {} format is not supported'.format( repr(toolset_version) ) return err_msg - toolset_vernum = float(toolset_verstr) + toolset_buildseries_def = Config.MSVC_BUILDSERIES_EXTERNAL.get(toolset_series) + if not toolset_buildseries_def: + debug('invalid: msvc version: toolset_version=%s', repr(toolset_version)) + err_msg = 'MSVC_TOOLSET_VERSION {} build series {} is not supported'.format( + repr(toolset_version), repr(toolset_series) + ) + return err_msg + + toolset_buildtools_def = Config.VC_BUILDTOOLS_MAP[toolset_buildseries_def.vc_buildseries] + + toolset_verstr = toolset_buildtools_def.msvc_version + toolset_vernum = toolset_buildtools_def.msvc_version_numeric - if toolset_vernum < VS2015.vc_buildtools_def.vc_version_numeric: + if toolset_vernum < VS2015.vc_buildtools_def.msvc_version_numeric: debug( 'invalid: toolset version constraint: %s < %s VS2015', - repr(toolset_vernum), repr(VS2015.vc_buildtools_def.vc_version_numeric) + repr(toolset_vernum), repr(VS2015.vc_buildtools_def.msvc_version_numeric) ) - err_msg = "MSVC_TOOLSET_VERSION ({}) constraint violation: toolset version {} < {} VS2015".format( - repr(toolset_version), repr(toolset_verstr), repr(VS2015.vc_buildtools_def.vc_version) + err_msg = "MSVC_TOOLSET_VERSION ({}) constraint violation: toolset msvc version {} < {} VS2015".format( + repr(toolset_version), repr(toolset_verstr), repr(VS2015.vc_buildtools_def.msvc_version) ) return err_msg - if toolset_vernum > msvc.vs_def.vc_buildtools_def.vc_version_numeric: + if toolset_vernum > msvc.vs_def.vc_buildtools_def.msvc_version_numeric: debug( 'invalid: toolset version constraint: toolset %s > %s msvc', - repr(toolset_vernum), repr(msvc.vs_def.vc_buildtools_def.vc_version_numeric) + repr(toolset_vernum), repr(msvc.vs_def.vc_buildtools_def.msvc_version_numeric) ) - err_msg = "MSVC_TOOLSET_VERSION ({}) constraint violation: toolset version {} > {} MSVC_VERSION".format( + err_msg = "MSVC_TOOLSET_VERSION ({}) constraint violation: toolset msvc version {} > {} MSVC_VERSION".format( repr(toolset_version), repr(toolset_verstr), repr(msvc.version) ) return err_msg - if toolset_vernum == VS2015.vc_buildtools_def.vc_version_numeric: + if toolset_vernum == VS2015.vc_buildtools_def.msvc_version_numeric: # tooset = 14.0 if Util.is_toolset_full(toolset_version): if not Util.is_toolset_140(toolset_version): @@ -653,7 +674,7 @@ def _msvc_script_argument_toolset_constraints(msvc, toolset_version): 'invalid: toolset version 14.0 constraint: %s != 14.0', repr(toolset_version) ) - err_msg = "MSVC_TOOLSET_VERSION ({}) constraint violation: toolset version {} != '14.0'".format( + err_msg = "MSVC_TOOLSET_VERSION ({}) constraint violation: toolset msvc version {} != '14.0'".format( repr(toolset_version), repr(toolset_version) ) return err_msg @@ -717,7 +738,7 @@ def _msvc_script_argument_toolset(env, msvc, vc_dir, arglist): def _msvc_script_default_toolset(env, msvc, vc_dir, arglist, force_toolset: bool=False): - if msvc.vs_def.vc_buildtools_def.vc_version_numeric < VS2017.vc_buildtools_def.vc_version_numeric: + if msvc.vs_def.vc_buildtools_def.msvc_version_numeric < VS2017.vc_buildtools_def.msvc_version_numeric: return None toolset_default = _msvc_default_toolset(msvc, vc_dir) @@ -758,26 +779,26 @@ def _user_script_argument_toolset(env, toolset_version, user_argstr): def _msvc_script_argument_spectre_constraints(msvc, toolset, spectre_libs, platform_def): - if msvc.vs_def.vc_buildtools_def.vc_version_numeric < VS2017.vc_buildtools_def.vc_version_numeric: + if msvc.vs_def.vc_buildtools_def.msvc_version_numeric < VS2017.vc_buildtools_def.msvc_version_numeric: debug( 'invalid: msvc version constraint: %s < %s VS2017', - repr(msvc.vs_def.vc_buildtools_def.vc_version_numeric), - repr(VS2017.vc_buildtools_def.vc_version_numeric) + repr(msvc.vs_def.vc_buildtools_def.msvc_version_numeric), + repr(VS2017.vc_buildtools_def.msvc_version_numeric) ) err_msg = "MSVC_SPECTRE_LIBS ({}) constraint violation: MSVC_VERSION {} < {} VS2017".format( - repr(spectre_libs), repr(msvc.version), repr(VS2017.vc_buildtools_def.vc_version) + repr(spectre_libs), repr(msvc.version), repr(VS2017.vc_buildtools_def.msvc_version) ) return err_msg if toolset: - if toolset.vs_def.vc_buildtools_def.vc_version_numeric < VS2017.vc_buildtools_def.vc_version_numeric: + if toolset.vc_buildtools_def.msvc_version_numeric < VS2017.vc_buildtools_def.msvc_version_numeric: debug( 'invalid: toolset version constraint: %s < %s VS2017', - repr(toolset.vs_def.vc_buildtools_def.vc_version_numeric), - repr(VS2017.vc_buildtools_def.vc_version_numeric) + repr(toolset.vc_buildtools_def.msvc_version_numeric), + repr(VS2017.vc_buildtools_def.msvc_version_numeric) ) err_msg = "MSVC_SPECTRE_LIBS ({}) constraint violation: toolset version {} < {} VS2017".format( - repr(spectre_libs), repr(toolset.version), repr(VS2017.vc_buildtools_def.vc_version) + repr(spectre_libs), repr(toolset.version), repr(VS2017.vc_buildtools_def.msvc_version) ) return err_msg @@ -865,14 +886,14 @@ def _msvc_script_argument_user(env, msvc, arglist): if not script_args: return None - if msvc.vs_def.vc_buildtools_def.vc_version_numeric < VS2015.vc_buildtools_def.vc_version_numeric: + if msvc.vs_def.vc_buildtools_def.msvc_version_numeric < VS2015.vc_buildtools_def.msvc_version_numeric: debug( 'invalid: msvc version constraint: %s < %s VS2015', - repr(msvc.vs_def.vc_buildtools_def.vc_version_numeric), - repr(VS2015.vc_buildtools_def.vc_version_numeric) + repr(msvc.vs_def.vc_buildtools_def.msvc_version_numeric), + repr(VS2015.vc_buildtools_def.msvc_version_numeric) ) err_msg = "MSVC_SCRIPT_ARGS ({}) constraint violation: MSVC_VERSION {} < {} VS2015".format( - repr(script_args), repr(msvc.version), repr(VS2015.vc_buildtools_def.vc_version) + repr(script_args), repr(msvc.version), repr(VS2015.vc_buildtools_def.msvc_version) ) raise MSVCArgumentError(err_msg) @@ -968,7 +989,7 @@ def msvc_script_arguments(env, version, vc_dir, arg=None): if user_toolset: toolset = None elif toolset_version: - toolset = _toolset_version(toolset_version) + toolset = _toolset_version(toolset_version, is_user=True) elif default_toolset: toolset = _toolset_version(default_toolset) else: @@ -1000,7 +1021,7 @@ def msvc_script_arguments(env, version, vc_dir, arg=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.vs_def.vc_buildtools_def.msvc_version == '14.0': if user_uwp and sdk_version and len(arglist) == 2: # VS2015 toolset argument order issue: SDK store => store SDK arglist_reverse = True diff --git a/SCons/Tool/MSCommon/MSVC/ScriptArgumentsTests.py b/SCons/Tool/MSCommon/MSVC/ScriptArgumentsTests.py index c79f044c55..308e436976 100644 --- a/SCons/Tool/MSCommon/MSVC/ScriptArgumentsTests.py +++ b/SCons/Tool/MSCommon/MSVC/ScriptArgumentsTests.py @@ -317,9 +317,14 @@ def run_msvc_script_args(self) -> None: toolset_def = toolset_versions[0] if toolset_versions else Util.msvc_extended_version_components(version_def.msvc_verstr) - earlier_toolset_versions = [toolset_def for toolset_def in toolset_versions if toolset_def.msvc_vernum != version_def.msvc_vernum] + earlier_toolset_versions = [earlier_toolset_def for earlier_toolset_def in toolset_versions if earlier_toolset_def.msvc_vernum != version_def.msvc_vernum] earlier_toolset_def = earlier_toolset_versions[0] if earlier_toolset_versions else None + vc_buildtools_def = Config.MSVC_BUILDTOOLS_EXTERNAL[toolset_def.msvc_buildtools] + vc_buildseries_def = vc_buildtools_def.vc_buildseries_list[0] + + latest_buildseries_major, latest_buildseries_minor = [int(comp) for comp in vc_buildseries_def.vc_version.split('.')] + # should not raise exception (argument not validated) env = Environment(MSVC_SCRIPT_ARGS='undefinedsymbol') _ = func(env, version_def.msvc_version, vc_dir) @@ -372,7 +377,7 @@ def run_msvc_script_args(self) -> None: (expect, { 'MSVC_SDK_VERSION': sdk_def.sdk_version, 'MSVC_UWP_APP': msvc_uwp_app, - 'MSVC_TOOLSET_VERSION': version_def.msvc_verstr + 'MSVC_TOOLSET_VERSION': toolset_def.msvc_toolset_version }), ] + more_tests: env = Environment(**kwargs) @@ -392,8 +397,8 @@ def run_msvc_script_args(self) -> None: _ = func(env, version_def.msvc_version, vc_dir) for kwargs in [ - {'MSVC_SCRIPT_ARGS': '-vcvars_ver={}'.format(version_def.msvc_verstr)}, - {'MSVC_TOOLSET_VERSION': version_def.msvc_verstr}, + {'MSVC_SCRIPT_ARGS': '-vcvars_ver={}'.format(toolset_def.msvc_toolset_version)}, + {'MSVC_TOOLSET_VERSION': toolset_def.msvc_toolset_version}, ]: env = Environment(**kwargs) _ = func(env, version_def.msvc_version, vc_dir) @@ -453,14 +458,14 @@ def run_msvc_script_args(self) -> None: }, (MSVCArgumentError, ), ), # multiple definitions - ({'MSVC_TOOLSET_VERSION': version_def.msvc_verstr, - 'MSVC_SCRIPT_ARGS': "-vcvars_ver={}".format(version_def.msvc_verstr) + ({'MSVC_TOOLSET_VERSION': toolset_def.msvc_toolset_version, + 'MSVC_SCRIPT_ARGS': "-vcvars_ver={}".format(toolset_def.msvc_toolset_version) }, (MSVCArgumentError, ), ), # multiple definitions (args) - ({'MSVC_TOOLSET_VERSION': version_def.msvc_verstr, - 'MSVC_SCRIPT_ARGS': "-vcvars_ver={0} undefined -vcvars_ver={0}".format(version_def.msvc_verstr) + ({'MSVC_TOOLSET_VERSION': toolset_def.msvc_toolset_version, + 'MSVC_SCRIPT_ARGS': "-vcvars_ver={0} undefined -vcvars_ver={0}".format(toolset_def.msvc_toolset_version) }, (MSVCArgumentError, ), ), @@ -494,7 +499,7 @@ def run_msvc_script_args(self) -> None: (MSVCArgumentError, ), ), # toolset > msvc_version - ({'MSVC_TOOLSET_VERSION': '{}.{}'.format(version_def.msvc_major, version_def.msvc_minor+1), + ({'MSVC_TOOLSET_VERSION': '{}.{}'.format(latest_buildseries_major, latest_buildseries_minor+1), }, (MSVCArgumentError, ), ), @@ -600,7 +605,7 @@ def run_msvc_script_args(self) -> None: 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 diff --git a/SCons/Tool/MSCommon/MSVC/Util.py b/SCons/Tool/MSCommon/MSVC/Util.py index f6178e2886..67edfec349 100644 --- a/SCons/Tool/MSCommon/MSVC/Util.py +++ b/SCons/Tool/MSCommon/MSVC/Util.py @@ -358,6 +358,8 @@ def msvc_version_components(vcver): 'msvc_major', # msvc major version integer number (e.g., 14) 'msvc_minor', # msvc minor version integer number (e.g., 1) 'msvc_comps', # msvc version components tuple (e.g., ('14', '1')) + 'msvc_buildtools', # msvc build tools + 'msvc_buildseries', # msvc build series 'msvc_toolset_version', # msvc toolset version 'msvc_toolset_comps', # msvc toolset version components 'version', # msvc version or msvc toolset version @@ -385,10 +387,17 @@ def msvc_extended_version_components(version): msvc_toolset_version = m.group('version') msvc_toolset_comps = tuple(msvc_toolset_version.split('.')) - msvc_verstr = get_msvc_version_prefix(msvc_toolset_version) - if not msvc_verstr: + vc_verstr = get_msvc_version_prefix(msvc_toolset_version) + if not vc_verstr: return None + vc_buildseries_def = Config.MSVC_BUILDSERIES_EXTERNAL.get(vc_verstr) + if not vc_buildseries_def: + return None + + vc_buildtools_def = Config.VC_BUILDTOOLS_MAP[vc_buildseries_def.vc_buildseries] + + msvc_verstr = vc_buildtools_def.msvc_version msvc_suffix = m.group('suffix') if m.group('suffix') else '' msvc_version = msvc_verstr + msvc_suffix @@ -409,6 +418,8 @@ def msvc_extended_version_components(version): msvc_major = msvc_major, msvc_minor = msvc_minor, msvc_comps = msvc_comps, + msvc_buildtools = vc_buildtools_def.vc_buildtools, + msvc_buildseries = vc_buildseries_def.vc_version, msvc_toolset_version = msvc_toolset_version, msvc_toolset_comps = msvc_toolset_comps, version = version, diff --git a/SCons/Tool/MSCommon/README.rst b/SCons/Tool/MSCommon/README.rst index 5a72885c00..b37f409d3d 100644 --- a/SCons/Tool/MSCommon/README.rst +++ b/SCons/Tool/MSCommon/README.rst @@ -224,6 +224,7 @@ Example usage: :: for version in [ + '14.4', '14.3', '14.2', '14.1', @@ -507,67 +508,100 @@ Notes: The Windows 11 SDK version number is 10.0.22000.194 and later. +BuildSeries Versions +-------------------- + ++-------------+-------+-------+ +| BuildSeries | VCVER | CLVER | ++=============+=======+=======+ +| 14.4 | 14.4X | 19.4 | ++-------------+-------+-------+ +| 14.3 | 14.3X | 19.3 | ++-------------+-------+-------+ +| 14.2 | 14.2X | 19.2 | ++-------------+-------+-------+ +| 14.1 | 14.1X | 19.1 | ++-------------+-------+-------+ +| 14.0 | 14.0 | 19.0 | ++-------------+-------+-------+ +| 12.0 | 12.0 | 18.0 | ++-------------+-------+-------+ +| 11.0 | 11.0 | 17.0 | ++-------------+-------+-------+ +| 10.0 | 10.0 | 16.0 | ++-------------+-------+-------+ +| 9.0 | 9.0 | 15.0 | ++-------------+-------+-------+ +| 8.0 | 8.0 | 14.0 | ++-------------+-------+-------+ +| 7.1 | 7.1 | 13.1 | ++-------------+-------+-------+ +| 7.0 | 7.0 | 13.0 | ++-------------+-------+-------+ +| 6.0 | 6.0 | 12.0 | ++-------------+-------+-------+ + BuildTools Versions ------------------- -+------------+-------+-------+----------+ -| BuildTools | VCVER | CLVER | MSVCRT | -+============+=======+=======+==========+ -| v143 | 14.3 | 19.3 | 140/ucrt | -+------------+-------+-------+----------+ -| v142 | 14.2 | 19.2 | 140/ucrt | -+------------+-------+-------+----------+ -| v141 | 14.1 | 19.1 | 140/ucrt | -+------------+-------+-------+----------+ -| v140 | 14.0 | 19.0 | 140/ucrt | -+------------+-------+-------+----------+ -| v120 | 12.0 | 18.0 | 120 | -+------------+-------+-------+----------+ -| v110 | 11.0 | 17.0 | 110 | -+------------+-------+-------+----------+ -| v100 | 10.0 | 16.0 | 100 | -+------------+-------+-------+----------+ -| v90 | 9.0 | 15.0 | 90 | -+------------+-------+-------+----------+ -| v80 | 8.0 | 14.0 | 80 | -+------------+-------+-------+----------+ -| v71 | 7.1 | 13.1 | 71 | -+------------+-------+-------+----------+ -| v70 | 7.0 | 13.0 | 70 | -+------------+-------+-------+----------+ -| v60 | 6.0 | 12.0 | 60 | -+------------+-------+-------+----------+ ++------------+-------------+----------+ +| BuildTools | BuildSeries | MSVCRT | ++============+=============+==========+ +| v143 | 14.4, 14.3 | 140/ucrt | ++------------+-------------+----------+ +| v142 | 14.2 | 140/ucrt | ++------------+-------------+----------+ +| v141 | 14.1 | 140/ucrt | ++------------+-------------+----------+ +| v140 | 14.0 | 140/ucrt | ++------------+-------------+----------+ +| v120 | 12.0 | 120 | ++------------+-------------+----------+ +| v110 | 11.0 | 110 | ++------------+-------------+----------+ +| v100 | 10.0 | 100 | ++------------+-------------+----------+ +| v90 | 9.0 | 90 | ++------------+-------------+----------+ +| v80 | 8.0 | 80 | ++------------+-------------+----------+ +| v71 | 7.1 | 71 | ++------------+-------------+----------+ +| v70 | 7.0 | 70 | ++------------+-------------+----------+ +| v60 | 6.0 | 60 | ++------------+-------------+----------+ Product Versions ---------------- -+----------+-------+-----------+------------------------+ -| Product | VSVER | SDK | BuildTools | -+==========+=======+===========+========================+ -| 2022 | 17.0 | 10.0, 8.1 | v143, v142, v141, v140 | -+----------+-------+-----------+------------------------+ -| 2019 | 16.0 | 10.0, 8.1 | v142, v141, v140 | -+----------+-------+-----------+------------------------+ -| 2017 | 15.0 | 10.0, 8.1 | v141, v140 | -+----------+-------+-----------+------------------------+ -| 2015 | 14.0 | 10.0, 8.1 | v140 | -+----------+-------+-----------+------------------------+ -| 2013 | 12.0 | | v120 | -+----------+-------+-----------+------------------------+ -| 2012 | 11.0 | | v110 | -+----------+-------+-----------+------------------------+ -| 2010 | 10.0 | | v100 | -+----------+-------+-----------+------------------------+ -| 2008 | 9.0 | | v90 | -+----------+-------+-----------+------------------------+ -| 2005 | 8.0 | | v80 | -+----------+-------+-----------+------------------------+ -| 2003.NET | 7.1 | | v71 | -+----------+-------+-----------+------------------------+ -| 2002.NET | 7.0 | | v70 | -+----------+-------+-----------+------------------------+ -| 6.0 | 6.0 | | v60 | -+----------+-------+-----------+------------------------+ ++----------+-------+-------+-----------+------------------------+ +| Product | VSVER | SCons | SDK | BuildTools | ++==========+=======+=======+===========+========================+ +| 2022 | 17.0 | 14.3 | 10.0, 8.1 | v143, v142, v141, v140 | ++----------+-------+-------+-----------+------------------------+ +| 2019 | 16.0 | 14.2 | 10.0, 8.1 | v142, v141, v140 | ++----------+-------+-------+-----------+------------------------+ +| 2017 | 15.0 | 14.1 | 10.0, 8.1 | v141, v140 | ++----------+-------+-------+-----------+------------------------+ +| 2015 | 14.0 | 14.0 | 10.0, 8.1 | v140 | ++----------+-------+-------+-----------+------------------------+ +| 2013 | 12.0 | 12.0 | | v120 | ++----------+-------+-------+-----------+------------------------+ +| 2012 | 11.0 | 11.0 | | v110 | ++----------+-------+-------+-----------+------------------------+ +| 2010 | 10.0 | 10.0 | | v100 | ++----------+-------+-------+-----------+------------------------+ +| 2008 | 9.0 | 9.0 | | v90 | ++----------+-------+-------+-----------+------------------------+ +| 2005 | 8.0 | 8.0 | | v80 | ++----------+-------+-------+-----------+------------------------+ +| 2003.NET | 7.1 | 7.1 | | v71 | ++----------+-------+-------+-----------+------------------------+ +| 2002.NET | 7.0 | 7.0 | | v70 | ++----------+-------+-------+-----------+------------------------+ +| 6.0 | 6.0 | 6.0 | | v60 | ++----------+-------+-------+-----------+------------------------+ SCons Implementation Notes diff --git a/SCons/Tool/MSCommon/vc.py b/SCons/Tool/MSCommon/vc.py index 8a2fd8e026..907b2455b7 100644 --- a/SCons/Tool/MSCommon/vc.py +++ b/SCons/Tool/MSCommon/vc.py @@ -2516,7 +2516,7 @@ def msvc_sdk_versions(version=None, msvc_uwp_app: bool=False): msg = f'Unsupported version {version!r}' raise MSVCArgumentError(msg) - rval = MSVC.WinSDK.get_msvc_sdk_version_list(version, msvc_uwp_app) + rval = MSVC.WinSDK.get_msvc_sdk_version_list(version_def.msvc_version, msvc_uwp_app) return rval def msvc_toolset_versions(msvc_version=None, full: bool=True, sxs: bool=False, vswhere_exe=None): diff --git a/test/MSVC/MSVC_SDK_VERSION.py b/test/MSVC/MSVC_SDK_VERSION.py index f3f9913b52..0f0e25a65b 100644 --- a/test/MSVC/MSVC_SDK_VERSION.py +++ b/test/MSVC/MSVC_SDK_VERSION.py @@ -197,8 +197,8 @@ def version_major_list(version_list): """.format(repr(supported.msvc_version), repr(toolset_version)) )) test.run(arguments='-Q -s', status=2, stderr=None) - expect = "MSVCArgumentError: MSVC_SDK_VERSION ('8.1') and platform type ('UWP') constraint violation: toolset version {} > '14.0' VS2015:".format( - repr(toolset_version) + expect = "MSVCArgumentError: MSVC_SDK_VERSION ('8.1') and platform type ('UWP') constraint violation: toolset {} MSVC_VERSION {} > '14.0' VS2015:".format( + repr(toolset_version), repr(supported.msvc_version) ) test.must_contain_all(test.stderr(), expect) diff --git a/test/MSVC/MSVC_TOOLSET_VERSION.py b/test/MSVC/MSVC_TOOLSET_VERSION.py index 7c93938019..e43f4d7f80 100644 --- a/test/MSVC/MSVC_TOOLSET_VERSION.py +++ b/test/MSVC/MSVC_TOOLSET_VERSION.py @@ -28,6 +28,7 @@ """ import textwrap +import SCons.Tool.MSCommon.MSVC.Config as Config from SCons.Tool.MSCommon.vc import get_installed_vcs_components from SCons.Tool.MSCommon import msvc_toolset_versions @@ -43,6 +44,26 @@ LT_VS2017_versions = [v for v in installed_versions if v.msvc_vernum < 14.1] LT_VS2015_versions = [v for v in LT_VS2017_versions if v.msvc_vernum < 14.0] +known_buildseries = set(Config.MSVC_BUILDSERIES_EXTERNAL.keys()) + +def get_toolset_buildseries_version(toolset_version): + comps = toolset_version.split('.') + buildseries_version = comps[0] + '.' + comps[1][0] + vc_buildseries_def = Config.MSVC_BUILDSERIES_EXTERNAL[buildseries_version] + vc_version = vc_buildseries_def.vc_version + return vc_version + +def get_latest_buildseries_version(v): + vs_def = Config.MSVC_VERSION_EXTERNAL[v.msvc_verstr] + vc_version = vs_def.vc_buildtools_def.vc_buildseries_list[0].vc_version + return vc_version + +def is_buildseries_known(toolset_version): + comps = toolset_version.split('.') + buildseries_version = comps[0] + '.' + comps[1][0] + rval = bool(buildseries_version in known_buildseries) + return rval + if GE_VS2017_versions: # VS2017 and later for toolset argument @@ -54,6 +75,11 @@ toolset_sxs_versions = msvc_toolset_versions(supported.msvc_version, full=False, sxs=True) toolset_sxs_version = toolset_sxs_versions[0] if toolset_sxs_versions else None + toolset_version = toolset_full_version if toolset_full_version else supported.msvc_version + buildseries_version = get_toolset_buildseries_version(toolset_version) + + latest_buildseries_version = get_latest_buildseries_version(supported) + if toolset_full_version: # toolset version using construction variable @@ -91,21 +117,21 @@ )) test.run(arguments='-Q -s', stdout='') - # msvc_version as toolset version + # build series version as toolset version test.write('SConstruct', textwrap.dedent( """ DefaultEnvironment(tools=[]) env = Environment(MSVC_VERSION={}, MSVC_TOOLSET_VERSION={}, tools=['msvc']) - """.format(repr(supported.msvc_version), repr(supported.msvc_verstr)) + """.format(repr(supported.msvc_version), repr(buildseries_version)) )) test.run(arguments='-Q -s', stdout='') - # msvc_version as toolset version using script argument + # build series version as toolset version using script argument test.write('SConstruct', textwrap.dedent( """ DefaultEnvironment(tools=[]) env = Environment(MSVC_VERSION={}, MSVC_SCRIPT_ARGS='-vcvars_ver={}', tools=['msvc']) - """.format(repr(supported.msvc_version), supported.msvc_verstr) + """.format(repr(supported.msvc_version), buildseries_version) )) test.run(arguments='-Q -s', stdout='') @@ -114,11 +140,11 @@ """ DefaultEnvironment(tools=[]) env = Environment(MSVC_VERSION={}, MSVC_TOOLSET_VERSION={}, MSVC_SCRIPT_ARGS='-vcvars_ver={}', tools=['msvc']) - """.format(repr(supported.msvc_version), repr(supported.msvc_verstr), supported.msvc_verstr) + """.format(repr(supported.msvc_version), repr(buildseries_version), buildseries_version) )) test.run(arguments='-Q -s', status=2, stderr=None) expect = "MSVCArgumentError: multiple toolset version declarations: MSVC_TOOLSET_VERSION={} and MSVC_SCRIPT_ARGS='-vcvars_ver={}':".format( - repr(supported.msvc_verstr), supported.msvc_verstr + repr(buildseries_version), buildseries_version ) test.must_contain_all(test.stderr(), expect) @@ -150,9 +176,19 @@ ) test.must_contain_all(test.stderr(), expect) - # msvc_toolset_version is invalid (version greater than msvc version) - invalid_toolset_vernum = round(supported.msvc_vernum + 0.1, 1) + # msvc_toolset_version is invalid (version not supported for msvc version) + invalid_toolset_vernum = round(float(latest_buildseries_version) + 0.1, 1) invalid_toolset_version = str(invalid_toolset_vernum) + + if is_buildseries_known(invalid_toolset_version): + expect = "MSVCArgumentError: MSVC_TOOLSET_VERSION ({}) constraint violation: toolset msvc version {} > {} MSVC_VERSION:".format( + repr(invalid_toolset_version), repr(invalid_toolset_version), repr(supported.msvc_version) + ) + else: + expect = "MSVCArgumentError: MSVC_TOOLSET_VERSION {} build series {} is not supported:".format( + repr(invalid_toolset_version), repr(invalid_toolset_version) + ) + test.write('SConstruct', textwrap.dedent( """ DefaultEnvironment(tools=[]) @@ -160,9 +196,6 @@ """.format(repr(supported.msvc_version), repr(invalid_toolset_version)) )) test.run(arguments='-Q -s', status=2, stderr=None) - expect = "MSVCArgumentError: MSVC_TOOLSET_VERSION ({}) constraint violation: toolset version {} > {} MSVC_VERSION:".format( - repr(invalid_toolset_version), repr(invalid_toolset_version), repr(supported.msvc_version) - ) test.must_contain_all(test.stderr(), expect) # msvc_toolset_version is invalid (version less than 14.0) @@ -174,7 +207,7 @@ """.format(repr(supported.msvc_version), repr(invalid_toolset_version)) )) test.run(arguments='-Q -s', status=2, stderr=None) - expect = "MSVCArgumentError: MSVC_TOOLSET_VERSION ({}) constraint violation: toolset version {} < '14.0' VS2015:".format( + expect = "MSVCArgumentError: MSVC_TOOLSET_VERSION ({}) constraint violation: toolset msvc version {} < '14.0' VS2015:".format( repr(invalid_toolset_version), repr(invalid_toolset_version) ) test.must_contain_all(test.stderr(), expect) @@ -188,7 +221,7 @@ """.format(repr(supported.msvc_version), repr(invalid_toolset_version)) )) test.run(arguments='-Q -s', status=2, stderr=None) - expect = "MSVCArgumentError: MSVC_TOOLSET_VERSION ({}) constraint violation: toolset version {} != '14.0':".format( + expect = "MSVCArgumentError: MSVC_TOOLSET_VERSION ({}) constraint violation: toolset msvc version {} != '14.0':".format( repr(invalid_toolset_version), repr(invalid_toolset_version) ) test.must_contain_all(test.stderr(), expect) From da726100ca098becf9802e28657bbae3af04ee5f Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Mon, 27 May 2024 18:12:39 -0400 Subject: [PATCH 31/35] Rework experimental msvc_query_version_toolset function. Changes: * Add context manager functions to temporary change the policy for msvc not found and msvc script errors within a context block. * Change msvc_query_version_toolset function behavior: raise exceptions if not found and for argument validation errors; otherwise return msvc version and toolset version. * Additional logging of exception messages . * Update tests for 14.3X/14.4X toolsets for VS 2022. --- SCons/Tool/MSCommon/MSVC/Policy.py | 29 +++++ SCons/Tool/MSCommon/__init__.py | 2 + SCons/Tool/MSCommon/vc.py | 188 ++++++++++++++++------------- SCons/Tool/MSCommon/vcTests.py | 52 ++++++-- test/MSVC/no_msvc.py | 3 +- 5 files changed, 180 insertions(+), 94 deletions(-) diff --git a/SCons/Tool/MSCommon/MSVC/Policy.py b/SCons/Tool/MSCommon/MSVC/Policy.py index fe8da3156b..7e11a63ba5 100644 --- a/SCons/Tool/MSCommon/MSVC/Policy.py +++ b/SCons/Tool/MSCommon/MSVC/Policy.py @@ -38,6 +38,10 @@ namedtuple, ) +from contextlib import ( + contextmanager, +) + import SCons.Warnings from ..common import ( @@ -215,6 +219,18 @@ def msvc_notfound_handler(env, msg): else: SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, msg) +@contextmanager +def msvc_notfound_policy_contextmanager(MSVC_NOTFOUND_POLICY=None): + """ Temporarily change the MSVC not found policy within a context. + + Args: + MSVC_NOTFOUND_POLICY: + string representing the policy behavior + when MSVC is not found or None + """ + prev_policy = msvc_set_notfound_policy(MSVC_NOTFOUND_POLICY) + yield + msvc_set_notfound_policy(prev_policy) def _msvc_scripterror_policy_lookup(symbol): @@ -299,3 +315,16 @@ def msvc_scripterror_handler(env, msg): else: SCons.Warnings.warn(MSVCScriptExecutionWarning, msg) +@contextmanager +def msvc_scripterror_policy_contextmanager(MSVC_SCRIPTERROR_POLICY=None): + """ Temporarily change the msvc batch execution errors policy within a context. + + Args: + MSVC_SCRIPTERROR_POLICY: + string representing the policy behavior + when msvc batch file execution errors are detected or None + """ + prev_policy = msvc_set_scripterror_policy(MSVC_SCRIPTERROR_POLICY) + yield + msvc_set_scripterror_policy(prev_policy) + diff --git a/SCons/Tool/MSCommon/__init__.py b/SCons/Tool/MSCommon/__init__.py index 4d7b8bcb3a..63f9ae4de6 100644 --- a/SCons/Tool/MSCommon/__init__.py +++ b/SCons/Tool/MSCommon/__init__.py @@ -63,6 +63,8 @@ msvc_get_notfound_policy, msvc_set_scripterror_policy, msvc_get_scripterror_policy, + msvc_notfound_policy_contextmanager, + msvc_scripterror_policy_contextmanager, ) from .MSVC.Exceptions import ( # noqa: F401 diff --git a/SCons/Tool/MSCommon/vc.py b/SCons/Tool/MSCommon/vc.py index 907b2455b7..a92dd19f68 100644 --- a/SCons/Tool/MSCommon/vc.py +++ b/SCons/Tool/MSCommon/vc.py @@ -2572,28 +2572,24 @@ def msvc_toolset_versions_spectre(msvc_version=None, vswhere_exe=None): def msvc_query_version_toolset(version=None, prefer_newest: bool=True, vswhere_exe=None): """ - Returns an msvc version and a toolset version given a version + Return an msvc version and a toolset version given a version specification. This is an EXPERIMENTAL proxy for using a toolset version to perform msvc instance selection. This function will be removed when toolset version is taken into account during msvc instance selection. - Search for an installed Visual Studio instance that supports the - specified version. + This function searches for an installed Visual Studio instance that + contains the requested version. A component suffix (e.g., Exp) is not + supported for toolset version specifications (e.g., 14.39). - When the specified version contains a component suffix (e.g., Exp), - the msvc version is returned and the toolset version is None. No - search if performed. + An MSVCArgumentError is raised when argument validation fails. An + MSVCToolsetVersionNotFound exception is raised when the requested + version is not found. - When the specified version does not contain a component suffix, the - version is treated as a toolset version specification. A search is - performed for the first msvc instance that contains the toolset - version. - - Only Visual Studio 2017 and later support toolset arguments. For - Visual Studio 2015 and earlier, the msvc version is returned and - the toolset version is None. + For Visual Studio 2015 and earlier, the msvc version is returned and + the toolset version is None. For Visual Studio 2017 and later, the + selected toolset version is returned. Args: @@ -2623,106 +2619,132 @@ def msvc_query_version_toolset(version=None, prefer_newest: bool=True, vswhere_e msvc_version = None msvc_toolset_version = None - if not version: - version = msvc_default_version() + with MSVC.Policy.msvc_notfound_policy_contextmanager('suppress'): - if not version: - debug('no msvc versions detected') - return msvc_version, msvc_toolset_version + _VSWhereExecutable.vswhere_freeze_executable(vswhere_exe) - version_def = MSVC.Util.msvc_extended_version_components(version) + vcs = get_installed_vcs() - if not version_def: - msg = f'Unsupported msvc version {version!r}' - raise MSVCArgumentError(msg) + if not version: + version = msvc_default_version() - if version_def.msvc_suffix: - if version_def.msvc_verstr != version_def.msvc_toolset_version: - # toolset version with component suffix - msg = f'Unsupported toolset version {version!r}' - raise MSVCArgumentError(msg) + if not version: + msg = f'No versions of the MSVC compiler were found' + debug(f'MSVCToolsetVersionNotFound: {msg}') + raise MSVCToolsetVersionNotFound(msg) - if version_def.msvc_vernum > 14.0: - # VS2017 and later - force_toolset_msvc_version = False - else: - # VS2015 and earlier - force_toolset_msvc_version = True - extended_version = version_def.msvc_verstr + '0.00000' - if not extended_version.startswith(version_def.msvc_toolset_version): - # toolset not equivalent to msvc version - msg = 'Unsupported toolset version {} (expected {})'.format( - repr(version), repr(extended_version) - ) + version_def = MSVC.Util.msvc_extended_version_components(version) + + if not version_def: + msg = f'Unsupported MSVC version {version!r}' + debug(f'MSVCArgumentError: {msg}') raise MSVCArgumentError(msg) - msvc_version = version_def.msvc_version + msvc_version = version_def.msvc_version - if msvc_version not in MSVC.Config.MSVC_VERSION_TOOLSET_SEARCH_MAP: - # VS2013 and earlier - debug( - 'ignore: msvc_version=%s, msvc_toolset_version=%s', - repr(msvc_version), repr(msvc_toolset_version) - ) - return msvc_version, msvc_toolset_version + if version_def.msvc_suffix: - if force_toolset_msvc_version: - query_msvc_toolset_version = version_def.msvc_verstr - else: - query_msvc_toolset_version = version_def.msvc_toolset_version + if version_def.msvc_verstr != version_def.msvc_toolset_version: + # toolset version with component suffix + msg = f'Unsupported MSVC toolset version {version!r}' + debug(f'MSVCArgumentError: {msg}') + raise MSVCArgumentError(msg) - if prefer_newest: - query_version_list = MSVC.Config.MSVC_VERSION_TOOLSET_SEARCH_MAP[msvc_version] - else: - query_version_list = MSVC.Config.MSVC_VERSION_TOOLSET_DEFAULTS_MAP[msvc_version] + \ - MSVC.Config.MSVC_VERSION_TOOLSET_SEARCH_MAP[msvc_version] + if msvc_version not in vcs: + msg = f'MSVC version {msvc_version!r} not found' + debug(f'MSVCToolsetVersionNotFound: {msg}') + raise MSVCToolsetVersionNotFound(msg) - seen_msvc_version = set() - for query_msvc_version in query_version_list: + if msvc_version not in MSVC.Config.MSVC_VERSION_TOOLSET_SEARCH_MAP: + debug( + 'suffix: msvc_version=%s, msvc_toolset_version=%s', + repr(msvc_version), repr(msvc_toolset_version) + ) + return msvc_version, msvc_toolset_version - if query_msvc_version in seen_msvc_version: - continue - seen_msvc_version.add(query_msvc_version) + if version_def.msvc_vernum > 14.0: + # VS2017 and later + force_toolset_msvc_version = False + else: + # VS2015 and earlier + force_toolset_msvc_version = True + extended_version = version_def.msvc_verstr + '0.00000' + if not extended_version.startswith(version_def.msvc_toolset_version): + # toolset not equivalent to msvc version + msg = 'Unsupported MSVC toolset version {} (expected {})'.format( + repr(version), repr(extended_version) + ) + debug(f'MSVCArgumentError: {msg}') + raise MSVCArgumentError(msg) - vc_dir = _find_vc_pdir(query_msvc_version, vswhere_exe) - if not vc_dir: - continue + if msvc_version not in MSVC.Config.MSVC_VERSION_TOOLSET_SEARCH_MAP: + + # VS2013 and earlier + + if msvc_version not in vcs: + msg = f'MSVC version {version!r} not found' + debug(f'MSVCToolsetVersionNotFound: {msg}') + raise MSVCToolsetVersionNotFound(msg) - if query_msvc_version.startswith('14.0'): - # VS2015 does not support toolset version argument - msvc_toolset_version = None debug( - 'found: msvc_version=%s, msvc_toolset_version=%s', - repr(query_msvc_version), repr(msvc_toolset_version) + 'earlier: msvc_version=%s, msvc_toolset_version=%s', + repr(msvc_version), repr(msvc_toolset_version) ) - return query_msvc_version, msvc_toolset_version + return msvc_version, msvc_toolset_version - try: - toolset_vcvars = MSVC.ScriptArguments._msvc_toolset_internal(query_msvc_version, query_msvc_toolset_version, vc_dir) - if toolset_vcvars: - msvc_toolset_version = toolset_vcvars + if force_toolset_msvc_version: + query_msvc_toolset_version = version_def.msvc_verstr + else: + query_msvc_toolset_version = version_def.msvc_toolset_version + + if prefer_newest: + query_version_list = MSVC.Config.MSVC_VERSION_TOOLSET_SEARCH_MAP[msvc_version] + else: + query_version_list = MSVC.Config.MSVC_VERSION_TOOLSET_DEFAULTS_MAP[msvc_version] + \ + MSVC.Config.MSVC_VERSION_TOOLSET_SEARCH_MAP[msvc_version] + + seen_msvc_version = set() + for query_msvc_version in query_version_list: + + if query_msvc_version in seen_msvc_version: + continue + seen_msvc_version.add(query_msvc_version) + + vc_dir = _find_vc_pdir(query_msvc_version, vswhere_exe) + if not vc_dir: + continue + + if query_msvc_version.startswith('14.0'): + # VS2015 does not support toolset version argument + msvc_toolset_version = None debug( 'found: msvc_version=%s, msvc_toolset_version=%s', repr(query_msvc_version), repr(msvc_toolset_version) ) return query_msvc_version, msvc_toolset_version - except MSVCToolsetVersionNotFound: - pass + try: + toolset_vcvars = MSVC.ScriptArguments._msvc_toolset_internal(query_msvc_version, query_msvc_toolset_version, vc_dir) + if toolset_vcvars: + msvc_toolset_version = toolset_vcvars + debug( + 'found: msvc_version=%s, msvc_toolset_version=%s', + repr(query_msvc_version), repr(msvc_toolset_version) + ) + return query_msvc_version, msvc_toolset_version - msvc_toolset_version = query_msvc_toolset_version + except MSVCToolsetVersionNotFound: + pass + + msvc_toolset_version = query_msvc_toolset_version debug( 'not found: msvc_version=%s, msvc_toolset_version=%s', repr(msvc_version), repr(msvc_toolset_version) ) - if version_def.msvc_verstr == msvc_toolset_version: - msg = f'MSVC version {version!r} was not found' - MSVC.Policy.msvc_notfound_handler(None, msg) - return msvc_version, msvc_toolset_version - msg = f'MSVC toolset version {version!r} not found' + debug(f'MSVCToolsetVersionNotFound: {msg}') raise MSVCToolsetVersionNotFound(msg) diff --git a/SCons/Tool/MSCommon/vcTests.py b/SCons/Tool/MSCommon/vcTests.py index 3b30a570a0..d8813da2bd 100644 --- a/SCons/Tool/MSCommon/vcTests.py +++ b/SCons/Tool/MSCommon/vcTests.py @@ -234,8 +234,18 @@ class Data: HAVE_MSVC = False DEFAULT_VERSION_DEF = None + INSTALLED_VCS = MSCommon.vc.get_installed_vcs() INSTALLED_VCS_COMPONENTS = MSCommon.vc.get_installed_vcs_components() + @classmethod + def query_version_list(cls, vcver): + # VS 2022 (14.3) can have either/both toolset versions 14.3X and 14.4X + if vcver == '14.3' or (vcver is None and cls.DEFAULT_VERSION == '14.3'): + vcver_list = ['14.4', '14.3'] + else: + vcver_list = [vcver] + return vcver_list + @classmethod def _msvc_toolset_notfound_list(cls, toolset_seen, toolset_list): new_toolset_list = [] @@ -454,15 +464,26 @@ class MsvcQueryVersionToolsetTests(unittest.TestCase): def run_valid_default_msvc(self, have_msvc) -> None: for prefer_newest in (True, False): - msvc_version, msvc_toolset_version = MSCommon.vc.msvc_query_version_toolset( - version=None, prefer_newest=prefer_newest - ) - expect = (have_msvc and msvc_version) or (not have_msvc and not msvc_version) - self.assertTrue(expect, "unexpected msvc_version {} for for msvc version {}".format( + if not have_msvc: + with self.assertRaises(MSCommon.vc.MSVCToolsetVersionNotFound): + msvc_version, msvc_toolset_version = MSCommon.vc.msvc_query_version_toolset( + version=None, prefer_newest=prefer_newest + ) + continue + msvc_version = msvc_toolset_version = None + for vcver in Data.query_version_list(None): + try: + msvc_version, msvc_toolset_version = MSCommon.vc.msvc_query_version_toolset( + version=vcver, prefer_newest=prefer_newest + ) + break + except MSCommon.vc.MSVCToolsetVersionNotFound: + pass + self.assertTrue(msvc_version, "unexpected msvc_version {} for for msvc version {}".format( repr(msvc_version), repr(None) )) version_def = MSCommon.msvc_version_components(msvc_version) - if have_msvc and version_def.msvc_vernum > 14.0: + if version_def.msvc_vernum > 14.0: # VS2017 and later for toolset version self.assertTrue(msvc_toolset_version, "msvc_toolset_version is undefined for msvc version {}".format( repr(None) @@ -477,11 +498,24 @@ def test_valid_default_msvc(self) -> None: def test_valid_vcver(self) -> None: for symbol in MSCommon.vc._VCVER: + have_msvc = bool(symbol in Data.INSTALLED_VCS) version_def = MSCommon.msvc_version_components(symbol) for prefer_newest in (True, False): - msvc_version, msvc_toolset_version = MSCommon.vc.msvc_query_version_toolset( - version=symbol, prefer_newest=prefer_newest - ) + if not have_msvc: + with self.assertRaises(MSCommon.vc.MSVCToolsetVersionNotFound): + msvc_version, msvc_toolset_version = MSCommon.vc.msvc_query_version_toolset( + version=symbol, prefer_newest=prefer_newest + ) + continue + msvc_version = msvc_toolset_version = None + for vcver in Data.query_version_list(symbol): + try: + msvc_version, msvc_toolset_version = MSCommon.vc.msvc_query_version_toolset( + version=vcver, prefer_newest=prefer_newest + ) + break + except: + pass self.assertTrue(msvc_version, "msvc_version is undefined for msvc version {}".format(repr(symbol))) if version_def.msvc_vernum > 14.0: # VS2017 and later for toolset version diff --git a/test/MSVC/no_msvc.py b/test/MSVC/no_msvc.py index 35cce9258d..aa9e2b8e57 100644 --- a/test/MSVC/no_msvc.py +++ b/test/MSVC/no_msvc.py @@ -75,8 +75,7 @@ def exists(env): # test no msvc's and msvc_query_version_toolset() call test.file_fixture('no_msvc/no_msvcs_sconstruct_msvc_query_toolset_version.py', 'SConstruct') -test.run(arguments='-Q -s') -test.must_contain_all(test.stdout(), 'msvc_version=None, msvc_toolset_version=None') +test.run(arguments='-Q -s', status=2, stderr=r"^.*MSVCToolsetVersionNotFound.+", match=TestSCons.match_re_dotall) test.pass_test() From 8abc02fcd2a1a7e5464ef01b992f7535ceba2bdd Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Tue, 28 May 2024 08:40:34 -0400 Subject: [PATCH 32/35] Fix msvc_query_version_toolset function test in vcTests.py Changes: * Add function to return msvc version and toolset version pairs for all installed vcs. * Construct set of installed toolset vcs for determining if msvc_query_version_toolset should raise an exception. --- SCons/Tool/MSCommon/vc.py | 32 ++++++++++++++++++++++++++++++-- SCons/Tool/MSCommon/vcTests.py | 8 +++++++- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/SCons/Tool/MSCommon/vc.py b/SCons/Tool/MSCommon/vc.py index a92dd19f68..63919bd7e2 100644 --- a/SCons/Tool/MSCommon/vc.py +++ b/SCons/Tool/MSCommon/vc.py @@ -2570,6 +2570,34 @@ def msvc_toolset_versions_spectre(msvc_version=None, vswhere_exe=None): rval = MSVC.ScriptArguments._msvc_toolset_versions_spectre_internal(msvc_version, vc_dir) return rval +def get_installed_vcs_toolsets_components(env=None): + + vcs = get_installed_vcs(env) + + msvc_toolset_component_defs = [] + + for msvc_version in vcs: + msvc_version_def = MSVC.Util.msvc_version_components(msvc_version) + if msvc_version_def.msvc_vernum > 14.0: + # VS2017 and later + toolset_all_list = msvc_toolset_versions(msvc_version=msvc_version, full=True, sxs=True) + for toolset_version in toolset_all_list: + debug('msvc_version=%s, toolset_version=%s', repr(msvc_version), repr(toolset_version)) + toolset_version_def = MSVC.Util.msvc_extended_version_components(toolset_version) + if not toolset_version_def: + continue + rval = (msvc_version_def, toolset_version_def) + msvc_toolset_component_defs.append(rval) + else: + # VS2015 and earlier + toolset_version = msvc_version_def.msvc_verstr + debug('msvc_version=%s, toolset_version=%s', repr(msvc_version), repr(toolset_version)) + toolset_version_def = MSVC.Util.msvc_extended_version_components(toolset_version) + rval = (msvc_version_def, toolset_version_def) + msvc_toolset_component_defs.append(rval) + + return msvc_toolset_component_defs + def msvc_query_version_toolset(version=None, prefer_newest: bool=True, vswhere_exe=None): """ Return an msvc version and a toolset version given a version @@ -2616,13 +2644,13 @@ def msvc_query_version_toolset(version=None, prefer_newest: bool=True, vswhere_e """ debug('version=%s, prefer_newest=%s', repr(version), repr(prefer_newest)) + _VSWhereExecutable.vswhere_freeze_executable(vswhere_exe) + msvc_version = None msvc_toolset_version = None with MSVC.Policy.msvc_notfound_policy_contextmanager('suppress'): - _VSWhereExecutable.vswhere_freeze_executable(vswhere_exe) - vcs = get_installed_vcs() if not version: diff --git a/SCons/Tool/MSCommon/vcTests.py b/SCons/Tool/MSCommon/vcTests.py index d8813da2bd..65ae904380 100644 --- a/SCons/Tool/MSCommon/vcTests.py +++ b/SCons/Tool/MSCommon/vcTests.py @@ -237,6 +237,12 @@ class Data: INSTALLED_VCS = MSCommon.vc.get_installed_vcs() INSTALLED_VCS_COMPONENTS = MSCommon.vc.get_installed_vcs_components() + INSTALLED_VCS_TOOLSETS_COMPONENTS = MSCommon.vc.get_installed_vcs_toolsets_components() + INSTALLED_VCS_TOOLSETS = set() + for msvc_version_def, toolset_version_def in INSTALLED_VCS_TOOLSETS_COMPONENTS: + INSTALLED_VCS_TOOLSETS.add(msvc_version_def.msvc_version) + INSTALLED_VCS_TOOLSETS.add(toolset_version_def.msvc_version) + @classmethod def query_version_list(cls, vcver): # VS 2022 (14.3) can have either/both toolset versions 14.3X and 14.4X @@ -498,7 +504,7 @@ def test_valid_default_msvc(self) -> None: def test_valid_vcver(self) -> None: for symbol in MSCommon.vc._VCVER: - have_msvc = bool(symbol in Data.INSTALLED_VCS) + have_msvc = bool(symbol in Data.INSTALLED_VCS_TOOLSETS) version_def = MSCommon.msvc_version_components(symbol) for prefer_newest in (True, False): if not have_msvc: From 8545d25c590c102d147f4a807aee39e10dcc0e95 Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Tue, 28 May 2024 08:45:02 -0400 Subject: [PATCH 33/35] Restore PCH time multiplier in test/MSVC/msvc.py (changed from 0.90 to 1.00). --- test/MSVC/msvc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/MSVC/msvc.py b/test/MSVC/msvc.py index 8d018f82bf..e81ecea7e8 100644 --- a/test/MSVC/msvc.py +++ b/test/MSVC/msvc.py @@ -116,7 +116,7 @@ # TODO: Reevaluate if having this part of the test makes sense any longer # using precompiled headers should be faster -limit = slow +limit = slow*1.00 if fast >= limit: print("Using precompiled headers was not fast enough:") print("slow.obj: %.3fs" % slow) From ddaa853d1b05d0cbdcb828267b502452160c64da Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Tue, 28 May 2024 09:10:33 -0400 Subject: [PATCH 34/35] Fix grammatical error in RELEASE.txt --- RELEASE.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE.txt b/RELEASE.txt index 7168ee5d1a..f88c91019f 100644 --- a/RELEASE.txt +++ b/RELEASE.txt @@ -69,7 +69,7 @@ FIXES - MSVC: For Visual Studio 2005 (8.0) to Visual Studio 2015 (14.0), detection of installed files was expanded to include the primary msvc batch file, dependent msvc batch file, and compiler executable. In certain installations, the - dependent msvc batch file may not exist while the compiler executable does exists + dependent msvc batch file may not exist while the compiler executable does exist resulting in a build failure. - MSVC: Visual Studio 2008 (9.0) Visual C++ For Python was not detected when installed using the ALLUSERS command-line option: From 6a4c52bb516657e71ab6a7355ee8c5d84a23fb4a Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Tue, 28 May 2024 13:04:18 -0400 Subject: [PATCH 35/35] Fix bug in Config.py initialization and rework support for MsvcQueryVersionToolsetTests. Changes: * module-level initialization index was not renamed from vc_version to msvc_version. * Add function to return toolset sxs map and versions list in ScriptArguments.py * Add additional fields to extended version components * Add data structure for installed vcs versions/toolsets for testing * Update vcTests.py to new data structure. --- SCons/Tool/MSCommon/MSVC/Config.py | 2 +- SCons/Tool/MSCommon/MSVC/ScriptArguments.py | 8 ++ SCons/Tool/MSCommon/MSVC/Util.py | 25 +++++-- SCons/Tool/MSCommon/vc.py | 81 ++++++++++++++++----- SCons/Tool/MSCommon/vcTests.py | 7 +- 5 files changed, 91 insertions(+), 32 deletions(-) diff --git a/SCons/Tool/MSCommon/MSVC/Config.py b/SCons/Tool/MSCommon/MSVC/Config.py index a04db4904e..d0ccb6d0ba 100644 --- a/SCons/Tool/MSCommon/MSVC/Config.py +++ b/SCons/Tool/MSCommon/MSVC/Config.py @@ -230,7 +230,7 @@ MSVC_BUILDTOOLS_INTERNAL[vc_buildtools] = vc_buildtools_def MSVC_BUILDTOOLS_EXTERNAL[vc_buildtools] = vc_buildtools_def - MSVC_BUILDTOOLS_EXTERNAL[vc_version] = vc_buildtools_def + MSVC_BUILDTOOLS_EXTERNAL[msvc_version] = vc_buildtools_def for vc_buildseries_def in vc_buildseries_list: VC_BUILDTOOLS_MAP[vc_buildseries_def.vc_buildseries] = vc_buildtools_def diff --git a/SCons/Tool/MSCommon/MSVC/ScriptArguments.py b/SCons/Tool/MSCommon/MSVC/ScriptArguments.py index 76ad8717b0..2c766fed4e 100644 --- a/SCons/Tool/MSCommon/MSVC/ScriptArguments.py +++ b/SCons/Tool/MSCommon/MSVC/ScriptArguments.py @@ -1066,6 +1066,14 @@ def _msvc_toolset_versions_internal(msvc_version, vc_dir, full: bool=True, sxs: return toolset_versions +def _msvc_version_toolsets_internal(msvc_version, vc_dir): + + msvc = _msvc_version(msvc_version) + + toolsets_sxs, toolsets_full = _msvc_version_toolsets(msvc, vc_dir) + + return toolsets_sxs, toolsets_full + def _msvc_toolset_versions_spectre_internal(msvc_version, vc_dir): msvc = _msvc_version(msvc_version) diff --git a/SCons/Tool/MSCommon/MSVC/Util.py b/SCons/Tool/MSCommon/MSVC/Util.py index 67edfec349..0da58e1e4d 100644 --- a/SCons/Tool/MSCommon/MSVC/Util.py +++ b/SCons/Tool/MSCommon/MSVC/Util.py @@ -351,17 +351,21 @@ def msvc_version_components(vcver): return msvc_version_components_def _MSVC_EXTENDED_VERSION_COMPONENTS_DEFINITION = namedtuple('MSVCExtendedVersionComponentsDefinition', [ - 'msvc_version', # msvc version (e.g., '14.1Exp') - 'msvc_verstr', # msvc version numeric string (e.g., '14.1') - 'msvc_suffix', # msvc version component type (e.g., 'Exp') - 'msvc_vernum', # msvc version floating point number (e.g, 14.1) - 'msvc_major', # msvc major version integer number (e.g., 14) - 'msvc_minor', # msvc minor version integer number (e.g., 1) - 'msvc_comps', # msvc version components tuple (e.g., ('14', '1')) + 'msvc_version', # msvc version (e.g., '14.1Exp') + 'msvc_verstr', # msvc version numeric string (e.g., '14.1') + 'msvc_suffix', # msvc version component type (e.g., 'Exp') + 'msvc_suffix_rank', # msvc version component rank (0, 1) + 'msvc_vernum', # msvc version floating point number (e.g, 14.1) + 'msvc_major', # msvc major version integer number (e.g., 14) + 'msvc_minor', # msvc minor version integer number (e.g., 1) + 'msvc_comps', # msvc version components tuple (e.g., ('14', '1')) 'msvc_buildtools', # msvc build tools + 'msvc_buildtools_num', # msvc build tools integer number 'msvc_buildseries', # msvc build series + 'msvc_buildseries_num', # msvc build series floating point number 'msvc_toolset_version', # msvc toolset version 'msvc_toolset_comps', # msvc toolset version components + 'msvc_toolset_is_sxs', # msvc toolset version is sxs 'version', # msvc version or msvc toolset version ]) @@ -386,6 +390,7 @@ def msvc_extended_version_components(version): msvc_toolset_version = m.group('version') msvc_toolset_comps = tuple(msvc_toolset_version.split('.')) + msvc_toolset_is_sxs = is_toolset_sxs(msvc_toolset_version) vc_verstr = get_msvc_version_prefix(msvc_toolset_version) if not vc_verstr: @@ -414,14 +419,18 @@ def msvc_extended_version_components(version): msvc_version = msvc_version, msvc_verstr = msvc_verstr, msvc_suffix = msvc_suffix, + msvc_suffix_rank = 0 if not msvc_suffix else 1, msvc_vernum = msvc_vernum, msvc_major = msvc_major, msvc_minor = msvc_minor, msvc_comps = msvc_comps, - msvc_buildtools = vc_buildtools_def.vc_buildtools, + msvc_buildtools = vc_buildtools_def.msvc_version, + msvc_buildtools_num = vc_buildtools_def.msvc_version_numeric, msvc_buildseries = vc_buildseries_def.vc_version, + msvc_buildseries_num = vc_buildseries_def.vc_version_numeric, msvc_toolset_version = msvc_toolset_version, msvc_toolset_comps = msvc_toolset_comps, + msvc_toolset_is_sxs = msvc_toolset_is_sxs, version = version, ) diff --git a/SCons/Tool/MSCommon/vc.py b/SCons/Tool/MSCommon/vc.py index 63919bd7e2..032dbf202e 100644 --- a/SCons/Tool/MSCommon/vc.py +++ b/SCons/Tool/MSCommon/vc.py @@ -1229,7 +1229,10 @@ def vswhere_freeze_env(cls, env): # external use def vswhere_register_executable(vswhere_exe, priority=None, freeze=False): - debug('register vswhere_exe=%s, priority=%s, freeze=%s', repr(vswhere_exe), repr(priority), repr(freeze)) + debug( + 'register vswhere_exe=%s, priority=%s, freeze=%s', + repr(vswhere_exe), repr(priority), repr(freeze) + ) _VSWhereExecutable.register_vswhere_executable(vswhere_exe, priority=priority) if freeze: _VSWhereExecutable.freeze_vswhere_executable() @@ -2525,6 +2528,8 @@ def msvc_toolset_versions(msvc_version=None, full: bool=True, sxs: bool=False, v repr(msvc_version), repr(full), repr(sxs), repr(vswhere_exe) ) + _VSWhereExecutable.vswhere_freeze_executable(vswhere_exe) + rval = [] if not msvc_version: @@ -2549,6 +2554,8 @@ def msvc_toolset_versions(msvc_version=None, full: bool=True, sxs: bool=False, v def msvc_toolset_versions_spectre(msvc_version=None, vswhere_exe=None): debug('msvc_version=%s, vswhere_exe=%s', repr(msvc_version), repr(vswhere_exe)) + _VSWhereExecutable.vswhere_freeze_executable(vswhere_exe) + rval = [] if not msvc_version: @@ -2570,33 +2577,70 @@ def msvc_toolset_versions_spectre(msvc_version=None, vswhere_exe=None): rval = MSVC.ScriptArguments._msvc_toolset_versions_spectre_internal(msvc_version, vc_dir) return rval -def get_installed_vcs_toolsets_components(env=None): +_InstalledVersionToolset = namedtuple('_InstalledVersionToolset', [ + 'msvc_version_def', + 'toolset_version_def', +]) - vcs = get_installed_vcs(env) +_InstalledVCSToolsetsComponents = namedtuple('_InstalledVCSToolsetComponents', [ + 'sxs_map', + 'toolset_vcs', + 'msvc_toolset_component_defs', +]) +def get_installed_vcs_toolsets_components(vswhere_exe=None): + debug('vswhere_exe=%s', repr(vswhere_exe)) + + _VSWhereExecutable.vswhere_freeze_executable(vswhere_exe) + + sxs_map = {} + toolset_vcs = set() msvc_toolset_component_defs = [] + vcs = get_installed_vcs() for msvc_version in vcs: + + vc_dir = _find_vc_pdir(msvc_version, vswhere_exe) + if not vc_dir: + continue + msvc_version_def = MSVC.Util.msvc_version_components(msvc_version) + toolset_vcs.add(msvc_version_def.msvc_version) + if msvc_version_def.msvc_vernum > 14.0: - # VS2017 and later - toolset_all_list = msvc_toolset_versions(msvc_version=msvc_version, full=True, sxs=True) - for toolset_version in toolset_all_list: - debug('msvc_version=%s, toolset_version=%s', repr(msvc_version), repr(toolset_version)) - toolset_version_def = MSVC.Util.msvc_extended_version_components(toolset_version) - if not toolset_version_def: - continue - rval = (msvc_version_def, toolset_version_def) - msvc_toolset_component_defs.append(rval) + + toolsets_sxs, toolsets_full = MSVC.ScriptArguments._msvc_version_toolsets_internal( + msvc_version, vc_dir + ) + + debug('msvc_version=%s, toolset_sxs=%s', repr(msvc_version), repr(toolsets_sxs)) + sxs_map.update(toolsets_sxs) + else: - # VS2015 and earlier - toolset_version = msvc_version_def.msvc_verstr + + toolsets_full = [msvc_version_def.msvc_verstr] + + for toolset_version in toolsets_full: debug('msvc_version=%s, toolset_version=%s', repr(msvc_version), repr(toolset_version)) + toolset_version_def = MSVC.Util.msvc_extended_version_components(toolset_version) - rval = (msvc_version_def, toolset_version_def) + if not toolset_version_def: + continue + toolset_vcs.add(toolset_version_def.msvc_version) + + rval = _InstalledVersionToolset( + msvc_version_def=msvc_version_def, + toolset_version_def=toolset_version_def, + ) msvc_toolset_component_defs.append(rval) - return msvc_toolset_component_defs + installed_vcs_toolsets_components = _InstalledVCSToolsetsComponents( + sxs_map=sxs_map, + toolset_vcs=toolset_vcs, + msvc_toolset_component_defs=msvc_toolset_component_defs, + ) + + return installed_vcs_toolsets_components def msvc_query_version_toolset(version=None, prefer_newest: bool=True, vswhere_exe=None): """ @@ -2642,7 +2686,10 @@ def msvc_query_version_toolset(version=None, prefer_newest: bool=True, vswhere_e MSVCToolsetVersionNotFound: when the specified version is not found. MSVCArgumentError: when argument validation fails. """ - debug('version=%s, prefer_newest=%s', repr(version), repr(prefer_newest)) + debug( + 'version=%s, prefer_newest=%s, vswhere_exe=%s', + repr(version), repr(prefer_newest), repr(vswhere_exe) + ) _VSWhereExecutable.vswhere_freeze_executable(vswhere_exe) diff --git a/SCons/Tool/MSCommon/vcTests.py b/SCons/Tool/MSCommon/vcTests.py index 65ae904380..e1146e89be 100644 --- a/SCons/Tool/MSCommon/vcTests.py +++ b/SCons/Tool/MSCommon/vcTests.py @@ -236,12 +236,7 @@ class Data: INSTALLED_VCS = MSCommon.vc.get_installed_vcs() INSTALLED_VCS_COMPONENTS = MSCommon.vc.get_installed_vcs_components() - INSTALLED_VCS_TOOLSETS_COMPONENTS = MSCommon.vc.get_installed_vcs_toolsets_components() - INSTALLED_VCS_TOOLSETS = set() - for msvc_version_def, toolset_version_def in INSTALLED_VCS_TOOLSETS_COMPONENTS: - INSTALLED_VCS_TOOLSETS.add(msvc_version_def.msvc_version) - INSTALLED_VCS_TOOLSETS.add(toolset_version_def.msvc_version) @classmethod def query_version_list(cls, vcver): @@ -504,7 +499,7 @@ def test_valid_default_msvc(self) -> None: def test_valid_vcver(self) -> None: for symbol in MSCommon.vc._VCVER: - have_msvc = bool(symbol in Data.INSTALLED_VCS_TOOLSETS) + have_msvc = bool(symbol in Data.INSTALLED_VCS_TOOLSETS_COMPONENTS.toolset_vcs) version_def = MSCommon.msvc_version_components(symbol) for prefer_newest in (True, False): if not have_msvc: