MSVC: Restore amalgamated compiler functionality (#46678)

Right now the Spack %msvc compiler is inherently a hybrid compiler
that uses Intel's oneAPI fortran compiler.

This was addressed in Spacks MSVC compiler class, but detection has
since stopped using the compiler class, so this PR moves the logic
into the `msvc` compiler package (does not delete the original code
because that is handled in #45189).

This includes a change to the general detection logic to deprioritize
paths that include a symlink anywhere in the path, in order to prefer
"2025.0/bin" over "latest/bin" for the oneAPI compiler.
This commit is contained in:
John W. Parent 2025-03-14 16:36:41 -04:00 committed by GitHub
parent e2c6914dfe
commit 69b7c32b5d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 39 additions and 3 deletions

View File

@ -7,6 +7,7 @@
import collections
import concurrent.futures
import os
import pathlib
import re
import sys
import traceback
@ -15,6 +16,7 @@
import llnl.util.filesystem
import llnl.util.lang
import llnl.util.symlink
import llnl.util.tty
import spack.error
@ -70,13 +72,21 @@ def dedupe_paths(paths: List[str]) -> List[str]:
"""Deduplicate paths based on inode and device number. In case the list contains first a
symlink and then the directory it points to, the symlink is replaced with the directory path.
This ensures that we pick for example ``/usr/bin`` over ``/bin`` if the latter is a symlink to
the former`."""
the former."""
seen: Dict[Tuple[int, int], str] = {}
linked_parent_check = lambda x: any(
[llnl.util.symlink.islink(str(y)) for y in pathlib.Path(x).parents]
)
for path in paths:
identifier = file_identifier(path)
if identifier not in seen:
seen[identifier] = path
elif not os.path.islink(path):
# we also want to deprioritize paths if they contain a symlink in any parent
# (not just the basedir): e.g. oneapi has "latest/bin",
# where "latest" is a symlink to 2025.0"
elif not (llnl.util.symlink.islink(path) or linked_parent_check(path)):
seen[identifier] = path
return list(seen.values())

View File

@ -6,6 +6,17 @@
import spack.compiler
from spack.package import *
FC_PATH: Dict[str, str] = dict()
def get_latest_valid_fortran_pth():
"""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_fc_ver = sorted(list(FC_PATH.keys()), key=sort_fn)
return FC_PATH[sort_fc_ver[-1]] if sort_fc_ver else None
class Msvc(Package, CompilerPackage):
"""
@ -20,9 +31,11 @@ def install(self, spec, prefix):
"detected on a system where they are externally installed"
)
compiler_languages = ["c", "cxx"]
compiler_languages = ["c", "cxx", "fortran"]
c_names = ["cl"]
cxx_names = ["cl"]
fortran_names = ["ifx", "ifort"]
compiler_version_argument = ""
compiler_version_regex = r"([1-9][0-9]*\.[0-9]*\.[0-9]*)"
@ -30,11 +43,14 @@ def install(self, spec, prefix):
def determine_version(cls, exe):
# MSVC compiler does not have a proper version argument
# Errors out and prints version info with no args
is_ifx = "ifx.exe" in str(exe)
match = re.search(
cls.compiler_version_regex,
spack.compiler.get_compiler_version_output(exe, version_arg=None, ignore_errors=True),
)
if match:
if is_ifx:
FC_PATH[match.group(1)] = str(exe)
return match.group(1)
@classmethod
@ -42,6 +58,16 @@ def determine_variants(cls, exes, version_str):
# MSVC uses same executable for both languages
spec, extras = super().determine_variants(exes, version_str)
extras["compilers"]["c"] = extras["compilers"]["cxx"]
# This depends on oneapi being processed before msvc
# which is guarunteed from detection behavior.
# Processing oneAPI tracks oneAPI installations within
# this module, which are then used to populate compatible
# MSVC version's fortran compiler spots
# TODO: remove this once #45189 lands
# TODO: interrogate intel and msvc for compatibility after
# #45189 lands
extras["compilers"]["fortran"] = get_latest_valid_fortran_pth()
return spec, extras
@property