Skip to content

Commit

Permalink
ENH: correctly handle install_rpath when installing into wheels
Browse files Browse the repository at this point in the history
  • Loading branch information
dnicolodi committed Feb 14, 2025
1 parent 196bb57 commit 5a47c49
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 48 deletions.
21 changes: 14 additions & 7 deletions mesonpy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ class InvalidLicenseExpression(Exception): # type: ignore[no-redef]
class Entry(typing.NamedTuple):
dst: pathlib.Path
src: str
rpath: Optional[str] = None


def _map_to_wheel(sources: Dict[str, Dict[str, Any]]) -> DefaultDict[str, List[Entry]]:
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -420,11 +421,14 @@ 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[str]) -> None:
"""Add a file to the wheel."""

if self._has_internal_libs:
if _is_native(origin):
if _is_native(origin):
libspath = None

if self._has_internal_libs:
if sys.platform == 'win32' and not self._allow_windows_shared_libs:
raise NotImplementedError(
'Loading shared libraries bundled in the Python wheel on Windows requires '
Expand All @@ -438,7 +442,10 @@ 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)

# Apply RPATH as per above and any ``install_rpath`` specified in meson.build
if rpath or libspath is not None:
mesonpy._rpath.fix_rpath(origin, rpath, libspath)

try:
wheel_file.write(origin, destination.as_posix())
Expand Down Expand Up @@ -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:
Expand All @@ -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

Expand Down
127 changes: 87 additions & 40 deletions mesonpy/_rpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,54 @@


if typing.TYPE_CHECKING:
from typing import List
from typing import List, Optional, TypeVar

from mesonpy._compat import Iterable, Path

T = TypeVar('T')

if sys.platform == 'win32' or sys.platform == 'cygwin':

def fix_rpath(filepath: Path, libs_relative_path: str) -> None:
pass
def unique(values: List[T]) -> List[T]:
r = []
for value in values:
if value not in r:
r.append(value)
return r

elif sys.platform == 'darwin':

def _get_rpath(filepath: Path) -> List[str]:
class RPATH:
origin = '$ORIGIN'

@staticmethod
def get_rpath(filepath: Path) -> List[str]:
raise NotImplementedError

@staticmethod
def set_rpath(filepath: Path, old: List[str], rpath: List[str]) -> None:
raise NotImplementedError

@classmethod
def fix_rpath(self, filepath: Path, install_rpath: Optional[str], libs_rpath: Optional[str]) -> None:
old_rpath = self.get_rpath(filepath)
new_rpath = []
if libs_rpath is not None:
if libs_rpath == '.':
libs_rpath = ''
for path in old_rpath:
if path.split('/', 1)[0] == self.origin:
new_rpath.append(f'{self.origin}/{libs_rpath}')
if install_rpath:
new_rpath.append(install_rpath)
new_rpath = unique(new_rpath)
if new_rpath != old_rpath:
self.set_rpath(filepath, old_rpath, new_rpath)


class _MacOS(RPATH):
origin = '@loader_path'

@staticmethod
def get_rpath(filepath: Path) -> List[str]:
rpath = []
r = subprocess.run(['otool', '-l', os.fspath(filepath)], capture_output=True, text=True)
rpath_tag = False
Expand All @@ -35,17 +70,31 @@ 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 fix_rpath(filepath: Path, libs_relative_path: str) -> None:
for path in _get_rpath(filepath):
if path.startswith('@loader_path/'):
_replace_rpath(filepath, path, '@loader_path/' + libs_relative_path)

elif sys.platform == 'sunos5':

def _get_rpath(filepath: Path) -> List[str]:
@staticmethod
def set_rpath(filepath: Path, old: List[str], rpath: List[str]) -> None:
args = []
for path in old:
if path == rpath[0]:
rpath.pop(0)
continue
args.extend(('-delete_rpath', path))
for path in rpath:
args.extend(('-add_rpath', path))
subprocess.run(['install_name_tool', *args, os.fspath(filepath)], check=True)

@classmethod
def fix_rpath(self, filepath: Path, install_rpath: Optional[str], libs_rpath: Optional[str]) -> None:
root, sep, stem = install_rpath.partition('/')
if root == '$ORIGIN':
install_rpath = f'{self.origin}{sep}{stem}'
#warnings.warn('something')
super().fix_rpath(filepath, install_rpath, libs_rpath)


class _SunOS(RPATH):

@staticmethod
def get_rpath(filepath: Path) -> List[str]:
rpath = []
r = subprocess.run(['/usr/bin/elfedit', '-r', '-e', 'dyn:rpath', os.fspath(filepath)],
capture_output=True, check=True, text=True)
Expand All @@ -56,35 +105,33 @@ def _get_rpath(filepath: Path) -> List[str]:
rpath.append(path)
return rpath

def _set_rpath(filepath: Path, rpath: Iterable[str]) -> None:
@staticmethod
def set_rpath(filepath: Path, old: Iterable[str], 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:
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 new_rpath != old_rpath:
_set_rpath(filepath, new_rpath)

else:
# Assume that any other platform uses ELF binaries.
class _ELF(RPATH):

def _get_rpath(filepath: Path) -> List[str]:
@staticmethod
def get_rpath(filepath: Path) -> List[str]:
r = subprocess.run(['patchelf', '--print-rpath', os.fspath(filepath)], capture_output=True, text=True)
return r.stdout.strip().split(':')

def _set_rpath(filepath: Path, rpath: Iterable[str]) -> None:
@staticmethod
def set_rpath(filepath: Path, old: Iterable[str], 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:
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 new_rpath != old_rpath:
_set_rpath(filepath, new_rpath)

if sys.platform == 'win32' or sys.platform == 'cygwin':

def fix_rpath(filepath: Path, install_rpath: Optional[str], libs_rpath: Optional[str]) -> None:
pass

elif sys.platform == 'darwin':
fix_rpath = _MacOS.fix_rpath

elif sys.platform == 'sunos5':
fix_rpath = _SunOS.fix_rpath

else:
fix_rpath = _ELF.fix_rpath
3 changes: 2 additions & 1 deletion tests/packages/link-against-local-lib/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ if meson.get_compiler('c').get_id() in ['msvc', 'clang-cl', 'intel-cl']
link_args = ['-DEXAMPLE_DLL_IMPORTS']
else
lib_compile_args = []
link_args = ['-Wl,-rpath,custom-rpath']
link_args = ['-Wl,-rpath,custom-rpath-wrong-way']
endif

subdir('lib')
Expand All @@ -26,6 +26,7 @@ py.extension_module(
'examplemod.c',
link_with: example_lib,
link_args: link_args,
install_rpath: 'custom-rpath',
install: true,
subdir: 'example',
)

0 comments on commit 5a47c49

Please sign in to comment.