diff --git a/lib/spack/spack/build_environment.py b/lib/spack/spack/build_environment.py index 00c1c5ab4f..a74816ef62 100644 --- a/lib/spack/spack/build_environment.py +++ b/lib/spack/spack/build_environment.py @@ -43,7 +43,7 @@ from collections import defaultdict from enum import Flag, auto from itertools import chain -from typing import List, Set, Tuple +from typing import Dict, List, Set, Tuple import llnl.util.tty as tty from llnl.string import plural @@ -730,12 +730,28 @@ def _static_to_shared_library(arch, compiler, static_lib, shared_lib=None, **kwa return compiler(*compiler_args, output=compiler_output) -def get_rpath_deps(pkg): - """Return immediate or transitive RPATHs depending on the package.""" - if pkg.transitive_rpaths: - return [d for d in pkg.spec.traverse(root=False, deptype=("link"))] - else: - return pkg.spec.dependencies(deptype="link") +def _get_rpath_deps_from_spec( + spec: spack.spec.Spec, transitive_rpaths: bool +) -> List[spack.spec.Spec]: + if not transitive_rpaths: + return spec.dependencies(deptype=dt.LINK) + + by_name: Dict[str, spack.spec.Spec] = {} + + for dep in spec.traverse(root=False, deptype=dt.LINK): + lookup = by_name.get(dep.name) + if lookup is None: + by_name[dep.name] = dep + elif lookup.version < dep.version: + by_name[dep.name] = dep + + return list(by_name.values()) + + +def get_rpath_deps(pkg: spack.package_base.PackageBase) -> List[spack.spec.Spec]: + """Return immediate or transitive dependencies (depending on the package) that need to be + rpath'ed. If a package occurs multiple times, the newest version is kept.""" + return _get_rpath_deps_from_spec(pkg.spec, pkg.transitive_rpaths) def get_rpaths(pkg): diff --git a/lib/spack/spack/test/build_environment.py b/lib/spack/spack/test/build_environment.py index cb47ee5977..e053e1bebc 100644 --- a/lib/spack/spack/test/build_environment.py +++ b/lib/spack/spack/test/build_environment.py @@ -14,6 +14,7 @@ import spack.build_environment import spack.config +import spack.deptypes as dt import spack.package_base import spack.spec import spack.util.spack_yaml as syaml @@ -716,3 +717,21 @@ def test_build_system_globals_only_set_on_root_during_build(default_mock_concret for depth, spec in root.traverse(depth=True, root=True): for variable in build_variables: assert hasattr(spec.package.module, variable) == should_be_set(depth) + + +def test_rpath_with_duplicate_link_deps(): + """If we have two instances of one package in the same link sub-dag, only the newest version is + rpath'ed. This is for runtime support without splicing.""" + runtime_1 = spack.spec.Spec("runtime@=1.0") + runtime_2 = spack.spec.Spec("runtime@=2.0") + child = spack.spec.Spec("child@=1.0") + root = spack.spec.Spec("root@=1.0") + + root.add_dependency_edge(child, depflag=dt.LINK, virtuals=()) + root.add_dependency_edge(runtime_2, depflag=dt.LINK, virtuals=()) + child.add_dependency_edge(runtime_1, depflag=dt.LINK, virtuals=()) + + rpath_deps = spack.build_environment._get_rpath_deps_from_spec(root, transitive_rpaths=True) + assert child in rpath_deps + assert runtime_2 in rpath_deps + assert runtime_1 not in rpath_deps