Skip to main content
`Ctrl`+`K`

# Distributing F2PY extensions with `meson-python`

The Using via meson page covers building F2PY extensions using raw `meson` commands. This page shows how to package those extensions into installable Python distributions (sdists and wheels) using meson-python as the PEP 517 build backend.

This is the recommended approach for distributing F2PY-wrapped Fortran code as a Python package on PyPI or for local `pip install` workflows.

Note

`meson-python` replaced `setuptools` / `numpy.distutils` as the standard way to build and distribute compiled extensions in the NumPy and SciPy ecosystem. See Status of numpy.distutils and migration advice for background.

## Prerequisites

You need:

- A C compiler

- A Fortran compiler (`gfortran`, `ifort`, `ifx`, `flang-new`, etc.), if you use any Fortran code in your package

- Python >= 3.10

- `meson`, `meson-python`, and `numpy` (installed automatically during the build when listed in `build-system.requires`)

## Minimal example

The project below wraps a Fortran `fib` subroutine into an importable Python package called `fib_wrapper`.

Project layout:

    fib_wrapper/           # project root
    ├── fib.f90            # Fortran source
    ├── fib_wrapper/       # Python package directory
    │   └── __init__.py
    ├── meson.build
    └── pyproject.toml

### Fortran source

Save the following as `fib.f90`:

    subroutine fib(a, n)
      use iso_c_binding
      integer(c_int), intent(in) :: n
      integer(c_int), intent(out) :: a(n)
      integer :: i
      do i = 1, n
         if (i == 1) then
            a(i) = 0
         else if (i == 2) then
            a(i) = 1
         else
            a(i) = a(i - 1) + a(i - 2)
         end if
      end do
    end subroutine fib

### `pyproject.toml`

    [build-system]
    # numpy>=2.0 is required for dependency('numpy') support in meson.build
    requires = ["meson-python>=0.15.0", "numpy>=2.0"]
    build-backend = "mesonpy"

    [project]
    name = "fib_wrapper"
    version = "0.1.0"
    requires-python = ">=3.10"
    dependencies = ["numpy"]

Two entries matter here:

- `build-backend = "mesonpy"` tells build frontends to use `meson-python`.

- `requires` lists build-time dependencies. `numpy >= 2.0` is required so that `f2py`, the NumPy headers, and `dependency('numpy')` support in Meson are available during compilation.

### `meson.build`

    project('fib_wrapper', 'c',
      version : '0.1.0',
      meson_version: '>=1.1.0',
      default_options : ['warning_level=2'],
    )

    add_languages('fortran', native: false)

    py = import('python').find_installation(pure: false)

    # NumPy >=2.0 provides include dirs via dependency()
    np_dep = dependency('numpy')

    incdir_f2py = run_command(py,
      ['-c', 'import numpy.f2py; print(numpy.f2py.get_include())'],
      check : true
    ).stdout().strip()

    # f2py include dir (for fortranobject.h) is not in dependency('numpy'),
    # so add it separately
    f2py_dep = declare_dependency(
      include_directories : incdir_f2py,
    )

    # Generate the f2py wrappers
    fib_source = custom_target('fibmodule.c',
      input : ['fib.f90'],
      output : ['fibmodule.c', 'fib-f2pywrappers.f'],
      command : [py, '-m', 'numpy.f2py', '@INPUT@', '-m', 'fib', '--lower']
    )

    py.extension_module('fib',
      ['fib.f90', fib_source, incdir_f2py / 'fortranobject.c'],
      dependencies : [np_dep, f2py_dep],
      subdir: 'fib_wrapper',
      install : true,
    )

    # Install the Python package files
    py.install_sources(
      'fib_wrapper/__init__.py',
      subdir: 'fib_wrapper',
    )

Note

The file is stored as `meson_mesonpy.build` in the documentation source tree to avoid collisions with other examples. In your project, name it `meson.build`.

The `meson.build` file does four things:

1. Uses `dependency('numpy')` to locate NumPy headers, and a `declare_dependency` to add the F2PY include directory (for `fortranobject.h`).

2. Runs `f2py` via `custom_target` to generate the C wrapper sources.

3. Compiles the generated C code together with the Fortran source into a Python extension module using `py.extension_module`.

4. Installs `__init__.py` into the package directory so the result is a proper Python package.

The `subdir: 'fib_wrapper'` argument on the extension module is required so that the compiled `fib` shared library is installed inside the `fib_wrapper/` package directory, next to `__init__.py`. Without it the extension would be installed at the top level and `import fib_wrapper` would not find the `fib` extension. The resulting installed layout is:

    site-packages/
    └── fib_wrapper/
        ├── __init__.py        # from .fib import fib
        └── fib.cpython-*.so   # compiled extension module

### `__init__.py`

A minimal `__init__.py` re-exports the wrapped function:

    from .fib import fib

## Building and installing

### Editable install (development)

    pip install --no-build-isolation --editable .

`--no-build-isolation` reuses the current environment, which is useful when iterating. This requires `meson-python`, `meson`, `ninja`, and `numpy` to already be installed.

### Building a wheel

    # If you don't yet have `pypa/build` installed: `pip install build`
    python -m build --wheel

The resulting `.whl` file in `dist/` can be uploaded to PyPI, or installed elsewhere with `pip install dist/fib_wrapper-0.1.0-*.whl`.

### Verifying the install

    >>> from fib_wrapper import fib
    >>> fib(10)
    array([ 0,  1,  1,  2,  3,  5,  8, 13, 21, 34], dtype=int32)

## Customizing the Fortran compiler

`meson-python` delegates compiler selection to `meson`. By default, `meson` will choose the first Fortran compiler it finds on the PATH. If you want more control over Fortran compiler selection, set the `FC` environment variable before building:

    FC=ifx python -m build --wheel

For more control, use a Meson native file:

    ; native.ini
    [binaries]
    fortran = 'ifx'
    c = 'icx'

    python -m build --wheel -Csetup-args="--native-file=native.ini"

## Adding dependencies (BLAS, LAPACK, etc.)

Use `dependency()` in `meson.build` to link against system libraries:

    lapack_dep = dependency('lapack')

    py.extension_module('mymod',
      [sources, generated, incdir_f2py / 'fortranobject.c'],
      dependencies : [np_dep, f2py_dep, lapack_dep],
      install : true,
    )

`meson` resolves dependencies through `pkg-config`, CMake, or its own detection logic. See the Meson dependency documentation for details.

## Differences from the `scikit-build-core` workflow

The `scikit-build-core` approach documented in Using via scikit-build uses CMake under the hood. `meson-python` provides:

- Native Fortran compiler support in `meson` (no CMake layer).

- Direct integration with `pip` / `build` via PEP 517.

- The same build system used by NumPy and SciPy themselves.

## Further reading

- meson-python documentation

- Meson build system

- SciPy’s meson build configuration (real-world F2PY usage)

- Using via meson (raw meson build without `meson-python`)

- Using via scikit-build (alternative using `scikit-build-core` / CMake)

- 1 Migrating to meson (migration from `distutils`)