meson-python icon indicating copy to clipboard operation
meson-python copied to clipboard

RPATH goes missing when using both `install_rpath` and an internal shared library dependency

Open rgommers opened this issue 11 months ago • 7 comments

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.

rgommers avatar Dec 06 '24 20:12 rgommers

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"
    },

rgommers avatar Dec 06 '24 20:12 rgommers

I think the correct behavior for fix_rpath is:

  1. If the install target has no RPATH entries, do nothing

  2. 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 $ORIGIN but 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 $ORIGIN unchanged
    • Keep other entries that don't start with $ORIGIN also unchanged (that is already the case)

rgommers avatar Dec 06 '24 21:12 rgommers

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.

dnicolodi avatar Feb 13 '25 08:02 dnicolodi

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.

rgommers avatar Feb 13 '25 08:02 rgommers

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.

dnicolodi avatar Feb 13 '25 09:02 dnicolodi

Thanks! Always nice to discover you solved the problem you think you're having yourself months ago:)

rgommers avatar Feb 13 '25 09:02 rgommers

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.

eli-schwartz avatar Feb 13 '25 16:02 eli-schwartz