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:
John W. Parent 2024-04-18 17:53:56 -04:00 committed by GitHub
parent 112cead00b
commit 6fba31ce34
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 58 additions and 44 deletions

View file

@ -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):

View file

@ -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