diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/spack/spack/detection/path.py | 20 | ||||
-rw-r--r-- | lib/spack/spack/test/detection.py | 26 |
2 files changed, 43 insertions, 3 deletions
diff --git a/lib/spack/spack/detection/path.py b/lib/spack/spack/detection/path.py index 7316d9124a..e8081f6e3c 100644 --- a/lib/spack/spack/detection/path.py +++ b/lib/spack/spack/detection/path.py @@ -66,6 +66,21 @@ def file_identifier(path): return s.st_dev, s.st_ino +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`.""" + seen: Dict[Tuple[int, int], str] = {} + for path in paths: + identifier = file_identifier(path) + if identifier not in seen: + seen[identifier] = path + elif not os.path.islink(path): + seen[identifier] = path + return list(seen.values()) + + def executables_in_path(path_hints: List[str]) -> Dict[str, str]: """Get the paths of all executables available from the current PATH. @@ -82,8 +97,7 @@ def executables_in_path(path_hints: List[str]) -> Dict[str, str]: """ search_paths = llnl.util.filesystem.search_paths_for_executables(*path_hints) # Make use we don't doubly list /usr/lib and /lib etc - search_paths = list(llnl.util.lang.dedupe(search_paths, key=file_identifier)) - return path_to_dict(search_paths) + return path_to_dict(dedupe_paths(search_paths)) def accept_elf(path, host_compat): @@ -144,7 +158,7 @@ def libraries_in_ld_and_system_library_path( search_paths = list(filter(os.path.isdir, search_paths)) # Make use we don't doubly list /usr/lib and /lib etc - search_paths = list(llnl.util.lang.dedupe(search_paths, key=file_identifier)) + search_paths = dedupe_paths(search_paths) try: host_compat = elf_utils.get_elf_compat(sys.executable) diff --git a/lib/spack/spack/test/detection.py b/lib/spack/spack/test/detection.py index f699308287..4bad37fc91 100644 --- a/lib/spack/spack/test/detection.py +++ b/lib/spack/spack/test/detection.py @@ -7,6 +7,7 @@ import collections import spack.config import spack.detection import spack.detection.common +import spack.detection.path import spack.spec @@ -26,3 +27,28 @@ def test_detection_update_config(mutable_config): external_gcc = externals[0] assert external_gcc["spec"] == "cmake@3.27.5" assert external_gcc["prefix"] == "/usr/bin" + + +def test_dedupe_paths(tmp_path): + """Test that ``dedupe_paths`` deals with symlinked directories, retaining the target""" + x = tmp_path / "x" + y = tmp_path / "y" + z = tmp_path / "z" + + x.mkdir() + y.mkdir() + z.symlink_to("x", target_is_directory=True) + + # dedupe repeated dirs, should preserve order + assert spack.detection.path.dedupe_paths([str(x), str(y), str(x)]) == [str(x), str(y)] + assert spack.detection.path.dedupe_paths([str(y), str(x), str(y)]) == [str(y), str(x)] + + # dedupe repeated symlinks + assert spack.detection.path.dedupe_paths([str(z), str(y), str(z)]) == [str(z), str(y)] + assert spack.detection.path.dedupe_paths([str(y), str(z), str(y)]) == [str(y), str(z)] + + # when both symlink and target are present, only target is retained, and it comes at the + # priority of the first occurrence. + assert spack.detection.path.dedupe_paths([str(x), str(y), str(z)]) == [str(x), str(y)] + assert spack.detection.path.dedupe_paths([str(z), str(y), str(x)]) == [str(x), str(y)] + assert spack.detection.path.dedupe_paths([str(y), str(z), str(x)]) == [str(y), str(x)] |