1111
1212
1313if typing .TYPE_CHECKING :
14- from typing import List
14+ from typing import List , Optional , TypeVar
1515
1616 from mesonpy ._compat import Iterable , Path
1717
18+ T = TypeVar ('T' )
1819
19- if sys .platform == 'win32' or sys .platform == 'cygwin' :
2020
21- def fix_rpath (filepath : Path , libs_relative_path : str ) -> None :
21+ def unique (values : List [T ]) -> List [T ]:
22+ r = []
23+ for value in values :
24+ if value not in r :
25+ r .append (value )
26+ return r
27+
28+
29+ class _Windows :
30+
31+ @staticmethod
32+ def get_rpath (filepath : Path ) -> List [str ]:
33+ return []
34+
35+ @classmethod
36+ def fix_rpath (cls , filepath : Path , install_rpath : Optional [str ], libs_rpath : Optional [str ]) -> None :
2237 pass
2338
24- elif sys .platform == 'darwin' :
2539
26- def _get_rpath (filepath : Path ) -> List [str ]:
40+ class RPATH :
41+ origin = '$ORIGIN'
42+
43+ @staticmethod
44+ def get_rpath (filepath : Path ) -> List [str ]:
45+ raise NotImplementedError
46+
47+ @staticmethod
48+ def set_rpath (filepath : Path , old : List [str ], rpath : List [str ]) -> None :
49+ raise NotImplementedError
50+
51+ @classmethod
52+ def fix_rpath (cls , filepath : Path , install_rpath : Optional [str ], libs_rpath : Optional [str ]) -> None :
53+ old_rpath = cls .get_rpath (filepath )
54+ new_rpath = []
55+ if libs_rpath is not None :
56+ if libs_rpath == '.' :
57+ libs_rpath = ''
58+ for path in old_rpath :
59+ if path .split ('/' , 1 )[0 ] == cls .origin :
60+ # Any RPATH entry relative to ``$ORIGIN`` is interpreted as
61+ # pointing to a location in the build directory added by
62+ # Meson. These need to be removed. Their presence indicates
63+ # that the executable, shared library, or Python module
64+ # depends on libraries build as part of the package. These
65+ # entries are thus replaced with entries pointing to the
66+ # ``.<package-name>.mesonpy.libs`` folder where meson-python
67+ # relocates shared libraries distributed with the package.
68+ # The package may however explicitly install these in a
69+ # different location, thus this is not a perfect heuristic
70+ # and may add not required RPATH entries. These are however
71+ # harmless.
72+ path = f'{ cls .origin } /{ libs_rpath } '
73+ # Any other RPATH entry is preserved.
74+ new_rpath .append (path )
75+ if install_rpath :
76+ # Add the RPATH entry spcified with the ``install_rpath`` argument.
77+ new_rpath .append (install_rpath )
78+ # Make the RPATH entries unique.
79+ new_rpath = unique (new_rpath )
80+ if new_rpath != old_rpath :
81+ cls .set_rpath (filepath , old_rpath , new_rpath )
82+
83+
84+ class _MacOS (RPATH ):
85+ origin = '@loader_path'
86+
87+ @staticmethod
88+ def get_rpath (filepath : Path ) -> List [str ]:
2789 rpath = []
2890 r = subprocess .run (['otool' , '-l' , os .fspath (filepath )], capture_output = True , text = True )
2991 rpath_tag = False
@@ -35,17 +97,31 @@ def _get_rpath(filepath: Path) -> List[str]:
3597 rpath_tag = False
3698 return rpath
3799
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 )
40-
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 )
45-
46- elif sys .platform == 'sunos5' :
47-
48- def _get_rpath (filepath : Path ) -> List [str ]:
100+ @staticmethod
101+ def set_rpath (filepath : Path , old : List [str ], rpath : List [str ]) -> None :
102+ args : List [str ] = []
103+ for path in rpath :
104+ if path not in old :
105+ args += ['-add_rpath' , path ]
106+ for path in old :
107+ if path not in rpath :
108+ args += ['-delete_rpath' , path ]
109+ subprocess .run (['install_name_tool' , * args , os .fspath (filepath )], check = True )
110+
111+ @classmethod
112+ def fix_rpath (cls , filepath : Path , install_rpath : Optional [str ], libs_rpath : Optional [str ]) -> None :
113+ if install_rpath is not None :
114+ root , sep , stem = install_rpath .partition ('/' )
115+ if root == '$ORIGIN' :
116+ install_rpath = f'{ cls .origin } { sep } { stem } '
117+ # warnings.warn('...')
118+ super ().fix_rpath (filepath , install_rpath , libs_rpath )
119+
120+
121+ class _SunOS (RPATH ):
122+
123+ @staticmethod
124+ def get_rpath (filepath : Path ) -> List [str ]:
49125 rpath = []
50126 r = subprocess .run (['/usr/bin/elfedit' , '-r' , '-e' , 'dyn:rpath' , os .fspath (filepath )],
51127 capture_output = True , check = True , text = True )
@@ -56,35 +132,31 @@ def _get_rpath(filepath: Path) -> List[str]:
56132 rpath .append (path )
57133 return rpath
58134
59- def _set_rpath (filepath : Path , rpath : Iterable [str ]) -> None :
135+ @staticmethod
136+ def set_rpath (filepath : Path , old : Iterable [str ], rpath : Iterable [str ]) -> None :
60137 subprocess .run (['/usr/bin/elfedit' , '-e' , 'dyn:rpath ' + ':' .join (rpath ), os .fspath (filepath )], check = True )
61138
62- def fix_rpath (filepath : Path , libs_relative_path : str ) -> None :
63- old_rpath = _get_rpath (filepath )
64- new_rpath = []
65- for path in old_rpath :
66- if path .startswith ('$ORIGIN/' ):
67- path = '$ORIGIN/' + libs_relative_path
68- new_rpath .append (path )
69- if new_rpath != old_rpath :
70- _set_rpath (filepath , new_rpath )
71139
72- else :
73- # Assume that any other platform uses ELF binaries.
140+ class _ELF (RPATH ):
74141
75- def _get_rpath (filepath : Path ) -> List [str ]:
142+ @staticmethod
143+ def get_rpath (filepath : Path ) -> List [str ]:
76144 r = subprocess .run (['patchelf' , '--print-rpath' , os .fspath (filepath )], capture_output = True , text = True )
77145 return r .stdout .strip ().split (':' )
78146
79- def _set_rpath (filepath : Path , rpath : Iterable [str ]) -> None :
147+ @staticmethod
148+ def set_rpath (filepath : Path , old : Iterable [str ], rpath : Iterable [str ]) -> None :
80149 subprocess .run (['patchelf' ,'--set-rpath' , ':' .join (rpath ), os .fspath (filepath )], check = True )
81150
82- def fix_rpath (filepath : Path , libs_relative_path : str ) -> None :
83- old_rpath = _get_rpath (filepath )
84- new_rpath = []
85- for path in old_rpath :
86- if path .startswith ('$ORIGIN/' ):
87- path = '$ORIGIN/' + libs_relative_path
88- new_rpath .append (path )
89- if new_rpath != old_rpath :
90- _set_rpath (filepath , new_rpath )
151+
152+ if sys .platform == 'win32' or sys .platform == 'cygwin' :
153+ _cls = _Windows
154+ elif sys .platform == 'darwin' :
155+ _cls = _MacOS
156+ elif sys .platform == 'sunos5' :
157+ _cls = _SunOS
158+ else :
159+ _cls = _ELF
160+
161+ _get_rpath = _cls .get_rpath
162+ fix_rpath = _cls .fix_rpath
0 commit comments