summaryrefslogtreecommitdiffstats
path: root/SCons
diff options
context:
space:
mode:
authorJoseph Brill <48932340+jcbrill@users.noreply.github.com>2022-05-03 23:33:34 (GMT)
committerJoseph Brill <48932340+jcbrill@users.noreply.github.com>2022-05-03 23:33:34 (GMT)
commit4aee3a86f929d6156c46ea410e6745a0c3e8df34 (patch)
tree847ee1c27b08119d1385c372afd431cbce08799c /SCons
parentfef535182c6c1d76a059e22391ec72906002d16d (diff)
parentf230fd34892754bca67742e93aae471fd58133ec (diff)
downloadSCons-4aee3a86f929d6156c46ea410e6745a0c3e8df34.zip
SCons-4aee3a86f929d6156c46ea410e6745a0c3e8df34.tar.gz
SCons-4aee3a86f929d6156c46ea410e6745a0c3e8df34.tar.bz2
Merge branch 'master' into jbrill-msvc-vscomntools
Diffstat (limited to 'SCons')
-rw-r--r--SCons/Tool/MSCommon/vc.py705
-rw-r--r--SCons/Tool/MSCommon/vcTests.py88
2 files changed, 519 insertions, 274 deletions
diff --git a/SCons/Tool/MSCommon/vc.py b/SCons/Tool/MSCommon/vc.py
index c2314d3..75777e2 100644
--- a/SCons/Tool/MSCommon/vc.py
+++ b/SCons/Tool/MSCommon/vc.py
@@ -45,6 +45,11 @@ import os
import platform
from string import digits as string_digits
from subprocess import PIPE
+import re
+from collections import (
+ namedtuple,
+ OrderedDict,
+)
import SCons.Util
import SCons.Warnings
@@ -96,84 +101,302 @@ _ARCH_TO_CANONICAL = {
"aarch64" : "arm64",
}
-# Starting with 14.1 (aka VS2017), the tools are organized by host directory.
-# subdirs for each target. They are now in .../VC/Auxuiliary/Build.
-# Note 2017 Express uses Hostx86 even if it's on 64-bit Windows,
-# not reflected in this table.
-_HOST_TARGET_TO_CL_DIR_GREATER_THAN_14 = {
- ("amd64","amd64") : ("Hostx64","x64"),
- ("amd64","x86") : ("Hostx64","x86"),
- ("amd64","arm") : ("Hostx64","arm"),
- ("amd64","arm64") : ("Hostx64","arm64"),
- ("x86","amd64") : ("Hostx86","x64"),
- ("x86","x86") : ("Hostx86","x86"),
- ("x86","arm") : ("Hostx86","arm"),
- ("x86","arm64") : ("Hostx86","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.
-# before 14.1 (VS2017): the original x86 tools are in the tools dir,
-# any others are in a subdir named by the host/target pair,
-# or just a single word if host==target
-_HOST_TARGET_TO_CL_DIR = {
- ("amd64","amd64") : "amd64",
- ("amd64","x86") : "amd64_x86",
- ("amd64","arm") : "amd64_arm",
- ("amd64","arm64") : "amd64_arm64",
- ("x86","amd64") : "x86_amd64",
- ("x86","x86") : "",
- ("x86","arm") : "x86_arm",
- ("x86","arm64") : "x86_arm64",
- ("arm","arm") : "arm",
-}
+_GE2017_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')),
-# 14.1 (VS2017) and later:
-# Given a (host, target) tuple, return the batch file to look for.
-# We can't rely on returning an arg to use for vcvarsall.bat,
-# because that script will run even if given a pair that isn't installed.
-# Targets that already look like a pair are pseudo targets that
-# effectively mean to skip whatever the host was specified as.
-_HOST_TARGET_TO_BAT_ARCH_GT14 = {
- ("amd64", "amd64"): "vcvars64.bat",
- ("amd64", "x86"): "vcvarsamd64_x86.bat",
- ("amd64", "x86_amd64"): "vcvarsx86_amd64.bat",
- ("amd64", "x86_x86"): "vcvars32.bat",
- ("amd64", "arm"): "vcvarsamd64_arm.bat",
- ("amd64", "x86_arm"): "vcvarsx86_arm.bat",
- ("amd64", "arm64"): "vcvarsamd64_arm64.bat",
- ("amd64", "x86_arm64"): "vcvarsx86_arm64.bat",
- ("x86", "x86"): "vcvars32.bat",
- ("x86", "amd64"): "vcvarsx86_amd64.bat",
- ("x86", "x86_amd64"): "vcvarsx86_amd64.bat",
- ("x86", "arm"): "vcvarsx86_arm.bat",
- ("x86", "x86_arm"): "vcvarsx86_arm.bat",
- ("x86", "arm64"): "vcvarsx86_arm64.bat",
- ("x86", "x86_arm64"): "vcvarsx86_arm64.bat",
}
-# before 14.1 (VS2017):
-# Given a (host, target) tuple, return the argument for the bat file;
-# Both host and target should be canoncalized.
-# If the target already looks like a pair, return it - these are
-# pseudo targets (mainly used by Express versions)
-_HOST_TARGET_ARCH_TO_BAT_ARCH = {
- ("x86", "x86"): "x86",
- ("x86", "amd64"): "x86_amd64",
- ("x86", "x86_amd64"): "x86_amd64",
- ("amd64", "x86_amd64"): "x86_amd64", # This is present in (at least) VS2012 express
- ("amd64", "amd64"): "amd64",
- ("amd64", "x86"): "x86",
- ("amd64", "x86_x86"): "x86",
- ("x86", "ia64"): "x86_ia64", # gone since 14.0
- ("x86", "arm"): "x86_arm", # since 14.0
- ("x86", "arm64"): "x86_arm64", # since 14.1
- ("amd64", "arm"): "amd64_arm", # since 14.0
- ("amd64", "arm64"): "amd64_arm64", # since 14.1
- ("x86", "x86_arm"): "x86_arm", # since 14.0
- ("x86", "x86_arm64"): "x86_arm64", # since 14.1
- ("amd64", "x86_arm"): "x86_arm", # since 14.0
- ("amd64", "x86_arm64"): "x86_arm64", # since 14.1
+_GE2017_HOST_TARGET_CFG = _host_target_config_factory(
+
+ label = 'GE2017',
+
+ 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': [],
+ 'arm': [],
+ },
+
+ host_def_targets = {
+ 'amd64': ['amd64', 'x86'],
+ 'x86': ['x86'],
+ 'arm64': ['arm64', 'arm'],
+ 'arm': ['arm'],
+ },
+
+)
+
+# debug("_GE2017_HOST_TARGET_CFG: %s", _GE2017_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')),
+
+ ('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']),
+ ('arm', ['arm']),
+ ('ia64', ['ia64']),
+ ]),
+
+ host_all_targets = {
+ 'amd64': ['amd64', 'x86', 'arm'],
+ 'x86': ['x86', 'amd64', 'arm', 'ia64'],
+ 'arm': ['arm'],
+ 'ia64': ['ia64'],
+ },
+
+ host_def_targets = {
+ 'amd64': ['amd64', 'x86'],
+ 'x86': ['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']),
+ ]),
+
+ host_all_targets = {
+ 'amd64': ['x86'],
+ 'x86': ['x86'],
+ },
+
+ host_def_targets = {
+ 'amd64': ['x86'],
+ 'x86': ['x86'],
+ },
+
+)
+
+# debug("_LE2003_HOST_TARGET_CFG: %s", _LE2003_HOST_TARGET_CFG)
+
_CL_EXE_NAME = 'cl.exe'
def get_msvc_version_numeric(msvc_version):
@@ -192,44 +415,100 @@ def get_msvc_version_numeric(msvc_version):
"""
return ''.join([x for x in msvc_version if x in string_digits + '.'])
-def get_host_target(env):
- host_platform = env.get('HOST_ARCH')
- debug("HOST_ARCH:%s", str(host_platform))
- if not host_platform:
- host_platform = platform.machine()
+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 host_platform == 'i86pc':
if platform.architecture()[0] == "64bit":
host_platform = "amd64"
else:
host_platform = "x86"
- # Retain user requested TARGET_ARCH
- req_target_platform = env.get('TARGET_ARCH')
- debug("TARGET_ARCH:%s", str(req_target_platform))
- if req_target_platform:
- # If user requested a specific platform then only try that one.
- target_platform = req_target_platform
- else:
- target_platform = host_platform
-
try:
- host = _ARCH_TO_CANONICAL[host_platform.lower()]
+ host =_ARCH_TO_CANONICAL[host_platform]
except KeyError:
msg = "Unrecognized host architecture %s"
raise MSVCUnsupportedHostArch(msg % repr(host_platform)) from None
- try:
- target = _ARCH_TO_CANONICAL[target_platform.lower()]
- except KeyError:
- all_archs = str(list(_ARCH_TO_CANONICAL.keys()))
- raise MSVCUnsupportedTargetArch(
- "Unrecognized target architecture %s\n\tValid architectures: %s"
- % (target_platform, all_archs)
- ) from None
+ return host
- return (host, target, req_target_platform)
+_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())
+
+ return _native_host_platform
+
+def get_host_target(env, msvc_version, all_host_targets=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:
+ # 14.0 (VS2015) to 8.0 (VS2005)
+ host_target_cfg = _LE2015_HOST_TARGET_CFG
+ else:
+ # 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)
# If you update this, update SupportedVSList in Tool/MSCommon/vs.py, and the
# MSVC_VERSION documentation in Tool/msvc.xml.
@@ -336,29 +615,6 @@ def msvc_version_to_maj_min(msvc_version):
raise ValueError("Unrecognized version %s (%s)" % (msvc_version,msvc_version_numeric)) from None
-def is_host_target_supported(host_target, msvc_version):
- """Check if (host, target) pair is supported for a VC version.
-
- Only checks whether a given version *may* support the given
- (host, target) pair, not that the toolchain is actually on the machine.
-
- Args:
- host_target: canonalized host-target pair, e.g.
- ("x86", "amd64") for cross compilation from 32- to 64-bit Windows.
- msvc_version: Visual C++ version (major.minor), e.g. "10.0"
-
- Returns:
- True or False
-
- """
- # We assume that any Visual Studio version supports x86 as a target
- if host_target[1] != "x86":
- maj, min = msvc_version_to_maj_min(msvc_version)
- if maj < 8:
- return False
- return True
-
-
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"),
@@ -493,15 +749,14 @@ 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(env, msvc_version, host_arch, target_arch):
"""
Find the location of the batch script which should set up the compiler
for any TARGET_ARCH whose compilers were installed by Visual Studio/VCExpress
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 indication we don't need the argument
- we would have computed to run vcvarsall.bat.
+ so use that and return an empty argument.
"""
pdir = find_vc_pdir(env, msvc_version)
if pdir is None:
@@ -510,19 +765,26 @@ def find_batch_file(env,msvc_version,host_arch,target_arch):
# filter out e.g. "Exp" from the version name
msvc_ver_numeric = get_msvc_version_numeric(msvc_version)
- use_arg = True
vernum = float(msvc_ver_numeric)
- if vernum < 8:
+
+ arg = ''
+ if vernum > 14:
+ # 14.1 (VS2017) and later
+ batfiledir = os.path.join(pdir, "Auxiliary", "Build")
+ batfile, _ = _GE2017_HOST_TARGET_BATCHFILE_CLPATHCOMPS[(host_arch, target_arch)]
+ batfilename = os.path.join(batfiledir, batfile)
+ elif 14 >= vernum >= 8:
+ # 14.0 (VS2015) to 8.0 (VS2005)
+ arg, _ = _LE2015_HOST_TARGET_BATCHARG_CLPATHCOMPS[(host_arch, target_arch)]
+ batfilename = os.path.join(pdir, "vcvarsall.bat")
+ elif 8 > vernum >= 7:
+ # 7.1 (VS2003) to 7.0 (VS2003)
+ pdir = os.path.join(pdir, "Bin")
+ batfilename = os.path.join(pdir, "vcvars32.bat")
+ else:
+ # 6.0 (VS6) and earlier
pdir = os.path.join(pdir, "Bin")
batfilename = os.path.join(pdir, "vcvars32.bat")
- use_arg = False
- elif 8 <= vernum <= 14:
- batfilename = os.path.join(pdir, "vcvarsall.bat")
- else: # vernum >= 14.1 VS2017 and above
- batfiledir = os.path.join(pdir, "Auxiliary", "Build")
- targ = _HOST_TARGET_TO_BAT_ARCH_GT14[(host_arch, target_arch)]
- batfilename = os.path.join(batfiledir, targ)
- use_arg = False
if not os.path.exists(batfilename):
debug("Not found: %s", batfilename)
@@ -530,16 +792,16 @@ def find_batch_file(env,msvc_version,host_arch,target_arch):
installed_sdks = get_installed_sdks()
for _sdk in installed_sdks:
- sdk_bat_file = _sdk.get_sdk_vc_script(host_arch,target_arch)
+ sdk_bat_file = _sdk.get_sdk_vc_script(host_arch, target_arch)
if not sdk_bat_file:
debug("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(pdir, sdk_bat_file)
if os.path.exists(sdk_bat_file_path):
debug('sdk_bat_file_path:%s', sdk_bat_file_path)
- return (batfilename, use_arg, sdk_bat_file_path)
- return (batfilename, use_arg, None)
+ return (batfilename, arg, sdk_bat_file_path)
+ return (batfilename, arg, None)
__INSTALLED_VCS_RUN = None
_VC_TOOLS_VERSION_FILE_PATH = ['Auxiliary', 'Build', 'Microsoft.VCToolsVersion.default.txt']
@@ -550,8 +812,8 @@ def _check_cl_exists_in_vc_dir(env, vc_dir, msvc_version):
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 it is None, in which case the native platform is
- assumed for both host and target.
+ 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
@@ -568,24 +830,16 @@ def _check_cl_exists_in_vc_dir(env, vc_dir, msvc_version):
"""
- # determine if there is a specific target platform we want to build for and
- # use that to find a list of valid VCs, default is host platform == target platform
- # and same for if no env is specified to extract target platform from
- if env:
- (host_platform, target_platform, req_target_platform) = get_host_target(env)
- else:
- host_platform = platform.machine().lower()
- target_platform = host_platform
-
- host_platform = _ARCH_TO_CANONICAL[host_platform]
- target_platform = _ARCH_TO_CANONICAL[target_platform]
+ # 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
- debug('host platform %s, target platform %s for version %s', host_platform, target_platform, msvc_version)
-
- ver_num = float(get_msvc_version_numeric(msvc_version))
+ vernum = float(get_msvc_version_numeric(msvc_version))
# make sure the cl.exe exists meaning the tool is installed
- if ver_num > 14:
+ if vernum > 14:
+ # 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
@@ -601,63 +855,54 @@ def _check_cl_exists_in_vc_dir(env, vc_dir, msvc_version):
debug('failed to find MSVC version in %s', default_toolset_file)
return False
- host_trgt_dir = _HOST_TARGET_TO_CL_DIR_GREATER_THAN_14.get((host_platform, target_platform), None)
- if host_trgt_dir is None:
- debug('unsupported host/target platform combo: (%s,%s)', host_platform, target_platform)
- return False
+ for host_platform, target_platform in host_target_list:
+
+ debug('host platform %s, target platform %s for version %s', host_platform, target_platform, msvc_version)
- cl_path = os.path.join(vc_dir, 'Tools','MSVC', vc_specific_version, 'bin', host_trgt_dir[0], host_trgt_dir[1], _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 host_platform == "amd64" and host_trgt_dir[0] == "Hostx64":
- # Special case: fallback to Hostx86 if Hostx64 was tried
- # and failed. This is because VS 2017 Express running on amd64
- # will look to our probe like the host dir should be Hostx64,
- # but Express uses Hostx86 anyway.
- # We should key this off the "x86_amd64" and related pseudo
- # targets, but we don't see those in this function.
- host_trgt_dir = ("Hostx86", host_trgt_dir[1])
- cl_path = os.path.join(vc_dir, 'Tools','MSVC', vc_specific_version, 'bin', host_trgt_dir[0], host_trgt_dir[1], _CL_EXE_NAME)
+ batchfile_clpathcomps = _GE2017_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 14 >= ver_num >= 8:
- # Set default value to be -1 as "", which is the value for x86/x86,
- # yields true when tested if not host_trgt_dir
- host_trgt_dir = _HOST_TARGET_TO_CL_DIR.get((host_platform, target_platform), None)
- if host_trgt_dir is None:
- debug('unsupported host/target platform combo')
- return False
+ elif 14 >= vernum >= 8:
+ # 14.0 (VS2015) to 8.0 (VS2005)
- cl_path = os.path.join(vc_dir, 'bin', host_trgt_dir, _CL_EXE_NAME)
- debug('checking for %s at %s', _CL_EXE_NAME, cl_path)
+ cl_path_prefixes = [None]
+ if msvc_version == '9.0':
+ # Visual C++ for Python registry key is installdir (root) not productdir (vc)
+ cl_path_prefixes.append(('VC',))
- cl_path_exists = os.path.exists(cl_path)
- if not cl_path_exists and host_platform == 'amd64':
- # older versions of visual studio only had x86 binaries,
- # so if the host platform is amd64, we need to check cross
- # compile options (x86 binary compiles some other target on a 64 bit os)
+ for host_platform, target_platform in host_target_list:
- # Set default value to be -1 as "" which is the value for x86/x86 yields true when tested
- # if not host_trgt_dir
- host_trgt_dir = _HOST_TARGET_TO_CL_DIR.get(('x86', target_platform), None)
- if host_trgt_dir is None:
- return False
+ debug('host platform %s, target platform %s for version %s', host_platform, target_platform, msvc_version)
- cl_path = os.path.join(vc_dir, 'bin', host_trgt_dir, _CL_EXE_NAME)
- debug('checking for %s at %s', _CL_EXE_NAME, cl_path)
- cl_path_exists = os.path.exists(cl_path)
+ 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
+ for cl_path_prefix in cl_path_prefixes:
- if cl_path_exists:
- debug('found %s', _CL_EXE_NAME)
- return True
+ cl_path_comps_adj = cl_path_prefix + cl_path_comps if cl_path_prefix else cl_path_comps
+ cl_path = os.path.join(vc_dir, *cl_path_comps_adj, _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 8 > vernum >= 6:
+ # 7.1 (VS2003) to 6.0 (VS6)
- elif 8 > ver_num >= 6:
# 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', ''):
@@ -673,9 +918,10 @@ def _check_cl_exists_in_vc_dir(env, vc_dir, msvc_version):
debug('%s found %s', _CL_EXE_NAME, cl_path)
return True
return False
+
else:
# version not support return false
- debug('unsupported MSVC version: %s', str(ver_num))
+ debug('unsupported MSVC version: %s', str(vernum))
return False
@@ -747,7 +993,7 @@ def script_env(script, args=None):
# Stupid batch files do not set return code: we take a look at the
# beginning of the output for an error message instead
olines = stdout.splitlines()
- if olines[0].startswith("The specified configuration type is missing"):
+ if re_script_output_error.match(olines[0]):
raise BatchFileExecutionError("\n".join(olines[:2]))
cache_data = common.parse_output(stdout)
@@ -814,57 +1060,20 @@ def msvc_find_valid_batch_script(env, version):
get it right.
"""
- # Find the host, target, and if present the requested target:
- platforms = get_host_target(env)
- debug("host_platform %s, target_platform %s req_target_platform %s", *platforms)
- host_platform, target_platform, req_target_platform = platforms
-
- # Most combinations of host + target are straightforward.
- # While all MSVC / Visual Studio tools are pysically 32-bit, they
- # make it look like there are 64-bit tools if the host is 64-bit,
- # so you can invoke the environment batch script to set up to build,
- # say, amd64 host -> x86 target. Express versions are an exception:
- # they always look 32-bit, so the batch scripts with 64-bit
- # host parts are absent. We try to fix that up in a couple of ways.
- # One is here: we make a table of "targets" to try, with the extra
- # targets being tags that tell us to try a different "host" instead
- # of the deduced host.
- try_target_archs = [target_platform]
- if req_target_platform in ('amd64', 'x86_64'):
- try_target_archs.append('x86_amd64')
- elif req_target_platform in ('x86',):
- try_target_archs.append('x86_x86')
- elif req_target_platform in ('arm',):
- try_target_archs.append('x86_arm')
- elif req_target_platform in ('arm64',):
- try_target_archs.append('x86_arm64')
- elif not req_target_platform:
- if target_platform in ('amd64', 'x86_64'):
- try_target_archs.append('x86_amd64')
- # If the user hasn't specifically requested a TARGET_ARCH,
- # and the TARGET_ARCH is amd64 then also try 32 bits
- # if there are no viable 64 bit tools installed
- try_target_archs.append('x86')
-
- debug("host_platform: %s, try_target_archs: %s", host_platform, try_target_archs)
+ # 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
- for tp in try_target_archs:
+ for host_arch, target_arch, in host_target_list:
# Set to current arch.
- env['TARGET_ARCH'] = tp
-
- debug("trying target_platform:%s", tp)
- host_target = (host_platform, tp)
- if not is_host_target_supported(host_target, version):
- warn_msg = "host, target = %s not supported for MSVC version %s" % \
- (host_target, version)
- SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, warn_msg)
- arg = _HOST_TARGET_ARCH_TO_BAT_ARCH[host_target]
+ env['TARGET_ARCH'] = target_arch
# Try to locate a batch file for this host/target platform combo
try:
- (vc_script, use_arg, sdk_script) = find_batch_file(env, version, host_platform, tp)
- debug('vc_script:%s sdk_script:%s', vc_script, sdk_script)
+ (vc_script, arg, 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)
except VisualCException as e:
msg = str(e)
debug('Caught exception while looking for batch file (%s)', msg)
@@ -879,8 +1088,6 @@ def msvc_find_valid_batch_script(env, version):
debug('use_script 2 %s, args:%s', repr(vc_script), arg)
found = None
if vc_script:
- if not use_arg:
- arg = '' # bat file will supply platform type
# Get just version numbers
maj, min = msvc_version_to_maj_min(version)
# VS2015+
@@ -914,7 +1121,7 @@ def msvc_find_valid_batch_script(env, version):
# If we cannot find a viable installed compiler, reset the TARGET_ARCH
# To it's initial value
if not d:
- env['TARGET_ARCH']=req_target_platform
+ env['TARGET_ARCH'] = target_platform
return d
diff --git a/SCons/Tool/MSCommon/vcTests.py b/SCons/Tool/MSCommon/vcTests.py
index eb09def..2b6fbe5 100644
--- a/SCons/Tool/MSCommon/vcTests.py
+++ b/SCons/Tool/MSCommon/vcTests.py
@@ -114,13 +114,14 @@ class MSVcTestCase(unittest.TestCase):
check = SCons.Tool.MSCommon.vc._check_cl_exists_in_vc_dir
env={'TARGET_ARCH':'x86'}
- p = SCons.Tool.MSCommon.vc._HOST_TARGET_TO_CL_DIR[('x86','x86')]
- MSVcTestCase._createDummyCl(p)
+ _, clpathcomps = SCons.Tool.MSCommon.vc._LE2015_HOST_TARGET_BATCHARG_CLPATHCOMPS[('x86','x86')]
+ path = os.path.join('.', *clpathcomps)
+ MSVcTestCase._createDummyCl(path, add_bin=False)
# print("retval:%s"%check(env, '.', '8.0'))
- # Setup for VC 14+ tests
+ # Setup for 14.1 (VS2017) and later tests
# Create the VC minor/major version file
tools_version_file = SCons.Tool.MSCommon.vc._VC_TOOLS_VERSION_FILE
@@ -134,25 +135,26 @@ class MSVcTestCase(unittest.TestCase):
print("Failed trying to write :%s :%s"%(tools_version_file, e))
- # Now walk all the valid combinations of host/target for VC 14 +
- vc_gt_14_map = SCons.Tool.MSCommon.vc._HOST_TARGET_TO_CL_DIR_GREATER_THAN_14
+ # 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
- for key, value in vc_gt_14_map.items():
- # print("GT 14 Got: %s -> %s"%(key,value))
+ 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))
- env={'TARGET_ARCH':key[1], 'HOST_ARCH':key[0]}
- path = os.path.join('.','Tools','MSVC', MS_TOOLS_VERSION, 'bin', value[0], value[1])
+ 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.1')
- # print("for:%s got :%s"%(key[1], result))
- self.assertTrue(result, "Checking host: %s target: %s"%(value[0], value[1]))
+ # print("for:(%s, %s) got :%s"%(host, target, result))
+ self.assertTrue(result, "Checking host: %s target: %s" % (host, target))
# Now test bogus value for HOST_ARCH
env={'TARGET_ARCH':'x86', 'HOST_ARCH':'GARBAGE'}
try:
result=check(env, '.', '14.1')
# print("for:%s got :%s"%(env, result))
- self.assertFalse(result, "Did not fail with bogus HOST_ARCH host: %s target: %s"%(value[0], value[1]))
+ self.assertFalse(result, "Did not fail with bogus HOST_ARCH host: %s target: %s" % (env['HOST_ARCH'], env['TARGET_ARCH']))
except MSVCUnsupportedHostArch:
pass
else:
@@ -163,22 +165,24 @@ class MSVcTestCase(unittest.TestCase):
try:
result=check(env, '.', '14.1')
# print("for:%s got :%s"%(env, result))
- self.assertFalse(result, "Did not fail with bogus TARGET_ARCH host: %s target: %s"%(value[0], value[1]))
+ 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 HOST_ARCH specified as: %s'%env['TARGET_ARCH'])
-
- # Test >8 < 14 VC versions
- vc_map = SCons.Tool.MSCommon.vc._HOST_TARGET_TO_CL_DIR
- for key,value in vc_map.items():
- # print("LT 14 Got: %s -> %s"%(key,value))
- env={'TARGET_ARCH':key[1], 'HOST_ARCH':key[0]}
- path = os.path.join('.', 'bin', value )
+ self.fail('Did not fail when TARGET_ARCH specified as: %s' % env['TARGET_ARCH'])
+
+ # Test 14.0 (VS2015) to 8.0 (VS2005) versions
+ vc_le2015_list = SCons.Tool.MSCommon.vc._LE2015_HOST_TARGET_CFG.all_pairs
+
+ for host, target in vc_le2015_list:
+ batarg, clpathcomps = SCons.Tool.MSCommon.vc._LE2015_HOST_TARGET_BATCHARG_CLPATHCOMPS[(host, target)]
+ # print("LE 14 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)
result=check(env, '.', '9.0')
- # print("for:%s got :%s"%(key[1], result))
- self.assertTrue(result, "Checking host: %s target: %s"%(key[0], key[1]))
+ # print("for:(%s, %s) got :%s"%(host, target, result))
+ self.assertTrue(result, "Checking host: %s target: %s" % (host, target))
# Now test bogus value for HOST_ARCH
env={'TARGET_ARCH':'x86', 'HOST_ARCH':'GARBAGE'}
@@ -189,7 +193,7 @@ class MSVcTestCase(unittest.TestCase):
except MSVCUnsupportedHostArch:
pass
else:
- self.fail('Did not fail when HOST_ARCH specified as: %s'%env['HOST_ARCH'])
+ 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'}
@@ -200,7 +204,41 @@ class MSVcTestCase(unittest.TestCase):
except MSVCUnsupportedTargetArch:
pass
else:
- self.fail('Did not fail when HOST_ARCH specified as: %s'%env['TARGET_ARCH'])
+ self.fail('Did not fail when TARGET_ARCH specified as: %s' % env['TARGET_ARCH'])
+
+ # Test 7.1 (VS2003) and earlier
+ vc_le2003_list = SCons.Tool.MSCommon.vc._LE2003_HOST_TARGET_CFG.all_pairs
+
+ for host, target in vc_le2003_list:
+ # print("LE 7.1 Got: (%s, %s)"%(host,target))
+ env={'TARGET_ARCH':target, 'HOST_ARCH':host}
+ path = os.path.join('.')
+ MSVcTestCase._createDummyCl(path)
+ result=check(env, '.', '6.0')
+ # print("for:(%s, %s) got :%s"%(host, target, result))
+ self.assertTrue(result, "Checking host: %s target: %s" % (host, target))
+
+ # Now test bogus value for HOST_ARCH
+ env={'TARGET_ARCH':'x86', 'HOST_ARCH':'GARBAGE'}
+ try:
+ result=check(env, '.', '6.0')
+ # print("for:%s got :%s"%(env, result))
+ self.assertFalse(result, "Did not fail with bogus HOST_ARCH host: %s target: %s" % (env['HOST_ARCH'], env['TARGET_ARCH']))
+ except MSVCUnsupportedHostArch:
+ pass
+ else:
+ self.fail('Did not fail when HOST_ARCH specified as: %s' % env['HOST_ARCH'])
+
+ # Now test bogus value for TARGET_ARCH
+ env={'TARGET_ARCH':'GARBAGE', 'HOST_ARCH':'x86'}
+ try:
+ result=check(env, '.', '6.0')
+ # print("for:%s got :%s"%(env, result))
+ self.assertFalse(result, "Did not fail with bogus TARGET_ARCH host: %s target: %s" % (env['HOST_ARCH'], env['TARGET_ARCH']))
+ except MSVCUnsupportedTargetArch:
+ pass
+ else:
+ self.fail('Did not fail when TARGET_ARCH specified as: %s' % env['TARGET_ARCH'])