diff --git a/CHANGES.txt b/CHANGES.txt index 1c2a843bb8..3725fa473b 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -16,6 +16,33 @@ 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 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. + From Mats Wichmann - C scanner's dictifyCPPDEFINES routine did not understand the possible combinations of CPPDEFINES - not aware of a "name=value" string either diff --git a/RELEASE.txt b/RELEASE.txt index 024f049961..bffe74014a 100644 --- a/RELEASE.txt +++ b/RELEASE.txt @@ -19,6 +19,10 @@ 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). DEPRECATED FUNCTIONALITY ------------------------ @@ -47,6 +51,14 @@ CHANGED/ENHANCED EXISTING FUNCTIONALITY 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. FIXES ----- @@ -59,6 +71,22 @@ FIXES 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. +- 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. IMPROVEMENTS ------------ diff --git a/SCons/Platform/Platform.xml b/SCons/Platform/Platform.xml index 08199f4c54..c449fa50da 100644 --- a/SCons/Platform/Platform.xml +++ b/SCons/Platform/Platform.xml @@ -173,8 +173,8 @@ else: Valid host arch values are x86 and arm for 32-bit hosts and - amd64 and x86_64 - for 64-bit hosts. + amd64, arm64, + and x86_64 for 64-bit hosts. Should be considered immutable. diff --git a/SCons/Platform/win32.py b/SCons/Platform/win32.py index c9fcd9502d..275a5d4121 100644 --- a/SCons/Platform/win32.py +++ b/SCons/Platform/win32.py @@ -298,6 +298,11 @@ def __init__(self, arch, synonyms=[]) -> None: ['AMD64', 'amd64', 'em64t', 'EM64T', 'x86_64'], ), + ArchDefinition( + 'arm64', + ['ARM64', 'aarch64', 'AARCH64', 'AArch64'], + ), + ArchDefinition( 'ia64', ['IA64'], @@ -315,9 +320,20 @@ def get_architecture(arch=None): """Returns the definition for the specified architecture string. If no string is specified, the system default is returned (as defined - by the PROCESSOR_ARCHITEW6432 or PROCESSOR_ARCHITECTURE environment - variables). + by the registry PROCESSOR_ARCHITECTURE value, PROCESSOR_ARCHITEW6432 + environment variable, PROCESSOR_ARCHITECTURE environment variable, or + the platform machine). """ + if arch is None: + if SCons.Util.can_read_reg: + try: + k=SCons.Util.RegOpenKeyEx(SCons.Util.hkey_mod.HKEY_LOCAL_MACHINE, + 'SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment') + val, tok = SCons.Util.RegQueryValueEx(k, 'PROCESSOR_ARCHITECTURE') + except SCons.Util.RegError: + val = '' + if val and val in SupportedArchitectureMap: + arch = val if arch is None: arch = os.environ.get('PROCESSOR_ARCHITEW6432') if not arch: diff --git a/SCons/Tool/MSCommon/MSVC/SetupEnvDefault.py b/SCons/Tool/MSCommon/MSVC/SetupEnvDefault.py index 34e60ab19b..76b9ed654b 100644 --- a/SCons/Tool/MSCommon/MSVC/SetupEnvDefault.py +++ b/SCons/Tool/MSCommon/MSVC/SetupEnvDefault.py @@ -188,19 +188,22 @@ def register_iserror(env, tool, msvc_exists_func): debug('msvc default:check tools:nchar=%d, tools=%s', tools_nchar, tools) # iteratively remove default tool sequences (longest to shortest) - re_nchar_min, re_tools_min = _Data.default_tools_re_list[-1] - if tools_nchar >= re_nchar_min and re_tools_min.search(tools): - # minimum characters satisfied and minimum pattern exists - for re_nchar, re_default_tool in _Data.default_tools_re_list: - if tools_nchar < re_nchar: - # not enough characters for pattern - continue - tools = re_default_tool.sub('', tools).strip(_Data.separator) - tools_nchar = len(tools) - debug('msvc default:check tools:nchar=%d, tools=%s', tools_nchar, tools) - if tools_nchar < re_nchar_min or not re_tools_min.search(tools): - # less than minimum characters or minimum pattern does not exist - break + if not _Data.default_tools_re_list: + debug('default_tools_re_list=%s', _Data.default_tools_re_list) + else: + re_nchar_min, re_tools_min = _Data.default_tools_re_list[-1] + if tools_nchar >= re_nchar_min and re_tools_min.search(tools): + # minimum characters satisfied and minimum pattern exists + for re_nchar, re_default_tool in _Data.default_tools_re_list: + if tools_nchar < re_nchar: + # not enough characters for pattern + continue + tools = re_default_tool.sub('', tools).strip(_Data.separator) + tools_nchar = len(tools) + debug('msvc default:check tools:nchar=%d, tools=%s', tools_nchar, tools) + if tools_nchar < re_nchar_min or not re_tools_min.search(tools): + # less than minimum characters or minimum pattern does not exist + break # construct non-default list(s) tools set tools_set = {msvc_tool for msvc_tool in tools.split(_Data.separator) if msvc_tool} diff --git a/SCons/Tool/MSCommon/common.py b/SCons/Tool/MSCommon/common.py index 0cadc10990..de460dc242 100644 --- a/SCons/Tool/MSCommon/common.py +++ b/SCons/Tool/MSCommon/common.py @@ -46,30 +46,31 @@ class MSVCCacheInvalidWarning(SCons.Warnings.WarningOnByDefault): if LOGFILE: import logging - modulelist = ( - # root module and parent/root module - 'MSCommon', 'Tool', - # python library and below: correct iff scons does not have a lib folder - 'lib', - # scons modules - 'SCons', 'test', 'scons' - ) + class _Debug_Filter(logging.Filter): + # custom filter for module relative filename - def get_relative_filename(filename, module_list): - if not filename: + modulelist = ( + # root module and parent/root module + 'MSCommon', 'Tool', + # python library and below: correct iff scons does not have a lib folder + 'lib', + # scons modules + 'SCons', 'test', 'scons' + ) + + def get_relative_filename(self, filename, module_list): + if not filename: + return filename + for module in module_list: + try: + ind = filename.rindex(module) + return filename[ind:] + except ValueError: + pass return filename - for module in module_list: - try: - ind = filename.rindex(module) - return filename[ind:] - except ValueError: - pass - return filename - class _Debug_Filter(logging.Filter): - # custom filter for module relative filename def filter(self, record) -> bool: - relfilename = get_relative_filename(record.pathname, modulelist) + relfilename = self.get_relative_filename(record.pathname, self.modulelist) relfilename = relfilename.replace('\\', '/') record.relfilename = relfilename return True @@ -208,6 +209,17 @@ def has_reg(value) -> bool: # Functions for fetching environment variable settings from batch files. +def _force_vscmd_skip_sendtelemetry(env): + + if 'VSCMD_SKIP_SENDTELEMETRY' in env['ENV']: + return False + + env['ENV']['VSCMD_SKIP_SENDTELEMETRY'] = '1' + debug("force env['ENV']['VSCMD_SKIP_SENDTELEMETRY']=%s", env['ENV']['VSCMD_SKIP_SENDTELEMETRY']) + + return True + + def normalize_env(env, keys, force: bool=False): """Given a dictionary representing a shell environment, add the variables from os.environ needed for the processing of .bat files; the keys are @@ -256,7 +268,7 @@ def normalize_env(env, keys, force: bool=False): return normenv -def get_output(vcbat, args=None, env=None): +def get_output(vcbat, args=None, env=None, skip_sendtelemetry=False): """Parse the output of given bat file, with given args.""" if env is None: @@ -295,20 +307,23 @@ def get_output(vcbat, args=None, env=None): ] env['ENV'] = normalize_env(env['ENV'], vs_vc_vars, force=False) + if skip_sendtelemetry: + _force_vscmd_skip_sendtelemetry(env) + if args: debug("Calling '%s %s'", vcbat, args) - popen = SCons.Action._subproc(env, - '"%s" %s & set' % (vcbat, args), - stdin='devnull', - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) + cmd_str = '"%s" %s & set' % (vcbat, args) else: debug("Calling '%s'", vcbat) - popen = SCons.Action._subproc(env, - '"%s" & set' % vcbat, - stdin='devnull', - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) + cmd_str = '"%s" & set' % vcbat + + popen = SCons.Action._subproc( + env, + cmd_str, + stdin='devnull', + stdout=subprocess.PIPE, + stderr=subprocess.PIPE + ) # Use the .stdout and .stderr attributes directly because the # .communicate() method uses the threading module on Windows diff --git a/SCons/Tool/MSCommon/vc.py b/SCons/Tool/MSCommon/vc.py index 1dc6b8d111..d7da4ea922 100644 --- a/SCons/Tool/MSCommon/vc.py +++ b/SCons/Tool/MSCommon/vc.py @@ -43,6 +43,7 @@ import subprocess import os import platform +import sysconfig from pathlib import Path from string import digits as string_digits from subprocess import PIPE @@ -69,8 +70,7 @@ MSVCToolsetVersionNotFound, ) -class UnsupportedVersion(VisualCException): - pass +# external exceptions class MSVCUnsupportedHostArch(VisualCException): pass @@ -78,21 +78,35 @@ class MSVCUnsupportedHostArch(VisualCException): class MSVCUnsupportedTargetArch(VisualCException): pass -class MissingConfiguration(VisualCException): +class MSVCScriptNotFound(MSVCUserError): pass -class NoVersionFound(VisualCException): +class MSVCUseSettingsError(MSVCUserError): pass -class BatchFileExecutionError(VisualCException): +# internal exceptions + +class UnsupportedVersion(VisualCException): pass -class MSVCScriptNotFound(MSVCUserError): +class MissingConfiguration(VisualCException): pass -class MSVCUseSettingsError(MSVCUserError): +class BatchFileExecutionError(VisualCException): pass +# undefined object for dict.get() in case key exists and value is None +UNDEFINED = object() + +# powershell error sending telemetry for arm32 process on arm64 host (VS2019+): +# True: force VSCMD_SKIP_SENDTELEMETRY=1 (if necessary) +# False: do nothing +_ARM32_ON_ARM64_SKIP_SENDTELEMETRY = True + +# MSVC 9.0 preferred query order: +# True: VCForPython, VisualStudio +# FAlse: VisualStudio, VCForPython +_VC90_Prefer_VCForPython = True # Dict to 'canonalize' the arch _ARCH_TO_CANONICAL = { @@ -282,7 +296,9 @@ def _make_target_host_map(all_hosts, host_all_targets_map): # The cl path fragment under the toolset version folder is the second value of # the stored tuple. -_GE2017_HOST_TARGET_BATCHFILE_CLPATHCOMPS = { +# 14.3 (VS2022) and later + +_GE2022_HOST_TARGET_BATCHFILE_CLPATHCOMPS = { ('amd64', 'amd64') : ('vcvars64.bat', ('bin', 'Hostx64', 'x64')), ('amd64', 'x86') : ('vcvarsamd64_x86.bat', ('bin', 'Hostx64', 'x86')), @@ -294,11 +310,66 @@ def _make_target_host_map(all_hosts, host_all_targets_map): ('x86', 'arm') : ('vcvarsx86_arm.bat', ('bin', 'Hostx86', 'arm')), ('x86', 'arm64') : ('vcvarsx86_arm64.bat', ('bin', 'Hostx86', 'arm64')), + ('arm64', 'amd64') : ('vcvarsarm64_amd64.bat', ('bin', 'Hostarm64', 'arm64_amd64')), + ('arm64', 'x86') : ('vcvarsarm64_x86.bat', ('bin', 'Hostarm64', 'arm64_x86')), + ('arm64', 'arm') : ('vcvarsarm64_arm.bat', ('bin', 'Hostarm64', 'arm64_arm')), + ('arm64', 'arm64') : ('vcvarsarm64.bat', ('bin', 'Hostarm64', 'arm64')), + } -_GE2017_HOST_TARGET_CFG = _host_target_config_factory( +_GE2022_HOST_TARGET_CFG = _host_target_config_factory( - label = 'GE2017', + label = 'GE2022', + + host_all_hosts = OrderedDict([ + ('amd64', ['amd64', 'x86']), + ('x86', ['x86']), + ('arm64', ['arm64', 'amd64', 'x86']), + ('arm', ['x86']), + ]), + + host_all_targets = { + 'amd64': ['amd64', 'x86', 'arm64', 'arm'], + 'x86': ['x86', 'amd64', 'arm', 'arm64'], + 'arm64': ['arm64', 'amd64', 'arm', 'x86'], + 'arm': [], + }, + + host_def_targets = { + 'amd64': ['amd64', 'x86'], + 'x86': ['x86'], + 'arm64': ['arm64', 'amd64', 'arm', 'x86'], + 'arm': ['arm'], + }, + +) + +# debug("_GE2022_HOST_TARGET_CFG: %s", _GE2022_HOST_TARGET_CFG) + +# 14.2 (VS2019) to 14.1 (VS2017) + +_LE2019_HOST_TARGET_BATCHFILE_CLPATHCOMPS = { + + ('amd64', 'amd64') : ('vcvars64.bat', ('bin', 'Hostx64', 'x64')), + ('amd64', 'x86') : ('vcvarsamd64_x86.bat', ('bin', 'Hostx64', 'x86')), + ('amd64', 'arm') : ('vcvarsamd64_arm.bat', ('bin', 'Hostx64', 'arm')), + ('amd64', 'arm64') : ('vcvarsamd64_arm64.bat', ('bin', 'Hostx64', 'arm64')), + + ('x86', 'amd64') : ('vcvarsx86_amd64.bat', ('bin', 'Hostx86', 'x64')), + ('x86', 'x86') : ('vcvars32.bat', ('bin', 'Hostx86', 'x86')), + ('x86', 'arm') : ('vcvarsx86_arm.bat', ('bin', 'Hostx86', 'arm')), + ('x86', 'arm64') : ('vcvarsx86_arm64.bat', ('bin', 'Hostx86', 'arm64')), + + ('arm64', 'amd64') : ('vcvars64.bat', ('bin', 'Hostx64', 'x64')), + ('arm64', 'x86') : ('vcvarsamd64_x86.bat', ('bin', 'Hostx64', 'x86')), + ('arm64', 'arm') : ('vcvarsamd64_arm.bat', ('bin', 'Hostx64', 'arm')), + ('arm64', 'arm64') : ('vcvarsamd64_arm64.bat', ('bin', 'Hostx64', 'arm64')), + +} + +_LE2019_HOST_TARGET_CFG = _host_target_config_factory( + + label = 'LE2019', host_all_hosts = OrderedDict([ ('amd64', ['amd64', 'x86']), @@ -310,20 +381,20 @@ def _make_target_host_map(all_hosts, host_all_targets_map): host_all_targets = { 'amd64': ['amd64', 'x86', 'arm64', 'arm'], 'x86': ['x86', 'amd64', 'arm', 'arm64'], - 'arm64': [], + 'arm64': ['arm64', 'amd64', 'arm', 'x86'], 'arm': [], }, host_def_targets = { 'amd64': ['amd64', 'x86'], 'x86': ['x86'], - 'arm64': ['arm64', 'arm'], + 'arm64': ['arm64', 'amd64', 'arm', 'x86'], 'arm': ['arm'], }, ) -# debug("_GE2017_HOST_TARGET_CFG: %s", _GE2017_HOST_TARGET_CFG) +# debug("_LE2019_HOST_TARGET_CFG: %s", _LE2019_HOST_TARGET_CFG) # 14.0 (VS2015) to 8.0 (VS2005) @@ -345,6 +416,10 @@ def _make_target_host_map(all_hosts, host_all_targets_map): ('x86', 'arm') : ('x86_arm', ('bin', 'x86_arm')), ('x86', 'ia64') : ('x86_ia64', ('bin', 'x86_ia64')), + ('arm64', 'amd64') : ('amd64', ('bin', 'amd64')), + ('arm64', 'x86') : ('amd64_x86', ('bin', 'amd64_x86')), + ('arm64', 'arm') : ('amd64_arm', ('bin', 'amd64_arm')), + ('arm', 'arm') : ('arm', ('bin', 'arm')), ('ia64', 'ia64') : ('ia64', ('bin', 'ia64')), @@ -357,6 +432,7 @@ def _make_target_host_map(all_hosts, host_all_targets_map): host_all_hosts = OrderedDict([ ('amd64', ['amd64', 'x86']), ('x86', ['x86']), + ('arm64', ['amd64', 'x86']), ('arm', ['arm']), ('ia64', ['ia64']), ]), @@ -364,6 +440,7 @@ def _make_target_host_map(all_hosts, host_all_targets_map): host_all_targets = { 'amd64': ['amd64', 'x86', 'arm'], 'x86': ['x86', 'amd64', 'arm', 'ia64'], + 'arm64': ['amd64', 'x86', 'arm'], 'arm': ['arm'], 'ia64': ['ia64'], }, @@ -371,6 +448,7 @@ def _make_target_host_map(all_hosts, host_all_targets_map): host_def_targets = { 'amd64': ['amd64', 'x86'], 'x86': ['x86'], + 'arm64': ['amd64', 'arm', 'x86'], 'arm': ['arm'], 'ia64': ['ia64'], }, @@ -391,16 +469,19 @@ def _make_target_host_map(all_hosts, host_all_targets_map): host_all_hosts = OrderedDict([ ('amd64', ['x86']), ('x86', ['x86']), + ('arm64', ['x86']), ]), host_all_targets = { 'amd64': ['x86'], 'x86': ['x86'], + 'arm64': ['x86'], }, host_def_targets = { 'amd64': ['x86'], 'x86': ['x86'], + 'arm64': ['x86'], }, ) @@ -444,28 +525,54 @@ def get_host_platform(host_platform): return host +_native_host_architecture = None + +def get_native_host_architecture(): + """Return the native host architecture.""" + global _native_host_architecture + + if _native_host_architecture is None: + + try: + arch = common.read_reg( + r'SYSTEM\CurrentControlSet\Control\Session Manager\Environment\PROCESSOR_ARCHITECTURE' + ) + except OSError: + arch = None + + if not arch: + arch = platform.machine() + + _native_host_architecture = arch + + return _native_host_architecture + _native_host_platform = None def get_native_host_platform(): global _native_host_platform if _native_host_platform is None: - - _native_host_platform = get_host_platform(platform.machine()) + arch = get_native_host_architecture() + _native_host_platform = get_host_platform(arch) return _native_host_platform def get_host_target(env, msvc_version, all_host_targets: bool=False): vernum = float(get_msvc_version_numeric(msvc_version)) - - if vernum > 14: - # 14.1 (VS2017) and later - host_target_cfg = _GE2017_HOST_TARGET_CFG - elif 14 >= vernum >= 8: + vernum_int = int(vernum * 10) + + if vernum_int >= 143: + # 14.3 (VS2022) and later + host_target_cfg = _GE2022_HOST_TARGET_CFG + elif 143 > vernum_int >= 141: + # 14.2 (VS2019) to 14.1 (VS2017) + host_target_cfg = _LE2019_HOST_TARGET_CFG + elif 141 > vernum_int >= 80: # 14.0 (VS2015) to 8.0 (VS2005) host_target_cfg = _LE2015_HOST_TARGET_CFG - else: + else: # 80 > vernum_int # 7.1 (VS2003) and earlier host_target_cfg = _LE2003_HOST_TARGET_CFG @@ -520,6 +627,53 @@ def get_host_target(env, msvc_version, all_host_targets: bool=False): return host_platform, target_platform, host_target_list +_arm32_process_arm64_host = None + +def is_arm32_process_arm64_host(): + global _arm32_process_arm64_host + + if _arm32_process_arm64_host is None: + + host = get_native_host_architecture() + host = _ARCH_TO_CANONICAL.get(host.lower(),'') + host_isarm64 = host == 'arm64' + + process = sysconfig.get_platform() + process_isarm32 = process == 'win-arm32' + + _arm32_process_arm64_host = host_isarm64 and process_isarm32 + + return _arm32_process_arm64_host + +_check_skip_sendtelemetry = None + +def _skip_sendtelemetry(env): + global _check_skip_sendtelemetry + + if _check_skip_sendtelemetry is None: + + if _ARM32_ON_ARM64_SKIP_SENDTELEMETRY and is_arm32_process_arm64_host(): + _check_skip_sendtelemetry = True + else: + _check_skip_sendtelemetry = False + + if not _check_skip_sendtelemetry: + return False + + msvc_version = env.get('MSVC_VERSION') if env else None + if not msvc_version: + msvc_version = msvc_default_version(env) + + if not msvc_version: + return False + + vernum = float(get_msvc_version_numeric(msvc_version)) + if vernum < 14.2: # VS2019 + return False + + # arm32 process, arm64 host, VS2019+ + return True + # If you update this, update SupportedVSList in Tool/MSCommon/vs.py, and the # MSVC_VERSION documentation in Tool/msvc.xml. _VCVER = [ @@ -589,6 +743,9 @@ def get_host_target(env, msvc_version, all_host_targets: bool=False): '9.0': [ (SCons.Util.HKEY_CURRENT_USER, r'Microsoft\DevDiv\VCForPython\9.0\installdir',), (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',), ], '9.0Exp': [ (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\9.0\Setup\VC\ProductDir'), @@ -763,7 +920,7 @@ def find_vc_pdir(env, msvc_version): raise MissingConfiguration("registry dir {} not found on the filesystem".format(comps)) return None -def find_batch_file(env, msvc_version, host_arch, target_arch): +def find_batch_file(msvc_version, host_arch, target_arch, pdir): """ Find the location of the batch script which should set up the compiler for any TARGET_ARCH whose compilers were installed by Visual Studio/VCExpress @@ -772,52 +929,73 @@ def find_batch_file(env, msvc_version, host_arch, target_arch): scripts named with a host_target pair that calls vcvarsall.bat properly, so use that and return an empty argument. """ - pdir = find_vc_pdir(env, msvc_version) - if pdir is None: - raise NoVersionFound("No version of Visual Studio found") - debug('looking in %s', pdir) # filter out e.g. "Exp" from the version name - msvc_ver_numeric = get_msvc_version_numeric(msvc_version) - vernum = float(msvc_ver_numeric) + vernum = float(get_msvc_version_numeric(msvc_version)) + vernum_int = int(vernum * 10) + + sdk_pdir = pdir arg = '' vcdir = None + clexe = None - if vernum > 14: - # 14.1 (VS2017) and later + if vernum_int >= 143: + # 14.3 (VS2022) and later + batfiledir = os.path.join(pdir, "Auxiliary", "Build") + batfile, _ = _GE2022_HOST_TARGET_BATCHFILE_CLPATHCOMPS[(host_arch, target_arch)] + batfilename = os.path.join(batfiledir, batfile) + vcdir = pdir + elif 143 > vernum_int >= 141: + # 14.2 (VS2019) to 14.1 (VS2017) batfiledir = os.path.join(pdir, "Auxiliary", "Build") - batfile, _ = _GE2017_HOST_TARGET_BATCHFILE_CLPATHCOMPS[(host_arch, target_arch)] + batfile, _ = _LE2019_HOST_TARGET_BATCHFILE_CLPATHCOMPS[(host_arch, target_arch)] batfilename = os.path.join(batfiledir, batfile) vcdir = pdir - elif 14 >= vernum >= 8: + elif 141 > vernum_int >= 80: # 14.0 (VS2015) to 8.0 (VS2005) - arg, _ = _LE2015_HOST_TARGET_BATCHARG_CLPATHCOMPS[(host_arch, target_arch)] + arg, cl_path_comps = _LE2015_HOST_TARGET_BATCHARG_CLPATHCOMPS[(host_arch, target_arch)] batfilename = os.path.join(pdir, "vcvarsall.bat") if msvc_version == '9.0' and not os.path.exists(batfilename): # Visual C++ for Python batch file is in installdir (root) not productdir (vc) batfilename = os.path.normpath(os.path.join(pdir, os.pardir, "vcvarsall.bat")) - else: + # Visual C++ for Python sdk batch files do not point to the VCForPython installation + sdk_pdir = None + clexe = os.path.join(pdir, *cl_path_comps, _CL_EXE_NAME) + else: # 80 > vernum_int # 7.1 (VS2003) and earlier pdir = os.path.join(pdir, "Bin") batfilename = os.path.join(pdir, "vcvars32.bat") + clexe = os.path.join(pdir, _CL_EXE_NAME) if not os.path.exists(batfilename): - debug("Not found: %s", batfilename) + debug("batch file not found: %s", batfilename) batfilename = None + if clexe and not os.path.exists(clexe): + debug("cl.exe not found: %s", clexe) + batfilename = None + + return batfilename, arg, vcdir, sdk_pdir + +def find_batch_file_sdk(host_arch, target_arch, sdk_pdir): + """ + Find the location of the sdk batch script which should set up the compiler + for any TARGET_ARCH whose compilers were installed by Visual Studio/VCExpress + """ + installed_sdks = get_installed_sdks() for _sdk in installed_sdks: sdk_bat_file = _sdk.get_sdk_vc_script(host_arch, target_arch) if not sdk_bat_file: - debug("batch file not found:%s", _sdk) + debug("sdk batch file not found:%s", _sdk) else: - sdk_bat_file_path = os.path.join(pdir, sdk_bat_file) + sdk_bat_file_path = os.path.join(sdk_pdir, sdk_bat_file) if os.path.exists(sdk_bat_file_path): debug('sdk_bat_file_path:%s', sdk_bat_file_path) - return batfilename, arg, vcdir, sdk_bat_file_path + return sdk_bat_file_path - return batfilename, arg, vcdir, None + return None __INSTALLED_VCS_RUN = None _VC_TOOLS_VERSION_FILE_PATH = ['Auxiliary', 'Build', 'Microsoft.VCToolsVersion.default.txt'] @@ -852,9 +1030,10 @@ def _check_cl_exists_in_vc_dir(env, vc_dir, msvc_version) -> bool: host_platform, target_platform, host_target_list = platforms vernum = float(get_msvc_version_numeric(msvc_version)) + vernum_int = int(vernum * 10) # make sure the cl.exe exists meaning the tool is installed - if vernum > 14: + if vernum_int >= 141: # 14.1 (VS2017) and later # 2017 and newer allowed multiple versions of the VC toolset to be # installed at the same time. This changes the layout. @@ -871,11 +1050,18 @@ def _check_cl_exists_in_vc_dir(env, vc_dir, msvc_version) -> bool: debug('failed to find MSVC version in %s', default_toolset_file) return False + if vernum_int >= 143: + # 14.3 (VS2022) and later + host_target_batchfile_clpathcomps = _GE2022_HOST_TARGET_BATCHFILE_CLPATHCOMPS + else: + # 14.2 (VS2019) to 14.1 (VS2017) + host_target_batchfile_clpathcomps = _LE2019_HOST_TARGET_BATCHFILE_CLPATHCOMPS + 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) - batchfile_clpathcomps = _GE2017_HOST_TARGET_BATCHFILE_CLPATHCOMPS.get((host_platform, target_platform), None) + batchfile_clpathcomps = host_target_batchfile_clpathcomps.get((host_platform, target_platform), None) if batchfile_clpathcomps is None: debug('unsupported host/target platform combo: (%s,%s)', host_platform, target_platform) continue @@ -888,7 +1074,7 @@ def _check_cl_exists_in_vc_dir(env, vc_dir, msvc_version) -> bool: debug('found %s!', _CL_EXE_NAME) return True - elif 14 >= vernum >= 8: + elif 141 > vernum_int >= 80: # 14.0 (VS2015) to 8.0 (VS2005) for host_platform, target_platform in host_target_list: @@ -908,7 +1094,7 @@ def _check_cl_exists_in_vc_dir(env, vc_dir, msvc_version) -> bool: debug('found %s', _CL_EXE_NAME) return True - elif 8 > vernum >= 6: + elif 80 > vernum_int >= 60: # 7.1 (VS2003) to 6.0 (VS6) # quick check for vc_dir/bin and vc_dir/ before walk @@ -928,7 +1114,7 @@ def _check_cl_exists_in_vc_dir(env, vc_dir, msvc_version) -> bool: return False else: - # version not support return false + # version not supported return false debug('unsupported MSVC version: %s', str(vernum)) return False @@ -939,6 +1125,13 @@ def get_installed_vcs(env=None): if __INSTALLED_VCS_RUN is not None: return __INSTALLED_VCS_RUN + save_target_arch = env.get('TARGET_ARCH', UNDEFINED) if env else None + force_target = env and save_target_arch and save_target_arch != UNDEFINED + + if force_target: + del env['TARGET_ARCH'] + debug("delete env['TARGET_ARCH']") + installed_versions = [] for ver in _VCVER: @@ -960,7 +1153,12 @@ def get_installed_vcs(env=None): except VisualCException as e: debug('did not find VC %s: caught exception %s', ver, str(e)) + if force_target: + env['TARGET_ARCH'] = save_target_arch + debug("restore env['TARGET_ARCH']=%s", save_target_arch) + __INSTALLED_VCS_RUN = installed_versions + debug("__INSTALLED_VCS_RUN=%s", __INSTALLED_VCS_RUN) return __INSTALLED_VCS_RUN def reset_installed_vcs() -> None: @@ -982,6 +1180,19 @@ def get_installed_vcs_components(env=None): msvc_version_component_defs = [MSVC.Util.msvc_version_components(vcver) for vcver in vcs] return msvc_version_component_defs +def _check_cl_exists_in_script_env(data): + """Find cl.exe in the script environment path.""" + cl_path = None + if data and 'PATH' in data: + for p in data['PATH']: + cl_exe = os.path.join(p, _CL_EXE_NAME) + if os.path.exists(cl_exe): + cl_path = cl_exe + break + have_cl = True if cl_path else False + debug('have_cl: %s, cl_path: %s', have_cl, cl_path) + return have_cl, cl_path + # Running these batch files isn't cheap: most of the time spent in # msvs.generate() is due to vcvars*.bat. In a build that uses "tools='msvs'" # in multiple environments, for example: @@ -1027,7 +1238,8 @@ def script_env(env, script, args=None): cache_data = None if cache_data is None: - stdout = common.get_output(script, args) + skip_sendtelemetry = _skip_sendtelemetry(env) + stdout = common.get_output(script, args, skip_sendtelemetry=skip_sendtelemetry) cache_data = common.parse_output(stdout) # debug(stdout) @@ -1044,12 +1256,7 @@ def script_env(env, script, args=None): if script_errlog: script_errmsg = '\n'.join(script_errlog) - have_cl = False - if cache_data and 'PATH' in cache_data: - for p in cache_data['PATH']: - if os.path.exists(os.path.join(p, _CL_EXE_NAME)): - have_cl = True - break + have_cl, _ = _check_cl_exists_in_script_env(cache_data) debug( 'script=%s args=%s have_cl=%s, errors=%s', @@ -1130,6 +1337,18 @@ def msvc_find_valid_batch_script(env, version): get it right. """ + # Find the product directory + pdir = None + try: + pdir = find_vc_pdir(env, version) + 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: platforms = get_host_target(env, version) debug("host_platform %s, target_platform %s host_target_list %s", *platforms) @@ -1137,48 +1356,81 @@ def msvc_find_valid_batch_script(env, version): d = None version_installed = False - for host_arch, target_arch, in host_target_list: - # Set to current arch. - env['TARGET_ARCH'] = target_arch - arg = '' - # Try to locate a batch file for this host/target platform combo - try: - (vc_script, arg, vc_dir, sdk_script) = find_batch_file(env, version, host_arch, target_arch) - debug('vc_script:%s vc_script_arg:%s sdk_script:%s', vc_script, arg, sdk_script) - version_installed = True - except VisualCException as e: - msg = str(e) - debug('Caught exception while looking for batch file (%s)', msg) - version_installed = False - continue + if pdir: + + # Query all candidate sdk (host, target, sdk_pdir) after vc_script pass if necessary + sdk_queries = [] + + for host_arch, target_arch, in host_target_list: + # Set to current arch. + env['TARGET_ARCH'] = target_arch + arg = '' + + # Try to locate a batch file for this host/target platform combo + try: + (vc_script, arg, vc_dir, sdk_pdir) = find_batch_file(version, host_arch, target_arch, pdir) + debug('vc_script:%s vc_script_arg:%s', vc_script, arg) + version_installed = True + except VisualCException as e: + msg = str(e) + debug('Caught exception while looking for batch file (%s)', msg) + version_installed = False + continue + + # Save (host, target, sdk_pdir) platform combo for sdk queries + if sdk_pdir: + sdk_query = (host_arch, target_arch, sdk_pdir) + if sdk_query not in sdk_queries: + debug('save sdk_query host=%s, target=%s, sdk_pdir=%s', host_arch, target_arch, sdk_pdir) + sdk_queries.append(sdk_query) + + if not vc_script: + continue - # Try to use the located batch file for this host/target platform combo - debug('use_script 2 %s, args:%s', repr(vc_script), arg) - found = None - if vc_script: + # 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) try: d = script_env(env, vc_script, args=arg) - found = vc_script except BatchFileExecutionError as e: - debug('use_script 3: failed running VC script %s: %s: Error:%s', repr(vc_script), arg, e) - vc_script=None + debug('failed vc_script:%s, vc_script_args:%s, error:%s', repr(vc_script), arg, e) + vc_script = None continue - if not vc_script and sdk_script: - debug('use_script 4: trying sdk script: %s', sdk_script) - try: - d = script_env(env, sdk_script) - found = sdk_script - except BatchFileExecutionError as e: - debug('use_script 5: failed running SDK script %s: Error:%s', repr(sdk_script), e) + + have_cl, _ = _check_cl_exists_in_script_env(d) + if not have_cl: + debug('skip cl.exe not found vc_script:%s, vc_script_args:%s', repr(vc_script), arg) continue - elif not vc_script and not sdk_script: - debug('use_script 6: Neither VC script nor SDK script found') - continue - debug("Found a working script/target: %s/%s", repr(found), arg) - break # We've found a working target_platform, so stop looking + debug("Found a working script/target: %s/%s", repr(vc_script), arg) + break # We've found a working target_platform, so stop looking + + if not d: + for host_arch, target_arch, sdk_pdir in sdk_queries: + # Set to current arch. + env['TARGET_ARCH'] = target_arch + + sdk_script = find_batch_file_sdk(host_arch, target_arch, sdk_pdir) + if not sdk_script: + continue + + # Try to use the sdk batch file for this (host, target, sdk_pdir) combo + debug('trying sdk_script:%s', repr(sdk_script)) + try: + d = script_env(env, sdk_script) + version_installed = True + except BatchFileExecutionError as e: + debug('failed sdk_script:%s, error=%s', repr(sdk_script), e) + continue + + have_cl, _ = _check_cl_exists_in_script_env(d) + if not have_cl: + debug('skip cl.exe not found sdk_script:%s', repr(sdk_script)) + continue + + debug("Found a working script/target: %s", repr(sdk_script)) + break # We've found a working script, so stop looking # If we cannot find a viable installed compiler, reset the TARGET_ARCH # To it's initial value @@ -1206,8 +1458,6 @@ def msvc_find_valid_batch_script(env, version): return d -_UNDEFINED = object() - def get_use_script_use_settings(env): # use_script use_settings return values action @@ -1217,9 +1467,9 @@ def get_use_script_use_settings(env): # None (documentation) or evaluates False (code): bypass detection # need to distinguish between undefined and None - use_script = env.get('MSVC_USE_SCRIPT', _UNDEFINED) + use_script = env.get('MSVC_USE_SCRIPT', UNDEFINED) - if use_script != _UNDEFINED: + if use_script != UNDEFINED: # use_script defined, use_settings ignored (not type checked) return use_script, None @@ -1293,8 +1543,7 @@ def msvc_exists(env=None, version=None): rval = len(vcs) > 0 else: rval = version in vcs - if not rval: - debug('version=%s, return=%s', repr(version), rval) + debug('version=%s, return=%s', repr(version), rval) return rval def msvc_setup_env_user(env=None): diff --git a/SCons/Tool/MSCommon/vcTests.py b/SCons/Tool/MSCommon/vcTests.py index c4cf2af699..9c2bd0ac8c 100644 --- a/SCons/Tool/MSCommon/vcTests.py +++ b/SCons/Tool/MSCommon/vcTests.py @@ -132,12 +132,48 @@ def runTest(self) -> None: print("Failed trying to write :%s :%s" % (tools_version_file, e)) - # Now walk all the valid combinations of host/target for 14.1 (VS2017) and later - vc_ge2017_list = SCons.Tool.MSCommon.vc._GE2017_HOST_TARGET_CFG.all_pairs + # Test 14.3 (VS2022) and later + vc_ge2022_list = SCons.Tool.MSCommon.vc._GE2022_HOST_TARGET_CFG.all_pairs - for host, target in vc_ge2017_list: - batfile, clpathcomps = SCons.Tool.MSCommon.vc._GE2017_HOST_TARGET_BATCHFILE_CLPATHCOMPS[(host,target)] - # print("GT 14 Got: (%s, %s) -> (%s, %s)"%(host,target,batfile,clpathcomps)) + 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('.', 'Tools', 'MSVC', MS_TOOLS_VERSION, *clpathcomps) + MSVcTestCase._createDummyCl(path, 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) @@ -173,7 +209,7 @@ def runTest(self) -> None: for host, target in vc_le2015_list: batarg, clpathcomps = SCons.Tool.MSCommon.vc._LE2015_HOST_TARGET_BATCHARG_CLPATHCOMPS[(host, target)] - # print("LE 14 Got: (%s, %s) -> (%s, %s)"%(host,target,batarg,clpathcomps)) + # print("LE 14.0 Got: (%s, %s) -> (%s, %s)"%(host,target,batarg,clpathcomps)) env={'TARGET_ARCH':target, 'HOST_ARCH':host} path = os.path.join('.', *clpathcomps) MSVcTestCase._createDummyCl(path, add_bin=False) diff --git a/SCons/Tool/MSCommon/vs.py b/SCons/Tool/MSCommon/vs.py index 00ccaef941..af0fd26e5a 100644 --- a/SCons/Tool/MSCommon/vs.py +++ b/SCons/Tool/MSCommon/vs.py @@ -207,7 +207,7 @@ def reset(self) -> None: executable_path=r'Common7\IDE\devenv.com', # should be a fallback, prefer use vswhere installationPath batch_file_path=r'Common7\Tools\VsDevCmd.bat', - supported_arch=['x86', 'amd64', "arm"], + supported_arch=['x86', 'amd64', "arm", 'arm64'], ), # Visual Studio 2019 @@ -219,7 +219,7 @@ def reset(self) -> None: executable_path=r'Common7\IDE\devenv.com', # should be a fallback, prefer use vswhere installationPath batch_file_path=r'Common7\Tools\VsDevCmd.bat', - supported_arch=['x86', 'amd64', "arm"], + supported_arch=['x86', 'amd64', "arm", 'arm64'], ), # Visual Studio 2017 @@ -231,7 +231,7 @@ def reset(self) -> None: executable_path=r'Common7\IDE\devenv.com', # should be a fallback, prefer use vswhere installationPath batch_file_path=r'Common7\Tools\VsDevCmd.bat', - supported_arch=['x86', 'amd64', "arm"], + supported_arch=['x86', 'amd64', "arm", 'arm64'], ), # Visual C++ 2017 Express Edition (for Desktop) @@ -243,7 +243,7 @@ def reset(self) -> None: executable_path=r'Common7\IDE\WDExpress.exe', # should be a fallback, prefer use vswhere installationPath batch_file_path=r'Common7\Tools\VsDevCmd.bat', - supported_arch=['x86', 'amd64', "arm"], + supported_arch=['x86', 'amd64', "arm", 'arm64'], ), # Visual Studio 2015 diff --git a/doc/generated/variables.gen b/doc/generated/variables.gen index 42935a3170..86395a70e1 100644 --- a/doc/generated/variables.gen +++ b/doc/generated/variables.gen @@ -3179,8 +3179,8 @@ is -dNOPAUSE -dBATCH -sDEVICE=pdfwrite Valid host arch values are x86 and arm for 32-bit hosts and - amd64 and x86_64 - for 64-bit hosts. + amd64, arm64, + and x86_64 for 64-bit hosts. Should be considered immutable. diff --git a/testing/framework/TestSConsMSVS.py b/testing/framework/TestSConsMSVS.py index 92e436d93a..8c34372b85 100644 --- a/testing/framework/TestSConsMSVS.py +++ b/testing/framework/TestSConsMSVS.py @@ -40,6 +40,10 @@ import platform import traceback from xml.etree import ElementTree +try: + import winreg +except ImportError: + winreg = None import SCons.Errors from TestSCons import * @@ -763,23 +767,42 @@ def run(self, *args, **kw): return result def get_vs_host_arch(self): - """ Returns an MSVS, SDK , and/or MSVS acceptable platform arch. """ + """ Returns an MSVS, SDK, and/or MSVS acceptable platform arch. """ - # Dict to 'canonalize' the arch + # Dict to 'canonicalize' the arch (synchronize with MSCommon\vc.py) _ARCH_TO_CANONICAL = { - "x86": "x86", - "amd64": "amd64", - "i386": "x86", - "emt64": "amd64", - "x86_64": "amd64", - "itanium": "ia64", - "ia64": "ia64", + "amd64" : "amd64", + "emt64" : "amd64", + "i386" : "x86", + "i486" : "x86", + "i586" : "x86", + "i686" : "x86", + "ia64" : "ia64", # deprecated + "itanium" : "ia64", # deprecated + "x86" : "x86", + "x86_64" : "amd64", + "arm" : "arm", + "arm64" : "arm64", + "aarch64" : "arm64", } - host_platform = platform.machine() + host_platform = None + + if winreg: + try: + winkey = winreg.OpenKeyEx( + winreg.HKEY_LOCAL_MACHINE, + r'SYSTEM\CurrentControlSet\Control\Session Manager\Environment' + ) + host_platform, _ = winreg.QueryValueEx(winkey, 'PROCESSOR_ARCHITECTURE') + except OSError: + pass + + if not host_platform: + host_platform = platform.machine() try: - host = _ARCH_TO_CANONICAL[host_platform] + host = _ARCH_TO_CANONICAL[host_platform.lower()] except KeyError as e: # Default to x86 for all other platforms host = 'x86'