From b112e303fa709fa180c0cc53e04ed5905a2aaff2 Mon Sep 17 00:00:00 2001 From: Daniele Nicolodi Date: Wed, 30 Jul 2025 17:58:38 +0200 Subject: [PATCH 01/11] ENH: drop empty strings when listing RPATH entries for ELF binaries This entries are not meaningful. This has the additional benefit of not returning an empty string RPATH entry for binaries with RPATH unset. --- mesonpy/_rpath.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mesonpy/_rpath.py b/mesonpy/_rpath.py index 9953d7b5..8bbc5bfe 100644 --- a/mesonpy/_rpath.py +++ b/mesonpy/_rpath.py @@ -73,7 +73,7 @@ def fix_rpath(filepath: Path, libs_relative_path: str) -> None: 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(':') + return [x for x in r.stdout.strip().split(':') if x] def _set_rpath(filepath: Path, rpath: Iterable[str]) -> None: subprocess.run(['patchelf','--set-rpath', ':'.join(rpath), os.fspath(filepath)], check=True) From 1bd3e2507c7177baf002600b575a47c5b6c790bc Mon Sep 17 00:00:00 2001 From: Daniele Nicolodi Date: Sat, 15 Feb 2025 12:03:21 +0100 Subject: [PATCH 02/11] TST: rework the sharedlib-in-package test package This reorganizes the test package to a flatter layout that helps visualizing all the parts involved in the test and introduces an asymmetry between the source layout and the installation layout that demonstrates the bugs in RPATH handling as currently implemented. --- .../packages/sharedlib-in-package/meson.build | 1 + .../sharedlib-in-package/mypkg/__init__.py | 4 +- .../sharedlib-in-package/mypkg/_examplemod.c | 25 +++--------- .../sharedlib-in-package/mypkg/examplelib.c | 9 ----- .../sharedlib-in-package/mypkg/examplelib.h | 7 ---- .../sharedlib-in-package/mypkg/meson.build | 26 +----------- .../mypkg/sub/examplelib2.h | 7 ---- tests/packages/sharedlib-in-package/src/lib.c | 10 +++++ tests/packages/sharedlib-in-package/src/lib.h | 13 ++++++ .../sharedlib-in-package/src/meson.build | 40 +++++++++++++++++++ .../{mypkg/sub/examplelib2.c => src/sublib.c} | 4 +- .../sharedlib-in-package/src/sublib.h | 13 ++++++ tests/test_wheel.py | 6 +-- 13 files changed, 90 insertions(+), 75 deletions(-) delete mode 100644 tests/packages/sharedlib-in-package/mypkg/examplelib.c delete mode 100644 tests/packages/sharedlib-in-package/mypkg/examplelib.h delete mode 100644 tests/packages/sharedlib-in-package/mypkg/sub/examplelib2.h create mode 100644 tests/packages/sharedlib-in-package/src/lib.c create mode 100644 tests/packages/sharedlib-in-package/src/lib.h create mode 100644 tests/packages/sharedlib-in-package/src/meson.build rename tests/packages/sharedlib-in-package/{mypkg/sub/examplelib2.c => src/sublib.c} (66%) create mode 100644 tests/packages/sharedlib-in-package/src/sublib.h diff --git a/tests/packages/sharedlib-in-package/meson.build b/tests/packages/sharedlib-in-package/meson.build index 71921cfe..e8f80243 100644 --- a/tests/packages/sharedlib-in-package/meson.build +++ b/tests/packages/sharedlib-in-package/meson.build @@ -6,4 +6,5 @@ project('sharedlib-in-package', 'c', version: '1.0.0') py = import('python').find_installation(pure: false) +subdir('src') subdir('mypkg') diff --git a/tests/packages/sharedlib-in-package/mypkg/__init__.py b/tests/packages/sharedlib-in-package/mypkg/__init__.py index 857d2e90..e4fbc2a9 100644 --- a/tests/packages/sharedlib-in-package/mypkg/__init__.py +++ b/tests/packages/sharedlib-in-package/mypkg/__init__.py @@ -45,7 +45,7 @@ def _append_to_sharedlib_load_path(): # end-literalinclude -from ._example import example_prod, example_sum #noqa: E402 +from ._example import prodsum # noqa: E402 -__all__ = ['example_prod', 'example_sum'] +__all__ = ['prodsum'] diff --git a/tests/packages/sharedlib-in-package/mypkg/_examplemod.c b/tests/packages/sharedlib-in-package/mypkg/_examplemod.c index 080e03c1..b4cc3f0c 100644 --- a/tests/packages/sharedlib-in-package/mypkg/_examplemod.c +++ b/tests/packages/sharedlib-in-package/mypkg/_examplemod.c @@ -4,36 +4,23 @@ #include -#include "examplelib.h" -#include "examplelib2.h" +#include "lib.h" -static PyObject* example_sum(PyObject* self, PyObject *args) +static PyObject* example_prodsum(PyObject* self, PyObject *args) { - int a, b; - if (!PyArg_ParseTuple(args, "ii", &a, &b)) { - return NULL; - } + int a, b, x; - long result = sum(a, b); - - return PyLong_FromLong(result); -} - -static PyObject* example_prod(PyObject* self, PyObject *args) -{ - int a, b; - if (!PyArg_ParseTuple(args, "ii", &a, &b)) { + if (!PyArg_ParseTuple(args, "iii", &a, &b, &x)) { return NULL; } - long result = prod(a, b); + long result = prodsum(a, b, x); return PyLong_FromLong(result); } static PyMethodDef methods[] = { - {"example_prod", (PyCFunction)example_prod, METH_VARARGS, NULL}, - {"example_sum", (PyCFunction)example_sum, METH_VARARGS, NULL}, + {"prodsum", (PyCFunction)example_prodsum, METH_VARARGS, NULL}, {NULL, NULL, 0, NULL}, }; diff --git a/tests/packages/sharedlib-in-package/mypkg/examplelib.c b/tests/packages/sharedlib-in-package/mypkg/examplelib.c deleted file mode 100644 index f486bd7f..00000000 --- a/tests/packages/sharedlib-in-package/mypkg/examplelib.c +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-FileCopyrightText: 2022 The meson-python developers -// -// SPDX-License-Identifier: MIT - -#include "sub/mypkg_dll.h" - -MYPKG_DLL int sum(int a, int b) { - return a + b; -} diff --git a/tests/packages/sharedlib-in-package/mypkg/examplelib.h b/tests/packages/sharedlib-in-package/mypkg/examplelib.h deleted file mode 100644 index c09f4f78..00000000 --- a/tests/packages/sharedlib-in-package/mypkg/examplelib.h +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-FileCopyrightText: 2022 The meson-python developers -// -// SPDX-License-Identifier: MIT - -#include "sub/mypkg_dll.h" - -MYPKG_DLL int sum(int a, int b); diff --git a/tests/packages/sharedlib-in-package/mypkg/meson.build b/tests/packages/sharedlib-in-package/mypkg/meson.build index 75904bed..5cad9636 100644 --- a/tests/packages/sharedlib-in-package/mypkg/meson.build +++ b/tests/packages/sharedlib-in-package/mypkg/meson.build @@ -2,34 +2,10 @@ # # SPDX-License-Identifier: MIT -if meson.get_compiler('c').get_id() in ['msvc', 'clang-cl', 'intel-cl'] - export_dll_args = ['-DMYPKG_DLL_EXPORTS'] - import_dll_args = ['-DMYPKG_DLL_IMPORTS'] -else - export_dll_args = [] - import_dll_args = [] -endif - -example_lib = shared_library( - 'examplelib', - 'examplelib.c', - c_args: export_dll_args, - install: true, - install_dir: py.get_install_dir() / 'mypkg', -) - -example_lib_dep = declare_dependency( - compile_args: import_dll_args, - link_with: example_lib, -) - -subdir('sub') - py.extension_module( '_example', '_examplemod.c', - dependencies: [example_lib_dep, example_lib2_dep], - include_directories: 'sub', + dependencies: lib_dep, install: true, subdir: 'mypkg', install_rpath: '$ORIGIN', diff --git a/tests/packages/sharedlib-in-package/mypkg/sub/examplelib2.h b/tests/packages/sharedlib-in-package/mypkg/sub/examplelib2.h deleted file mode 100644 index 64b6a907..00000000 --- a/tests/packages/sharedlib-in-package/mypkg/sub/examplelib2.h +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-FileCopyrightText: 2022 The meson-python developers -// -// SPDX-License-Identifier: MIT - -#include "mypkg_dll.h" - -MYPKG_DLL int prod(int a, int b); diff --git a/tests/packages/sharedlib-in-package/src/lib.c b/tests/packages/sharedlib-in-package/src/lib.c new file mode 100644 index 00000000..e4fe1478 --- /dev/null +++ b/tests/packages/sharedlib-in-package/src/lib.c @@ -0,0 +1,10 @@ +// SPDX-FileCopyrightText: 2022 The meson-python developers +// +// SPDX-License-Identifier: MIT + +#include "lib.h" +#include "sublib.h" + +int prodsum(int a, int b, int x) { + return prod(a, x) + b; +} diff --git a/tests/packages/sharedlib-in-package/src/lib.h b/tests/packages/sharedlib-in-package/src/lib.h new file mode 100644 index 00000000..fb6a02d8 --- /dev/null +++ b/tests/packages/sharedlib-in-package/src/lib.h @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: 2022 The meson-python developers +// +// SPDX-License-Identifier: MIT + +#if defined(MYPKG_DLL_EXPORTS) + #define EXPORT __declspec(dllexport) +#elif defined(MYPKG_DLL_IMPORTS) + #define EXPORT __declspec(dllimport) +#else + #define EXPORT +#endif + +EXPORT int prodsum(int a, int b, int x); diff --git a/tests/packages/sharedlib-in-package/src/meson.build b/tests/packages/sharedlib-in-package/src/meson.build new file mode 100644 index 00000000..5097a76e --- /dev/null +++ b/tests/packages/sharedlib-in-package/src/meson.build @@ -0,0 +1,40 @@ +# SPDX-FileCopyrightText: 2022 The meson-python developers +# +# SPDX-License-Identifier: MIT + +if meson.get_compiler('c').get_id() in ['msvc', 'clang-cl', 'intel-cl'] + export_dll_args = ['-DMYPKG_DLL_EXPORTS'] + import_dll_args = ['-DMYPKG_DLL_IMPORTS'] +else + export_dll_args = [] + import_dll_args = [] +endif + +sublib = shared_library( + 'sublib', + 'sublib.c', + c_args: export_dll_args, + install: true, + install_dir: py.get_install_dir() / 'mypkg/sub', +) + +sublib_dep = declare_dependency( + compile_args: import_dll_args, + link_with: sublib, +) + +lib = shared_library( + 'lib', + 'lib.c', + dependencies: sublib_dep, + c_args: export_dll_args, + install: true, + install_dir: py.get_install_dir() / 'mypkg', + install_rpath: '$ORIGIN/sub', +) + +lib_dep = declare_dependency( + compile_args: import_dll_args, + link_with: lib, + include_directories: include_directories('.'), +) diff --git a/tests/packages/sharedlib-in-package/mypkg/sub/examplelib2.c b/tests/packages/sharedlib-in-package/src/sublib.c similarity index 66% rename from tests/packages/sharedlib-in-package/mypkg/sub/examplelib2.c rename to tests/packages/sharedlib-in-package/src/sublib.c index 12f5b87a..facfdf2e 100644 --- a/tests/packages/sharedlib-in-package/mypkg/sub/examplelib2.c +++ b/tests/packages/sharedlib-in-package/src/sublib.c @@ -2,8 +2,8 @@ // // SPDX-License-Identifier: MIT -#include "mypkg_dll.h" +#include "sublib.h" -MYPKG_DLL int prod(int a, int b) { +int prod(int a, int b) { return a * b; } diff --git a/tests/packages/sharedlib-in-package/src/sublib.h b/tests/packages/sharedlib-in-package/src/sublib.h new file mode 100644 index 00000000..9fc7ae51 --- /dev/null +++ b/tests/packages/sharedlib-in-package/src/sublib.h @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: 2022 The meson-python developers +// +// SPDX-License-Identifier: MIT + +#if defined(MYPKG_DLL_EXPORTS) + #define EXPORT __declspec(dllexport) +#elif defined(MYPKG_DLL_IMPORTS) + #define EXPORT __declspec(dllimport) +#else + #define EXPORT +#endif + +EXPORT int prod(int a, int b); diff --git a/tests/test_wheel.py b/tests/test_wheel.py index 2871326b..664ddcd1 100644 --- a/tests/test_wheel.py +++ b/tests/test_wheel.py @@ -180,10 +180,8 @@ def test_local_lib(venv, wheel_link_against_local_lib): def test_sharedlib_in_package(venv, wheel_sharedlib_in_package): venv.pip('install', wheel_sharedlib_in_package) - output = venv.python('-c', 'import mypkg; print(mypkg.example_sum(2, 5))') - assert int(output) == 7 - output = venv.python('-c', 'import mypkg; print(mypkg.example_prod(6, 7))') - assert int(output) == 42 + output = venv.python('-c', 'import mypkg; print(mypkg.prodsum(2, 3, 4))') + assert int(output) == 11 @pytest.mark.skipif(MESON_VERSION < (1, 3, 0), reason='meson too old') From cf86e66dd9902118d2abdcb2c6f36f7e94870832 Mon Sep 17 00:00:00 2001 From: Daniele Nicolodi Date: Wed, 30 Jul 2025 14:39:27 +0200 Subject: [PATCH 03/11] TST: use linker flags instead of install_rpath to set the RPATH meson-python does not support `install_rpath` yet and Meson does not expose it in introspection data prior to version 1.6.0. Use `link_args` to set the RPATH. --- tests/conftest.py | 8 ++++---- tests/packages/sharedlib-in-package/mypkg/meson.build | 6 +++++- tests/packages/sharedlib-in-package/src/meson.build | 6 +++++- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 6a09b3d2..1bb9a2de 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -209,11 +209,11 @@ def __init__(self, source_dir, build_dir, meson_args=None, editable_verbose=None # toolchains. 'cmake-subproject', - # The ``link-against-local-lib`` package uses linker arguments - # to add RPATH entries. This functionality is deprecated in - # Meson but it is used in the wild thus we should make sure it - # keeps working. + # These packages use linker arguments to add RPATH entries. + # This functionality is deprecated in Meson but it is used + # in the wild thus we should make sure it keeps working. 'link-against-local-lib', + 'sharedlib-in-package', }: if meson_args is None: diff --git a/tests/packages/sharedlib-in-package/mypkg/meson.build b/tests/packages/sharedlib-in-package/mypkg/meson.build index 5cad9636..ff1d85a0 100644 --- a/tests/packages/sharedlib-in-package/mypkg/meson.build +++ b/tests/packages/sharedlib-in-package/mypkg/meson.build @@ -8,7 +8,11 @@ py.extension_module( dependencies: lib_dep, install: true, subdir: 'mypkg', - install_rpath: '$ORIGIN', + # install_rpath is not exposed in the Meson introspection data in Meson + # versions prior to 1.6.0 and thus cannot be set by meson-python when + # building the Python wheel. Use link_args to set the RPATH. + # install_rpath: '$ORIGIN', + link_args: '-Wl,-rpath,$ORIGIN', ) py.install_sources( diff --git a/tests/packages/sharedlib-in-package/src/meson.build b/tests/packages/sharedlib-in-package/src/meson.build index 5097a76e..d27a2d39 100644 --- a/tests/packages/sharedlib-in-package/src/meson.build +++ b/tests/packages/sharedlib-in-package/src/meson.build @@ -30,7 +30,11 @@ lib = shared_library( c_args: export_dll_args, install: true, install_dir: py.get_install_dir() / 'mypkg', - install_rpath: '$ORIGIN/sub', + # install_rpath is not exposed in the Meson introspection data in Meson + # versions prior to 1.6.0 and thus cannot be set by meson-python when + # building the Python wheel. Use link_args to set the RPATH. + # install_rpath: '$ORIGIN/sub', + link_args: '-Wl,-rpath,$ORIGIN/sub', ) lib_dep = declare_dependency( From fc449388520cedc5e26ad3906de983f2811a238b Mon Sep 17 00:00:00 2001 From: Daniele Nicolodi Date: Wed, 30 Jul 2025 14:45:25 +0200 Subject: [PATCH 04/11] TST: use platform specific anchors in RPATH entries macOS requires using `@loader_path` in place of `$ORIGIN`. --- tests/packages/sharedlib-in-package/meson.build | 2 ++ tests/packages/sharedlib-in-package/mypkg/meson.build | 4 ++-- tests/packages/sharedlib-in-package/src/meson.build | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/packages/sharedlib-in-package/meson.build b/tests/packages/sharedlib-in-package/meson.build index e8f80243..d311a58a 100644 --- a/tests/packages/sharedlib-in-package/meson.build +++ b/tests/packages/sharedlib-in-package/meson.build @@ -6,5 +6,7 @@ project('sharedlib-in-package', 'c', version: '1.0.0') py = import('python').find_installation(pure: false) +origin = build_machine.system() == 'darwin' ? '@loader_path' : '$ORIGIN' + subdir('src') subdir('mypkg') diff --git a/tests/packages/sharedlib-in-package/mypkg/meson.build b/tests/packages/sharedlib-in-package/mypkg/meson.build index ff1d85a0..cebf2d52 100644 --- a/tests/packages/sharedlib-in-package/mypkg/meson.build +++ b/tests/packages/sharedlib-in-package/mypkg/meson.build @@ -11,8 +11,8 @@ py.extension_module( # install_rpath is not exposed in the Meson introspection data in Meson # versions prior to 1.6.0 and thus cannot be set by meson-python when # building the Python wheel. Use link_args to set the RPATH. - # install_rpath: '$ORIGIN', - link_args: '-Wl,-rpath,$ORIGIN', + # install_rpath: f'@origin@', + link_args: f'-Wl,-rpath,@origin@', ) py.install_sources( diff --git a/tests/packages/sharedlib-in-package/src/meson.build b/tests/packages/sharedlib-in-package/src/meson.build index d27a2d39..b673ef29 100644 --- a/tests/packages/sharedlib-in-package/src/meson.build +++ b/tests/packages/sharedlib-in-package/src/meson.build @@ -33,8 +33,8 @@ lib = shared_library( # install_rpath is not exposed in the Meson introspection data in Meson # versions prior to 1.6.0 and thus cannot be set by meson-python when # building the Python wheel. Use link_args to set the RPATH. - # install_rpath: '$ORIGIN/sub', - link_args: '-Wl,-rpath,$ORIGIN/sub', + # install_rpath: f'@origin@/sub', + link_args: f'-Wl,-rpath,@origin@/sub', ) lib_dep = declare_dependency( From 513748cc7207d51d4b4a8c471690c798db3dca6e Mon Sep 17 00:00:00 2001 From: Daniele Nicolodi Date: Wed, 30 Jul 2025 16:42:59 +0200 Subject: [PATCH 05/11] TST: make RPATH test stricter --- tests/test_wheel.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/test_wheel.py b/tests/test_wheel.py index 664ddcd1..5d5f902e 100644 --- a/tests/test_wheel.py +++ b/tests/test_wheel.py @@ -192,7 +192,7 @@ def test_link_library_in_subproject(venv, wheel_link_library_in_subproject): @pytest.mark.skipif(sys.platform in {'win32', 'cygwin'}, reason='requires RPATH support') -def test_rpath(wheel_link_against_local_lib, tmp_path): +def test_link_against_local_lib_rpath(wheel_link_against_local_lib, tmp_path): artifact = wheel.wheelfile.WheelFile(wheel_link_against_local_lib) artifact.extractall(tmp_path) @@ -200,9 +200,7 @@ def test_rpath(wheel_link_against_local_lib, tmp_path): expected = {f'{origin}/../.link_against_local_lib.mesonpy.libs', 'custom-rpath',} rpath = set(mesonpy._rpath._get_rpath(tmp_path / 'example' / f'_example{EXT_SUFFIX}')) - # Verify that rpath is a superset of the expected one: linking to - # the Python runtime may require additional rpath entries. - assert rpath >= expected + assert rpath == expected @pytest.mark.skipif(sys.platform in {'win32', 'cygwin'}, reason='requires RPATH support') From 2138dcab903978fb5b2654ed0907c45733f87a30 Mon Sep 17 00:00:00 2001 From: Daniele Nicolodi Date: Wed, 30 Jul 2025 16:43:24 +0200 Subject: [PATCH 06/11] TST: add another RPATH handling verification test This shows that build RPATHs are not correctly stripped. --- tests/test_wheel.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/test_wheel.py b/tests/test_wheel.py index 5d5f902e..9e817bb4 100644 --- a/tests/test_wheel.py +++ b/tests/test_wheel.py @@ -178,6 +178,25 @@ def test_local_lib(venv, wheel_link_against_local_lib): assert int(output) == 3 +@pytest.mark.skipif(sys.platform in {'win32', 'cygwin'}, reason='requires RPATH support') +def test_sharedlib_in_package_rpath(wheel_sharedlib_in_package, tmp_path): + artifact = wheel.wheelfile.WheelFile(wheel_sharedlib_in_package) + artifact.extractall(tmp_path) + + origin = '@loader_path' if sys.platform == 'darwin' else '$ORIGIN' + + rpath = set(mesonpy._rpath._get_rpath(tmp_path / 'mypkg' / f'_example{EXT_SUFFIX}')) + # FIXME: RPATH entries added by Meson to point to the build directory are not removed. + assert rpath >= {origin} + + rpath = set(mesonpy._rpath._get_rpath(tmp_path / 'mypkg' / f'liblib{LIB_SUFFIX}')) + # FIXME: RPATH entries added by Meson to point to the build directory are not removed. + assert rpath >= {f'{origin}/sub'} + + rpath = set(mesonpy._rpath._get_rpath(tmp_path / 'mypkg' / 'sub' / f'libsublib{LIB_SUFFIX}')) + assert rpath == set() + + def test_sharedlib_in_package(venv, wheel_sharedlib_in_package): venv.pip('install', wheel_sharedlib_in_package) output = venv.python('-c', 'import mypkg; print(mypkg.prodsum(2, 3, 4))') From a012573de1581ccf9b1c05220296bf340f8a584e Mon Sep 17 00:00:00 2001 From: Daniele Nicolodi Date: Wed, 30 Jul 2025 17:25:38 +0200 Subject: [PATCH 07/11] TST: test RPATH entries added via flags in $LDFLAGS environment variable --- tests/test_wheel.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/test_wheel.py b/tests/test_wheel.py index 9e817bb4..7173540c 100644 --- a/tests/test_wheel.py +++ b/tests/test_wheel.py @@ -197,6 +197,22 @@ def test_sharedlib_in_package_rpath(wheel_sharedlib_in_package, tmp_path): assert rpath == set() +@pytest.mark.skipif(sys.platform in {'win32', 'cygwin'}, reason='requires RPATH support') +def test_sharedlib_in_package_rpath_ldflags(package_sharedlib_in_package, tmp_path, monkeypatch): + origin = '@loader_path' if sys.platform == 'darwin' else '$ORIGIN' + extra_rpath = {f'{origin}/test-ldflags', '/usr/lib/test-ldflags'} + ldflags = ' '.join(f'-Wl,-rpath,{p}' for p in extra_rpath) + monkeypatch.setenv('LDFLAGS', ldflags) + + filename = mesonpy.build_wheel(tmp_path) + artifact = wheel.wheelfile.WheelFile(tmp_path / filename) + artifact.extractall(tmp_path) + + for path in f'_example{EXT_SUFFIX}', f'liblib{LIB_SUFFIX}', f'sub/libsublib{LIB_SUFFIX}': + rpath = set(mesonpy._rpath._get_rpath(tmp_path / 'mypkg' / path)) + assert extra_rpath <= rpath + + def test_sharedlib_in_package(venv, wheel_sharedlib_in_package): venv.pip('install', wheel_sharedlib_in_package) output = venv.python('-c', 'import mypkg; print(mypkg.prodsum(2, 3, 4))') @@ -222,6 +238,21 @@ def test_link_against_local_lib_rpath(wheel_link_against_local_lib, tmp_path): assert rpath == expected +@pytest.mark.skipif(sys.platform in {'win32', 'cygwin'}, reason='requires RPATH support') +def test_link_against_local_lib_rpath_ldflags(package_link_against_local_lib, tmp_path, monkeypatch): + origin = '@loader_path' if sys.platform == 'darwin' else '$ORIGIN' + extra_rpath = {f'{origin}/test-ldflags', '/usr/lib/test-ldflags'} + ldflags = ' '.join(f'-Wl,-rpath,{p}' for p in extra_rpath) + monkeypatch.setenv('LDFLAGS', ldflags) + + filename = mesonpy.build_wheel(tmp_path) + artifact = wheel.wheelfile.WheelFile(tmp_path / filename) + artifact.extractall(tmp_path) + + rpath = set(mesonpy._rpath._get_rpath(tmp_path / 'example' / f'_example{EXT_SUFFIX}')) + assert extra_rpath <= rpath + + @pytest.mark.skipif(sys.platform in {'win32', 'cygwin'}, reason='requires RPATH support') def test_uneeded_rpath(wheel_purelib_and_platlib, tmp_path): artifact = wheel.wheelfile.WheelFile(wheel_purelib_and_platlib) From 4c09f685fc2813ce5706e000ef7f9063bd69f55c Mon Sep 17 00:00:00 2001 From: Daniele Nicolodi Date: Sun, 10 Aug 2025 13:54:53 +0200 Subject: [PATCH 08/11] MAINT: refactor RPATH handling code and add tests This does not introduce any functional changes, except removing duplicates entries from RPATH. Fixes #813. --- mesonpy/_rpath.py | 115 ++++++++++++++++++++++++++++++-------------- tests/test_rpath.py | 39 +++++++++++++++ tests/test_wheel.py | 14 +++--- 3 files changed, 125 insertions(+), 43 deletions(-) create mode 100644 tests/test_rpath.py diff --git a/mesonpy/_rpath.py b/mesonpy/_rpath.py index 8bbc5bfe..8988b025 100644 --- a/mesonpy/_rpath.py +++ b/mesonpy/_rpath.py @@ -11,18 +11,57 @@ if typing.TYPE_CHECKING: - from typing import Iterable, List, Union + from typing import List, TypeVar, Union Path = Union[str, os.PathLike[str]] + T = TypeVar('T') -if sys.platform == 'win32' or sys.platform == 'cygwin': +def unique(values: List[T]) -> List[T]: + r = [] + for value in values: + if value not in r: + r.append(value) + return r + + +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(cls, filepath: Path, libs_relative_path: str) -> None: + old_rpath = cls.get_rpath(filepath) + new_rpath = [] + for path in old_rpath: + if path.startswith(cls.origin): + path = os.path.join(cls.origin, libs_relative_path) + new_rpath.append(path) + new_rpath = unique(new_rpath) + if new_rpath != old_rpath: + cls.set_rpath(filepath, old_rpath, new_rpath) + - def fix_rpath(filepath: Path, libs_relative_path: str) -> None: +class _Windows(RPATH): + + @classmethod + def fix_rpath(cls, filepath: Path, libs_relative_path: str) -> None: pass -elif sys.platform == 'darwin': - def _get_rpath(filepath: Path) -> List[str]: +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 @@ -34,17 +73,24 @@ 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) + @staticmethod + def set_rpath(filepath: Path, old: List[str], rpath: List[str]) -> None: + # This implementation does not preserve the ordering of RPATH + # entries. Meson does the same, thus it should not be a problem. + args: List[str] = [] + for path in rpath: + if path not in old: + args += ['-add_rpath', path] + for path in old: + if path not in rpath: + args += ['-delete_rpath', path] + subprocess.run(['install_name_tool', *args, 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': +class _SunOS5(RPATH): - def _get_rpath(filepath: Path) -> List[str]: + @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) @@ -55,35 +101,32 @@ 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: List[str], rpath: List[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 [x for x in r.stdout.strip().split(':') if x] - def _set_rpath(filepath: Path, rpath: Iterable[str]) -> None: + @staticmethod + def set_rpath(filepath: Path, old: List[str], rpath: List[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': + _cls = _Windows +elif sys.platform == 'darwin': + _cls = _MacOS +elif sys.platform == 'sunos5': + _cls = _SunOS5 +else: + _cls = _ELF + +get_rpath = _cls.get_rpath +set_rpath = _cls.set_rpath +fix_rpath = _cls.fix_rpath diff --git a/tests/test_rpath.py b/tests/test_rpath.py new file mode 100644 index 00000000..425decd8 --- /dev/null +++ b/tests/test_rpath.py @@ -0,0 +1,39 @@ +# SPDX-FileCopyrightText: 2025 The meson-python developers +# +# SPDX-License-Identifier: MIT + +import sys + +import pytest +import wheel.wheelfile + +from mesonpy._rpath import get_rpath, set_rpath + + +@pytest.mark.skipif(sys.platform in {'win32', 'cygwin'}, reason='requires RPATH support') +def test_rpath_get_set(wheel_sharedlib_in_package, tmp_path): + artifact = wheel.wheelfile.WheelFile(wheel_sharedlib_in_package) + artifact.extractall(tmp_path) + obj = list(tmp_path.joinpath('mypkg').glob('_example.*'))[0] + + rpath = get_rpath(obj) + assert rpath + + set_rpath(obj, rpath, []) + rpath = get_rpath(obj) + assert rpath == [] + + new_rpath = ['one', 'two'] + set_rpath(obj, rpath, new_rpath) + rpath = get_rpath(obj) + assert set(rpath) == set(new_rpath) + + new_rpath = ['one', 'three', 'two'] + set_rpath(obj, rpath, new_rpath) + rpath = get_rpath(obj) + assert set(rpath) == set(new_rpath) + + new_rpath = ['one'] + set_rpath(obj, rpath, new_rpath) + rpath = get_rpath(obj) + assert set(rpath) == set(new_rpath) diff --git a/tests/test_wheel.py b/tests/test_wheel.py index 7173540c..ee2624ab 100644 --- a/tests/test_wheel.py +++ b/tests/test_wheel.py @@ -185,15 +185,15 @@ def test_sharedlib_in_package_rpath(wheel_sharedlib_in_package, tmp_path): origin = '@loader_path' if sys.platform == 'darwin' else '$ORIGIN' - rpath = set(mesonpy._rpath._get_rpath(tmp_path / 'mypkg' / f'_example{EXT_SUFFIX}')) + rpath = set(mesonpy._rpath.get_rpath(tmp_path / 'mypkg' / f'_example{EXT_SUFFIX}')) # FIXME: RPATH entries added by Meson to point to the build directory are not removed. assert rpath >= {origin} - rpath = set(mesonpy._rpath._get_rpath(tmp_path / 'mypkg' / f'liblib{LIB_SUFFIX}')) + rpath = set(mesonpy._rpath.get_rpath(tmp_path / 'mypkg' / f'liblib{LIB_SUFFIX}')) # FIXME: RPATH entries added by Meson to point to the build directory are not removed. assert rpath >= {f'{origin}/sub'} - rpath = set(mesonpy._rpath._get_rpath(tmp_path / 'mypkg' / 'sub' / f'libsublib{LIB_SUFFIX}')) + rpath = set(mesonpy._rpath.get_rpath(tmp_path / 'mypkg' / 'sub' / f'libsublib{LIB_SUFFIX}')) assert rpath == set() @@ -209,7 +209,7 @@ def test_sharedlib_in_package_rpath_ldflags(package_sharedlib_in_package, tmp_pa artifact.extractall(tmp_path) for path in f'_example{EXT_SUFFIX}', f'liblib{LIB_SUFFIX}', f'sub/libsublib{LIB_SUFFIX}': - rpath = set(mesonpy._rpath._get_rpath(tmp_path / 'mypkg' / path)) + rpath = set(mesonpy._rpath.get_rpath(tmp_path / 'mypkg' / path)) assert extra_rpath <= rpath @@ -234,7 +234,7 @@ def test_link_against_local_lib_rpath(wheel_link_against_local_lib, tmp_path): origin = '@loader_path' if sys.platform == 'darwin' else '$ORIGIN' expected = {f'{origin}/../.link_against_local_lib.mesonpy.libs', 'custom-rpath',} - rpath = set(mesonpy._rpath._get_rpath(tmp_path / 'example' / f'_example{EXT_SUFFIX}')) + rpath = set(mesonpy._rpath.get_rpath(tmp_path / 'example' / f'_example{EXT_SUFFIX}')) assert rpath == expected @@ -249,7 +249,7 @@ def test_link_against_local_lib_rpath_ldflags(package_link_against_local_lib, tm artifact = wheel.wheelfile.WheelFile(tmp_path / filename) artifact.extractall(tmp_path) - rpath = set(mesonpy._rpath._get_rpath(tmp_path / 'example' / f'_example{EXT_SUFFIX}')) + rpath = set(mesonpy._rpath.get_rpath(tmp_path / 'example' / f'_example{EXT_SUFFIX}')) assert extra_rpath <= rpath @@ -259,7 +259,7 @@ def test_uneeded_rpath(wheel_purelib_and_platlib, tmp_path): artifact.extractall(tmp_path) origin = '@loader_path' if sys.platform == 'darwin' else '$ORIGIN' - rpath = mesonpy._rpath._get_rpath(tmp_path / f'plat{EXT_SUFFIX}') + rpath = mesonpy._rpath.get_rpath(tmp_path / f'plat{EXT_SUFFIX}') for path in rpath: assert origin not in path From ac11e9d42f9bb21d1108a5fb74b50b1e41ab9f34 Mon Sep 17 00:00:00 2001 From: Daniele Nicolodi Date: Sun, 10 Aug 2025 16:14:03 +0200 Subject: [PATCH 09/11] MAINT: move checking shared libs on Windows There is no need to perform the check for every native file installed. --- mesonpy/__init__.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mesonpy/__init__.py b/mesonpy/__init__.py index 94bdc6bb..ec180971 100644 --- a/mesonpy/__init__.py +++ b/mesonpy/__init__.py @@ -454,12 +454,6 @@ def _install_path(self, wheel_file: mesonpy._wheelfile.WheelFile, origin: Path, if self._has_internal_libs: if _is_native(origin): - if sys.platform == 'win32' and not self._allow_windows_shared_libs: - raise NotImplementedError( - 'Loading shared libraries bundled in the Python wheel on Windows requires ' - 'setting the DLL load path or preloading. See the documentation for ' - 'the "tool.meson-python.allow-windows-internal-shared-libs" option.') - # When an executable, libray, or Python extension module is # dynamically linked to a library built as part of the project, # Meson adds a library load path to it pointing to the build @@ -496,6 +490,12 @@ def _wheel_write_metadata(self, whl: mesonpy._wheelfile.WheelFile) -> None: whl.write(f, f'{self._distinfo_dir}/licenses/{pathlib.Path(f).as_posix()}') def build(self, directory: Path) -> pathlib.Path: + if sys.platform == 'win32' and self._has_internal_libs and not self._allow_windows_shared_libs: + raise NotImplementedError( + 'Loading shared libraries bundled in the Python wheel on Windows requires ' + 'setting the DLL load path or preloading. See the documentation for ' + 'the "tool.meson-python.allow-windows-internal-shared-libs" option.') + wheel_file = pathlib.Path(directory, f'{self.name}.whl') with mesonpy._wheelfile.WheelFile(wheel_file, 'w') as whl: self._wheel_write_metadata(whl) From 7c271bcc2496fb1f4f4f010aaf16e5413c6901dc Mon Sep 17 00:00:00 2001 From: Daniele Nicolodi Date: Sun, 10 Aug 2025 17:40:17 +0200 Subject: [PATCH 10/11] BUG: do not remove RPATH entries relative to $ORIGIN for packages using internal shared libraries relocated by meson-python. --- mesonpy/__init__.py | 6 ------ mesonpy/_rpath.py | 20 +++++++++++++++----- tests/test_wheel.py | 3 ++- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/mesonpy/__init__.py b/mesonpy/__init__.py index ec180971..0d60eef3 100644 --- a/mesonpy/__init__.py +++ b/mesonpy/__init__.py @@ -454,12 +454,6 @@ def _install_path(self, wheel_file: mesonpy._wheelfile.WheelFile, origin: Path, if self._has_internal_libs: if _is_native(origin): - # When an executable, libray, or Python extension module is - # dynamically linked to a library built as part of the project, - # Meson adds a library load path to it pointing to the build - # directory, in the form of a relative RPATH entry. meson-python - # 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) diff --git a/mesonpy/_rpath.py b/mesonpy/_rpath.py index 8988b025..84ffbb57 100644 --- a/mesonpy/_rpath.py +++ b/mesonpy/_rpath.py @@ -39,11 +39,21 @@ def set_rpath(filepath: Path, old: List[str], rpath: List[str]) -> None: @classmethod def fix_rpath(cls, filepath: Path, libs_relative_path: str) -> None: old_rpath = cls.get_rpath(filepath) - new_rpath = [] - for path in old_rpath: - if path.startswith(cls.origin): - path = os.path.join(cls.origin, libs_relative_path) - new_rpath.append(path) + new_rpath = old_rpath[:] + + # When an executable, libray, or Python extension module is + # dynamically linked to a library built as part of the project, Meson + # adds a build RPATH pointing to the build directory, in the form of a + # relative RPATH entry. We can use the presence of any RPATH entries + # relative to ``$ORIGIN`` as an indicator that the installed object + # depends on shared libraries internal to the project. In this case we + # need to add an RPATH entry pointing to the meson-python shared + # library install location. This heuristic is not perfect: RPATH + # entries relative to ``$ORIGIN`` can exist for other reasons. + # However, this only results in harmless additional RPATH entries. + if any(path.startswith(cls.origin) for path in old_rpath): + new_rpath.append(os.path.join(cls.origin, libs_relative_path)) + new_rpath = unique(new_rpath) if new_rpath != old_rpath: cls.set_rpath(filepath, old_rpath, new_rpath) diff --git a/tests/test_wheel.py b/tests/test_wheel.py index ee2624ab..284c0c50 100644 --- a/tests/test_wheel.py +++ b/tests/test_wheel.py @@ -235,7 +235,8 @@ def test_link_against_local_lib_rpath(wheel_link_against_local_lib, tmp_path): expected = {f'{origin}/../.link_against_local_lib.mesonpy.libs', 'custom-rpath',} rpath = set(mesonpy._rpath.get_rpath(tmp_path / 'example' / f'_example{EXT_SUFFIX}')) - assert rpath == expected + # FIXME: RPATH entries added by Meson to point to the build directory are not removed. + assert rpath >= expected @pytest.mark.skipif(sys.platform in {'win32', 'cygwin'}, reason='requires RPATH support') From f94a68f216828297f3d8f43246ba3721110df778 Mon Sep 17 00:00:00 2001 From: Daniele Nicolodi Date: Sat, 18 Oct 2025 19:05:17 +0200 Subject: [PATCH 11/11] ENH: implement support for install_rpath Revise tests to exercise support when executed with Meson > 1.6 --- mesonpy/__init__.py | 23 ++++++++++++------- mesonpy/_rpath.py | 11 +++++---- .../sharedlib-in-package/mypkg/meson.build | 12 ++++++---- .../sharedlib-in-package/src/meson.build | 12 ++++++---- 4 files changed, 36 insertions(+), 22 deletions(-) diff --git a/mesonpy/__init__.py b/mesonpy/__init__.py index 0d60eef3..4af64ee3 100644 --- a/mesonpy/__init__.py +++ b/mesonpy/__init__.py @@ -125,6 +125,10 @@ def _compile_patterns(patterns: List[str]) -> Callable[[str], bool]: class _Entry(typing.NamedTuple): dst: pathlib.Path src: str + # Meson support only one install_rpath entry per target. Use a + # list to store install RPATH to be able to add append more + # entries when needed. + install_rpath: List[str] = [] def _map_to_wheel( @@ -183,7 +187,8 @@ def _map_to_wheel( filedst = dst / relpath wheel_files[path].append(_Entry(filedst, filesrc)) else: - wheel_files[path].append(_Entry(dst, src)) + install_rpath = target.get('install_rpath') + wheel_files[path].append(_Entry(dst, src, [install_rpath] if install_rpath else [])) return wheel_files @@ -449,13 +454,15 @@ def _stable_abi(self) -> Optional[str]: return 'abi3.abi3t' if abi3t else '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, + install_rpath: List[str]) -> None: """Add a file to the wheel.""" - if self._has_internal_libs: - if _is_native(origin): - libspath = os.path.relpath(self._libs_dir, destination.parent) - mesonpy._rpath.fix_rpath(origin, libspath) + if self._has_internal_libs and _is_native(origin): + libspath = os.path.relpath(self._libs_dir, destination.parent) + mesonpy._rpath.fix_rpath(origin, install_rpath, libspath) + elif install_rpath: + mesonpy._rpath.fix_rpath(origin, install_rpath, None) try: wheel_file.write(origin, destination.as_posix()) @@ -499,7 +506,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: @@ -510,7 +517,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 diff --git a/mesonpy/_rpath.py b/mesonpy/_rpath.py index 84ffbb57..8ddca018 100644 --- a/mesonpy/_rpath.py +++ b/mesonpy/_rpath.py @@ -11,7 +11,7 @@ if typing.TYPE_CHECKING: - from typing import List, TypeVar, Union + from typing import List, Optional, TypeVar, Union Path = Union[str, os.PathLike[str]] T = TypeVar('T') @@ -37,7 +37,7 @@ def set_rpath(filepath: Path, old: List[str], rpath: List[str]) -> None: raise NotImplementedError @classmethod - def fix_rpath(cls, filepath: Path, libs_relative_path: str) -> None: + def fix_rpath(cls, filepath: Path, install_rpath: List[str], libs_relative_path: Optional[str]) -> None: old_rpath = cls.get_rpath(filepath) new_rpath = old_rpath[:] @@ -51,9 +51,12 @@ def fix_rpath(cls, filepath: Path, libs_relative_path: str) -> None: # library install location. This heuristic is not perfect: RPATH # entries relative to ``$ORIGIN`` can exist for other reasons. # However, this only results in harmless additional RPATH entries. - if any(path.startswith(cls.origin) for path in old_rpath): + if libs_relative_path and any(path.startswith(cls.origin) for path in old_rpath): new_rpath.append(os.path.join(cls.origin, libs_relative_path)) + # Add install_rpath. + new_rpath += install_rpath + new_rpath = unique(new_rpath) if new_rpath != old_rpath: cls.set_rpath(filepath, old_rpath, new_rpath) @@ -62,7 +65,7 @@ def fix_rpath(cls, filepath: Path, libs_relative_path: str) -> None: class _Windows(RPATH): @classmethod - def fix_rpath(cls, filepath: Path, libs_relative_path: str) -> None: + def fix_rpath(cls, filepath: Path, install_rpath: List[str], libs_relative_path: str) -> None: pass diff --git a/tests/packages/sharedlib-in-package/mypkg/meson.build b/tests/packages/sharedlib-in-package/mypkg/meson.build index cebf2d52..915d7c9c 100644 --- a/tests/packages/sharedlib-in-package/mypkg/meson.build +++ b/tests/packages/sharedlib-in-package/mypkg/meson.build @@ -2,17 +2,19 @@ # # SPDX-License-Identifier: MIT +# install_rpath is not exposed in the Meson introspection data in +# Meson versions prior to 1.6.0 and thus cannot be set by meson-python +# when building the Python wheel. Use link_args to set the RPATH when +# using older Meson. +kwargs = meson.version().version_compare('< 1.6') ? {'link_args': f'-Wl,-rpath,@origin@'} : {'install_rpath': f'@origin@'} + py.extension_module( '_example', '_examplemod.c', dependencies: lib_dep, install: true, subdir: 'mypkg', - # install_rpath is not exposed in the Meson introspection data in Meson - # versions prior to 1.6.0 and thus cannot be set by meson-python when - # building the Python wheel. Use link_args to set the RPATH. - # install_rpath: f'@origin@', - link_args: f'-Wl,-rpath,@origin@', + kwargs: kwargs, ) py.install_sources( diff --git a/tests/packages/sharedlib-in-package/src/meson.build b/tests/packages/sharedlib-in-package/src/meson.build index b673ef29..78ae3c9a 100644 --- a/tests/packages/sharedlib-in-package/src/meson.build +++ b/tests/packages/sharedlib-in-package/src/meson.build @@ -23,6 +23,12 @@ sublib_dep = declare_dependency( link_with: sublib, ) +# install_rpath is not exposed in the Meson introspection data in +# Meson versions prior to 1.6.0 and thus cannot be set by meson-python +# when building the Python wheel. Use link_args to set the RPATH when +# using older Meson. +kwargs = meson.version().version_compare('< 1.6') ? {'link_args': f'-Wl,-rpath,@origin@/sub'} : {'install_rpath': f'@origin@/sub'} + lib = shared_library( 'lib', 'lib.c', @@ -30,11 +36,7 @@ lib = shared_library( c_args: export_dll_args, install: true, install_dir: py.get_install_dir() / 'mypkg', - # install_rpath is not exposed in the Meson introspection data in Meson - # versions prior to 1.6.0 and thus cannot be set by meson-python when - # building the Python wheel. Use link_args to set the RPATH. - # install_rpath: f'@origin@/sub', - link_args: f'-Wl,-rpath,@origin@/sub', + kwargs: kwargs, ) lib_dep = declare_dependency(