Skip to content

Commit 4f361a9

Browse files
committed
ENH: correctly handle install_rpath when installing into wheels
Fixes #711.
1 parent 8b61d26 commit 4f361a9

File tree

3 files changed

+76
-26
lines changed

3 files changed

+76
-26
lines changed

Diff for: mesonpy/__init__.py

+14-7
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ class InvalidLicenseExpression(Exception): # type: ignore[no-redef]
113113
class Entry(typing.NamedTuple):
114114
dst: pathlib.Path
115115
src: str
116+
rpath: Optional[str] = None
116117

117118

118119
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
161162
filedst = dst / relpath
162163
wheel_files[path].append(Entry(filedst, filesrc))
163164
else:
164-
wheel_files[path].append(Entry(dst, src))
165+
wheel_files[path].append(Entry(dst, src, target.get('install_rpath')))
165166

166167
return wheel_files
167168

@@ -420,11 +421,14 @@ def _stable_abi(self) -> Optional[str]:
420421
return 'abi3'
421422
return None
422423

423-
def _install_path(self, wheel_file: mesonpy._wheelfile.WheelFile, origin: Path, destination: pathlib.Path) -> None:
424+
def _install_path(self, wheel_file: mesonpy._wheelfile.WheelFile,
425+
origin: Path, destination: pathlib.Path, rpath: Optional[str]) -> None:
424426
"""Add a file to the wheel."""
425427

