diff --git a/docs/reference/pyproject-settings.rst b/docs/reference/pyproject-settings.rst index b7806f569..78a9cfc33 100644 --- a/docs/reference/pyproject-settings.rst +++ b/docs/reference/pyproject-settings.rst @@ -63,6 +63,21 @@ use them and examples. Extra arguments to be passed to the ``meson install`` command. +.. option:: tool.meson-python.wheel.exclude + + List of glob patterns matching paths of files that must be excluded from + the Python wheel. The accepted glob patterns are the ones implemented by + the Python :mod:`fnmatch` with case sensitive matching. The paths to be + matched are as they appear in the Meson introspection data, namely they are + rooted in one of the Meson install locations: ``{bindir}``, ``{datadir}``, + ``{includedir}``, ``{libdir_shared}``, ``{libdir_static}``, et cetera. + + This configuration setting is measure of last resort to exclude installed + files from a Python wheel. It is to be used when the project includes + subprojects that do not allow fine control on the installed files. Better + solutions include the use of Meson install tags and excluding subprojects + to be installed via :option:`tool.meson-python.args.install`. + __ https://docs.python.org/3/c-api/stable.html?highlight=limited%20api#stable-application-binary-interface __ https://mesonbuild.com/Python-module.html#extension_module diff --git a/mesonpy/__init__.py b/mesonpy/__init__.py index 35ef729af..fca444049 100644 --- a/mesonpy/__init__.py +++ b/mesonpy/__init__.py @@ -16,6 +16,7 @@ import contextlib import copy import difflib +import fnmatch import functools import importlib.machinery import io @@ -111,13 +112,27 @@ class InvalidLicenseExpression(Exception): # type: ignore[no-redef] } -def _map_to_wheel(sources: Dict[str, Dict[str, Any]]) -> DefaultDict[str, List[Tuple[pathlib.Path, str]]]: +def _compile_patterns(patterns: List[str]) -> Callable[[str], bool]: + if not patterns: + return lambda x: False + func = re.compile('|'.join(fnmatch.translate(os.path.normpath(p)) for p in patterns)).match + return typing.cast('Callable[[str], bool]', func) + + +def _map_to_wheel( + sources: Dict[str, Dict[str, Any]], + exclude: List[str] +) -> DefaultDict[str, List[Tuple[pathlib.Path, str]]]: """Map files to the wheel, organized by wheel installation directory.""" wheel_files: DefaultDict[str, List[Tuple[pathlib.Path, str]]] = collections.defaultdict(list) packages: Dict[str, str] = {} + excluded = _compile_patterns(exclude) for key, group in sources.items(): for src, target in group.items(): + if excluded(target['destination']): + continue + destination = pathlib.Path(target['destination']) anchor = destination.parts[0] dst = pathlib.Path(*destination.parts[1:]) @@ -581,6 +596,9 @@ def _string_or_path(value: Any, name: str) -> str: 'args': _table({ name: _strings for name in _MESON_ARGS_KEYS }), + 'wheel': _table({ + 'exclude': _strings, + }), }) table = pyproject.get('tool', {}).get('meson-python', {}) @@ -823,6 +841,9 @@ def __init__( # from the package, make sure the developers acknowledge this. self._allow_windows_shared_libs = pyproject_config.get('allow-windows-internal-shared-libs', False) + # Files to be excluded from the wheel + self._excluded_files = pyproject_config.get('wheel', {}).get('exclude', []) + def _run(self, cmd: Sequence[str]) -> None: """Invoke a subprocess.""" # Flush the line to ensure that the log line with the executed @@ -906,7 +927,7 @@ def _manifest(self) -> DefaultDict[str, List[Tuple[pathlib.Path, str]]]: sources[key][target] = details # Map Meson installation locations to wheel paths. - return _map_to_wheel(sources) + return _map_to_wheel(sources, self._excluded_files) @property def _meson_name(self) -> str: diff --git a/tests/packages/subproject/pyproject.toml b/tests/packages/subproject/pyproject.toml index 79b6ea07f..45d204ba7 100644 --- a/tests/packages/subproject/pyproject.toml +++ b/tests/packages/subproject/pyproject.toml @@ -5,3 +5,8 @@ [build-system] build-backend = 'mesonpy' requires = ['meson-python'] + +[tool.meson-python.wheel] +# Meson versions before 1.3.0 install data files in {datadir}/{project name}/, +# later versions install in the more correct {datadir}/{dubproject name}/ +exclude = ['{datadir}/*/data.txt'] diff --git a/tests/packages/subproject/subprojects/dep/data.txt b/tests/packages/subproject/subprojects/dep/data.txt new file mode 100644 index 000000000..cfa9c9661 --- /dev/null +++ b/tests/packages/subproject/subprojects/dep/data.txt @@ -0,0 +1 @@ +excluded via tool.meson-python.wheel.exclude diff --git a/tests/packages/subproject/subprojects/dep/meson.build b/tests/packages/subproject/subprojects/dep/meson.build index a7567f449..34c6b7508 100644 --- a/tests/packages/subproject/subprojects/dep/meson.build +++ b/tests/packages/subproject/subprojects/dep/meson.build @@ -7,3 +7,5 @@ project('dep') py = import('python').find_installation() py.install_sources('dep.py') + +install_data('data.txt')