meson-python
meson-python copied to clipboard
RPATH goes missing when using both `install_rpath` and an internal shared library dependency
This is a case that probably hasn't been exercised before: a Python extension module that links against both a shared library that's part of the package and a shared library built in a subproject that's folded into the wheel by meson-python.
I ran into this in SciPy when adding a subproject. This worked as advertised, except for this scipy.special._ufuncs extension module, which depends on this libsf_error shared library as well as on the library in the subproject.
What one sees then is that the installed wheel can no longer find libsf_error.so, because the RPATH entry for install_rpath: '$ORIGIN' goes missing.
With a default SciPy build (no subproject), the Library rpath|runpath entries on the _ufuncs target in the build and install directories looks like this (has $ORIGIN as it should):
$ readelf -d build/scipy/special/_ufuncs.cpython-312-x86_64-linux-gnu.so | rg "Library r"
0x000000000000000f (RPATH) Library rpath: [/home/rgommers/mambaforge/envs/scipy-dev-py312/lib:$ORIGIN/]
$ readelf -d build-install/lib/python3.12/site-packages/scipy/special/_ufuncs.cpython-312-x86_64-linux-gnu.so | rg "Library r"
0x000000000000000f (RPATH) Library rpath: [$ORIGIN:/home/rgommers/mambaforge/envs/scipy-dev-py312/lib]
On the branch with the subproject, we see this instead:
$ readelf -d build-whl/scipy/special/_ufuncs.cpython-312-x86_64-linux-gnu.so | rg Library
0x000000000000001d (RUNPATH) Library runpath: [/home/rgommers/code/pixi-dev-scipystack/scipy/.pixi/envs/openblas-src/lib:$ORIGIN/../../.scipy.mesonpy.libs:$ORIGIN/../../.scipy.mesonpy.libs:$ORIGIN/../../.scipy.mesonpy.libs:$ORIGIN/../../.scipy.mesonpy.libs]
$ readelf -d site-packages/scipy/special/_ufuncs.cpython-312-x86_64-linux-gnu.so | rg "Library r"
0x000000000000001d (RUNPATH) Library runpath: [/home/rgommers/code/pixi-dev-scipystack/scipy/.pixi/envs/openblas-src/lib:$ORIGIN/../../.scipy.mesonpy.libs:$ORIGIN/../../.scipy.mesonpy.libs:$ORIGIN/../../.scipy.mesonpy.libs:$ORIGIN/../../.scipy.mesonpy.libs]
The duplication of the .scipy.mesonpy.libs entries is a minor stylistic issue, we may be able to de-duplicate but it doesn't matter. What does matter is that $ORIGIN is no longer present.
This looks like a bug in the fix_rpath implementation at:
https://github.com/mesonbuild/meson-python/blob/91d64d1d08aa2702ceaf1bde3edc53d69429100e/mesonpy/_rpath.py#L28-L36
The problem seems clear: all RPATH entries are cleared, and the ones that are added back all get libs_relative_path appended (i.e. to the subproject-relocated location). Any existing RPATHs within the package, like '$ORIGIN', will get lost.
Adding the relevant content from the build-whl/meson-info/intro-install_plan.json install metadata file:
{
"targets": {
"/home/.../build-whl/subprojects/openblas/lapack-netlib/SRC/libscipy_lapack.so": {
"destination": "{libdir_shared}/libscipy_lapack.so",
"tag": "runtime",
"subproject": "openblas",
"install_rpath": null
},
"/home/.../build-whl/scipy/special/libsf_error_state.so": {
"destination": "{py_platlib}/scipy/special/libsf_error_state.so",
"tag": "runtime",
"subproject": null,
"install_rpath": null
},
"/home/.../build-whl/scipy/special/_ufuncs.cpython-312-x86_64-linux-gnu.so": {
"destination": "{py_platlib}/scipy/special/_ufuncs.cpython-312-x86_64-linux-gnu.so",
"tag": "runtime",
"subproject": null,
"install_rpath": "$ORIGIN"
},
I think the correct behavior for fix_rpath is:
-
If the install target has no RPATH entries, do nothing
-
If the install target has >=1 RPATH entries, then:
- Add a single new relative entry pointing to
.<project-name>.mesonpy.libs - Remove entries starting with
$ORIGINbut pointing outside of the package's own tree and into subprojects (e.g.,$ORIGIN/../../subprojects). Those are the ones being relocated. - Keep other entries that start with
$ORIGINunchanged - Keep other entries that don't start with
$ORIGINalso unchanged (that is already the case)
- Add a single new relative entry pointing to
I've tried to brush up on the reason why the RPATH adjustment code does what it does. IIRC, what it needs to do is to remove RPATH entries added by meson that point to the build tree and add an RPATH entry that points to .<project-name>.mesonpy.libs. Removing all RPATH entries that start with $ORIGIN was an attempt for an heuristic to determine which RPATH entries have been added by meson. We would need to find a better one. I'm not sure what that could be. meson keeps a list of the RPATH entries it added, but meson-python does not have access to it.
intro-install_plan.json does contain the install_rpath entries originating from meson.build, so I think we can process those. Snippet from a SciPy build:
"/Users/rgommers/code/pixi-dev/scipy/scipy/build/scipy/special/libsf_error_state.dylib": {
"destination": "{py_platlib}/scipy/special/libsf_error_state.dylib",
"tag": "runtime",
"subproject": null,
"install_rpath": null
},
"/Users/rgommers/code/pixi-dev/scipy/scipy/build/scipy/special/_special_ufuncs.cpython-312-darwin.so": {
"destination": "{py_platlib}/scipy/special/_special_ufuncs.cpython-312-darwin.so",
"tag": "runtime",
"subproject": null,
"install_rpath": "$ORIGIN"
},
This metadata matches with https://github.com/scipy/scipy/blob/1336806190a3810342a6cd74e3aee49d49e3ce20/scipy/special/meson.build#L83. All extension modules and shared libraries have the install_rpath metadata, it's either null or a string.
Oh, right, I added that to the meson introspection data exactly for this purpose https://github.com/mesonbuild/meson/pull/13625 🙂 This does not solve the issue for RPATH entries added passing linker arguments, but meson already emits a warning for these and we can say that these are not supported. I guess that what we should do is thus to remove all RPATH entries that are not listed in the introspection data. I'll have a look at this later today.
Thanks! Always nice to discover you solved the problem you think you're having yourself months ago:)
There are reasons why someone might want to pass rpath via LDFLAGS but none of them are applicable to internal dependencies. They will always be for directories that aren't inside the meson source/build trees.