Skip to content

Commit f3393d2

Browse files
committed
EHN: add tool.meson-python.wheel.{exclude,include} settings
1 parent 09e18fc commit f3393d2

File tree

6 files changed

+111
-45
lines changed

6 files changed

+111
-45
lines changed

docs/how-to-guides/shared-libraries.rst

Lines changed: 33 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -187,38 +187,27 @@ strategies for folding a library built in a subproject into a wheel built with
187187
to be within the Python package's tree, or rely on ``meson-python`` to fold
188188
it into the wheel when it'd otherwise be installed to ``libdir``.
189189

190-
Option (1) tends to be easier, so unless the library of interest cannot be
191-
built as a static library or it would inflate the wheel size too much because
192-
it's needed by multiple Python extension modules, we recommend trying option
193-
(1) first.
194-
195-
A typical C or C++ project providing a library to link against tends to provide
196-
(a) one or more ``library()`` targets, which can be built as shared, static, or both,
197-
and (b) headers, pkg-config files, tests and perhaps other development targets
198-
that are needed to use the ``library()`` target(s). One of the challenges to use
199-
such projects as a subproject is that the headers and other installable targets
200-
are targeting system locations (e.g., ``<prefix>/include/``) which isn't supported
201-
by wheels and hence ``meson-python`` errors out when it encounters such an install
202-
target. This is perhaps the main issue one encounters with subproject usage,
203-
and the following two sections discuss how options (1) and (2) can work around
204-
that.
190+
Static linking tends to be easier, and it is the recommended solution, unless
191+
the library of interest cannot be built as a static library or it would
192+
inflate the wheel size too much because it's needed by multiple Python
193+
extension modules.
205194

206195
Static library from subproject
207196
------------------------------
208197

209-
The major advantage of building a library target as static and folding it directly
210-
into an extension module is that no targets from the subproject need to be installed.
211-
To configure the subproject for this use case, add the following to the
212-
``pyproject.toml`` file of your package:
198+
The major advantage of building a library target as static and folding it
199+
directly into an extension module is that the RPATH or the DLL search path do
200+
not need to be adjusted and no targets from the subproject need to be
201+
installed. To ensures that ``library()`` targets are built as static, and that
202+
no parts of the subprojects are installed, the following configuration can be
203+
added in ``pyproject.toml`` to ensure the relevant options are passed to Meson:
213204

214205
.. code-block:: toml
215206
216207
[tool.meson-python.args]
217208
setup = ['--default-library=static']
218209
install = ['--skip-subprojects']
219210
220-
This ensures that ``library`` targets are built as static, and nothing gets installed.
221-
222211
To then link against the static library in the subproject, say for a subproject
223212
named ``bar`` with the main library target contained in a ``bar_dep`` dependency,
224213
add this to your ``meson.build`` file:
@@ -235,30 +224,37 @@ add this to your ``meson.build`` file:
235224
install: true,
236225
)
237226
238-
That is all!
239-
240227
Shared library from subproject
241228
------------------------------
242229

243-
If we can't use the static library approach from the section above and we need
244-
a shared library, then we must have ``install: true`` for that shared library
245-
target. This can only work if we can pass some build option to the subproject
246-
that tells it to *only* install the shared library and not headers or other
247-
targets that we don't need. Install tags don't work per subproject, so
248-
this will look something like:
230+
Sometimes it may be necessary or preferable to use dynamic linking to a shared
231+
library provided in a subproject, for example to avoid inflating the wheel
232+
size having multiple copies of the same object code in different extension
233+
modules using the same library. In this case, the subproject needs to install
234+
the shared library in the usual location in ``libdir``. ``meson-python``
235+
will automatically include it into the wheel in
236+
``.<project-name>.mesonpy.libs`` just like an internal shared library.
237+
238+
Most projects, however, install more than the shared library and the extra
239+
components, such as header files or documentation, should not be included in
240+
the Python wheel. Projects may have configuration options to disable building
241+
and installing additional components, in this case, these options can be
242+
passed to the ``subproject()`` call:
249243