426-
if self._has_internal_libs:
427-
if _is_native(origin):
428+
if _is_native(origin):
429+
libspath = None
430+
431+
if self._has_internal_libs:
428432
if sys.platform == 'win32' and not self._allow_windows_shared_libs:
429433
raise NotImplementedError(
430434
'Loading shared libraries bundled in the Python wheel on Windows requires '
@@ -438,7 +442,10 @@ def _install_path(self, wheel_file: mesonpy._wheelfile.WheelFile, origin: Path,
438442
# relocates the shared libraries to the $project.mesonpy.libs
439443
# folder. Rewrite the RPATH to point to that folder instead.
440444
libspath = os.path.relpath(self._libs_dir, destination.parent)
441-
mesonpy._rpath.fix_rpath(origin, libspath)
445+
446+
# Apply RPATH as per above and any ``install_rpath`` specified in meson.build
447+
if rpath or libspath is not None:
448+
mesonpy._rpath.fix_rpath(origin, rpath, libspath)
442449

443450
try:
444451
wheel_file.write(origin, destination.as_posix())
@@ -476,7 +483,7 @@ def build(self, directory: Path) -> pathlib.Path:
476483
root = 'purelib' if self._pure else 'platlib'
477484

478485
for path, entries in self._manifest.items():
479-
for dst, src in entries:
486+
for dst, src, rpath in entries:
480487
counter.update(src)
481488

482489
if path == root:
@@ -487,7 +494,7 @@ def build(self, directory: Path) -> pathlib.Path:
487494
else:
488495
dst = pathlib.Path(self._data_dir, path, dst)
489496

490-
self._install_path(whl, src, dst)
497+
self._install_path(whl, src, dst, rpath)
491498

492499
return wheel_file
493500

Diff for: mesonpy/_rpath.py

+60-18
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,28 @@
99
import sys
1010
import typing
1111

12+
from itertools import chain
13+
1214

1315
if typing.TYPE_CHECKING:
14-
from typing import List
16+
from typing import List, Optional, TypeVar
1517

1618
from mesonpy._compat import Iterable, Path
1719

20+
T = TypeVar('T')
21+
22+
23+
def unique(values: List[T]) -> List[T]:
24+
r = []
25+
for value in values:
26+
if value not in r:
27+
r.append(value)
28+
return r
29+
1830

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

21-
def fix_rpath(filepath: Path, libs_relative_path: str) -> None:
33+
def fix_rpath(filepath: Path, install_rpath: Optional[str], libs_rpath: Optional[str]) -> None:
2234
pass
2335

2436
elif sys.platform == 'darwin':
@@ -35,13 +47,33 @@ def _get_rpath(filepath: Path) -> List[str]:
3547
rpath_tag = False
3648
return rpath
3749

38-
def _replace_rpath(filepath: Path, old: str, new: str) -> None:
39-
subprocess.run(['install_name_tool', '-rpath', old, new, os.fspath(filepath)], check=True)
50+
def _delete_rpath(filepath: Path, rpath: Iterable[str]) -> None:
51+
args = list(chain(*(('-delete_rpath', path) for path in rpath)))
52+
if not args:
53+
return
54+
subprocess.run(['install_name_tool', *args, os.fspath(filepath)], check=True)
4055

41-
def fix_rpath(filepath: Path, libs_relative_path: str) -> None:
42-
for path in _get_rpath(filepath):
43-
if path.startswith('@loader_path/'):
44-
_replace_rpath(filepath, path, '@loader_path/' + libs_relative_path)
56+
def _add_rpath(filepath: Path, rpath: Iterable[str]) -> None:
57+
args = list(chain(*(('-add_rpath', path) for path in rpath)))
58+
if not args:
59+
return
60+
subprocess.run(['install_name_tool', *args, os.fspath(filepath)], check=True)
61+
62+
def fix_rpath(filepath: Path, install_rpath: Optional[str], libs_rpath: Optional[str]) -> None:
63+
old_rpath = _get_rpath(filepath)
64+
new_rpath = []
65+
if libs_rpath is not None:
66+
if libs_rpath == '.':
67+
libs_rpath = ''
68+
for path in old_rpath:
69+
if path.startswith('@loader_path/'):
70+
new_rpath.append('@loader_path/' + libs_rpath)
71+
if install_rpath:
72+
new_rpath.append(install_rpath)
73+
new_rpath = unique(new_rpath)
74+
if new_rpath != old_rpath:
75+
_delete_rpath(filepath, old_rpath)
76+
_add_rpath(filepath, new_rpath)
4577

4678
elif sys.platform == 'sunos5':
4779

@@ -59,13 +91,18 @@ def _get_rpath(filepath: Path) -> List[str]:
5991
def _set_rpath(filepath: Path, rpath: Iterable[str]) -> None:
6092
subprocess.run(['/usr/bin/elfedit', '-e', 'dyn:rpath ' + ':'.join(rpath), os.fspath(filepath)], check=True)
6193

62-
def fix_rpath(filepath: Path, libs_relative_path: str) -> None:
94+
def fix_rpath(filepath: Path, install_rpath: Optional[str], libs_rpath: Optional[str]) -> None:
6395
old_rpath = _get_rpath(filepath)
6496
new_rpath = []
65-
for path in old_rpath:
66-
if path.startswith('$ORIGIN/'):
67-
path = '$ORIGIN/' + libs_relative_path
68-
new_rpath.append(path)
97+
if libs_rpath is not None:
98+
if libs_rpath == '.':
99+
libs_rpath = ''
100+
for path in old_rpath:
101+
if path.startswith('$ORIGIN/'):
102+
new_rpath.append('$ORIGIN/' + libs_rpath)
103+
if install_rpath:
104+
new_rpath.append(install_rpath)
105+
new_rpath = unique(new_rpath)
69106
if new_rpath != old_rpath:
70107
_set_rpath(filepath, new_rpath)
71108

@@ -79,12 +116,17 @@ def _get_rpath(filepath: Path) -> List[str]:
79116
def _set_rpath(filepath: Path, rpath: Iterable[str]) -> None:
80117
subprocess.run(['patchelf','--set-rpath', ':'.join(rpath), os.fspath(filepath)], check=True)
81118

82-
def fix_rpath(filepath: Path, libs_relative_path: str) -> None:
119+
def fix_rpath(filepath: Path, install_rpath: Optional[str], libs_rpath: Optional[str]) -> None:
83120
old_rpath = _get_rpath(filepath)
84121
new_rpath = []
85-
for path in old_rpath:
86-
if path.startswith('$ORIGIN/'):
87-
path = '$ORIGIN/' + libs_relative_path
88-
new_rpath.append(path)
122+
if libs_rpath is not None:
123+
if libs_rpath == '.':
124+
libs_rpath = ''
125+
for path in old_rpath:
126+
if path.startswith('$ORIGIN/'):
127+
new_rpath.append('$ORIGIN/' + libs_rpath)
128+
if install_rpath:
129+
new_rpath.append(install_rpath)
130+
new_rpath = unique(new_rpath)
89131
if new_rpath != old_rpath:
90132
_set_rpath(filepath, new_rpath)

Diff for: tests/packages/link-against-local-lib/meson.build

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ if meson.get_compiler('c').get_id() in ['msvc', 'clang-cl', 'intel-cl']
99
link_args = ['-DEXAMPLE_DLL_IMPORTS']
1010
else
1111
lib_compile_args = []
12-
link_args = ['-Wl,-rpath,custom-rpath']
12+
link_args = ['-Wl,-rpath,custom-rpath-wrong-way']
1313
endif
1414

1515
subdir('lib')
@@ -26,6 +26,7 @@ py.extension_module(
2626
'examplemod.c',
2727
link_with: example_lib,
2828
link_args: link_args,
29+
install_rpath: 'custom-rpath',
2930
install: true,
3031
subdir: 'example',
3132
)

0 commit comments

Comments
 (0)