Skip to content

Commit 44a3e6c

Browse files
authored
Merge pull request numpy#30968 from HaoZeke/doc/f2py-distribution-guide
DOC: Add f2py distribution guide for meson-python
2 parents 758d986 + 97c2ec9 commit 44a3e6c

6 files changed

Lines changed: 278 additions & 1 deletion

File tree

doc/source/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,7 @@ def setup(app):
423423
'matplotlib': ('https://matplotlib.org/stable', None),
424424
'imageio': ('https://imageio.readthedocs.io/en/stable', None),
425425
'skimage': ('https://scikit-image.org/docs/stable', None),
426-
'pandas': ('https://pandas.pydata.org/pandas-docs/stable', None),
426+
'pandas': ('https://pandas.pydata.org/docs', None),
427427
'scipy-lecture-notes': ('https://scipy-lectures.org', None),
428428
'pytest': ('https://docs.pytest.org/en/stable', None),
429429
'numpy-tutorials': ('https://numpy.org/numpy-tutorials', None),

doc/source/f2py/buildtools/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ Build systems
108108
:maxdepth: 2
109109

110110
meson
111+
meson-python
111112
cmake
112113
skbuild
113114
distutils-to-meson
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
.. _f2py-meson-python:
2+
3+
=====================================================
4+
Distributing F2PY extensions with ``meson-python``
5+
=====================================================
6+
7+
The :ref:`f2py-meson` page covers building F2PY extensions using raw ``meson``
8+
commands. This page shows how to package those extensions into installable
9+
Python distributions (sdists and wheels) using `meson-python
10+
<https://meson-python.readthedocs.io/>`_ as the PEP 517 build backend.
11+
12+
This is the recommended approach for distributing F2PY-wrapped Fortran code as a
13+
Python package on PyPI or for local ``pip install`` workflows.
14+
15+
.. note::
16+
17+
``meson-python`` replaced ``setuptools`` / ``numpy.distutils`` as the
18+
standard way to build and distribute compiled extensions in the NumPy and
19+
SciPy ecosystem. See :ref:`distutils-status-migration` for background.
20+
21+
Prerequisites
22+
=============
23+
24+
You need:
25+
26+
* A C compiler
27+
* A Fortran compiler (``gfortran``, ``ifort``, ``ifx``, ``flang-new``, etc.),
28+
if you use any Fortran code in your package
29+
* Python >= 3.10
30+
* ``meson``, ``meson-python``, and ``numpy`` (installed automatically during the
31+
build when listed in ``build-system.requires``)
32+
33+
Minimal example
34+
===============
35+
36+
The project below wraps a Fortran ``fib`` subroutine into an importable Python
37+
package called ``fib_wrapper``.
38+
39+
Project layout::
40+
41+
fib_wrapper/ # project root
42+
├── fib.f90 # Fortran source
43+
├── fib_wrapper/ # Python package directory
44+
│ └── __init__.py
45+
├── meson.build
46+
└── pyproject.toml
47+
48+
Fortran source
49+
--------------
50+
51+
Save the following as ``fib.f90``:
52+
53+
.. literalinclude:: ../code/fib_mesonpy.f90
54+
:language: fortran
55+
56+
``pyproject.toml``
57+
------------------
58+
59+
.. literalinclude:: ../code/pyproj_mesonpy.toml
60+
:language: toml
61+
62+
Two entries matter here:
63+
64+
* ``build-backend = "mesonpy"`` tells build frontends to use ``meson-python``.
65+
* ``requires`` lists build-time dependencies. ``numpy >= 2.0`` is required so
66+
that ``f2py``, the NumPy headers, and ``dependency('numpy')`` support in Meson
67+
are available during compilation.
68+
69+
``meson.build``
70+
---------------
71+
72+
.. literalinclude:: ../code/meson_mesonpy.build
73+
74+
.. note::
75+
76+
The file is stored as ``meson_mesonpy.build`` in the documentation source
77+
tree to avoid collisions with other examples. In your project, name it
78+
``meson.build``.
79+
80+
The ``meson.build`` file does four things:
81+
82+
1. Uses ``dependency('numpy')`` to locate NumPy headers, and a
83+
``declare_dependency`` to add the F2PY include directory (for
84+
``fortranobject.h``).
85+
2. Runs ``f2py`` via ``custom_target`` to generate the C wrapper sources.
86+
3. Compiles the generated C code together with the Fortran source into a Python
87+
extension module using ``py.extension_module``.
88+
4. Installs ``__init__.py`` into the package directory so the result is a proper
89+
Python package.
90+
91+
The ``subdir: 'fib_wrapper'`` argument on the extension module is required so
92+
that the compiled ``fib`` shared library is installed inside the ``fib_wrapper/``
93+
package directory, next to ``__init__.py``. Without it the extension would
94+
be installed at the top level and ``import fib_wrapper`` would not find the
95+
``fib`` extension. The resulting installed layout is::
96+
97+
site-packages/
98+
└── fib_wrapper/
99+
├── __init__.py # from .fib import fib
100+
└── fib.cpython-*.so # compiled extension module
101+
102+
``__init__.py``
103+
---------------
104+
105+
A minimal ``__init__.py`` re-exports the wrapped function:
106+
107+
.. code-block:: python
108+
109+
from .fib import fib
110+
111+
Building and installing
112+
=======================
113+
114+
Editable install (development)
115+
------------------------------
116+
117+
.. code-block:: bash
118+
119+
pip install --no-build-isolation --editable .
120+
121+
``--no-build-isolation`` reuses the current environment, which is useful when
122+
iterating. This requires ``meson-python``, ``meson``, ``ninja``, and ``numpy``
123+
to already be installed.
124+
125+
Building a wheel
126+
----------------
127+
128+
.. code-block:: bash
129+
130+
# If you don't yet have `pypa/build` installed: `pip install build`
131+
python -m build --wheel
132+
133+
The resulting ``.whl`` file in ``dist/`` can be uploaded to PyPI, or installed
134+
elsewhere with ``pip install dist/fib_wrapper-0.1.0-*.whl``.
135+
136+
Verifying the install
137+
---------------------
138+
139+
.. code-block:: python
140+
141+
>>> from fib_wrapper import fib
142+
>>> fib(10)
143+
array([ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34], dtype=int32)
144+
145+
Customizing the Fortran compiler
146+
================================
147+
148+
``meson-python`` delegates compiler selection to ``meson``. By default,
149+
``meson`` will choose the first Fortran compiler it finds on the PATH.
150+
If you want more control over Fortran compiler selection, set the ``FC``
151+
environment variable before building:
152+
153+
.. code-block:: bash
154+
155+
FC=ifx python -m build --wheel
156+
157+
For more control, use a `Meson native file
158+
<https://mesonbuild.com/Native-environments.html>`_:
159+
160+
.. code-block:: ini
161+
162+
; native.ini
163+
[binaries]
164+
fortran = 'ifx'
165+
c = 'icx'
166+
167+
.. code-block:: bash
168+
169+
python -m build --wheel -Csetup-args="--native-file=native.ini"
170+
171+
Adding dependencies (BLAS, LAPACK, etc.)
172+
========================================
173+
174+
Use ``dependency()`` in ``meson.build`` to link against system libraries:
175+
176+
.. code-block:: none
177+
178+
lapack_dep = dependency('lapack')
179+
180+
py.extension_module('mymod',
181+
[sources, generated, incdir_f2py / 'fortranobject.c'],
182+
dependencies : [np_dep, f2py_dep, lapack_dep],
183+
install : true,
184+
)
185+
186+
``meson`` resolves dependencies through ``pkg-config``, CMake, or its own
187+
detection logic. See the `Meson dependency documentation
188+
<https://mesonbuild.com/Dependencies.html>`_ for details.
189+
190+
Differences from the ``scikit-build-core`` workflow
191+
====================================================
192+
193+
The ``scikit-build-core`` approach documented in :ref:`f2py-skbuild` uses CMake
194+
under the hood. ``meson-python`` provides:
195+
196+
* Native Fortran compiler support in ``meson`` (no CMake layer).
197+
* Direct integration with ``pip`` / ``build`` via PEP 517.
198+
* The same build system used by NumPy and SciPy themselves.
199+
200+
Further reading
201+
===============
202+
203+
* `meson-python documentation <https://meson-python.readthedocs.io/>`_
204+
* `Meson build system <https://mesonbuild.com/>`_
205+
* `SciPy's meson build configuration <https://github.com/scipy/scipy/blob/main/meson.build>`_ (real-world F2PY usage)
206+
* :ref:`f2py-meson` (raw meson build without ``meson-python``)
207+
* :ref:`f2py-skbuild` (alternative using ``scikit-build-core`` / CMake)
208+
* :ref:`f2py-meson-distutils` (migration from ``distutils``)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
subroutine fib(a, n)
2+
use iso_c_binding
3+
integer(c_int), intent(in) :: n
4+
integer(c_int), intent(out) :: a(n)
5+
integer :: i
6+
do i = 1, n
7+
if (i == 1) then
8+
a(i) = 0
9+
else if (i == 2) then
10+
a(i) = 1
11+
else
12+
a(i) = a(i - 1) + a(i - 2)
13+
end if
14+
end do
15+
end subroutine fib
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
project('fib_wrapper', 'c',
2+
version : '0.1.0',
3+
meson_version: '>=1.1.0',
4+
default_options : ['warning_level=2'],
5+
)
6+
7+
add_languages('fortran', native: false)
8+
9+
py = import('python').find_installation(pure: false)
10+
11+
# NumPy >=2.0 provides include dirs via dependency()
12+
np_dep = dependency('numpy')
13+
14+
incdir_f2py = run_command(py,
15+
['-c', 'import numpy.f2py; print(numpy.f2py.get_include())'],
16+
check : true
17+
).stdout().strip()
18+
19+
# f2py include dir (for fortranobject.h) is not in dependency('numpy'),
20+
# so add it separately
21+
f2py_dep = declare_dependency(
22+
include_directories : incdir_f2py,
23+
)
24+
25+
# Generate the f2py wrappers
26+
fib_source = custom_target('fibmodule.c',
27+
input : ['fib.f90'],
28+
output : ['fibmodule.c', 'fib-f2pywrappers.f'],
29+
command : [py, '-m', 'numpy.f2py', '@INPUT@', '-m', 'fib', '--lower']
30+
)
31+
32+
py.extension_module('fib',
33+
['fib.f90', fib_source, incdir_f2py / 'fortranobject.c'],
34+
dependencies : [np_dep, f2py_dep],
35+
subdir: 'fib_wrapper',
36+
install : true,
37+
)
38+
39+
# Install the Python package files
40+
py.install_sources(
41+
'fib_wrapper/__init__.py',
42+
subdir: 'fib_wrapper',
43+
)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[build-system]
2+
# numpy>=2.0 is required for dependency('numpy') support in meson.build
3+
requires = ["meson-python>=0.15.0", "numpy>=2.0"]
4+
build-backend = "mesonpy"
5+
6+
[project]
7+
name = "fib_wrapper"
8+
version = "0.1.0"
9+
requires-python = ">=3.10"
10+
dependencies = ["numpy"]

0 commit comments

Comments
 (0)