1515from typing import Dict , List , Optional , Set , Tuple
1616
1717import llnl .util .filesystem
18+ import llnl .util .lang
1819import llnl .util .tty
1920
21+ import spack .util .elf as elf_utils
2022import spack .util .environment
23+ import spack .util .environment as environment
2124import spack .util .ld_so_conf
2225
2326from .common import (
@@ -57,6 +60,11 @@ def common_windows_package_paths(pkg_cls=None) -> List[str]:
5760 return paths
5861
5962
63+ def file_identifier (path ):
64+ s = os .stat (path )
65+ return (s .st_dev , s .st_ino )
66+
67+
6068def executables_in_path (path_hints : List [str ]) -> Dict [str , str ]:
6169 """Get the paths of all executables available from the current PATH.
6270
@@ -75,12 +83,40 @@ def executables_in_path(path_hints: List[str]) -> Dict[str, str]:
7583 return path_to_dict (search_paths )
7684
7785
86+ def get_elf_compat (path ):
87+ """For ELF files, get a triplet (EI_CLASS, EI_DATA, e_machine) and see if
88+ it is host-compatible."""
89+ # On ELF platforms supporting, we try to be a bit smarter when it comes to shared
90+ # libraries, by dropping those that are not host compatible.
91+ with open (path , "rb" ) as f :
92+ elf = elf_utils .parse_elf (f , only_header = True )
93+ return (elf .is_64_bit , elf .is_little_endian , elf .elf_hdr .e_machine )
94+
95+
96+ def accept_elf (path , host_compat ):
97+ """Accept an ELF file if the header matches the given compat triplet,
98+ obtained with :py:func:`get_elf_compat`. In case it's not an ELF (e.g.
99+ static library, or some arbitrary file, fall back to is_readable_file)."""
100+ # Fast path: assume libraries at least have .so in their basename.
101+ # Note: don't replace with splitext, because of libsmth.so.1.2.3 file names.
102+ if ".so" not in os .path .basename (path ):
103+ return llnl .util .filesystem .is_readable_file (path )
104+ try :
105+ return host_compat == get_elf_compat (path )
106+ except (OSError , elf_utils .ElfParsingError ):
107+ return llnl .util .filesystem .is_readable_file (path )
108+
109+
78110def libraries_in_ld_and_system_library_path (
79111 path_hints : Optional [List [str ]] = None ,
80112) -> Dict [str , str ]:
81- """Get the paths of all libraries available from LD_LIBRARY_PATH,
82- LIBRARY_PATH, DYLD_LIBRARY_PATH, DYLD_FALLBACK_LIBRARY_PATH, and
83- standard system library paths.
113+ """Get the paths of all libraries available from ``path_hints`` or the
114+ following defaults:
115+
116+ - Environment variables (Linux: ``LD_LIBRARY_PATH``, Darwin: ``DYLD_LIBRARY_PATH``,
117+ and ``DYLD_FALLBACK_LIBRARY_PATH``)
118+ - Dynamic linker default paths (glibc: ld.so.conf, musl: ld-musl-<arch>.path)
119+ - Default system library paths.
84120
85121 For convenience, this is constructed as a dictionary where the keys are
86122 the library paths and the values are the names of the libraries
@@ -94,17 +130,45 @@ def libraries_in_ld_and_system_library_path(
94130 constructed based on the set of LD_LIBRARY_PATH, LIBRARY_PATH,
95131 DYLD_LIBRARY_PATH, and DYLD_FALLBACK_LIBRARY_PATH environment
96132 variables as well as the standard system library paths.
133+ path_hints (list): list of paths to be searched. If ``None``, the default
134+ system paths are used.
97135 """
98- default_lib_search_paths = (
99- spack .util .environment .get_path ("LD_LIBRARY_PATH" )
100- + spack .util .environment .get_path ("DYLD_LIBRARY_PATH" )
101- + spack .util .environment .get_path ("DYLD_FALLBACK_LIBRARY_PATH" )
102- + spack .util .ld_so_conf .host_dynamic_linker_search_paths ()
103- )
104- path_hints = path_hints if path_hints is not None else default_lib_search_paths
105-
106- search_paths = llnl .util .filesystem .search_paths_for_libraries (* path_hints )
107- return path_to_dict (search_paths )
136+ if path_hints :
137+ search_paths = llnl .util .filesystem .search_paths_for_libraries (* path_hints )
138+ else :
139+ search_paths = []
140+
141+ # Environment variables
142+ if sys .platform == "darwin" :
143+ search_paths .extend (environment .get_path ("DYLD_LIBRARY_PATH" ))
144+ search_paths .extend (environment .get_path ("DYLD_FALLBACK_LIBRARY_PATH" ))
145+ elif sys .platform .startswith ("linux" ):
146+ search_paths .extend (environment .get_path ("LD_LIBRARY_PATH" ))
147+
148+ # Dynamic linker paths
149+ search_paths .extend (spack .util .ld_so_conf .host_dynamic_linker_search_paths ())
150+
151+ # Drop redundant paths
152+ search_paths = list (filter (os .path .isdir , search_paths ))
153+
154+ # Make use we don't doubly list /usr/lib and /lib etc
155+ search_paths = list (llnl .util .lang .dedupe (search_paths , key = file_identifier ))
156+
157+ try :
158+ host_compat = get_elf_compat (sys .executable )
159+ accept = lambda path : accept_elf (path , host_compat )
160+ except (OSError , elf_utils .ElfParsingError ):
161+ accept = llnl .util .filesystem .is_readable_file
162+
163+ path_to_lib = {}
164+ # Reverse order of search directories so that a lib in the first
165+ # search path entry overrides later entries
166+ for search_path in reversed (search_paths ):
167+ for lib in os .listdir (search_path ):
168+ lib_path = os .path .join (search_path , lib )
169+ if accept (lib_path ):
170+ path_to_lib [lib_path ] = lib
171+ return path_to_lib
108172
109173
110174def libraries_in_windows_paths (path_hints : Optional [List [str ]] = None ) -> Dict [str , str ]:
0 commit comments