Windows: Update MSVC + oneAPI detection and integration (#43646)
* Later versions of oneAPI have moved, so update detection to find it in both old and new location * Remove reliance on ONEAPI_ROOT env variable when determining Fortran compiler version for %msvc * When finding a Fortran compiler for MSVC, there was logic enforcing a maximum MSVC version for a given oneAPI Fortran version. This mapping was out of date and excluding valid combinations, so has been removed (the logic now just picks the latest available oneAPI Fortran compiler for any given MSVC version).
This commit is contained in:
parent
112cead00b
commit
6fba31ce34
2 changed files with 58 additions and 44 deletions
|
@ -8,7 +8,7 @@
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
from typing import Dict, List, Set
|
from typing import Dict, List
|
||||||
|
|
||||||
import archspec.cpu
|
import archspec.cpu
|
||||||
|
|
||||||
|
@ -20,15 +20,7 @@
|
||||||
from spack.error import SpackError
|
from spack.error import SpackError
|
||||||
from spack.version import Version, VersionRange
|
from spack.version import Version, VersionRange
|
||||||
|
|
||||||
avail_fc_version: Set[str] = set()
|
FC_PATH: Dict[str, str] = dict()
|
||||||
fc_path: Dict[str, str] = dict()
|
|
||||||
|
|
||||||
fortran_mapping = {
|
|
||||||
"2021.3.0": "19.29.30133",
|
|
||||||
"2021.2.1": "19.28.29913",
|
|
||||||
"2021.2.0": "19.28.29334",
|
|
||||||
"2021.1.0": "19.28.29333",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class CmdCall:
|
class CmdCall:
|
||||||
|
@ -115,15 +107,13 @@ def command_str(self):
|
||||||
return f"{script} {self.arch} {self.sdk_ver} {self.vcvars_ver}"
|
return f"{script} {self.arch} {self.sdk_ver} {self.vcvars_ver}"
|
||||||
|
|
||||||
|
|
||||||
def get_valid_fortran_pth(comp_ver):
|
def get_valid_fortran_pth():
|
||||||
cl_ver = str(comp_ver)
|
"""Assign maximum available fortran compiler version"""
|
||||||
|
# TODO (johnwparent): validate compatibility w/ try compiler
|
||||||
|
# functionality when added
|
||||||
sort_fn = lambda fc_ver: Version(fc_ver)
|
sort_fn = lambda fc_ver: Version(fc_ver)
|
||||||
sort_fc_ver = sorted(list(avail_fc_version), key=sort_fn)
|
sort_fc_ver = sorted(list(FC_PATH.keys()), key=sort_fn)
|
||||||
for ver in sort_fc_ver:
|
return FC_PATH[sort_fc_ver[-1]] if sort_fc_ver else None
|
||||||
if ver in fortran_mapping:
|
|
||||||
if Version(cl_ver) <= Version(fortran_mapping[ver]):
|
|
||||||
return fc_path[ver]
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class Msvc(Compiler):
|
class Msvc(Compiler):
|
||||||
|
@ -167,11 +157,9 @@ def __init__(self, *args, **kwargs):
|
||||||
# This positional argument "paths" is later parsed and process by the base class
|
# This positional argument "paths" is later parsed and process by the base class
|
||||||
# via the call to `super` later in this method
|
# via the call to `super` later in this method
|
||||||
paths = args[3]
|
paths = args[3]
|
||||||
# This positional argument "cspec" is also parsed and handled by the base class
|
latest_fc = get_valid_fortran_pth()
|
||||||
# constructor
|
new_pth = [pth if pth else latest_fc for pth in paths[2:]]
|
||||||
cspec = args[0]
|
paths[2:] = new_pth
|
||||||
new_pth = [pth if pth else get_valid_fortran_pth(cspec.version) for pth in paths]
|
|
||||||
paths[:] = new_pth
|
|
||||||
# Initialize, deferring to base class but then adding the vcvarsallfile
|
# Initialize, deferring to base class but then adding the vcvarsallfile
|
||||||
# file based on compiler executable path.
|
# file based on compiler executable path.
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
@ -183,7 +171,7 @@ def __init__(self, *args, **kwargs):
|
||||||
# and stores their path, but their respective VCVARS
|
# and stores their path, but their respective VCVARS
|
||||||
# file must be invoked before useage.
|
# file must be invoked before useage.
|
||||||
env_cmds = []
|
env_cmds = []
|
||||||
compiler_root = os.path.join(self.cc, "../../../../../../..")
|
compiler_root = os.path.join(os.path.dirname(self.cc), "../../../../../..")
|
||||||
vcvars_script_path = os.path.join(compiler_root, "Auxiliary", "Build", "vcvars64.bat")
|
vcvars_script_path = os.path.join(compiler_root, "Auxiliary", "Build", "vcvars64.bat")
|
||||||
# get current platform architecture and format for vcvars argument
|
# get current platform architecture and format for vcvars argument
|
||||||
arch = spack.platforms.real_host().default.lower()
|
arch = spack.platforms.real_host().default.lower()
|
||||||
|
@ -198,11 +186,34 @@ def __init__(self, *args, **kwargs):
|
||||||
# paths[2] refers to the fc path and is a generic check
|
# paths[2] refers to the fc path and is a generic check
|
||||||
# for a fortran compiler
|
# for a fortran compiler
|
||||||
if paths[2]:
|
if paths[2]:
|
||||||
|
|
||||||
|
def get_oneapi_root(pth: str):
|
||||||
|
"""From within a prefix known to be a oneAPI path
|
||||||
|
determine the oneAPI root path from arbitrary point
|
||||||
|
under root
|
||||||
|
|
||||||
|
Args:
|
||||||
|
pth: path prefixed within oneAPI root
|
||||||
|
"""
|
||||||
|
if not pth:
|
||||||
|
return ""
|
||||||
|
while os.path.basename(pth) and os.path.basename(pth) != "oneAPI":
|
||||||
|
pth = os.path.dirname(pth)
|
||||||
|
return pth
|
||||||
|
|
||||||
# If this found, it sets all the vars
|
# If this found, it sets all the vars
|
||||||
oneapi_root = os.path.join(self.cc, "../../..")
|
oneapi_root = get_oneapi_root(self.fc)
|
||||||
|
if not oneapi_root:
|
||||||
|
raise RuntimeError(f"Non-oneAPI Fortran compiler {self.fc} assigned to MSVC")
|
||||||
oneapi_root_setvars = os.path.join(oneapi_root, "setvars.bat")
|
oneapi_root_setvars = os.path.join(oneapi_root, "setvars.bat")
|
||||||
|
# some oneAPI exes return a version more precise than their
|
||||||
|
# install paths specify, so we determine path from
|
||||||
|
# the install path rather than the fc executable itself
|
||||||
|
numver = r"\d+\.\d+(?:\.\d+)?"
|
||||||
|
pattern = f"((?:{numver})|(?:latest))"
|
||||||
|
version_from_path = re.search(pattern, self.fc).group(1)
|
||||||
oneapi_version_setvars = os.path.join(
|
oneapi_version_setvars = os.path.join(
|
||||||
oneapi_root, "compiler", str(self.ifx_version), "env", "vars.bat"
|
oneapi_root, "compiler", version_from_path, "env", "vars.bat"
|
||||||
)
|
)
|
||||||
# order matters here, the specific version env must be invoked first,
|
# order matters here, the specific version env must be invoked first,
|
||||||
# otherwise it will be ignored if the root setvars sets up the oneapi
|
# otherwise it will be ignored if the root setvars sets up the oneapi
|
||||||
|
@ -314,23 +325,19 @@ def setup_custom_environment(self, pkg, env):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def fc_version(cls, fc):
|
def fc_version(cls, fc):
|
||||||
# We're using intel for the Fortran compilers, which exist if
|
|
||||||
# ONEAPI_ROOT is a meaningful variable
|
|
||||||
if not sys.platform == "win32":
|
if not sys.platform == "win32":
|
||||||
return "unknown"
|
return "unknown"
|
||||||
fc_ver = cls.default_version(fc)
|
fc_ver = cls.default_version(fc)
|
||||||
avail_fc_version.add(fc_ver)
|
FC_PATH[fc_ver] = fc
|
||||||
fc_path[fc_ver] = fc
|
try:
|
||||||
if os.getenv("ONEAPI_ROOT"):
|
sps = spack.operating_systems.windows_os.WindowsOs().compiler_search_paths
|
||||||
try:
|
except AttributeError:
|
||||||
sps = spack.operating_systems.windows_os.WindowsOs().compiler_search_paths
|
raise SpackError(
|
||||||
except AttributeError:
|
"Windows compiler search paths not established, "
|
||||||
raise SpackError("Windows compiler search paths not established")
|
"please report this behavior to github.com/spack/spack"
|
||||||
clp = spack.util.executable.which_string("cl", path=sps)
|
)
|
||||||
ver = cls.default_version(clp)
|
clp = spack.util.executable.which_string("cl", path=sps)
|
||||||
else:
|
return cls.default_version(clp) if clp else fc_ver
|
||||||
ver = fc_ver
|
|
||||||
return ver
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def f77_version(cls, f77):
|
def f77_version(cls, f77):
|
||||||
|
|
|
@ -73,17 +73,24 @@ def vs_install_paths(self):
|
||||||
def msvc_paths(self):
|
def msvc_paths(self):
|
||||||
return [os.path.join(path, "VC", "Tools", "MSVC") for path in self.vs_install_paths]
|
return [os.path.join(path, "VC", "Tools", "MSVC") for path in self.vs_install_paths]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def oneapi_root(self):
|
||||||
|
root = os.environ.get("ONEAPI_ROOT", "") or os.path.join(
|
||||||
|
os.environ.get("ProgramFiles(x86)", ""), "Intel", "oneAPI"
|
||||||
|
)
|
||||||
|
if os.path.exists(root):
|
||||||
|
return root
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def compiler_search_paths(self):
|
def compiler_search_paths(self):
|
||||||
# First Strategy: Find MSVC directories using vswhere
|
# First Strategy: Find MSVC directories using vswhere
|
||||||
_compiler_search_paths = []
|
_compiler_search_paths = []
|
||||||
for p in self.msvc_paths:
|
for p in self.msvc_paths:
|
||||||
_compiler_search_paths.extend(glob.glob(os.path.join(p, "*", "bin", "Hostx64", "x64")))
|
_compiler_search_paths.extend(glob.glob(os.path.join(p, "*", "bin", "Hostx64", "x64")))
|
||||||
if os.getenv("ONEAPI_ROOT"):
|
oneapi_root = self.oneapi_root
|
||||||
|
if oneapi_root:
|
||||||
_compiler_search_paths.extend(
|
_compiler_search_paths.extend(
|
||||||
glob.glob(
|
glob.glob(os.path.join(oneapi_root, "compiler", "**", "bin"), recursive=True)
|
||||||
os.path.join(str(os.getenv("ONEAPI_ROOT")), "compiler", "*", "windows", "bin")
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Second strategy: Find MSVC via the registry
|
# Second strategy: Find MSVC via the registry
|
||||||
|
|
Loading…
Reference in a new issue