Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit e577eac

Browse filesBrowse files
rgommersdnicolodi
authored andcommitted
DOC: add documentation about using shared libraries
1 parent 75a89c3 commit e577eac
Copy full SHA for e577eac

File tree

Expand file treeCollapse file tree

2 files changed

+251
-0
lines changed
Filter options
Expand file treeCollapse file tree

2 files changed

+251
-0
lines changed
+250Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
.. SPDX-FileCopyrightText: 2024 The meson-python developers
2+
..
3+
.. SPDX-License-Identifier: MIT
4+
5+
.. _shared-libraries:
6+
7+
**********************
8+
Using shared libraries
9+
**********************
10+
11+
Python projects may build shared libraries as part of their project, or link
12+
with shared libraries from a dependency. This tends to be a common source of
13+
issues, hence this page aims to explain how to include shared libraries in
14+
wheels, any limitations and gotchas, and how support is implemented in
15+
``meson-python`` under the hood.
16+
17+
We distinguish between *internal* shared libraries that are built as part of
18+
the project, and *external* shared libraries that are provided by project
19+
dependencies and that are linked with the project build artifacts.
20+
For internal shared libraries, we also distinguish whether the shared library
21+
is being installed to its default system location (typically
22+
``/usr/local/lib`` on Unix-like systems, and ``C:\\lib`` on Windows - we call
23+
this ``libdir`` in this guide) or to a location in ``site-packages`` within the
24+
Python package install tree. All these scenarios are (or will be) supported,
25+
with some caveats:
26+
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+
+-----------------------+------------------+---------+-------+-------+
36+
37+
.. TODO: add subproject as a source
38+
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.
43+
44+
2: External shared libraries require ``delvewheel`` usage on Windows (or
45+
some equivalent way, like amending the DLL search path to include the directory
46+
in which the external shared library is located). Due to the lack of RPATH
47+
support on Windows, there is no good way around this.
48+
49+
.. _internal-shared-libraries:
50+
51+
Internal shared libraries
52+
=========================
53+
54+
A shared library produced by ``library()`` or ``shared_library()`` built like this
55+
56+
.. code-block:: meson
57+
58+
example_lib = shared_library(
59+
'example',
60+
'examplelib.c',
61+
install: true,
62+
)
63+
64+
is installed to ``libdir`` by default. If the only reason the shared library exists
65+
is to be used inside the Python package being built, then it is best to modify
66+
the install location to be within the Python package itself:
67+
68+
.. code-block:: python
69+
70+
install_path: py.get_install_dir() / 'mypkg/subdir'
71+
72+
Then an extension module in the same install directory can link against the
73+
shared library in a portable manner by using ``install_rpath``:
74+
75+
.. code-block:: meson
76+
77+
py3.extension_module('_extmodule',
78+
'_extmodule.c',
79+
link_with: example_lib,
80+
install: true,
81+
subdir: 'mypkg/subdir',
82+
install_rpath: '$ORIGIN'
83+
)
84+
85+
The above method will work as advertised on macOS and Linux; ``meson-python`` does
86+
nothing special for this case. Windows needs some special handling though, due to
87+
the lack of RPATH support:
88+
89+
.. literalinclude:: ../../tests/packages/sharedlib-in-package/mypkg/__init__.py
90+
:start-after: start-literalinclude
91+
:end-before: end-literalinclude
92+
93+
If an internal shared library is not only used as part of a Python package, but
94+
for example also as a regular shared library in a C/C++ project or as a
95+
standalone library, then the method shown above won't work. The library is`
96+
then marked for installation into the system default ``libdir`` location.
97+
Actually installing into ``libdir`` isn't possible with wheels, hence
98+
``meson-python`` will instead do the following *on platforms other than
99+
Windows*:
100+
101+
1. Install the shared library to ``<project-name>.mesonpy.libs`` (i.e., a
102+
top-level directory in the wheel, which on install will end up in
103+
``site-packages``).
104+
2. Rewrite RPATH entries for install targets that depend on the shared library
105+
to point to that new install location instead.
106+
107+
This will make the shared library work automatically, with no other action needed
108+
from the package author. *However*, currently an error is raised for this situation
109+
on Windows. This is documented also in :ref:`reference-limitations`.
110+
111+
112+
External shared libraries
113+
=========================
114+
115+
External shared libraries are installed somewhere on the build machine, and
116+
usually detected by a ``dependency()`` or ``compiler.find_library()`` call in a
117+
``meson.build`` file. When a Python extension module or executable uses the
118+
dependency, the shared library will be linked against at build time.
119+
120+
If the shared library is located in a directory on the loader search path,
121+
the wheel created by ``meson-python`` will work locally when installed.
122+
If it's in a non-standard location however, the shared library will go
123+
missing at runtime. The Python extension module linked against it needs an
124+
RPATH entry - and Meson will not automatically manage RPATH entries for you.
125+
Hence you'll need to add the needed RPATH yourself, for example by adding
126+
``-Wl,rpath=/path/to/dir/sharedlib/is/in`` to ``LDFLAGS`` before starting
127+
the build. In case you run into this problem after a wheel is built and
128+
installed, adding that same path to ``LD_LIBRARY_PATH`` is a quick way of
129+
checking if that is indeed the problem.
130+
131+
On Windows, the solution is similar - the shared library can either be
132+
preloaded, or the directory that the library is in added to ``PATH`` or with
133+
``os.add_dll_directory``, or vendored into the wheel with ``delvewheel`` in
134+
order to make the built Python package usable locally.
135+
136+
Publishing wheels which depend on external shared libraries
137+
-----------------------------------------------------------
138+
139+
On all platforms, wheels which depend on external shared libraries usually need
140+
post-processing to make them usable on machines other than the one on which
141+
they were built. This is because the RPATH entry for an external shared library
142+
contains a path specific to the build machine. This post-processing is done by
143+
tools like ``auditwheel`` (Linux), ``delvewheel`` (Windows), ``delocate``
144+
(macOS) or ``repair-wheel`` (any platform, wraps the other tools).
145+
146+
Running any of those tools on a wheel produced by ``meson-python`` will vendor
147+
the external shared library into the wheel and rewrite the RPATH entries (it
148+
may also do some other things, like symbol mangling).
149+
150+
On Windows, the package author may also have to add the preloading like shown
151+
above with ``_enable_sharedlib_loading()`` to the main ``__init__.py`` of the
152+
package, ``delvewheel`` may or may not take care of this (please check its
153+
documentation if your shared library goes missing at runtime).
154+
155+
Note that we don't cover using shared libraries contained in another wheel
156+
and depending on such a wheel at runtime in this guide. This is inherently
157+
complex and not recommended (you need to be in control of both packages, or
158+
upgrades may be impossible/breaking).
159+
160+
161+
Using libraries from a Meson subproject
162+
=======================================
163+
164+
It can often be useful to build a shared library in a
165+
`Meson subproject <https://mesonbuild.com/Subprojects.html>`__, for example as
166+
a fallback in case an external dependency isn't detected. There are two main
167+
strategies for folding a library built in a subproject into a wheel built with
168+
``meson-python``:
169+
170+
1. Build the library as a static library instead of a shared library, and
171+
link it into a Python extension module that needs it.
172+
2. Build the library as a shared library, and either change its install path
173+
to be within the Python package's tree, or rely on ``meson-python`` to fold
174+
it into the wheel when it'd otherwise be installed to ``libdir``.
175+
176+
Option (1) tends to be easier, so unless the library of interest cannot be
177+
built as a static library or it would inflate the wheel size too much because
178+
it's needed by multiple Python extension modules, we recommend trying option
179+
(1) first.
180+
181+
A typical C or C++ project providing a library to link against tends to provide
182+
(a) one or more ``library()`` targets, which can be built as shared, static, or both,
183+
and (b) headers, pkg-config files, tests and perhaps other development targets
184+
that are needed to use the ``library()`` target(s). One of the challenges to use
185+
such projects as a subproject is that the headers and other installable targets
186+
are targeting system locations (e.g., ``<prefix>/include/``) which isn't supported
187+
by wheels and hence ``meson-python`` errors out when it encounters such an install
188+
target. This is perhaps the main issue one encounters with subproject usage,
189+
and the following two sections discuss how options (1) and (2) can work around
190+
that.
191+
192+
Static library from subproject
193+
------------------------------
194+
195+
The major advantage of building a library target as static and folding it directly
196+
into an extension module is that no targets from the subproject need to be installed.
197+
To configure the subproject for this use case, add the following to the
198+
``pyproject.toml`` file of your package:
199+
200+
.. code-block:: toml
201+
202+
[tool.meson-python.args]
203+
setup = ['--default-library=static']
204+
install = ['--skip-subprojects']
205+
206+
This ensures that ``library`` targets are built as static, and nothing gets installed.
207+
208+
To then link against the static library in the subproject, say for a subproject
209+
named ``bar`` with the main library target contained in a ``bar_dep`` dependency,
210+
add this to your ``meson.build`` file:
211+
212+
.. code-block:: meson
213+
214+
bar_proj = subproject('bar')
215+
bar_dep = bar_proj.get_variable('bar_dep')
216+
217+
py.extension_module(
218+
'_example',
219+
'_examplemod.c',
220+
dependencies: bar_dep,
221+
install: true,
222+
)
223+
224+
That is all!
225+
226+
Shared library from subproject
227+
------------------------------
228+
229+
If we can't use the static library approach from the section above and we need
230+
a shared library, then we must have ``install: true`` for that shared library
231+
target. This can only work if we can pass some build option to the subproject
232+
that tells it to *only* install the shared library and not headers or other
233+
targets that we don't need. Install tags don't work per subproject, so
234+
this will look something like:
235+
236+
.. code-block:: meson
237+
238+
foo_subproj = subproject('foo',
239+
default_options: {
240+
# This is a custom option - if it doesn't exist, can you add it
241+
# upstream or in WrapDB?
242+
'only_install_main_lib': true,
243+
})
244+
foo_dep = foo_subproj.get_variable('foo_dep')
245+
246+
Now we can use ``foo_dep`` like a normal dependency, ``meson-python`` will
247+
include it into the wheel in ``<project-name>.mesonpy.libs`` just like an
248+
internal shared library that targets ``libdir`` (see
249+
:ref:`internal-shared-libraries`).
250+
*Remember: this method doesn't support Windows (yet)!*

‎docs/index.rst

Copy file name to clipboardExpand all lines: docs/index.rst
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ the use of ``meson-python`` and Meson for Python packaging.
8282
how-to-guides/config-settings
8383
how-to-guides/meson-args
8484
how-to-guides/debug-builds
85+
how-to-guides/shared-libraries
8586
reference/limitations
8687
projects-using-meson-python
8788

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.