250244
.. code-block:: meson
251245
252246
foo_subproj = subproject('foo',
253247
default_options: {
254-
# This is a custom option - if it doesn't exist, can you add it
255-
# upstream or in WrapDB?
256-
'only_install_main_lib': true,
248+
'docs': 'disabled',
257249
})
258250
foo_dep = foo_subproj.get_variable('foo_dep')
259251
260-
Now we can use ``foo_dep`` like a normal dependency, ``meson-python`` will
261-
include it into the wheel in ``.<project-name>.mesonpy.libs`` just like an
262-
internal shared library that targets ``libdir`` (see
263-
:ref:`internal-shared-libraries`).
264-
*Remember: this method doesn't support Windows (yet)!*
252+
Install tags do not work per subproject, therefore to exclude other parts of
253+
the subproject from being included in the wheel, we need to resort to
254+
``meson-python`` install location filters using the
255+
:option:`tool.meson-python.wheel.exclude` build option:
256+
257+
.. code-block:: toml
258+
259+
[tool.meson-python.wheel]
260+
exclude = ['{prefix}/include/*']

docs/reference/pyproject-settings.rst

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ use them and examples.
2929
.. option:: tool.meson-python.limited-api
3030

3131
A boolean indicating whether the extension modules contained in the
32-
Python package target the `Python limited API`__. Extension
32+
Python package target the `Python limited API`_. Extension
3333
modules can be compiled for the Python limited API specifying the
34-
``limited_api`` argument to the |extension_module()|__ function
34+
``limited_api`` argument to the |extension_module()|_ function
3535
in the Meson Python module. When this setting is set to true, the
3636
value ``abi3`` is used for the Python wheel filename ABI tag.
3737

@@ -63,8 +63,36 @@ use them and examples.
6363

6464
Extra arguments to be passed to the ``meson install`` command.
6565

