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