diff options
-rw-r--r-- | lib/spack/spack/build_environment.py | 56 | ||||
-rw-r--r-- | lib/spack/spack/compiler.py | 11 | ||||
-rw-r--r-- | lib/spack/spack/util/libc.py | 18 |
3 files changed, 71 insertions, 14 deletions
diff --git a/lib/spack/spack/build_environment.py b/lib/spack/spack/build_environment.py index fd0adbf39d..cf771a10ac 100644 --- a/lib/spack/spack/build_environment.py +++ b/lib/spack/spack/build_environment.py @@ -37,13 +37,14 @@ import io import multiprocessing import os import re +import stat import sys import traceback import types from collections import defaultdict from enum import Flag, auto from itertools import chain -from typing import Dict, List, Set, Tuple +from typing import Dict, List, Optional, Set, Tuple import archspec.cpu @@ -74,6 +75,7 @@ import spack.stage import spack.store import spack.subprocess_context import spack.util.executable +import spack.util.libc from spack import traverse from spack.context import Context from spack.error import InstallError, NoHeadersError, NoLibrariesError @@ -435,6 +437,35 @@ def optimization_flags(compiler, target): return result +class FilterDefaultDynamicLinkerSearchPaths: + """Remove rpaths to directories that are default search paths of the dynamic linker.""" + + def __init__(self, dynamic_linker: Optional[str]) -> None: + # Identify directories by (inode, device) tuple, which handles symlinks too. + self.default_path_identifiers: Set[Tuple[int, int]] = set() + if not dynamic_linker: + return + for path in spack.util.libc.default_search_paths_from_dynamic_linker(dynamic_linker): + try: + s = os.stat(path) + if stat.S_ISDIR(s.st_mode): + self.default_path_identifiers.add((s.st_ino, s.st_dev)) + except OSError: + continue + + def is_dynamic_loader_default_path(self, p: str) -> bool: + try: + s = os.stat(p) + return (s.st_ino, s.st_dev) in self.default_path_identifiers + except OSError: + return False + + def __call__(self, dirs: List[str]) -> List[str]: + if not self.default_path_identifiers: + return dirs + return [p for p in dirs if not self.is_dynamic_loader_default_path(p)] + + def set_wrapper_variables(pkg, env): """Set environment variables used by the Spack compiler wrapper (which have the prefix `SPACK_`) and also add the compiler wrappers to PATH. @@ -508,17 +539,17 @@ def set_wrapper_variables(pkg, env): def update_compiler_args_for_dep(dep): if dep in link_deps and (not is_system_path(dep.prefix)): query = pkg.spec[dep.name] - dep_link_dirs = list() + dep_link_dirs = [] try: # In some circumstances (particularly for externals) finding # libraries packages can be time consuming, so indicate that # we are performing this operation (and also report when it # finishes). - tty.debug("Collecting libraries for {0}".format(dep.name)) + tty.debug(f"Collecting libraries for {dep.name}") dep_link_dirs.extend(query.libs.directories) - tty.debug("Libraries for {0} have been collected.".format(dep.name)) + tty.debug(f"Libraries for {dep.name} have been collected.") except NoLibrariesError: - tty.debug("No libraries found for {0}".format(dep.name)) + tty.debug(f"No libraries found for {dep.name}") for default_lib_dir in ["lib", "lib64"]: default_lib_prefix = os.path.join(dep.prefix, default_lib_dir) @@ -532,7 +563,7 @@ def set_wrapper_variables(pkg, env): try: _prepend_all(include_dirs, query.headers.directories) except NoHeadersError: - tty.debug("No headers found for {0}".format(dep.name)) + tty.debug(f"No headers found for {dep.name}") for dspec in pkg.spec.traverse(root=False, order="post"): if dspec.external: @@ -552,9 +583,18 @@ def set_wrapper_variables(pkg, env): lib_path = os.path.join(pkg.prefix, libdir) rpath_dirs.insert(0, lib_path) + filter_default_dynamic_linker_search_paths = FilterDefaultDynamicLinkerSearchPaths( + pkg.compiler.default_dynamic_linker + ) + link_dirs = list(dedupe(filter_system_paths(link_dirs))) include_dirs = list(dedupe(filter_system_paths(include_dirs))) rpath_dirs = list(dedupe(filter_system_paths(rpath_dirs))) + rpath_dirs = filter_default_dynamic_linker_search_paths(rpath_dirs) + + implicit_rpaths = filter_default_dynamic_linker_search_paths(pkg.compiler.implicit_rpaths()) + if implicit_rpaths: + env.set("SPACK_COMPILER_IMPLICIT_RPATHS", ":".join(implicit_rpaths)) # Spack managed directories include the stage, store and upstream stores. We extend this with # their real paths to make it more robust (e.g. /tmp vs /private/tmp on macOS). @@ -841,10 +881,6 @@ def setup_package(pkg, dirty, context: Context = Context.BUILD): load_external_modules(pkg) - implicit_rpaths = pkg.compiler.implicit_rpaths() - if implicit_rpaths: - env_mods.set("SPACK_COMPILER_IMPLICIT_RPATHS", ":".join(implicit_rpaths)) - # Make sure nothing's strange about the Spack environment. validate(env_mods, tty.warn) env_mods.apply_modifications() diff --git a/lib/spack/spack/compiler.py b/lib/spack/spack/compiler.py index 5e9b463dbb..8ed125ae24 100644 --- a/lib/spack/spack/compiler.py +++ b/lib/spack/spack/compiler.py @@ -415,14 +415,19 @@ class Compiler: return list(paths_containing_libs(link_dirs, all_required_libs)) @property - def default_libc(self) -> Optional["spack.spec.Spec"]: - """Determine libc targeted by the compiler from link line""" + def default_dynamic_linker(self) -> Optional[str]: + """Determine default dynamic linker from compiler link line""" output = self.compiler_verbose_output if not output: return None - dynamic_linker = spack.util.libc.parse_dynamic_linker(output) + return spack.util.libc.parse_dynamic_linker(output) + + @property + def default_libc(self) -> Optional["spack.spec.Spec"]: + """Determine libc targeted by the compiler from link line""" + dynamic_linker = self.default_dynamic_linker if not dynamic_linker: return None diff --git a/lib/spack/spack/util/libc.py b/lib/spack/spack/util/libc.py index 402bf6f244..55e8e3d26b 100644 --- a/lib/spack/spack/util/libc.py +++ b/lib/spack/spack/util/libc.py @@ -9,7 +9,7 @@ import re import shlex import sys from subprocess import PIPE, run -from typing import Optional +from typing import List, Optional import spack.spec import spack.util.elf @@ -34,6 +34,22 @@ def _libc_from_ldd(ldd: str) -> Optional["spack.spec.Spec"]: return None +def default_search_paths_from_dynamic_linker(dynamic_linker: str) -> List[str]: + """If the dynamic linker is glibc at a certain version, we can query the hard-coded library + search paths""" + try: + result = run([dynamic_linker, "--help"], stdout=PIPE, stderr=PIPE, check=False) + assert result.returncode == 0 + out = result.stdout.decode("utf-8") + except Exception: + return [] + + return [ + match.group(1).strip() + for match in re.finditer(r"^ (/.+) \(system search path\)$", out, re.MULTILINE) + ] + + def libc_from_dynamic_linker(dynamic_linker: str) -> Optional["spack.spec.Spec"]: if not os.path.exists(dynamic_linker): return None |