Skip to content

Commit de3234b

Browse files
committed
ENH: support shared libraries on Windows when explicitly enabled
Fixes #525.
1 parent 1ac3f34 commit de3234b

File tree

9 files changed

+80
-44
lines changed

9 files changed

+80
-44
lines changed

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

+31-28
Original file line numberDiff line numberDiff line change
@@ -24,22 +24,22 @@ this ``libdir`` in this guide) or to a location in ``site-packages`` within the
2424
Python package install tree. All these scenarios are (or will be) supported,
2525
with some caveats:
2626

27-
+-----------------------+------------------+---------+-------+-------+
28-
| shared library source | install location | Windows | macOS | Linux |
29-
+=======================+==================+=========+=======+=======+
30-
| internal | libdir | no (1) |||
31-
+-----------------------+------------------+---------+-------+-------+
32-
| internal | site-packages ||||
33-
+-----------------------+------------------+---------+-------+-------+
34-
| external | n/a |(2) |||
35-
+-----------------------+------------------+---------+-------+-------+
27+
+-----------------------+------------------+------------+-------+-------+
28+
| shared library source | install location | Windows | macOS | Linux |
29+
+=======================+==================+============+=======+=======+
30+
| internal | libdir | :sup:`1` |||
31+
+-----------------------+------------------+------------+-------+-------+
32+
| internal | site-packages | |||
33+
+-----------------------+------------------+------------+-------+-------+
34+
| external | --- |:sup:`2` |||
35+
+-----------------------+------------------+------------+-------+-------+
3636

3737
.. TODO: add subproject as a source
3838
39-
1. Internal shared libraries on Windows cannot be automatically handled
40-
correctly, and currently ``meson-python`` therefore raises an error for them.
41-
`PR meson-python#551 <https://github.com/mesonbuild/meson-python/pull/551>`__
42-
may improve that situation in the near future.
39+
1. Support for internal shared libraries on Windows is enabled with the
40+
:option:`allow-windows-internal-shared-libs` option and requires cooperation
41+
from the package to extend the DLL search path or pre-load the required
42+
libraries. See below for more details.
4343

