1828 lines
66 KiB
Python
Vendored
1828 lines
66 KiB
Python
Vendored
# 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.
|
|
|
|
"""
|
|
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
|
|
# * print warning when msvc version specified but not found
|
|
# * find out why warning do not print
|
|
# * test on 64 bits XP + VS 2005 (and VS 6 if possible)
|
|
# * SDK
|
|
# * Assembly
|
|
"""
|
|
|
|
import SCons.compat
|
|
|
|
import subprocess
|
|
import os
|
|
import platform
|
|
import sysconfig
|
|
from pathlib import Path
|
|
from string import digits as string_digits
|
|
from subprocess import PIPE
|
|
import re
|
|
from collections import (
|
|
namedtuple,
|
|
OrderedDict,
|
|
)
|
|
|
|
import SCons.Util
|
|
import SCons.Warnings
|
|
from SCons.Tool import find_program_path
|
|
|
|
from . import common
|
|
from .common import CONFIG_CACHE, debug
|
|
from .sdk import get_installed_sdks
|
|
|
|
from . import MSVC
|
|
|
|
from .MSVC.Exceptions import (
|
|
VisualCException,
|
|
MSVCUserError,
|
|
MSVCArgumentError,
|
|
MSVCToolsetVersionNotFound,
|
|
)
|
|
|
|
# external exceptions
|
|
|
|
class MSVCUnsupportedHostArch(VisualCException):
|
|
pass
|
|
|
|
class MSVCUnsupportedTargetArch(VisualCException):
|
|
pass
|
|
|
|
class MSVCScriptNotFound(MSVCUserError):
|
|
pass
|
|
|
|
class MSVCUseSettingsError(MSVCUserError):
|
|
pass
|
|
|
|
# internal exceptions
|
|
|
|
class UnsupportedVersion(VisualCException):
|
|
pass
|
|
|
|
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 = {
|
|
"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",
|
|
}
|
|
|
|
# The msvc batch files report errors via stdout. The following
|
|
# regular expression attempts to match known msvc error messages
|
|
# written to stdout.
|
|
re_script_output_error = re.compile(
|
|
r'^(' + r'|'.join([
|
|
r'VSINSTALLDIR variable is not set', # 2002-2003
|
|
r'The specified configuration type is missing', # 2005+
|
|
r'Error in script usage', # 2005+
|
|
r'ERROR\:', # 2005+
|
|
r'\!ERROR\!', # 2015-2015
|
|
r'\[ERROR\:', # 2017+
|
|
r'\[ERROR\]', # 2017+
|
|
r'Syntax\:', # 2017+
|
|
]) + r')'
|
|
)
|
|
|
|
# Lists of compatible host/target combinations are derived from a set of defined
|
|
# constant data structures for each host architecture. The derived data structures
|
|
# implicitly handle the differences in full versions and express versions of visual
|
|
# studio. The host/target combination search lists are contructed in order of
|
|
# preference. The construction of the derived data structures is independent of actual
|
|
# visual studio installations. The host/target configurations are used in both the
|
|
# initial msvc detection and when finding a valid batch file for a given host/target
|
|
# combination.
|
|
#
|
|
# HostTargetConfig description:
|
|
#
|
|
# label:
|
|
# Name used for identification.
|
|
#
|
|
# host_all_hosts:
|
|
# Defined list of compatible architectures for each host architecture.
|
|
#
|
|
# host_all_targets:
|
|
# Defined list of target architectures for each host architecture.
|
|
#
|
|
# host_def_targets:
|
|
# Defined list of default target architectures for each host architecture.
|
|
#
|
|
# all_pairs:
|
|
# Derived list of all host/target combination tuples.
|
|
#
|
|
# host_target_map:
|
|
# Derived list of all compatible host/target combinations for each
|
|
# supported host/target combination.
|
|
#
|
|
# host_all_targets_map:
|
|
# Derived list of all compatible host/target combinations for each
|
|
# supported host. This is used in the initial check that cl.exe exists
|
|
# in the requisite visual studio vc host/target directory for a given host.
|
|
#
|
|
# host_def_targets_map:
|
|
# Derived list of default compatible host/target combinations for each
|
|
# supported host. This is used for a given host when the user does not
|
|
# request a target archicture.
|
|
#
|
|
# target_host_map:
|
|
# Derived list of compatible host/target combinations for each supported
|
|
# target/host combination. This is used for a given host and target when
|
|
# the user requests a target architecture.
|
|
|
|
_HOST_TARGET_CONFIG_NT = namedtuple("HostTargetConfig", [
|
|
# defined
|
|
"label", # name for debugging/output
|
|
"host_all_hosts", # host_all_hosts[host] -> host_list
|
|
"host_all_targets", # host_all_targets[host] -> target_list
|
|
"host_def_targets", # host_def_targets[host] -> target_list
|
|
# derived
|
|
"all_pairs", # host_target_list
|
|
"host_target_map", # host_target_map[host][target] -> host_target_list
|
|
"host_all_targets_map", # host_all_targets_map[host][target] -> host_target_list
|
|
"host_def_targets_map", # host_def_targets_map[host][target] -> host_target_list
|
|
"target_host_map", # target_host_map[target][host] -> host_target_list
|
|
])
|
|
|
|
def _host_target_config_factory(*, label, host_all_hosts, host_all_targets, host_def_targets):
|
|
|
|
def _make_host_target_map(all_hosts, all_targets):
|
|
# host_target_map[host][target] -> host_target_list
|
|
host_target_map = {}
|
|
for host, host_list in all_hosts.items():
|
|
host_target_map[host] = {}
|
|
for host_platform in host_list:
|
|
for target_platform in all_targets[host_platform]:
|
|
if target_platform not in host_target_map[host]:
|
|
host_target_map[host][target_platform] = []
|
|
host_target_map[host][target_platform].append((host_platform, target_platform))
|
|
return host_target_map
|
|
|
|
def _make_host_all_targets_map(all_hosts, host_target_map, all_targets):
|
|
# host_all_target_map[host] -> host_target_list
|
|
# special host key '_all_' contains all (host,target) combinations
|
|
all = '_all_'
|
|
host_all_targets_map = {}
|
|
host_all_targets_map[all] = []
|
|
for host, host_list in all_hosts.items():
|
|
host_all_targets_map[host] = []
|
|
for host_platform in host_list:
|
|
# all_targets[host_platform]: all targets for compatible host
|
|
for target in all_targets[host_platform]:
|
|
for host_target in host_target_map[host_platform][target]:
|
|
for host_key in (host, all):
|
|
if host_target not in host_all_targets_map[host_key]:
|
|
host_all_targets_map[host_key].append(host_target)
|
|
return host_all_targets_map
|
|
|
|
def _make_host_def_targets_map(all_hosts, host_target_map, def_targets):
|
|
# host_def_targets_map[host] -> host_target_list
|
|
host_def_targets_map = {}
|
|
for host, host_list in all_hosts.items():
|
|
host_def_targets_map[host] = []
|
|
for host_platform in host_list:
|
|
# def_targets[host]: default targets for true host
|
|
for target in def_targets[host]:
|
|
for host_target in host_target_map[host_platform][target]:
|
|
if host_target not in host_def_targets_map[host]:
|
|
host_def_targets_map[host].append(host_target)
|
|
return host_def_targets_map
|
|
|
|
def _make_target_host_map(all_hosts, host_all_targets_map):
|
|
# target_host_map[target][host] -> host_target_list
|
|
target_host_map = {}
|
|
for host_platform in all_hosts.keys():
|
|
for host_target in host_all_targets_map[host_platform]:
|
|
_, target = host_target
|
|
if target not in target_host_map:
|
|
target_host_map[target] = {}
|
|
if host_platform not in target_host_map[target]:
|
|
target_host_map[target][host_platform] = []
|
|
if host_target not in target_host_map[target][host_platform]:
|
|
target_host_map[target][host_platform].append(host_target)
|
|
return target_host_map
|
|
|
|
host_target_map = _make_host_target_map(host_all_hosts, host_all_targets)
|
|
host_all_targets_map = _make_host_all_targets_map(host_all_hosts, host_target_map, host_all_targets)
|
|
host_def_targets_map = _make_host_def_targets_map(host_all_hosts, host_target_map, host_def_targets)
|
|
target_host_map = _make_target_host_map(host_all_hosts, host_all_targets_map)
|
|
|
|
all_pairs = host_all_targets_map['_all_']
|
|
del host_all_targets_map['_all_']
|
|
|
|
host_target_cfg = _HOST_TARGET_CONFIG_NT(
|
|
label = label,
|
|
host_all_hosts = dict(host_all_hosts),
|
|
host_all_targets = host_all_targets,
|
|
host_def_targets = host_def_targets,
|
|
all_pairs = all_pairs,
|
|
host_target_map = host_target_map,
|
|
host_all_targets_map = host_all_targets_map,
|
|
host_def_targets_map = host_def_targets_map,
|
|
target_host_map = target_host_map,
|
|
)
|
|
|
|
return host_target_cfg
|
|
|
|
# 14.1 (VS2017) and later
|
|
|
|
# Given a (host, target) tuple, return a tuple containing the batch file to
|
|
# look for and a tuple of path components to find cl.exe. We can't rely on returning
|
|
# an arg to use for vcvarsall.bat, because that script will run even if given
|
|
# a host/target pair that isn't installed.
|
|
#
|
|
# Starting with 14.1 (VS2017), the batch files are located in directory
|
|
# <VSROOT>/VC/Auxiliary/Build. The batch file name is the first value of the
|
|
# stored tuple.
|
|
#
|
|
# The build tools are organized by host and target subdirectories under each toolset
|
|
# version directory. For example, <VSROOT>/VC/Tools/MSVC/14.31.31103/bin/Hostx64/x64.
|
|
# The cl path fragment under the toolset version folder is the second value of
|
|
# the stored tuple.
|
|
|
|
# 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')),
|
|
('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') : ('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')),
|
|
|
|
}
|
|
|
|
_GE2022_HOST_TARGET_CFG = _host_target_config_factory(
|
|
|
|
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']),
|
|
('x86', ['x86']),
|
|
('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("_LE2019_HOST_TARGET_CFG: %s", _LE2019_HOST_TARGET_CFG)
|
|
|
|
# 14.0 (VS2015) to 8.0 (VS2005)
|
|
|
|
# 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.
|
|
#
|
|
# In 14.0 (VS2015) and earlier, the original x86 tools are in the tools
|
|
# bin directory (i.e., <VSROOT>/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 = {
|
|
|
|
('amd64', 'amd64') : ('amd64', ('bin', 'amd64')),
|
|
('amd64', 'x86') : ('amd64_x86', ('bin', 'amd64_x86')),
|
|
('amd64', 'arm') : ('amd64_arm', ('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')),
|
|
|
|
('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')),
|
|
|
|
}
|
|
|
|
_LE2015_HOST_TARGET_CFG = _host_target_config_factory(
|
|
|
|
label = 'LE2015',
|
|
|
|
host_all_hosts = OrderedDict([
|
|
('amd64', ['amd64', 'x86']),
|
|
('x86', ['x86']),
|
|
('arm64', ['amd64', 'x86']),
|
|
('arm', ['arm']),
|
|
('ia64', ['ia64']),
|
|
]),
|
|
|
|
host_all_targets = {
|
|
'amd64': ['amd64', 'x86', 'arm'],
|
|
'x86': ['x86', 'amd64', 'arm', 'ia64'],
|
|
'arm64': ['amd64', 'x86', 'arm'],
|
|
'arm': ['arm'],
|
|
'ia64': ['ia64'],
|
|
},
|
|
|
|
host_def_targets = {
|
|
'amd64': ['amd64', 'x86'],
|
|
'x86': ['x86'],
|
|
'arm64': ['amd64', 'arm', 'x86'],
|
|
'arm': ['arm'],
|
|
'ia64': ['ia64'],
|
|
},
|
|
|
|
)
|
|
|
|
# debug("_LE2015_HOST_TARGET_CFG: %s", _LE2015_HOST_TARGET_CFG)
|
|
|
|
# 7.1 (VS2003) and earlier
|
|
|
|
# For 7.1 (VS2003) and earlier, there are only x86 targets and the batch files
|
|
# take no arguments.
|
|
|
|
_LE2003_HOST_TARGET_CFG = _host_target_config_factory(
|
|
|
|
label = 'LE2003',
|
|
|
|
host_all_hosts = OrderedDict([
|
|
('amd64', ['x86']),
|
|
('x86', ['x86']),
|
|
('arm64', ['x86']),
|
|
]),
|
|
|
|
host_all_targets = {
|
|
'amd64': ['x86'],
|
|
'x86': ['x86'],
|
|
'arm64': ['x86'],
|
|
},
|
|
|
|
host_def_targets = {
|
|
'amd64': ['x86'],
|
|
'x86': ['x86'],
|
|
'arm64': ['x86'],
|
|
},
|
|
|
|
)
|
|
|
|
# debug("_LE2003_HOST_TARGET_CFG: %s", _LE2003_HOST_TARGET_CFG)
|
|
|
|
_CL_EXE_NAME = 'cl.exe'
|
|
|
|
def get_msvc_version_numeric(msvc_version):
|
|
"""Get the raw version numbers from a MSVC_VERSION string, so it
|
|
could be cast to float or other numeric values. For example, '14.0Exp'
|
|
would get converted to '14.0'.
|
|
|
|
Args:
|
|
msvc_version: str
|
|
string representing the version number, could contain non
|
|
digit characters
|
|
|
|
Returns:
|
|
str: the value converted to a numeric only string
|
|
|
|
"""
|
|
return ''.join([x for x in msvc_version if x in string_digits + '.'])
|
|
|
|
def get_host_platform(host_platform):
|
|
|
|
host_platform = host_platform.lower()
|
|
|
|
# Solaris returns i86pc for both 32 and 64 bit architectures
|
|
if host_platform == 'i86pc':
|
|
if platform.architecture()[0] == "64bit":
|
|
host_platform = "amd64"
|
|
else:
|
|
host_platform = "x86"
|
|
|
|
try:
|
|
host =_ARCH_TO_CANONICAL[host_platform]
|
|
except KeyError:
|
|
msg = "Unrecognized host architecture %s"
|
|
raise MSVCUnsupportedHostArch(msg % repr(host_platform)) from None
|
|
|
|
return host
|
|
|
|
_native_host_architecture = None
|
|
|
|
def get_native_host_architecture():
|
|
"""Return the native host architecture."""
|
|
global _native_host_architecture
|
|
|
|
if _native_host_architecture is None:
|
|
|
|
try:
|
|
arch = common.read_reg(
|
|
r'SYSTEM\CurrentControlSet\Control\Session Manager\Environment\PROCESSOR_ARCHITECTURE'
|
|
)
|
|
except OSError:
|
|
arch = None
|
|
|
|
if not arch:
|
|
arch = platform.machine()
|
|
|
|
_native_host_architecture = arch
|
|
|
|
return _native_host_architecture
|
|
|
|
_native_host_platform = None
|
|
|
|
def get_native_host_platform():
|
|
global _native_host_platform
|
|
|
|
if _native_host_platform is None:
|
|
arch = get_native_host_architecture()
|
|
_native_host_platform = get_host_platform(arch)
|
|
|
|
return _native_host_platform
|
|
|
|
def get_host_target(env, msvc_version, all_host_targets: bool=False):
|
|
|
|
vernum = float(get_msvc_version_numeric(msvc_version))
|
|
vernum_int = int(vernum * 10)
|
|
|
|
if vernum_int >= 143:
|
|
# 14.3 (VS2022) and later
|
|
host_target_cfg = _GE2022_HOST_TARGET_CFG
|
|
elif 143 > vernum_int >= 141:
|
|
# 14.2 (VS2019) to 14.1 (VS2017)
|
|
host_target_cfg = _LE2019_HOST_TARGET_CFG
|
|
elif 141 > vernum_int >= 80:
|
|
# 14.0 (VS2015) to 8.0 (VS2005)
|
|
host_target_cfg = _LE2015_HOST_TARGET_CFG
|
|
else: # 80 > vernum_int
|
|
# 7.1 (VS2003) and earlier
|
|
host_target_cfg = _LE2003_HOST_TARGET_CFG
|
|
|
|
host_arch = env.get('HOST_ARCH') if env else None
|
|
debug("HOST_ARCH:%s", str(host_arch))
|
|
|
|
if host_arch:
|
|
host_platform = get_host_platform(host_arch)
|
|
else:
|
|
host_platform = get_native_host_platform()
|
|
|
|
target_arch = env.get('TARGET_ARCH') if env else None
|
|
debug("TARGET_ARCH:%s", str(target_arch))
|
|
|
|
if target_arch:
|
|
|
|
try:
|
|
target_platform = _ARCH_TO_CANONICAL[target_arch.lower()]
|
|
except KeyError:
|
|
all_archs = str(list(_ARCH_TO_CANONICAL.keys()))
|
|
raise MSVCUnsupportedTargetArch(
|
|
"Unrecognized target architecture %s\n\tValid architectures: %s"
|
|
% (repr(target_arch), all_archs)
|
|
) from None
|
|
|
|
target_host_map = host_target_cfg.target_host_map
|
|
|
|
try:
|
|
host_target_list = target_host_map[target_platform][host_platform]
|
|
except KeyError:
|
|
host_target_list = []
|
|
warn_msg = "unsupported host, target combination ({}, {}) for MSVC version {}".format(
|
|
repr(host_platform), repr(target_platform), msvc_version
|
|
)
|
|
SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, warn_msg)
|
|
debug(warn_msg)
|
|
|
|
else:
|
|
|
|
target_platform = None
|
|
|
|
if all_host_targets:
|
|
host_targets_map = host_target_cfg.host_all_targets_map
|
|
else:
|
|
host_targets_map = host_target_cfg.host_def_targets_map
|
|
|
|
try:
|
|
host_target_list = host_targets_map[host_platform]
|
|
except KeyError:
|
|
msg = "Unrecognized host architecture %s for version %s"
|
|
raise MSVCUnsupportedHostArch(msg % (repr(host_platform), msvc_version)) from None
|
|
|
|
return host_platform, target_platform, host_target_list
|
|
|
|
_arm32_process_arm64_host = None
|
|
|
|
def is_arm32_process_arm64_host():
|
|
global _arm32_process_arm64_host
|
|
|
|
if _arm32_process_arm64_host is None:
|
|
|
|
host = get_native_host_architecture()
|
|
host = _ARCH_TO_CANONICAL.get(host.lower(),'')
|
|
host_isarm64 = host == 'arm64'
|
|
|
|
process = sysconfig.get_platform()
|
|
process_isarm32 = process == 'win-arm32'
|
|
|
|
_arm32_process_arm64_host = host_isarm64 and process_isarm32
|
|
|
|
return _arm32_process_arm64_host
|
|
|
|
_check_skip_sendtelemetry = None
|
|
|
|
def _skip_sendtelemetry(env):
|
|
global _check_skip_sendtelemetry
|
|
|
|
if _check_skip_sendtelemetry is None:
|
|
|
|
if _ARM32_ON_ARM64_SKIP_SENDTELEMETRY and is_arm32_process_arm64_host():
|
|
_check_skip_sendtelemetry = True
|
|
else:
|
|
_check_skip_sendtelemetry = False
|
|
|
|
if not _check_skip_sendtelemetry:
|
|
return False
|
|
|
|
msvc_version = env.get('MSVC_VERSION') if env else None
|
|
if not msvc_version:
|
|
msvc_version = msvc_default_version(env)
|
|
|
|
if not msvc_version:
|
|
return False
|
|
|
|
vernum = float(get_msvc_version_numeric(msvc_version))
|
|
if vernum < 14.2: # VS2019
|
|
return False
|
|
|
|
# arm32 process, arm64 host, VS2019+
|
|
return True
|
|
|
|
# If you update this, update SupportedVSList in Tool/MSCommon/vs.py, and the
|
|
# MSVC_VERSION documentation in Tool/msvc.xml.
|
|
_VCVER = [
|
|
"14.3",
|
|
"14.2",
|
|
"14.1", "14.1Exp",
|
|
"14.0", "14.0Exp",
|
|
"12.0", "12.0Exp",
|
|
"11.0", "11.0Exp",
|
|
"10.0", "10.0Exp",
|
|
"9.0", "9.0Exp",
|
|
"8.0", "8.0Exp",
|
|
"7.1",
|
|
"7.0",
|
|
"6.0"]
|
|
|
|
# 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
|
|
],
|
|
}
|
|
|
|
_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')],
|
|
'14.0Exp': [
|
|
(SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\WDExpress\14.0\Setup\VS\ProductDir'),
|
|
(SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\14.0\Setup\VC\ProductDir')],
|
|
'12.0': [
|
|
(SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\12.0\Setup\VC\ProductDir'),
|
|
],
|
|
'12.0Exp': [
|
|
(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\VCExpress\11.0\Setup\VC\ProductDir'),
|
|
],
|
|
'10.0': [
|
|
(SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\10.0\Setup\VC\ProductDir'),
|
|
],
|
|
'10.0Exp': [
|
|
(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_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'),
|
|
],
|
|
'8.0': [
|
|
(SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\8.0\Setup\VC\ProductDir'),
|
|
],
|
|
'8.0Exp': [
|
|
(SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\8.0\Setup\VC\ProductDir'),
|
|
],
|
|
'7.1': [
|
|
(SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\7.1\Setup\VC\ProductDir'),
|
|
],
|
|
'7.0': [
|
|
(SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\7.0\Setup\VC\ProductDir'),
|
|
],
|
|
'6.0': [
|
|
(SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\6.0\Setup\Microsoft Visual C++\ProductDir'),
|
|
]
|
|
}
|
|
|
|
|
|
def msvc_version_to_maj_min(msvc_version):
|
|
msvc_version_numeric = get_msvc_version_numeric(msvc_version)
|
|
|
|
t = msvc_version_numeric.split(".")
|
|
if not len(t) == 2:
|
|
raise ValueError("Unrecognized version %s (%s)" % (msvc_version,msvc_version_numeric))
|
|
try:
|
|
maj = int(t[0])
|
|
min = int(t[1])
|
|
return maj, min
|
|
except ValueError:
|
|
raise ValueError("Unrecognized version %s (%s)" % (msvc_version,msvc_version_numeric)) from None
|
|
|
|
|
|
VSWHERE_PATHS = [os.path.join(p,'vswhere.exe') for p in [
|
|
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"),
|
|
]]
|
|
|
|
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
|
|
|
|
def find_vc_pdir_vswhere(msvc_version, env=None):
|
|
""" Find the MSVC product directory using the vswhere program.
|
|
|
|
Args:
|
|
msvc_version: MSVC version to search for
|
|
env: optional to look up VSWHERE variable
|
|
|
|
Returns:
|
|
MSVC install dir or None
|
|
|
|
Raises:
|
|
UnsupportedVersion: if the version is not known by this file
|
|
|
|
"""
|
|
try:
|
|
vswhere_version = _VCVER_TO_VSWHERE_VER[msvc_version]
|
|
except KeyError:
|
|
debug("Unknown version of MSVC: %s", msvc_version)
|
|
raise UnsupportedVersion("Unknown version %s" % msvc_version) from None
|
|
|
|
if env is None or not env.get('VSWHERE'):
|
|
vswhere_path = msvc_find_vswhere()
|
|
else:
|
|
vswhere_path = env.subst('$VSWHERE')
|
|
|
|
if vswhere_path is None:
|
|
return None
|
|
|
|
debug('VSWHERE: %s', vswhere_path)
|
|
for vswhere_version_args in vswhere_version:
|
|
|
|
vswhere_cmd = [vswhere_path] + vswhere_version_args + ["-property", "installationPath"]
|
|
|
|
debug("running: %s", vswhere_cmd)
|
|
|
|
# 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')
|
|
else:
|
|
# We found vswhere, but no install info available for this version
|
|
pass
|
|
|
|
return None
|
|
|
|
|
|
def find_vc_pdir(env, msvc_version):
|
|
"""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.
|
|
|
|
Args:
|
|
msvc_version: str
|
|
msvc version (major.minor, e.g. 10.0)
|
|
|
|
Returns:
|
|
str: Path found in registry, or None
|
|
|
|
Raises:
|
|
UnsupportedVersion: if the version is not known by this file.
|
|
|
|
UnsupportedVersion inherits from VisualCException.
|
|
|
|
"""
|
|
root = 'Software\\'
|
|
try:
|
|
hkeys = _VCVER_TO_PRODUCT_DIR[msvc_version]
|
|
except KeyError:
|
|
debug("Unknown version of MSVC: %s", msvc_version)
|
|
raise UnsupportedVersion("Unknown version %s" % msvc_version) from None
|
|
|
|
for hkroot, key in hkeys:
|
|
try:
|
|
comps = None
|
|
if not key:
|
|
comps = find_vc_pdir_vswhere(msvc_version, env)
|
|
if not comps:
|
|
debug('no VC found for version %s', repr(msvc_version))
|
|
raise OSError
|
|
debug('VC found: %s', repr(msvc_version))
|
|
return comps
|
|
else:
|
|
if common.is_win64():
|
|
try:
|
|
# ordinarily at win64, try Wow6432Node first.
|
|
comps = common.read_reg(root + 'Wow6432Node\\' + key, hkroot)
|
|
except OSError:
|
|
# at Microsoft Visual Studio for Python 2.7, value is not in Wow6432Node
|
|
pass
|
|
if not comps:
|
|
# not Win64, or Microsoft Visual Studio for Python 2.7
|
|
comps = common.read_reg(root + key, hkroot)
|
|
except OSError:
|
|
debug('no VC registry key %s', repr(key))
|
|
else:
|
|
if msvc_version == '9.0':
|
|
if key.lower().endswith('\\vcforpython\\9.0\\installdir'):
|
|
# Visual C++ for Python registry key is installdir (root) not productdir (vc)
|
|
comps = os.path.join(comps, 'VC')
|
|
elif msvc_version == '14.0Exp':
|
|
if key.lower().endswith('\\setup\\vs\\productdir'):
|
|
# Visual Studio 14.0 Express registry key is installdir (root) not productdir (vc)
|
|
comps = os.path.join(comps, 'VC')
|
|
debug('found VC in registry: %s', comps)
|
|
if os.path.exists(comps):
|
|
return comps
|
|
else:
|
|
debug('reg says dir is %s, but it does not exist. (ignoring)', comps)
|
|
return None
|
|
|
|
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
|
|
|
|
In newer (2017+) compilers, make use of the fact there are vcvars
|
|
scripts named with a host_target pair that calls vcvarsall.bat properly,
|
|
so use that and return an empty argument.
|
|
"""
|
|
|
|
# filter out e.g. "Exp" from the version name
|
|
vernum = float(get_msvc_version_numeric(msvc_version))
|
|
vernum_int = int(vernum * 10)
|
|
|
|
sdk_pdir = pdir
|
|
|
|
arg = ''
|
|
vcdir = None
|
|
clexe = None
|
|
|
|
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, _ = _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)]
|
|
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
|
|
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("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("sdk batch file not found:%s", _sdk)
|
|
else:
|
|
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 sdk_bat_file_path
|
|
|
|
return None
|
|
|
|
__INSTALLED_VCS_RUN = None
|
|
_VC_TOOLS_VERSION_FILE_PATH = ['Auxiliary', 'Build', 'Microsoft.VCToolsVersion.default.txt']
|
|
_VC_TOOLS_VERSION_FILE = os.sep.join(_VC_TOOLS_VERSION_FILE_PATH)
|
|
|
|
def _check_cl_exists_in_vc_dir(env, vc_dir, msvc_version) -> bool:
|
|
"""Return status of finding a cl.exe to use.
|
|
|
|
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.
|
|
|
|
Args:
|
|
env: Environment
|
|
a construction environment, usually if this is passed its
|
|
because there is a desired TARGET_ARCH to be used when searching
|
|
for a cl.exe
|
|
vc_dir: str
|
|
the path to the VC dir in the MSVC installation
|
|
msvc_version: str
|
|
msvc version (major.minor, e.g. 10.0)
|
|
|
|
Returns:
|
|
bool:
|
|
|
|
"""
|
|
|
|
# Find the host, target, and all candidate (host, target) platform combinations:
|
|
platforms = get_host_target(env, msvc_version, all_host_targets=True)
|
|
debug("host_platform %s, target_platform %s host_target_list %s", *platforms)
|
|
host_platform, target_platform, host_target_list = platforms
|
|
|
|
vernum = float(get_msvc_version_numeric(msvc_version))
|
|
vernum_int = int(vernum * 10)
|
|
|
|
# make sure the cl.exe exists meaning the tool is installed
|
|
if vernum_int >= 141:
|
|
# 14.1 (VS2017) and later
|
|
# 2017 and newer allowed multiple versions of the VC toolset to be
|
|
# installed at the same time. This changes the layout.
|
|
# Just get the default tool version for now
|
|
# TODO: support setting a specific minor VC version
|
|
default_toolset_file = os.path.join(vc_dir, _VC_TOOLS_VERSION_FILE)
|
|
try:
|
|
with open(default_toolset_file) as f:
|
|
vc_specific_version = f.readlines()[0].strip()
|
|
except OSError:
|
|
debug('failed to read %s', default_toolset_file)
|
|
return False
|
|
except IndexError:
|
|
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 = 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
|
|
|
|
_, cl_path_comps = batchfile_clpathcomps
|
|
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 os.path.exists(cl_path):
|
|
debug('found %s!', _CL_EXE_NAME)
|
|
return True
|
|
|
|
elif 141 > vernum_int >= 80:
|
|
# 14.0 (VS2015) to 8.0 (VS2005)
|
|
|
|
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:
|
|
debug('unsupported host/target platform combo: (%s,%s)', host_platform, target_platform)
|
|
continue
|
|
|
|
_, cl_path_comps = batcharg_clpathcomps
|
|
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 os.path.exists(cl_path):
|
|
debug('found %s', _CL_EXE_NAME)
|
|
return True
|
|
|
|
elif 80 > vernum_int >= 60:
|
|
# 7.1 (VS2003) to 6.0 (VS6)
|
|
|
|
# quick check for vc_dir/bin and vc_dir/ before walk
|
|
# need to check root as the walk only considers subdirectories
|
|
for cl_dir in ('bin', ''):
|
|
cl_path = os.path.join(vc_dir, cl_dir, _CL_EXE_NAME)
|
|
if os.path.exists(cl_path):
|
|
debug('%s found %s', _CL_EXE_NAME, cl_path)
|
|
return True
|
|
# not in bin or root: must be in a subdirectory
|
|
for cl_root, cl_dirs, _ in os.walk(vc_dir):
|
|
for cl_dir in cl_dirs:
|
|
cl_path = os.path.join(cl_root, cl_dir, _CL_EXE_NAME)
|
|
if os.path.exists(cl_path):
|
|
debug('%s found %s', _CL_EXE_NAME, cl_path)
|
|
return True
|
|
return False
|
|
|
|
else:
|
|
# version not supported return false
|
|
debug('unsupported MSVC version: %s', str(vernum))
|
|
|
|
return False
|
|
|
|
def get_installed_vcs(env=None):
|
|
global __INSTALLED_VCS_RUN
|
|
|
|
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:
|
|
debug('trying to find VC %s', ver)
|
|
try:
|
|
VC_DIR = find_vc_pdir(env, ver)
|
|
if VC_DIR:
|
|
debug('found VC %s', ver)
|
|
if _check_cl_exists_in_vc_dir(env, VC_DIR, ver):
|
|
installed_versions.append(ver)
|
|
else:
|
|
debug('no compiler found %s', ver)
|
|
else:
|
|
debug('return None for ver %s', ver)
|
|
except (MSVCUnsupportedTargetArch, MSVCUnsupportedHostArch):
|
|
# Allow this exception to propagate further as it should cause
|
|
# SCons to exit with an error code
|
|
raise
|
|
except VisualCException as e:
|
|
debug('did not find VC %s: caught exception %s', ver, str(e))
|
|
|
|
if force_target:
|
|
env['TARGET_ARCH'] = save_target_arch
|
|
debug("restore env['TARGET_ARCH']=%s", save_target_arch)
|
|
|
|
__INSTALLED_VCS_RUN = installed_versions
|
|
debug("__INSTALLED_VCS_RUN=%s", __INSTALLED_VCS_RUN)
|
|
return __INSTALLED_VCS_RUN
|
|
|
|
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
|
|
MSVC._reset()
|
|
|
|
def msvc_default_version(env=None):
|
|
"""Get default msvc version."""
|
|
vcs = get_installed_vcs(env)
|
|
msvc_version = vcs[0] if vcs else None
|
|
debug('msvc_version=%s', repr(msvc_version))
|
|
return msvc_version
|
|
|
|
def get_installed_vcs_components(env=None):
|
|
"""Test suite convenience function: return list of installed msvc version component tuples"""
|
|
vcs = get_installed_vcs(env)
|
|
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:
|
|
# env1 = Environment(tools='msvs')
|
|
# env2 = Environment(tools='msvs')
|
|
# we can greatly improve the speed of the second and subsequent Environment
|
|
# (or Clone) calls by memoizing the environment variables set by vcvars*.bat.
|
|
#
|
|
# Updated: by 2018, vcvarsall.bat had gotten so expensive (vs2017 era)
|
|
# it was breaking CI builds because the test suite starts scons so many
|
|
# times and the existing memo logic only helped with repeated calls
|
|
# within the same scons run. Windows builds on the CI system were split
|
|
# into chunks to get around single-build time limits.
|
|
# With VS2019 it got even slower and an optional persistent cache file
|
|
# was introduced. The cache now also stores only the parsed vars,
|
|
# not the entire output of running the batch file - saves a bit
|
|
# of time not parsing every time.
|
|
|
|
script_env_cache = None
|
|
|
|
def script_env(env, script, args=None):
|
|
global script_env_cache
|
|
|
|
if script_env_cache is None:
|
|
script_env_cache = common.read_script_env_cache()
|
|
cache_key = (script, args if args else None)
|
|
cache_data = script_env_cache.get(cache_key, None)
|
|
|
|
# Brief sanity check: if we got a value for the key,
|
|
# see if it has a VCToolsInstallDir entry that is not empty.
|
|
# If so, and that path does not exist, invalidate the entry.
|
|
# If empty, this is an old compiler, just leave it alone.
|
|
if cache_data is not None:
|
|
try:
|
|
toolsdir = cache_data["VCToolsInstallDir"]
|
|
except KeyError:
|
|
# we write this value, so should not happen
|
|
pass
|
|
else:
|
|
if toolsdir:
|
|
toolpath = Path(toolsdir[0])
|
|
if not toolpath.exists():
|
|
cache_data = None
|
|
|
|
if cache_data is None:
|
|
skip_sendtelemetry = _skip_sendtelemetry(env)
|
|
stdout = common.get_output(script, args, skip_sendtelemetry=skip_sendtelemetry)
|
|
cache_data = common.parse_output(stdout)
|
|
|
|
# debug(stdout)
|
|
olines = stdout.splitlines()
|
|
|
|
# process stdout: batch file errors (not necessarily first line)
|
|
script_errlog = []
|
|
for line in olines:
|
|
if re_script_output_error.match(line):
|
|
if not script_errlog:
|
|
script_errlog.append('vc script errors detected:')
|
|
script_errlog.append(line)
|
|
|
|
if script_errlog:
|
|
script_errmsg = '\n'.join(script_errlog)
|
|
|
|
have_cl, _ = _check_cl_exists_in_script_env(cache_data)
|
|
|
|
debug(
|
|
'script=%s args=%s have_cl=%s, errors=%s',
|
|
repr(script), repr(args), repr(have_cl), script_errmsg
|
|
)
|
|
MSVC.Policy.msvc_scripterror_handler(env, script_errmsg)
|
|
|
|
if not have_cl:
|
|
# detected errors, cl.exe not on path
|
|
raise BatchFileExecutionError(script_errmsg)
|
|
|
|
# once we updated cache, give a chance to write out if user wanted
|
|
script_env_cache[cache_key] = cache_data
|
|
common.write_script_env_cache(script_env_cache)
|
|
|
|
return cache_data
|
|
|
|
def get_default_version(env):
|
|
msvc_version = env.get('MSVC_VERSION')
|
|
msvs_version = env.get('MSVS_VERSION')
|
|
debug('msvc_version:%s msvs_version:%s', msvc_version, msvs_version)
|
|
|
|
if msvs_version and not msvc_version:
|
|
SCons.Warnings.warn(
|
|
SCons.Warnings.DeprecatedWarning,
|
|
"MSVS_VERSION is deprecated: please use MSVC_VERSION instead ")
|
|
return msvs_version
|
|
elif msvc_version and msvs_version:
|
|
if not msvc_version == msvs_version:
|
|
SCons.Warnings.warn(
|
|
SCons.Warnings.VisualVersionMismatch,
|
|
"Requested msvc version (%s) and msvs version (%s) do "
|
|
"not match: please use MSVC_VERSION only to request a "
|
|
"visual studio version, MSVS_VERSION is deprecated"
|
|
% (msvc_version, msvs_version))
|
|
return msvs_version
|
|
|
|
if not msvc_version:
|
|
msvc_version = msvc_default_version(env)
|
|
if not msvc_version:
|
|
#SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, msg)
|
|
debug('No installed VCs')
|
|
return None
|
|
debug('using default installed MSVC version %s', repr(msvc_version))
|
|
else:
|
|
debug('using specified MSVC version %s', repr(msvc_version))
|
|
|
|
return msvc_version
|
|
|
|
def msvc_setup_env_once(env, tool=None) -> None:
|
|
try:
|
|
has_run = env["MSVC_SETUP_RUN"]
|
|
except KeyError:
|
|
has_run = False
|
|
|
|
if not has_run:
|
|
MSVC.SetupEnvDefault.register_setup(env, msvc_exists)
|
|
msvc_setup_env(env)
|
|
env["MSVC_SETUP_RUN"] = True
|
|
|
|
req_tools = MSVC.SetupEnvDefault.register_iserror(env, tool, msvc_exists)
|
|
if req_tools:
|
|
msg = "No versions of the MSVC compiler were found.\n" \
|
|
" Visual Studio C/C++ compilers may not be set correctly.\n" \
|
|
" Requested tool(s) are: {}".format(req_tools)
|
|
MSVC.Policy.msvc_notfound_handler(env, msg)
|
|
|
|
def msvc_find_valid_batch_script(env, version):
|
|
"""Find and execute appropriate batch script to set up build env.
|
|
|
|
The MSVC build environment depends heavily on having the shell
|
|
environment set. SCons does not inherit that, and does not count
|
|
on that being set up correctly anyway, so it tries to find the right
|
|
MSVC batch script, or the right arguments to the generic batch script
|
|
vcvarsall.bat, and run that, so we have a valid environment to build in.
|
|
There are dragons here: the batch scripts don't fail (see comments
|
|
elsewhere), they just leave you with a bad setup, so try hard to
|
|
get it right.
|
|
"""
|
|
|
|
# Find the product directory
|
|
pdir = None
|
|
try:
|
|
pdir = find_vc_pdir(env, version)
|
|
except UnsupportedVersion:
|
|
# Unsupported msvc version (raise MSVCArgumentError?)
|
|
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)
|
|
host_platform, target_platform, host_target_list = platforms
|
|
|
|
d = None
|
|
version_installed = False
|
|
|
|
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
|
|
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)
|
|
except BatchFileExecutionError as e:
|
|
debug('failed vc_script:%s, vc_script_args:%s, error:%s', repr(vc_script), arg, e)
|
|
vc_script = None
|
|
continue
|
|
|
|
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
|
|
|
|
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
|
|
if not d:
|
|
env['TARGET_ARCH'] = target_platform
|
|
|
|
if version_installed:
|
|
msg = "MSVC version '{}' working host/target script was not found.\n" \
|
|
" Host = '{}', Target = '{}'\n" \
|
|
" Visual Studio C/C++ compilers may not be set correctly".format(
|
|
version, host_platform, target_platform
|
|
)
|
|
else:
|
|
installed_vcs = get_installed_vcs(env)
|
|
if installed_vcs:
|
|
msg = "MSVC version '{}' was not found.\n" \
|
|
" Visual Studio C/C++ compilers may not be set correctly.\n" \
|
|
" Installed versions are: {}".format(version, installed_vcs)
|
|
else:
|
|
msg = "MSVC version '{}' was not found.\n" \
|
|
" No versions of the MSVC compiler were found.\n" \
|
|
" Visual Studio C/C++ compilers may not be set correctly".format(version)
|
|
|
|
MSVC.Policy.msvc_notfound_handler(env, msg)
|
|
|
|
return d
|
|
|
|
def get_use_script_use_settings(env):
|
|
|
|
# use_script use_settings return values action
|
|
# value ignored (value, None) use script or bypass detection
|
|
# undefined value not None (False, value) use dictionary
|
|
# undefined undefined/None (True, None) msvc detection
|
|
|
|
# None (documentation) or evaluates False (code): bypass detection
|
|
# need to distinguish between undefined and None
|
|
use_script = env.get('MSVC_USE_SCRIPT', UNDEFINED)
|
|
|
|
if use_script != UNDEFINED:
|
|
# use_script defined, use_settings ignored (not type checked)
|
|
return use_script, None
|
|
|
|
# undefined or None: use_settings ignored
|
|
use_settings = env.get('MSVC_USE_SETTINGS', None)
|
|
|
|
if use_settings is not None:
|
|
# use script undefined, use_settings defined and not None (type checked)
|
|
return False, use_settings
|
|
|
|
# use script undefined, use_settings undefined or None
|
|
return True, None
|
|
|
|
def msvc_setup_env(env):
|
|
debug('called')
|
|
version = get_default_version(env)
|
|
if version is None:
|
|
if not msvc_setup_env_user(env):
|
|
MSVC.SetupEnvDefault.set_nodefault()
|
|
return None
|
|
|
|
# XXX: we set-up both MSVS version for backward
|
|
# compatibility with the msvs tool
|
|
env['MSVC_VERSION'] = version
|
|
env['MSVS_VERSION'] = version
|
|
env['MSVS'] = {}
|
|
|
|
use_script, use_settings = get_use_script_use_settings(env)
|
|
if SCons.Util.is_String(use_script):
|
|
use_script = use_script.strip()
|
|
if not os.path.exists(use_script):
|
|
raise MSVCScriptNotFound(f'Script specified by MSVC_USE_SCRIPT not found: "{use_script}"')
|
|
args = env.subst('$MSVC_USE_SCRIPT_ARGS')
|
|
debug('use_script 1 %s %s', repr(use_script), repr(args))
|
|
d = script_env(env, use_script, args)
|
|
elif use_script:
|
|
d = msvc_find_valid_batch_script(env,version)
|
|
debug('use_script 2 %s', d)
|
|
if not d:
|
|
return d
|
|
elif use_settings is not None:
|
|
if not SCons.Util.is_Dict(use_settings):
|
|
error_msg = f'MSVC_USE_SETTINGS type error: expected a dictionary, found {type(use_settings).__name__}'
|
|
raise MSVCUseSettingsError(error_msg)
|
|
d = use_settings
|
|
debug('use_settings %s', d)
|
|
else:
|
|
debug('MSVC_USE_SCRIPT set to False')
|
|
warn_msg = "MSVC_USE_SCRIPT set to False, assuming environment " \
|
|
"set correctly."
|
|
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 == '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])
|
|
|
|
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:
|
|
warn_msg = "Could not find requested MSVC compiler 'cl'."
|
|
if CONFIG_CACHE:
|
|
warn_msg += f" SCONS_CACHE_MSVC_CONFIG caching enabled, remove cache file {CONFIG_CACHE} if out of date."
|
|
else:
|
|
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."
|
|
debug(warn_msg)
|
|
SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, warn_msg)
|
|
|
|
def msvc_exists(env=None, version=None):
|
|
vcs = get_installed_vcs(env)
|
|
if version is None:
|
|
rval = len(vcs) > 0
|
|
else:
|
|
rval = version in vcs
|
|
debug('version=%s, return=%s', repr(version), rval)
|
|
return rval
|
|
|
|
def msvc_setup_env_user(env=None):
|
|
rval = False
|
|
if env:
|
|
|
|
# Intent is to use msvc tools:
|
|
# MSVC_VERSION: defined and evaluates True
|
|
# MSVS_VERSION: defined and evaluates True
|
|
# MSVC_USE_SCRIPT: defined and (is string or evaluates False)
|
|
# MSVC_USE_SETTINGS: defined and is not None
|
|
|
|
# defined and is True
|
|
for key in ['MSVC_VERSION', 'MSVS_VERSION']:
|
|
if key in env and env[key]:
|
|
rval = True
|
|
debug('key=%s, return=%s', repr(key), rval)
|
|
return rval
|
|
|
|
# defined and (is string or is False)
|
|
for key in ['MSVC_USE_SCRIPT']:
|
|
if key in env and (SCons.Util.is_String(env[key]) or not env[key]):
|
|
rval = True
|
|
debug('key=%s, return=%s', repr(key), rval)
|
|
return rval
|
|
|
|
# defined and is not None
|
|
for key in ['MSVC_USE_SETTINGS']:
|
|
if key in env and env[key] is not None:
|
|
rval = True
|
|
debug('key=%s, return=%s', repr(key), rval)
|
|
return rval
|
|
|
|
debug('return=%s', rval)
|
|
return rval
|
|
|
|
def msvc_setup_env_tool(env=None, version=None, tool=None):
|
|
MSVC.SetupEnvDefault.register_tool(env, tool, msvc_exists)
|
|
rval = False
|
|
if not rval and msvc_exists(env, version):
|
|
rval = True
|
|
if not rval and msvc_setup_env_user(env):
|
|
rval = True
|
|
return rval
|
|
|
|
def msvc_sdk_versions(version=None, msvc_uwp_app: bool=False):
|
|
debug('version=%s, msvc_uwp_app=%s', repr(version), repr(msvc_uwp_app))
|
|
|
|
rval = []
|
|
|
|
if not version:
|
|
version = msvc_default_version()
|
|
|
|
if not version:
|
|
debug('no msvc versions detected')
|
|
return rval
|
|
|
|
version_def = MSVC.Util.msvc_extended_version_components(version)
|
|
if not version_def:
|
|
msg = f'Unsupported version {version!r}'
|
|
raise MSVCArgumentError(msg)
|
|
|
|
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))
|
|
|
|
env = None
|
|
rval = []
|
|
|
|
if not msvc_version:
|
|
msvc_version = msvc_default_version()
|
|
|
|
if not msvc_version:
|
|
debug('no msvc versions detected')
|
|
return rval
|
|
|
|
if msvc_version not in _VCVER:
|
|
msg = f'Unsupported msvc version {msvc_version!r}'
|
|
raise MSVCArgumentError(msg)
|
|
|
|
vc_dir = find_vc_pdir(env, msvc_version)
|
|
if not vc_dir:
|
|
debug('VC folder not found for version %s', repr(msvc_version))
|
|
return rval
|
|
|
|
rval = MSVC.ScriptArguments._msvc_toolset_versions_internal(msvc_version, vc_dir, full=full, sxs=sxs)
|
|
return rval
|
|
|
|
def msvc_toolset_versions_spectre(msvc_version=None):
|
|
debug('msvc_version=%s', repr(msvc_version))
|
|
|
|
env = None
|
|
rval = []
|
|
|
|
if not msvc_version:
|
|
msvc_version = msvc_default_version()
|
|
|
|
if not msvc_version:
|
|
debug('no msvc versions detected')
|
|
return rval
|
|
|
|
if msvc_version not in _VCVER:
|
|
msg = f'Unsupported msvc version {msvc_version!r}'
|
|
raise MSVCArgumentError(msg)
|
|
|
|
vc_dir = find_vc_pdir(env, msvc_version)
|
|
if not vc_dir:
|
|
debug('VC folder not found for version %s', repr(msvc_version))
|
|
return rval
|
|
|
|
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):
|
|
"""
|
|
Returns 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.
|
|
|
|
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.
|
|
|
|
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.
|
|
|
|
Args:
|
|
|
|
version: str
|
|
The version specification may be an msvc version or a toolset
|
|
version.
|
|
|
|
prefer_newest: bool
|
|
True: prefer newer Visual Studio instances.
|
|
False: prefer the "native" Visual Studio instance first. If
|
|
the native Visual Studio instance is not detected, prefer
|
|
newer Visual Studio instances.
|
|
|
|
Returns:
|
|
tuple: A tuple containing the msvc version and the msvc toolset version.
|
|
The msvc toolset version may be None.
|
|
|
|
Raises:
|
|
MSVCToolsetVersionNotFound: when the specified version is not found.
|
|
MSVCArgumentError: when argument validation fails.
|
|
"""
|
|
debug('version=%s, prefer_newest=%s', repr(version), repr(prefer_newest))
|
|
|
|
env = None
|
|
msvc_version = None
|
|
msvc_toolset_version = None
|
|
|
|
if not version:
|
|
version = msvc_default_version()
|
|
|
|
if not version:
|
|
debug('no msvc versions detected')
|
|
return msvc_version, msvc_toolset_version
|
|
|
|
version_def = MSVC.Util.msvc_extended_version_components(version)
|
|
|
|
if not version_def:
|
|
msg = f'Unsupported msvc version {version!r}'
|
|
raise MSVCArgumentError(msg)
|
|
|
|
if version_def.msvc_suffix:
|
|
if version_def.msvc_verstr != version_def.msvc_toolset_version:
|
|
# toolset version with component suffix
|
|
msg = f'Unsupported toolset version {version!r}'
|
|
raise MSVCArgumentError(msg)
|
|
|
|
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)
|
|
)
|
|
raise MSVCArgumentError(msg)
|
|
|
|
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 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(env, query_msvc_version)
|
|
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
|
|
|
|
try:
|
|
toolset_vcvars = MSVC.ScriptArguments._msvc_toolset_internal(query_msvc_version, query_msvc_toolset_version, vc_dir)
|
|
if toolset_vcvars:
|
|
msvc_toolset_version = toolset_vcvars
|
|
debug(
|
|
'found: msvc_version=%s, msvc_toolset_version=%s',
|
|
repr(query_msvc_version), repr(msvc_toolset_version)
|
|
)
|
|
return query_msvc_version, msvc_toolset_version
|
|
|
|
except MSVCToolsetVersionNotFound:
|
|
pass
|
|
|
|
msvc_toolset_version = query_msvc_toolset_version
|
|
|
|
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'
|
|
raise MSVCToolsetVersionNotFound(msg)
|
|
|
|
|
|
# internal consistency check (should be last)
|
|
MSVC._verify()
|
|
|