From 89aee9c0c97b91afc8c01df0a10b0695bcde00b6 Mon Sep 17 00:00:00 2001 From: Daniele Nicolodi Date: Thu, 13 Feb 2025 18:15:28 +0100 Subject: [PATCH] ENH: correctly handle install_rpath when installing into wheels Fixes #711. --- mesonpy/__init__.py | 17 +++++++++++----- mesonpy/_rpath.py | 48 +++++++++++++++++++++++++++++++++++++-------- 2 files changed, 52 insertions(+), 13 deletions(-) diff --git a/mesonpy/__init__.py b/mesonpy/__init__.py index c56e5592..6781a6dd 100644 --- a/mesonpy/__init__.py +++ b/mesonpy/__init__.py @@ -113,6 +113,7 @@ class InvalidLicenseExpression(Exception): # type: ignore[no-redef] class Entry(typing.NamedTuple): dst: pathlib.Path src: str + rpath: Optional[List[str]] = None def _map_to_wheel(sources: Dict[str, Dict[str, Any]]) -> DefaultDict[str, List[Entry]]: @@ -161,7 +162,7 @@ def _map_to_wheel(sources: Dict[str, Dict[str, Any]]) -> DefaultDict[str, List[E filedst = dst / relpath wheel_files[path].append(Entry(filedst, filesrc)) else: - wheel_files[path].append(Entry(dst, src)) + wheel_files[path].append(Entry(dst, src, target.get('install_rpath'))) return wheel_files @@ -420,7 +421,8 @@ def _stable_abi(self) -> Optional[str]: return 'abi3' return None - def _install_path(self, wheel_file: mesonpy._wheelfile.WheelFile, origin: Path, destination: pathlib.Path) -> None: + def _install_path(self, wheel_file: mesonpy._wheelfile.WheelFile, + origin: Path, destination: pathlib.Path, rpath: Optional[List[str]]) -> None: """Add a file to the wheel.""" if self._has_internal_libs: @@ -438,7 +440,12 @@ def _install_path(self, wheel_file: mesonpy._wheelfile.WheelFile, origin: Path, # relocates the shared libraries to the $project.mesonpy.libs # folder. Rewrite the RPATH to point to that folder instead. libspath = os.path.relpath(self._libs_dir, destination.parent) - mesonpy._rpath.fix_rpath(origin, libspath) + rpath = rpath or [] + rpath.append(libspath) + + # Apply RPATH as per above and any ``install_rpath`` specified in meson.build + if rpath: + mesonpy._rpath.fix_rpath(origin, rpath) try: wheel_file.write(origin, destination.as_posix()) @@ -476,7 +483,7 @@ def build(self, directory: Path) -> pathlib.Path: root = 'purelib' if self._pure else 'platlib' for path, entries in self._manifest.items(): - for dst, src in entries: + for dst, src, rpath in entries: counter.update(src) if path == root: @@ -487,7 +494,7 @@ def build(self, directory: Path) -> pathlib.Path: else: dst = pathlib.Path(self._data_dir, path, dst) - self._install_path(whl, src, dst) + self._install_path(whl, src, dst, rpath) return wheel_file diff --git a/mesonpy/_rpath.py b/mesonpy/_rpath.py index a7cbbb92..8a4d50d2 100644 --- a/mesonpy/_rpath.py +++ b/mesonpy/_rpath.py @@ -9,16 +9,28 @@ import sys import typing +from itertools import chain + if typing.TYPE_CHECKING: - from typing import List + from typing import List, TypeVar from mesonpy._compat import Iterable, Path + T = TypeVar('T') + + +def unique(values: List[T]) -> List[T]: + r = [] + for value in values: + if value not in r: + r.append(value) + return r + if sys.platform == 'win32' or sys.platform == 'cygwin': - def fix_rpath(filepath: Path, libs_relative_path: str) -> None: + def fix_rpath(filepath: Path, rpath: List[str]) -> None: pass elif sys.platform == 'darwin': @@ -35,13 +47,27 @@ def _get_rpath(filepath: Path) -> List[str]: rpath_tag = False return rpath - def _replace_rpath(filepath: Path, old: str, new: str) -> None: - subprocess.run(['install_name_tool', '-rpath', old, new, os.fspath(filepath)], check=True) + def _delete_rpath(filepath: Path, paths: List[str]) -> None: + args = chain(*(('-delete_rpath', path) for path in paths)) + subprocess.run(['install_name_tool', *args, os.fspath(filepath)], check=True) + def _add_rpath(filepath: Path, paths: List[str]) -> None: + args = chain(*(('-add_rpath', path) for path in paths)) + subprocess.run(['install_name_tool', *args, os.fspath(filepath)], check=True) + def fix_rpath(filepath: Path, libs_relative_path: str) -> None: - for path in _get_rpath(filepath): + old_rpath = _get_rpath(filepath) + new_rpath = [] + for path in old_rpath: if path.startswith('@loader_path/'): - _replace_rpath(filepath, path, '@loader_path/' + libs_relative_path) + path = '$ORIGIN/' + libs_relative_path + new_rpath.append(path) + if rpath: + new_rpath.extend(rpath) + new_rpath = unique(rpath) + if new_rpath != old_rpath: + _delete_rpath(old_rpath) + _add_rpath(new_rpath) elif sys.platform == 'sunos5': @@ -59,13 +85,16 @@ def _get_rpath(filepath: Path) -> List[str]: def _set_rpath(filepath: Path, rpath: Iterable[str]) -> None: subprocess.run(['/usr/bin/elfedit', '-e', 'dyn:rpath ' + ':'.join(rpath), os.fspath(filepath)], check=True) - def fix_rpath(filepath: Path, libs_relative_path: str) -> None: + def fix_rpath(filepath: Path, rpath: str) -> None: old_rpath = _get_rpath(filepath) new_rpath = [] for path in old_rpath: if path.startswith('$ORIGIN/'): path = '$ORIGIN/' + libs_relative_path new_rpath.append(path) + if rpath: + new_rpath.extend(rpath) + new_rpath = unique(rpath) if new_rpath != old_rpath: _set_rpath(filepath, new_rpath) @@ -79,12 +108,15 @@ def _get_rpath(filepath: Path) -> List[str]: def _set_rpath(filepath: Path, rpath: Iterable[str]) -> None: subprocess.run(['patchelf','--set-rpath', ':'.join(rpath), os.fspath(filepath)], check=True) - def fix_rpath(filepath: Path, libs_relative_path: str) -> None: + def fix_rpath(filepath: Path, rpath: str) -> None: old_rpath = _get_rpath(filepath) new_rpath = [] for path in old_rpath: if path.startswith('$ORIGIN/'): path = '$ORIGIN/' + libs_relative_path new_rpath.append(path) + if rpath: + new_rpath.extend(rpath) + new_rpath = unique(rpath) if new_rpath != old_rpath: _set_rpath(filepath, new_rpath)