From 62dd14163a16bec7d3da3eb13781b42d73f0e462 Mon Sep 17 00:00:00 2001 From: Daniele Nicolodi Date: Sat, 9 Dec 2023 20:32:13 +0100 Subject: [PATCH 1/2] ENH: extend RPATH support to more platforms Assume all platforms except Windows and macOS use ELF binaries. --- mesonpy/_rpath.py | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/mesonpy/_rpath.py b/mesonpy/_rpath.py index 084467162..6c0016b34 100644 --- a/mesonpy/_rpath.py +++ b/mesonpy/_rpath.py @@ -16,25 +16,10 @@ from mesonpy._compat import Iterable, Path -if sys.platform == 'linux': - - 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(':') - - def _set_rpath(filepath: Path, rpath: Iterable[str]) -> None: - subprocess.run(['patchelf','--set-rpath', ':'.join(rpath), os.fspath(filepath)], check=True) +if sys.platform == 'win32' or sys.platform == 'cygwin': 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) - + raise NotImplementedError(f'Bundling libraries in wheel is not supported on {sys.platform}') elif sys.platform == 'darwin': @@ -59,6 +44,21 @@ def fix_rpath(filepath: Path, libs_relative_path: str) -> None: _replace_rpath(filepath, path, '@loader_path/' + libs_relative_path) else: + # Assume that any other platform uses ELF binaries. + + 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(':') + + def _set_rpath(filepath: Path, rpath: Iterable[str]) -> None: + subprocess.run(['patchelf','--set-rpath', ':'.join(rpath), os.fspath(filepath)], check=True) def fix_rpath(filepath: Path, libs_relative_path: str) -> None: - raise NotImplementedError(f'Bundling libraries in wheel is not supported on {sys.platform}') + 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) From 1778ab62cb28c702161576016b1d84761412b64b Mon Sep 17 00:00:00 2001 From: Daniele Nicolodi Date: Sat, 9 Dec 2023 20:33:44 +0100 Subject: [PATCH 2/2] ENH: support shared libraries on Windows when explicitly enabled Fixes #525. --- docs/reference/pyproject-settings.rst | 11 +++++++++++ mesonpy/__init__.py | 14 ++++++++++++-- mesonpy/_rpath.py | 2 +- tests/test_tags.py | 2 +- tests/test_wheel.py | 3 ++- 5 files changed, 27 insertions(+), 5 deletions(-) diff --git a/docs/reference/pyproject-settings.rst b/docs/reference/pyproject-settings.rst index 3beb47c61..1473ad6ee 100644 --- a/docs/reference/pyproject-settings.rst +++ b/docs/reference/pyproject-settings.rst @@ -34,6 +34,17 @@ use them and examples. ``meson-python`` itself. It can be overrridden by the :envvar:`MESON` environment variable. +.. option:: tool.meson-python.shared-libs-win32 + + A boolean indicating whether shared libraries should be supported on + Windows. ``meson-python`` installs shared libraries in a dedicated location + and uses RPATH or equivalent mechanisms to have Python modules and native + executables load them form there. Windows does not have an equivalent + mechanism to set the DLL load path. Supporting shared libraries on Windows + requires collaboration from the package. To make sure that package authors + are aware of this requirement, ``meson-python`` raises an error if a + package contains DLLs and this option is not set. + .. option:: tool.meson-python.args.dist Extra arguments to be passed to the ``meson dist`` command. diff --git a/mesonpy/__init__.py b/mesonpy/__init__.py index 05016a1c3..b3db7799e 100644 --- a/mesonpy/__init__.py +++ b/mesonpy/__init__.py @@ -311,10 +311,12 @@ def __init__( metadata: Metadata, manifest: Dict[str, List[Tuple[pathlib.Path, str]]], limited_api: bool, + shared_libs_win32: bool, ) -> None: self._metadata = metadata self._manifest = manifest self._limited_api = limited_api + self._shared_libs_win32 = shared_libs_win32 @property def _has_internal_libs(self) -> bool: @@ -430,6 +432,9 @@ 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._shared_libs_win32: + raise NotImplementedError(f'Bundling libraries in wheel is not supported on {sys.platform}') + # 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 @@ -566,6 +571,7 @@ def _string_or_path(value: Any, name: str) -> str: scheme = _table({ 'meson': _string_or_path, 'limited-api': _bool, + 'shared-libs-win32': _bool, 'args': _table({ name: _strings for name in _MESON_ARGS_KEYS }), @@ -757,6 +763,10 @@ def __init__( if not value: self._limited_api = False + # Shared library support on Windows requires collaboration + # from the package, make sure the developpers aknowledge this. + self._shared_libs_win32 = pyproject_config.get('shared-libs-win32', False) + def _run(self, cmd: Sequence[str]) -> None: """Invoke a subprocess.""" # Flush the line to ensure that the log line with the executed @@ -920,13 +930,13 @@ def sdist(self, directory: Path) -> pathlib.Path: def wheel(self, directory: Path) -> pathlib.Path: """Generates a wheel in the specified directory.""" self.build() - builder = _WheelBuilder(self._metadata, self._manifest, self._limited_api) + builder = _WheelBuilder(self._metadata, self._manifest, self._limited_api, self._shared_libs_win32) return builder.build(directory) def editable(self, directory: Path) -> pathlib.Path: """Generates an editable wheel in the specified directory.""" self.build() - builder = _EditableWheelBuilder(self._metadata, self._manifest, self._limited_api) + builder = _EditableWheelBuilder(self._metadata, self._manifest, self._limited_api, self._shared_libs_win32) return builder.build(directory, self._source_dir, self._build_dir, self._build_command, self._editable_verbose) diff --git a/mesonpy/_rpath.py b/mesonpy/_rpath.py index 6c0016b34..e618fbb76 100644 --- a/mesonpy/_rpath.py +++ b/mesonpy/_rpath.py @@ -19,7 +19,7 @@ if sys.platform == 'win32' or sys.platform == 'cygwin': def fix_rpath(filepath: Path, libs_relative_path: str) -> None: - raise NotImplementedError(f'Bundling libraries in wheel is not supported on {sys.platform}') + pass elif sys.platform == 'darwin': diff --git a/tests/test_tags.py b/tests/test_tags.py index c55d69739..73d1187f0 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -77,7 +77,7 @@ def test_python_host_platform(monkeypatch): def wheel_builder_test_factory(content, pure=True, limited_api=False): manifest = defaultdict(list) manifest.update({key: [(pathlib.Path(x), os.path.join('build', x)) for x in value] for key, value in content.items()}) - return mesonpy._WheelBuilder(None, manifest, limited_api) + return mesonpy._WheelBuilder(None, manifest, limited_api, False) def test_tag_empty_wheel(): diff --git a/tests/test_wheel.py b/tests/test_wheel.py index 33a974c41..e8ece6868 100644 --- a/tests/test_wheel.py +++ b/tests/test_wheel.py @@ -231,7 +231,8 @@ def test_entrypoints(wheel_full_metadata): def test_top_level_modules(package_module_types): with mesonpy._project() as project: - builder = mesonpy._EditableWheelBuilder(project._metadata, project._manifest, project._limited_api) + builder = mesonpy._EditableWheelBuilder( + project._metadata, project._manifest, project._limited_api, project._shared_libs_win32) assert set(builder._top_level_modules) == { 'file', 'package',