4444
2. External shared libraries require ``delvewheel`` usage on Windows (or some
4545
equivalent way, like amending the DLL search path to include the directory
@@ -91,23 +91,26 @@ the lack of RPATH support:
9191
:start-after: start-literalinclude
9292
:end-before: end-literalinclude
9393

94-
If an internal shared library is not only used as part of a Python package, but
95-
for example also as a regular shared library in a C/C++ project or as a
96-
standalone library, then the method shown above won't work. The library is
97-
then marked for installation into the system default ``libdir`` location.
98-
Actually installing into ``libdir`` isn't possible with wheels, hence
99-
``meson-python`` will instead do the following *on platforms other than
100-
Windows*:
94+
If an internal shared library is not only used as part of a Python package,
95+
but for example also as a regular shared library then the method shown above
96+
won't work. The library is then marked for installation into the system
97+
default ``libdir`` location. Actually installing into ``libdir`` isn't
98+
possible with wheels, hence ``meson-python`` will instead do the following:
10199

102-
1. Install the shared library to ``<project-name>.mesonpy.libs`` (i.e., a
100+
1. Install the shared library to the ``.<project-name>.mesonpy.libs``
103101
top-level directory in the wheel, which on install will end up in
104-
``site-packages``).
105-
2. Rewrite RPATH entries for install targets that depend on the shared library
106-
to point to that new install location instead.
102+
``site-packages``.
103+
2. On platforms other than Windows, rewrite RPATH entries for install targets
104+
that depend on the shared library to point to that new install location
105+
instead.
107106

108-
This will make the shared library work automatically, with no other action needed
109-
from the package author. *However*, currently an error is raised for this situation
110-
on Windows. This is documented also in :ref:`reference-limitations`.
107+
On platforms other than Windows, this will make the shared library work
108+
automatically, with no other action needed from the package author. On
109+
Windows, due to the lack of RPATH support, the ``.<project-name>.mesonpy.libs``
110+
location search path needs to be added to the DLL search path, with code
111+
similar to the one presented above. For this reason, handling of internal
112+
shared libraries on Windows is conditional to setting the
113+
:option:`allow-windows-internal-shared-libs` option.
111114

112115

113116
External shared libraries
@@ -245,7 +248,7 @@ this will look something like:
245248
foo_dep = foo_subproj.get_variable('foo_dep')
246249
247250
Now we can use ``foo_dep`` like a normal dependency, ``meson-python`` will
248-
include it into the wheel in ``<project-name>.mesonpy.libs`` just like an
251+
include it into the wheel in ``.<project-name>.mesonpy.libs`` just like an
249252
internal shared library that targets ``libdir`` (see
250253
:ref:`internal-shared-libraries`).
251254
*Remember: this method doesn't support Windows (yet)!*

docs/reference/config-settings.rst

+13
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,19 @@ settings you can pass when building the project. Please refer to the
1414
:ref:`how-to-guides-meson-args` guides for information on how to use
1515
them.
1616

17+
.. option:: allow-windows-internal-shared-libs
18+
19+
Enable support for relocating internal shared libraries that would be
20+
installed into the system shared library location to the
21+
``.<package-name>.mesonpy.libs`` folder also on Windows. The relocation can
22+
be done transparently on UNIX platforms and on macOS, where the shared
23+
library load path can be adjusted via RPATH or equivalent mechanisms.
24+
Windows lacks a similar facility, thus the Python package is responsible to
25+
extend the DLL load path to include this directory or to pre-load the
26+
shared libraries. See :ref:`here <internal-shared-libraries>` for detailed
27+
documentation. This option ensures that the package authors are aware of
28+
this requirement.
29+
1730
.. option:: build-dir
1831

1932
By default ``meson-python`` uses a temporary build directory.

docs/reference/limitations.rst

+3-7
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,12 @@ Shared libraries on Windows
3131
On Windows, ``meson-python`` cannot encapsulate shared libraries
3232
installed as part of the Meson project into the Python wheel for
3333
Python extension modules or executables, in a way suitable for them to
34-
be found at run-time.
35-
36-
This limitation can be overcome with static linking or using
37-
`delvewheel`_ to post-process the Python wheel to bundle the required
38-
shared libraries and include the setup code to properly set the
39-
library search path.
34+
be found at run-time without cooperation from the Python package, see
35+
the documentation for the :option:`allow-windows-internal-shared-libs`
36+
and the :ref:`shared-libraries` guide.
4037

4138

4239
.. _install_data(): https://mesonbuild.com/Reference-manual_functions.html#install_data
4340
.. _importlib-resources: https://importlib-resources.readthedocs.io/en/latest/index.html
44-
.. _delvewheel: https://github.com/adang1345/delvewheel
4541

4642
.. |install_data()| replace:: ``install_data()``

docs/reference/pyproject-settings.rst

+11
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,17 @@ use them and examples.
3434
``meson-python`` itself. It can be overridden by the :envvar:`MESON`
3535
environment variable.
3636

37+
.. option:: tool.meson-python.shared-libs-win32
38+
39+
A boolean indicating whether shared libraries should be supported on
40+
Windows. ``meson-python`` installs shared libraries in a dedicated location
41+
and uses RPATH or equivalent mechanisms to have Python modules and native
42+
executables load them form there. Windows does not have an equivalent
43+
mechanism to set the DLL load path. Supporting shared libraries on Windows
44+
requires collaboration from the package. To make sure that package authors
45+
are aware of this requirement, ``meson-python`` raises an error if a
46+
package contains DLLs and this option is not set.
47+
3748
.. option:: tool.meson-python.args.dist
3849

3950
Extra arguments to be passed to the ``meson dist`` command.

mesonpy/__init__.py

+15-2
Original file line numberDiff line numberDiff line change
@@ -309,10 +309,12 @@ def __init__(
309309
metadata: Metadata,
310310
manifest: Dict[str, List[Tuple[pathlib.Path, str]]],
311311
limited_api: bool,
312+
windows_shared_libs: bool,
312313
) -> None:
313314
self._metadata = metadata
314315
self._manifest = manifest
315316
self._limited_api = limited_api
317+
self._windows_shared_libs = windows_shared_libs
316318

317319
@property
318320
def _has_internal_libs(self) -> bool:
@@ -422,6 +424,12 @@ def _install_path(self, wheel_file: mesonpy._wheelfile.WheelFile, origin: Path,
422424

423425
if self._has_internal_libs:
424426
if _is_native(origin):
427+
if sys.platform == 'win32' and not self._windows_shared_libs:
428+
raise NotImplementedError(
429+
'Loading shared libraries bundled in the Python wheel on Windows requires '
430+
'setting the DLL load path or pre-loading. See the documentation for '
431+
'the "tool.meson-pyhton.allow-windows-internal-shared-libs" option.')
432+
425433
# When an executable, libray, or Python extension module is
426434
# dynamically linked to a library built as part of the project,
427435
# Meson adds a library load path to it pointing to the build
@@ -567,6 +575,7 @@ def _string_or_path(value: Any, name: str) -> str:
567575
scheme = _table({
568576
'meson': _string_or_path,
569577
'limited-api': _bool,
578+
'allow-windows-internal-shared-libs': _bool,
570579
'args': _table({
571580
name: _strings for name in _MESON_ARGS_KEYS
572581
}),
@@ -784,6 +793,10 @@ def __init__(
784793
'The package targets Python\'s Limited API, which is not supported by free-threaded CPython. '
785794
'The "python.allow_limited_api" Meson build option may be used to override the package default.')
786795

796+
# Shared library support on Windows requires collaboration
797+
# from the package, make sure the developers aknowledge this.
798+
self._windows_shared_libs = pyproject_config.get('allow-windows-internal-shared-libs', False)
799+
787800
def _run(self, cmd: Sequence[str]) -> None:
788801
"""Invoke a subprocess."""
789802
# Flush the line to ensure that the log line with the executed
@@ -988,13 +1001,13 @@ def sdist(self, directory: Path) -> pathlib.Path:
9881001
def wheel(self, directory: Path) -> pathlib.Path:
9891002
"""Generates a wheel in the specified directory."""
9901003
self.build()
991-
builder = _WheelBuilder(self._metadata, self._manifest, self._limited_api)
1004+
builder = _WheelBuilder(self._metadata, self._manifest, self._limited_api, self._windows_shared_libs)
9921005
return builder.build(directory)
9931006

9941007
def editable(self, directory: Path) -> pathlib.Path:
9951008
"""Generates an editable wheel in the specified directory."""
9961009
self.build()
997-
builder = _EditableWheelBuilder(self._metadata, self._manifest, self._limited_api)
1010+
builder = _EditableWheelBuilder(self._metadata, self._manifest, self._limited_api, self._windows_shared_libs)
9981011
return builder.build(directory, self._source_dir, self._build_dir, self._build_command, self._editable_verbose)
9991012

10001013

mesonpy/_rpath.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
if sys.platform == 'win32' or sys.platform == 'cygwin':
2020

2121
def fix_rpath(filepath: Path, libs_relative_path: str) -> None:
22-
raise NotImplementedError(f'Bundling libraries in wheel is not supported on {sys.platform}')
22+
pass
2323

2424
elif sys.platform == 'darwin':
2525

tests/packages/sharedlib-in-package/mypkg/__init__.py

+3-4
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,15 @@
88

99
# start-literalinclude
1010
def _append_to_sharedlib_load_path():
11-
"""
12-
Ensure the shared libraries in this package can be loaded on Windows.
11+
"""Ensure the shared libraries in this package can be loaded on Windows.
1312
1413
Windows lacks a concept equivalent to RPATH: Python extension modules
1514
cannot find DLLs installed outside the DLL search path. This function
1615
ensures that the location of the shared libraries distributed inside this
1716
Python package is in the DLL search path of the process.
1817
19-
The Windows DLL search path includes the object depending on it is located:
20-
the DLL search path needs to be augmented only when the Python extension
18+
The Windows DLL search path includes the path to the object attempting
19+
to load the DLL: it needs to be augmented only when the Python extension
2120
modules and the DLLs they require are installed in separate directories.
2221
Cygwin does not have the same default library search path: all locations
2322
where the shared libraries are installed need to be added to the search

tests/test_tags.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ def test_python_host_platform(monkeypatch):
7777
def wheel_builder_test_factory(content, pure=True, limited_api=False):
7878
manifest = defaultdict(list)
7979
manifest.update({key: [(pathlib.Path(x), os.path.join('build', x)) for x in value] for key, value in content.items()})
80-
return mesonpy._WheelBuilder(None, manifest, limited_api)
80+
return mesonpy._WheelBuilder(None, manifest, limited_api, False)
8181

8282

8383
def test_tag_empty_wheel():

tests/test_wheel.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,8 @@ def test_entrypoints(wheel_full_metadata):
266266

267267
def test_top_level_modules(package_module_types):
268268
with mesonpy._project() as project:
269-
builder = mesonpy._EditableWheelBuilder(project._metadata, project._manifest, project._limited_api)
269+
builder = mesonpy._EditableWheelBuilder(
270+
project._metadata, project._manifest, project._limited_api, project._windows_shared_libs)
270271
assert set(builder._top_level_modules) == {
271272
'file',
272273
'package',

0 commit comments

Comments
 (0)