66-
67-
__ https://docs.python.org/3/c-api/stable.html?highlight=limited%20api#stable-application-binary-interface
68-
__ https://mesonbuild.com/Python-module.html#extension_module
66+
.. option:: tool.meson-python.wheel.exclude
67+
68+
List of glob patterns matching paths of files that must be excluded from
69+
the Python wheel. The accepted glob patterns are the ones implemented by
70+
the Python :mod:`fnmatch` with case sensitive matching. The paths to be
71+
matched are as they appear in the Meson introspection data, namely they are
72+
rooted in one of the Meson install locations: ``{bindir}``, ``{datadir}``,
73+
``{includedir}``, ``{libdir_shared}``, ``{libdir_static}``, et cetera.
74+
75+
Inspecting the `Meson introspection data`_ may be useful to craft the exclude
76+
patterns. It is accessible as the ``meson-info/intro-install_plan.json`` JSON
77+
document in the build directory.
78+
79+
This configuration setting is measure of last resort to exclude installed
80+
files from a Python wheel. It is to be used when the project includes
81+
subprojects that do not allow fine control on the installed files. Better
82+
solutions include the use of Meson install tags and excluding subprojects
83+
to be installed via :option:`tool.meson-python.args.install`.
84+
85+
.. option:: tool.meson-python.wheel.include
86+
87+
List of glob patterns matching paths of files that must not be excluded
88+
from the Python wheel. All files recorded for installation in the Meson
89+
project are included in the Python wheel unless matching an exclude glob
90+
pattern specified in :option:`tool.meson-python.wheel.exclude`. An include
91+
glob pattern is useful exclusively to limit the effect of an exclude
92+
pattern that matches too many files.
93+
94+
.. _python limited api: https://docs.python.org/3/c-api/stable.html?highlight=limited%20api#stable-application-binary-interface
95+
.. _extension_module(): `https://mesonbuild.com/Python-module.html#extension_module
96+
.. _meson introspection data: https://mesonbuild.com/IDE-integration.html#install-plan
6997

7098
.. |extension_module()| replace:: ``extension_module()``

mesonpy/__init__.py

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import contextlib
1717
import copy
1818
import difflib
19+
import fnmatch
1920
import functools
2021
import importlib.machinery
2122
import io
@@ -112,14 +113,32 @@ class InvalidLicenseExpression(Exception): # type: ignore[no-redef]
112113
}
113114

114115

115-
def _map_to_wheel(sources: Dict[str, Dict[str, Any]]) -> DefaultDict[str, List[Tuple[pathlib.Path, str]]]:
116+
def _compile_patterns(patterns: List[str]) -> Callable[[str], bool]:
117+
if not patterns:
118+
return lambda x: False
119+
func = re.compile('|'.join(fnmatch.translate(os.path.normpath(p)) for p in patterns)).match
120+
return typing.cast('Callable[[str], bool]', func)
121+
122+
123+
def _map_to_wheel(
124+
sources: Dict[str, Dict[str, Any]],
125+
exclude: List[str],
126+
include: List[str],
127+
) -> DefaultDict[str, List[Tuple[pathlib.Path, str]]]:
116128
"""Map files to the wheel, organized by wheel installation directory."""
117129
wheel_files: DefaultDict[str, List[Tuple[pathlib.Path, str]]] = collections.defaultdict(list)
118130
packages: Dict[str, str] = {}
131+
excluded = _compile_patterns(exclude)
132+
included = _compile_patterns(include)
119133

120134
for key, group in sources.items():
121135
for src, target in group.items():
122-
destination = pathlib.Path(target['destination'])
136+
target_destination = os.path.normpath(target['destination'])
137+
138+
if excluded(target_destination) and not included(target_destination):
139+
continue
140+
141+
destination = pathlib.Path(target_destination)
123142
anchor = destination.parts[0]
124143
dst = pathlib.Path(*destination.parts[1:])
125144

@@ -580,6 +599,10 @@ def _string_or_path(value: Any, name: str) -> str:
580599
'limited-api': _bool,
581600
'allow-windows-internal-shared-libs': _bool,
582601
'args': _table(dict.fromkeys(_MESON_ARGS_KEYS, _strings)),
602+
'wheel': _table({
603+
'exclude': _strings,
604+
'include': _strings,
605+
}),
583606
})
584607

585608
table = pyproject.get('tool', {}).get('meson-python', {})
@@ -828,6 +851,10 @@ def __init__(
828851
# from the package, make sure the developers acknowledge this.
829852
self._allow_windows_shared_libs = pyproject_config.get('allow-windows-internal-shared-libs', False)
830853

854+
# Files to be excluded from the wheel
855+
self._excluded_files = pyproject_config.get('wheel', {}).get('exclude', [])
856+
self._included_files = pyproject_config.get('wheel', {}).get('include', [])
857+
831858
def _run(self, cmd: Sequence[str]) -> None:
832859
"""Invoke a subprocess."""
833860
# Flush the line to ensure that the log line with the executed
@@ -914,7 +941,7 @@ def _manifest(self) -> DefaultDict[str, List[Tuple[pathlib.Path, str]]]:
914941
sources[key][target] = details
915942

916943
# Map Meson installation locations to wheel paths.
917-
return _map_to_wheel(sources)
944+
return _map_to_wheel(sources, self._excluded_files, self._included_files)
918945

919946
@property
920947
def _meson_name(self) -> str:

tests/packages/subproject/pyproject.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,15 @@
55
[build-system]
66
build-backend = 'mesonpy'
77
requires = ['meson-python']
8+
9+
[tool.meson-python.wheel]
10+
exclude = [
11+
# Meson before version 1.3.0 install data files in
12+
# ``{datadir}/{project name}/``, later versions install
13+
# in the more correct ``{datadir}/{subproject name}/``.
14+
'{datadir}/*/data.txt',
15+
'{py_purelib}/dep.*',
16+
]
17+
include = [
18+
'{py_purelib}/dep.py',
19+
]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
excluded via tool.meson-python.wheel.exclude

tests/packages/subproject/subprojects/dep/meson.build

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,5 @@ project('dep')
77
py = import('python').find_installation()
88

99
py.install_sources('dep.py')
10+
11+
install_data('data.txt')

0 commit comments

Comments
 (0)