From 69b7c32b5d3847dfdd4122bf4b97fde801700103 Mon Sep 17 00:00:00 2001 From: "John W. Parent" <45471568+johnwparent@users.noreply.github.com> Date: Fri, 14 Mar 2025 16:36:41 -0400 Subject: [PATCH] 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. --- lib/spack/spack/detection/path.py | 14 ++++++++-- .../repos/builtin/packages/msvc/package.py | 28 ++++++++++++++++++- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/lib/spack/spack/detection/path.py b/lib/spack/spack/detection/path.py index f96a0276a35..00da8d639a3 100644 --- a/lib/spack/spack/detection/path.py +++ b/lib/spack/spack/detection/path.py @@ -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()) diff --git a/var/spack/repos/builtin/packages/msvc/package.py b/var/spack/repos/builtin/packages/msvc/package.py index ec365721b1e..347e969e236 100644 --- a/var/spack/repos/builtin/packages/msvc/package.py +++ b/var/spack/repos/builtin/packages/msvc/package.py @@ -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