From 10fd54f18c75b5ddeab8a88e86174867115e0fdf Mon Sep 17 00:00:00 2001 From: Daniele Nicolodi Date: Fri, 14 Feb 2025 23:34:00 +0100 Subject: [PATCH 1/4] CI: test with Meson 1.6.0 --- .github/workflows/tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index cc48c21c..ba7b2214 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -91,6 +91,9 @@ jobs: - os: ubuntu-latest python: '3.12' meson: '~=1.5.0' + - os: ubuntu-latest + python: '3.13' + meson: '~=1.6.0' # Test with Meson master branch. - os: ubuntu-latest python: '3.12' From 196bb571c454bb4162120c1a7cdcfd2ea6967dd1 Mon Sep 17 00:00:00 2001 From: Daniele Nicolodi Date: Thu, 13 Feb 2025 17:57:10 +0100 Subject: [PATCH 2/4] MAINT: refactoring --- mesonpy/__init__.py | 49 +++++++++++++++++++++++---------------------- tests/test_tags.py | 3 ++- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/mesonpy/__init__.py b/mesonpy/__init__.py index 51c6f0fb..c56e5592 100644 --- a/mesonpy/__init__.py +++ b/mesonpy/__init__.py @@ -14,6 +14,7 @@ import argparse import collections import contextlib +import dataclasses import difflib import functools import importlib.machinery @@ -109,9 +110,14 @@ class InvalidLicenseExpression(Exception): # type: ignore[no-redef] } -def _map_to_wheel(sources: Dict[str, Dict[str, Any]]) -> DefaultDict[str, List[Tuple[pathlib.Path, str]]]: +class Entry(typing.NamedTuple): + dst: pathlib.Path + src: str + + +def _map_to_wheel(sources: Dict[str, Dict[str, Any]]) -> DefaultDict[str, List[Entry]]: """Map files to the wheel, organized by wheel installation directory.""" - wheel_files: DefaultDict[str, List[Tuple[pathlib.Path, str]]] = collections.defaultdict(list) + wheel_files: DefaultDict[str, List[Entry]] = collections.defaultdict(list) packages: Dict[str, str] = {} for key, group in sources.items(): @@ -129,7 +135,8 @@ def _map_to_wheel(sources: Dict[str, Dict[str, Any]]) -> DefaultDict[str, List[T other = packages.setdefault(package, path) if other != path: this = os.fspath(pathlib.Path(path, *destination.parts[1:])) - that = os.fspath(other / next(d for d, s in wheel_files[other] if d.parts[0] == destination.parts[1])) + module = next(entry.dst for entry in wheel_files[other] if entry.dst.parts[0] == destination.parts[1]) + that = os.fspath(other / module) raise BuildError( f'The {package} package is split between {path} and {other}: ' f'{this!r} and {that!r}, a "pure: false" argument may be missing in meson.build. ' @@ -152,9 +159,9 @@ def _map_to_wheel(sources: Dict[str, Dict[str, Any]]) -> DefaultDict[str, List[T if relpath in exclude_files: continue filedst = dst / relpath - wheel_files[path].append((filedst, filesrc)) + wheel_files[path].append(Entry(filedst, filesrc)) else: - wheel_files[path].append((dst, src)) + wheel_files[path].append(Entry(dst, src)) return wheel_files @@ -301,20 +308,14 @@ def _is_native(file: Path) -> bool: return f.read(4) == b'\x7fELF' # ELF +@dataclasses.dataclass class _WheelBuilder(): """Helper class to build wheels from projects.""" - def __init__( - self, - metadata: Metadata, - manifest: Dict[str, List[Tuple[pathlib.Path, str]]], - limited_api: bool, - allow_windows_shared_libs: bool, - ) -> None: - self._metadata = metadata - self._manifest = manifest - self._limited_api = limited_api - self._allow_windows_shared_libs = allow_windows_shared_libs + _metadata: Metadata + _manifest: Dict[str, List[Entry]] + _limited_api: bool + _allow_windows_shared_libs: bool @property def _has_internal_libs(self) -> bool: @@ -330,8 +331,8 @@ def _pure(self) -> bool: """Whether the wheel is architecture independent""" if self._manifest['platlib'] or self._manifest['mesonpy-libs']: return False - for _, file in self._manifest['scripts']: - if _is_native(file): + for entry in self._manifest['scripts']: + if _is_native(entry.src): return False return True @@ -408,14 +409,14 @@ def _stable_abi(self) -> Optional[str]: # in {platlib} that look like extension modules, and raise # an exception if any of them has a Python version # specific extension filename suffix ABI tag. - for path, _ in self._manifest['platlib']: - match = _EXTENSION_SUFFIX_REGEX.match(path.name) + for entry in self._manifest['platlib']: + match = _EXTENSION_SUFFIX_REGEX.match(entry.dst.name) if match: abi = match.group('abi') if abi is not None and abi != 'abi3': raise BuildError( f'The package declares compatibility with Python limited API but extension ' - f'module {os.fspath(path)!r} is tagged for a specific Python version.') + f'module {os.fspath(entry.dst)!r} is tagged for a specific Python version.') return 'abi3' return None @@ -497,8 +498,8 @@ class _EditableWheelBuilder(_WheelBuilder): def _top_level_modules(self) -> Collection[str]: modules = set() for type_ in self._manifest: - for path, _ in self._manifest[type_]: - name, dot, ext = path.parts[0].partition('.') + for entry in self._manifest[type_]: + name, dot, ext = entry.dst.parts[0].partition('.') if dot: # module suffix = dot + ext @@ -854,7 +855,7 @@ def _info(self, name: str) -> Any: return json.loads(info.read_text(encoding='utf-8')) @property - def _manifest(self) -> DefaultDict[str, List[Tuple[pathlib.Path, str]]]: + def _manifest(self) -> DefaultDict[str, List[Entry]]: """The files to be added to the wheel, organized by wheel path.""" # Obtain the list of files Meson would install. diff --git a/tests/test_tags.py b/tests/test_tags.py index 433628b8..0a8d975a 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -76,7 +76,8 @@ 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()}) + for key, value in content.items(): + manifest[key] = [mesonpy.Entry(pathlib.Path(x), os.path.join('build', x)) for x in value] return mesonpy._WheelBuilder(None, manifest, limited_api, False) From d356a4216695728eb8da6d719f4c84451f5476c4 Mon Sep 17 00:00:00 2001 From: Daniele Nicolodi Date: Thu, 13 Feb 2025 18:15:28 +0100 Subject: [PATCH 3/4] ENH: improve RPATH handling Always strip RPATH pointing to the build directory automatically added by meson at build time to all artifacts linking to a shared library built as part of the project. Before this was done only when the project contained a shared library relocated to ..mesonpy.libs. Add the RPATH entry specified in the meson.build definition via the install_rpath argument to all artifacts. This automatically remaps the $ORIGIN anchor to @loader_path on macOS. This requires Meson 1.6.0 or later. Deduplicate RPATH entries. Fixes #711. --- docs/reference/meson-compatibility.rst | 4 + mesonpy/__init__.py | 38 ++++--- mesonpy/_rpath.py | 150 ++++++++++++++++++------- 3 files changed, 138 insertions(+), 54 deletions(-) diff --git a/docs/reference/meson-compatibility.rst b/docs/reference/meson-compatibility.rst index 6b339d60..dfa96432 100644 --- a/docs/reference/meson-compatibility.rst +++ b/docs/reference/meson-compatibility.rst @@ -53,6 +53,10 @@ versions. declared via the ``project()`` call in ``meson.build``. This also requires ``pyproject-metadata`` version 0.9.0 or later. + Meson 1.6.0 or later is also required for support for the + ``install_rpath`` argument to Meson functions declaring build rules + for object files. + Build front-ends by default build packages in an isolated Python environment where build dependencies are installed. Most often, unless a package or its build dependencies declare explicitly a version diff --git a/mesonpy/__init__.py b/mesonpy/__init__.py index c56e5592..c01ab1cd 100644 --- a/mesonpy/__init__.py +++ b/mesonpy/__init__.py @@ -113,6 +113,7 @@ class InvalidLicenseExpression(Exception): # type: ignore[no-redef] class Entry(typing.NamedTuple): dst: pathlib.Path src: str + rpath: Optional[str] = None def _map_to_wheel(sources: Dict[str, Dict[str, Any]]) -> DefaultDict[str, List[Entry]]: @@ -161,7 +162,7 @@ def _map_to_wheel(sources: Dict[str, Dict[str, Any]]) -> DefaultDict[str, List[E filedst = dst / relpath wheel_files[path].append(Entry(filedst, filesrc)) else: - wheel_files[path].append(Entry(dst, src)) + wheel_files[path].append(Entry(dst, src, target.get('install_rpath'))) return wheel_files @@ -420,25 +421,25 @@ def _stable_abi(self) -> Optional[str]: return '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, rpath: Optional[str]) -> None: """Add a file to the wheel.""" - 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 + if _is_native(origin): + libspath = None + if self._has_internal_libs: + # When an executable, library, 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 + # relocates the shared libraries to the ``..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) + + # Adjust RPATH: remove build RPATH added by meson, add an RPATH + # entries as per above, and add any ``install_rpath`` specified in + # meson.build + mesonpy._rpath.fix_rpath(origin, rpath, libspath) try: wheel_file.write(origin, destination.as_posix()) @@ -467,6 +468,13 @@ 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 ConfigError( + '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) @@ -476,7 +484,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: @@ -487,7 +495,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 a7cbbb92..69103e64 100644 --- a/mesonpy/_rpath.py +++ b/mesonpy/_rpath.py @@ -11,19 +11,81 @@ if typing.TYPE_CHECKING: - from typing import List + from typing import List, Optional, TypeVar from mesonpy._compat import Iterable, Path + T = TypeVar('T') -if sys.platform == 'win32' or sys.platform == 'cygwin': - def fix_rpath(filepath: Path, libs_relative_path: str) -> None: +def unique(values: List[T]) -> List[T]: + r = [] + for value in values: + if value not in r: + r.append(value) + return r + + +class _Windows: + + @staticmethod + def get_rpath(filepath: Path) -> List[str]: + return [] + + @classmethod + def fix_rpath(cls, filepath: Path, install_rpath: Optional[str], libs_rpath: Optional[str]) -> None: pass -elif sys.platform == 'darwin': - def _get_rpath(filepath: Path) -> List[str]: +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, install_rpath: Optional[str], libs_rpath: Optional[str]) -> None: + old_rpath = cls.get_rpath(filepath) + new_rpath = [] + if libs_rpath is not None: + if libs_rpath == '.': + libs_rpath = '' + for path in old_rpath: + if path.split('/', 1)[0] == cls.origin: + # Any RPATH entry relative to ``$ORIGIN`` is interpreted as + # pointing to a location in the build directory added by + # Meson. These need to be removed. Their presence indicates + # that the executable, shared library, or Python module + # depends on libraries build as part of the package. These + # entries are thus replaced with entries pointing to the + # ``..mesonpy.libs`` folder where meson-python + # relocates shared libraries distributed with the package. + # The package may however explicitly install these in a + # different location, thus this is not a perfect heuristic + # and may add not required RPATH entries. These are however + # harmless. + path = f'{cls.origin}/{libs_rpath}' + # Any other RPATH entry is preserved. + new_rpath.append(path) + if install_rpath: + # Add the RPATH entry spcified with the ``install_rpath`` argument. + new_rpath.append(install_rpath) + # Make the RPATH entries unique. + new_rpath = unique(new_rpath) + if new_rpath != old_rpath: + cls.set_rpath(filepath, old_rpath, new_rpath) + + +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 @@ -35,17 +97,31 @@ 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) - - 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': - - def _get_rpath(filepath: Path) -> List[str]: + @staticmethod + def set_rpath(filepath: Path, old: List[str], rpath: List[str]) -> None: + 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) + + @classmethod + def fix_rpath(cls, filepath: Path, install_rpath: Optional[str], libs_rpath: Optional[str]) -> None: + if install_rpath is not None: + root, sep, stem = install_rpath.partition('/') + if root == '$ORIGIN': + install_rpath = f'{cls.origin}{sep}{stem}' + # warnings.warn('...') + super().fix_rpath(filepath, install_rpath, libs_rpath) + + +class _SunOS(RPATH): + + @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) @@ -56,35 +132,31 @@ 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: Iterable[str], rpath: Iterable[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 r.stdout.strip().split(':') - def _set_rpath(filepath: Path, rpath: Iterable[str]) -> None: + @staticmethod + def set_rpath(filepath: Path, old: Iterable[str], 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: - 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 = _SunOS +else: + _cls = _ELF + +_get_rpath = _cls.get_rpath +fix_rpath = _cls.fix_rpath From 9951f6f39649fee091f55498b8b2f784723b94ff Mon Sep 17 00:00:00 2001 From: Daniele Nicolodi Date: Sat, 15 Feb 2025 12:03:21 +0100 Subject: [PATCH 4/4] TST: fix test and reorganize Rework the dependencies between the Python extension module and the package libraries so that they can be loaded with a single additional RPATH entry for each. The previous dependencies required two RPATH entries to be added to the extension module to find the two libraries installed in two different paths, however setting two RPATH entries is not possible via install_rpath. Meson version 1.6.0 or later is required for insrall_rpath to be present in meson introspection data. Reorganizes the test package to a flatter layout that helps visualizing all the parts involved in the test. --- .../link-against-local-lib/meson.build | 3 +- .../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 ----- .../packages/sharedlib-in-package/mypkg/lib.c | 10 ++++++ .../packages/sharedlib-in-package/mypkg/lib.h | 13 ++++++++ .../sharedlib-in-package/mypkg/meson.build | 31 +++++++++++++------ .../mypkg/sub/examplelib2.h | 7 ----- .../mypkg/{sub/examplelib2.c => sublib.c} | 4 +-- .../sharedlib-in-package/mypkg/sublib.h | 13 ++++++++ tests/test_wheel.py | 15 +++++---- 12 files changed, 79 insertions(+), 62 deletions(-) delete mode 100644 tests/packages/sharedlib-in-package/mypkg/examplelib.c delete mode 100644 tests/packages/sharedlib-in-package/mypkg/examplelib.h create mode 100644 tests/packages/sharedlib-in-package/mypkg/lib.c create mode 100644 tests/packages/sharedlib-in-package/mypkg/lib.h delete mode 100644 tests/packages/sharedlib-in-package/mypkg/sub/examplelib2.h rename tests/packages/sharedlib-in-package/mypkg/{sub/examplelib2.c => sublib.c} (66%) create mode 100644 tests/packages/sharedlib-in-package/mypkg/sublib.h diff --git a/tests/packages/link-against-local-lib/meson.build b/tests/packages/link-against-local-lib/meson.build index e8cd2830..ff041820 100644 --- a/tests/packages/link-against-local-lib/meson.build +++ b/tests/packages/link-against-local-lib/meson.build @@ -9,7 +9,7 @@ if meson.get_compiler('c').get_id() in ['msvc', 'clang-cl', 'intel-cl'] link_args = ['-DEXAMPLE_DLL_IMPORTS'] else lib_compile_args = [] - link_args = ['-Wl,-rpath,custom-rpath'] + link_args = ['-Wl,-rpath,rpath-from-linker-arguments'] endif subdir('lib') @@ -26,6 +26,7 @@ py.extension_module( 'examplemod.c', link_with: example_lib, link_args: link_args, + install_rpath: 'custom-rpath', install: true, subdir: 'example', ) 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/lib.c b/tests/packages/sharedlib-in-package/mypkg/lib.c new file mode 100644 index 00000000..e4fe1478 --- /dev/null +++ b/tests/packages/sharedlib-in-package/mypkg/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/mypkg/lib.h b/tests/packages/sharedlib-in-package/mypkg/lib.h new file mode 100644 index 00000000..fb6a02d8 --- /dev/null +++ b/tests/packages/sharedlib-in-package/mypkg/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/mypkg/meson.build b/tests/packages/sharedlib-in-package/mypkg/meson.build index 75904bed..47dc1c40 100644 --- a/tests/packages/sharedlib-in-package/mypkg/meson.build +++ b/tests/packages/sharedlib-in-package/mypkg/meson.build @@ -10,29 +10,42 @@ else import_dll_args = [] endif -example_lib = shared_library( - 'examplelib', - 'examplelib.c', +sublib = shared_library( + 'sublib', + 'sublib.c', c_args: export_dll_args, install: true, - install_dir: py.get_install_dir() / 'mypkg', + install_dir: py.get_install_dir() / 'mypkg/sub', ) -example_lib_dep = declare_dependency( +sublib_dep = declare_dependency( compile_args: import_dll_args, - link_with: example_lib, + link_with: sublib, +) + +lib = shared_library( + 'lib', + 'lib.c', + dependencies: sublib_dep, + c_args: export_dll_args, + install_rpath: '$ORIGIN/sub', + install: true, + install_dir: py.get_install_dir() / 'mypkg', ) -subdir('sub') +lib_dep = declare_dependency( + compile_args: import_dll_args, + link_with: lib, +) py.extension_module( '_example', '_examplemod.c', - dependencies: [example_lib_dep, example_lib2_dep], + dependencies: lib_dep, include_directories: 'sub', install: true, subdir: 'mypkg', - install_rpath: '$ORIGIN', + install_rpath: build_machine.system() == 'darwin' ? '@loader_path' : '$ORIGIN', ) py.install_sources( 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/mypkg/sub/examplelib2.c b/tests/packages/sharedlib-in-package/mypkg/sublib.c similarity index 66% rename from tests/packages/sharedlib-in-package/mypkg/sub/examplelib2.c rename to tests/packages/sharedlib-in-package/mypkg/sublib.c index 12f5b87a..facfdf2e 100644 --- a/tests/packages/sharedlib-in-package/mypkg/sub/examplelib2.c +++ b/tests/packages/sharedlib-in-package/mypkg/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/mypkg/sublib.h b/tests/packages/sharedlib-in-package/mypkg/sublib.h new file mode 100644 index 00000000..9fc7ae51 --- /dev/null +++ b/tests/packages/sharedlib-in-package/mypkg/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 b6f64cee..426221d7 100644 --- a/tests/test_wheel.py +++ b/tests/test_wheel.py @@ -178,15 +178,14 @@ def test_local_lib(venv, wheel_link_against_local_lib): assert int(output) == 3 +@pytest.mark.skipif(MESON_VERSION < (1, 6, 0), reason='meson too old') 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 version too old') +@pytest.mark.skipif(MESON_VERSION < (1, 3, 0), reason='meson too old') def test_link_library_in_subproject(venv, wheel_link_library_in_subproject): venv.pip('install', wheel_link_library_in_subproject) output = venv.python('-c', 'import foo; print(foo.example_sum(3, 6))') @@ -199,7 +198,11 @@ def test_rpath(wheel_link_against_local_lib, tmp_path): artifact.extractall(tmp_path) origin = '@loader_path' if sys.platform == 'darwin' else '$ORIGIN' - expected = {f'{origin}/../.link_against_local_lib.mesonpy.libs', 'custom-rpath',} + expected = {f'{origin}/../.link_against_local_lib.mesonpy.libs', 'rpath-from-linker-arguments',} + + # ``install_rpath`` is supported starting with meson 1.6.0 + if MESON_VERSION >= (1, 6, 0): + expected.add('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