Maturin User Guide

Welcome to the maturin user guide! It contains examples and documentation to explain all of maturin's use cases in detail.

Please choose from the chapters on the left to jump to individual topics, or continue below to start with maturin's README.

Sponsors

Development of maturin is made possible by the following sponsors:

And many more who kindly sponsor @messense on GitHub Sponsors.

Maturin

formerly pyo3-pack

Maturin User Guide Crates.io PyPI Actions Status FreeBSD discord server

Build and publish crates with pyo3, cffi and uniffi bindings as well as rust binaries as python packages with minimal configuration. It supports building wheels for python 3.8+ on windows, linux, mac and freebsd, can upload them to pypi and has basic pypy and graalpy support.

Check out the User Guide!

Usage

You can either download binaries from the latest release or install it with pipx:

pipx install maturin

[!NOTE]

pip install maturin should also work if you don't want to use pipx.

There are four main commands:

  • maturin new creates a new cargo project with maturin configured.
  • maturin publish builds the crate into python packages and publishes them to pypi.
  • maturin build builds the wheels and stores them in a folder (target/wheels by default), but doesn't upload them. It's possible to upload those with twine or maturin upload.
  • maturin develop builds the crate and installs it as a python module directly in the current virtualenv. Note that while maturin develop is faster, it doesn't support all the feature that running pip install after maturin build supports.

pyo3 bindings are automatically detected. For cffi or binaries, you need to pass -b cffi or -b bin. maturin doesn't need extra configuration files and doesn't clash with an existing setuptools-rust or milksnake configuration. You can even integrate it with testing tools such as tox. There are examples for the different bindings in the test-crates folder.

The name of the package will be the name of the cargo project, i.e. the name field in the [package] section of Cargo.toml. The name of the module, which you are using when importing, will be the name value in the [lib] section (which defaults to the name of the package). For binaries, it's simply the name of the binary generated by cargo.

When using maturin build and maturin develop commands, you can compile a performance-optimized program by adding the -r or --release flag.

Python packaging basics

Python packages come in two formats: A built form called wheel and source distributions (sdist), both of which are archives. A wheel can be compatible with any python version, interpreter (cpython and pypy, mainly), operating system and hardware architecture (for pure python wheels), can be limited to a specific platform and architecture (e.g. when using ctypes or cffi) or to a specific python interpreter and version on a specific architecture and operating system (e.g. with pyo3).

When using pip install on a package, pip tries to find a matching wheel and install that. If it doesn't find one, it downloads the source distribution and builds a wheel for the current platform, which requires the right compilers to be installed. Installing a wheel is much faster than installing a source distribution as building wheels is generally slow.

When you publish a package to be installable with pip install, you upload it to pypi, the official package repository. For testing, you can use test pypi instead, which you can use with pip install --index-url https://test.pypi.org/simple/. Note that for publishing for linux, you need to use the manylinux docker container, while for publishing from your repository you can use the PyO3/maturin-action github action.

pyo3

For pyo3, maturin can only build packages for installed python versions. On linux and mac, all python versions in PATH are used. If you don't set your own interpreters with -i, a heuristic is used to search for python installations. On windows all versions from the python launcher (which is installed by default by the python.org installer) and all conda environments except base are used. You can check which versions are picked up with the list-python subcommand.

pyo3 will set the used python interpreter in the environment variable PYTHON_SYS_EXECUTABLE, which can be used from custom build scripts. Maturin can build and upload wheels for pypy with pyo3, even though only pypy3.7-7.3 on linux is tested.

Cffi

Cffi wheels are compatible with all python versions including pypy. If cffi isn't installed and python is running inside a virtualenv, maturin will install it, otherwise you have to install it yourself (pip install cffi).

maturin uses cbindgen to generate a header file, which can be customized by configuring cbindgen through a cbindgen.toml file inside your project root. Alternatively you can use a build script that writes a header file to $PROJECT_ROOT/target/header.h.

Based on the header file maturin generates a module which exports an ffi and a lib object.

Example of a custom build script
use cbindgen;
use std::env;
use std::path::Path;

fn main() {
    let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();

    let bindings = cbindgen::Builder::new()
        .with_no_includes()
        .with_language(cbindgen::Language::C)
        .with_crate(crate_dir)
        .generate()
        .unwrap();
    bindings.write_to_file(Path::new("target").join("header.h"));
}

uniffi

uniffi bindings use uniffi-rs to generate Python ctypes bindings from an interface definition file. uniffi wheels are compatible with all python versions including pypy.

Mixed rust/python projects

To create a mixed rust/python project, create a folder with your module name (i.e. lib.name in Cargo.toml) next to your Cargo.toml and add your python sources there:

my-project
├── Cargo.toml
├── my_project
│   ├── __init__.py
│   └── bar.py
├── pyproject.toml
├── README.md
└── src
    └── lib.rs

You can specify a different python source directory in pyproject.toml by setting tool.maturin.python-source, for example

pyproject.toml

[tool.maturin]
python-source = "python"
module-name = "my_project._lib_name"

then the project structure would look like this:

my-project
├── Cargo.toml
├── python
│   └── my_project
│       ├── __init__.py
│       └── bar.py
├── pyproject.toml
├── README.md
└── src
    └── lib.rs

[!NOTE]

This structure is recommended to avoid a common ImportError pitfall

maturin will add the native extension as a module in your python folder. When using develop, maturin will copy the native library and for cffi also the glue code to your python folder. You should add those files to your gitignore.

With cffi you can do from .my_project import lib and then use lib.my_native_function, with pyo3 you can directly from .my_project import my_native_function.

Example layout with pyo3 after maturin develop:

my-project
├── Cargo.toml
├── my_project
│   ├── __init__.py
│   ├── bar.py
│   └── _lib_name.cpython-36m-x86_64-linux-gnu.so
├── README.md
└── src
    └── lib.rs

When doing this also be sure to set the module name in your code to match the last part of module-name (don't include the package path):

#![allow(unused)]
fn main() {
#[pymodule]
#[pyo3(name="_lib_name")]
fn my_lib_name(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
    m.add_class::<MyPythonRustClass>()?;
    Ok(())
}
}

Python metadata

maturin supports PEP 621, you can specify python package metadata in pyproject.toml. maturin merges metadata from Cargo.toml and pyproject.toml, pyproject.toml takes precedence over Cargo.toml.

To specify python dependencies, add a list dependencies in a [project] section in the pyproject.toml. This list is equivalent to install_requires in setuptools:

[project]
name = "my-project"
dependencies = ["flask~=1.1.0", "toml==0.10.0"]

Pip allows adding so called console scripts, which are shell commands that execute some function in your program. You can add console scripts in a section [project.scripts]. The keys are the script names while the values are the path to the function in the format some.module.path:class.function, where the class part is optional. The function is called with no arguments. Example:

[project.scripts]
get_42 = "my_project:DummyClass.get_42"

You can also specify trove classifiers in your pyproject.toml under project.classifiers:

[project]
name = "my-project"
classifiers = ["Programming Language :: Python"]

Source distribution

maturin supports building through pyproject.toml. To use it, create a pyproject.toml next to your Cargo.toml with the following content:

[build-system]
requires = ["maturin>=1.0,<2.0"]
build-backend = "maturin"

If a pyproject.toml with a [build-system] entry is present, maturin can build a source distribution of your package when --sdist is specified. The source distribution will contain the same files as cargo package. To only build a source distribution, pass --interpreter without any values.

You can then e.g. install your package with pip install .. With pip install . -v you can see the output of cargo and maturin.

You can use the options compatibility, skip-auditwheel, bindings, strip and common Cargo build options such as features under [tool.maturin] the same way you would when running maturin directly. The bindings key is required for cffi and bin projects as those can't be automatically detected. Currently, all builds are in release mode (see this thread for details).

For a non-manylinux build with cffi bindings you could use the following:

[build-system]
requires = ["maturin>=1.0,<2.0"]
build-backend = "maturin"

[tool.maturin]
bindings = "cffi"
compatibility = "linux"

manylinux option is also accepted as an alias of compatibility for backward compatibility with old version of maturin.

To include arbitrary files in the sdist for use during compilation specify include as an array of path globs with format set to sdist:

[tool.maturin]
include = [{ path = "path/**/*", format = "sdist" }]

There's a maturin sdist command for only building a source distribution as workaround for pypa/pip#6041.

Manylinux and auditwheel

For portability reasons, native python modules on linux must only dynamically link a set of very few libraries which are installed basically everywhere, hence the name manylinux. The pypa offers special docker images and a tool called auditwheel to ensure compliance with the manylinux rules. If you want to publish widely usable wheels for linux pypi, you need to use a manylinux docker image.

The Rust compiler since version 1.64 requires at least glibc 2.17, so you need to use at least manylinux2014. For publishing, we recommend enforcing the same manylinux version as the image with the manylinux flag, e.g. use --manylinux 2014 if you are building in quay.io/pypa/manylinux2014_x86_64. The PyO3/maturin-action github action already takes care of this if you set e.g. manylinux: 2014.

maturin contains a reimplementation of auditwheel automatically checks the generated library and gives the wheel the proper platform tag. If your system's glibc is too new or you link other shared libraries, it will assign the linux tag. You can also manually disable those checks and directly use native linux target with --manylinux off.

For full manylinux compliance you need to compile in a CentOS docker container. The pyo3/maturin image is based on the manylinux2014 image, and passes arguments to the maturin binary. You can use it like this:

docker run --rm -v $(pwd):/io ghcr.io/pyo3/maturin build --release  # or other maturin arguments

Note that this image is very basic and only contains python, maturin and stable rust. If you need additional tools, you can run commands inside the manylinux container. See konstin/complex-manylinux-maturin-docker for a small educational example or nanoporetech/fast-ctc-decode for a real world setup.

maturin itself is manylinux compliant when compiled for the musl target.

Examples

  • ballista-python - A Python library that binds to Apache Arrow distributed query engine Ballista
  • bleuscore - A BLEU score calculation library, written in pure Rust
  • chardetng-py - Python binding for the chardetng character encoding detector.
  • connector-x - ConnectorX enables you to load data from databases into Python in the fastest and most memory efficient way
  • datafusion-python - a Python library that binds to Apache Arrow in-memory query engine DataFusion
  • deltalake-python - Native Delta Lake Python binding based on delta-rs with Pandas integration
  • opendal - OpenDAL Python Binding to access data freely
  • orjson - A fast, correct JSON library for Python
  • polars - Fast multi-threaded DataFrame library in Rust | Python | Node.js
  • pydantic-core - Core validation logic for pydantic written in Rust
  • pyrus-cramjam - Thin Python wrapper to de/compression algorithms in Rust
  • pyxel - A retro game engine for Python
  • roapi - ROAPI automatically spins up read-only APIs for static datasets without requiring you to write a single line of code
  • robyn - A fast and extensible async python web server with a Rust runtime
  • ruff - An extremely fast Python linter, written in Rust
  • tantivy-py - Python bindings for Tantivy
  • watchfiles - Simple, modern and high performance file watching and code reload in python
  • wonnx - Wonnx is a GPU-accelerated ONNX inference run-time written 100% in Rust

Contributing

Everyone is welcomed to contribute to maturin! There are many ways to support the project, such as:

  • help maturin users with issues on GitHub and Gitter
  • improve documentation
  • write features and bugfixes
  • publish blogs and examples of how to use maturin

Our contributing notes have more resources if you wish to volunteer time for maturin and are searching where to start.

If you don't have time to contribute yourself but still wish to support the project's future success, some of our maintainers have GitHub sponsorship pages:

License

Licensed under either of:

  • Apache License, Version 2.0, (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
  • MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)

at your option.

Installation

Install from package managers

Packaging status

PyPI

maturin is published as Python binary wheel to PyPI, you can install it using pipx:

pipx install maturin

There are some extra dependencies for certain scenarios:

  • zig: use zig as linker for easier cross compiling and manylinux compliance.
  • patchelf: repair wheels that links other shared libraries.

For example, to install patchelf dependencies: pipx install maturin[patchelf].

Note

pip install maturin should also work if you don't want to use pipx.

Homebrew

On macOS maturin is in Homebrew and you can install maturin from Homebrew:

brew install maturin

conda

Installing from the conda-forge channel can be achieved by adding conda-forge to your conda channels with:

conda config --add channels conda-forge
conda config --set channel_priority strict

Once the conda-forge channel has been enabled, maturin can be installed with:

conda install maturin

Alpine Linux

On Alpine Linux, maturin is in community repository and can be installed with apk after enabling the community repository:

apk add maturin

Download from GitHub Releases

You can download precompiled maturin binaries from the latest GitHub Releases.

You can also use cargo-binstall to install maturin from GitHub Releases:

# Run `cargo install cargo-binstall` first if you don't have cargo-binstall installed.
cargo binstall maturin

Build from source

crates.io

You can install maturin from crates.io using cargo:

cargo install --locked maturin

Git repository

cargo install --locked --git https://github.com/PyO3/maturin.git maturin

Tutorial

In this tutorial we will wrap a version of the guessing game from The Rust Book to run in Python using pyo3.

Create a new Rust project

First, create a new Rust library project using cargo new --lib --edition 2021 guessing-game. This will create a directory with the following structure.

guessing-game/
├── Cargo.toml
└── src
    └── lib.rs

Edit Cargo.toml to configure the project and module name, and add the dependencies (rand and pyo3). Configure pyo3 with additional features to make an extension module compatible with multiple Python versions using the stable ABI (abi3).

[package]
name = "guessing-game"
version = "0.1.0"
edition = "2021"

[lib]
name = "guessing_game"
# "cdylib" is necessary to produce a shared library for Python to import from.
crate-type = ["cdylib"]

[dependencies]
rand = "0.8.4"

[dependencies.pyo3]
version = "0.23.1"
# "abi3-py38" tells pyo3 (and maturin) to build using the stable ABI with minimum Python version 3.8
features = ["abi3-py38"]

Add a pyproject.toml to configure PEP 518 build system requirements and enable the extension-module feature of pyo3.

[build-system]
requires = ["maturin>=1.0,<2.0"]
build-backend = "maturin"

[tool.maturin]
# "extension-module" tells pyo3 we want to build an extension module (skips linking against libpython.so)
features = ["pyo3/extension-module"]

Use maturin new

New projects can also be quickly created using the maturin new command:

maturin new --help
Create a new cargo project

Usage: maturin new [OPTIONS] <PATH>

Arguments:
  <PATH>  Project path

Options:
      --name <NAME>          Set the resulting package name, defaults to the directory name
      --mixed                Use mixed Rust/Python project layout
      --src                  Use Python first src layout for mixed Rust/Python project
  -b, --bindings <BINDINGS>  Which kind of bindings to use [possible values: pyo3, cffi, uniffi, bin]
  -h, --help                 Print help information

The above process can be achieved by running maturin new -b pyo3 guessing_game then edit Cargo.toml to add abi3-py38 feature.

Install and configure maturin (in a virtual environment)

Create a virtual environment and install maturin. Note maturin has minimal dependencies!

ferris@rustbox [~/src/rust/guessing-game] % python3 -m venv .venv
ferris@rustbox [~/src/rust/guessing-game] % source .venv/bin/activate
(.venv) ferris@rustbox [~/src/rust/guessing-game] % pip install -U pip maturin
(.venv) ferris@rustbox [~/src/rust/guessing-game] % pip freeze
maturin==1.3.0
tomli==2.0.1

maturin is configured in pyproject.toml as introduced by PEP 518. This file lives in the root of your project tree:

guessing-game/
├── Cargo.toml
├── pyproject.toml  #  <<< add this file
└── src
    └── lib.rs

Configuration in this file is quite simple for most projects. You just need to indicate maturin as a requirement (and restrict the version) and as the build-backend (Python supports a number of build-backends since PEP 517).

[build-system]
requires = ["maturin>=1.0,<2.0"]
build-backend = "maturin"

Various other tools may also be configured in pyproject.toml and the Python community seems to be consolidating declarative configuration in this file.

Program the guessing game in Rust

When you create a lib project with cargo new it creates a file src/lib.rs with some default code. Edit that file and replace the default code with the code below. As mentioned, we will implement a slightly modified version of the guessing game from The Rust Book. Instead of implementing as a bin crate, we're using a lib and will expose the main logic as a Python function.

#![allow(unused)]
fn main() {
use pyo3::prelude::*;
use rand::Rng;
use std::cmp::Ordering;
use std::io;

#[pyfunction]
fn guess_the_number() {
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1..101);

    loop {
        println!("Please input your guess.");

        let mut guess = String::new();

        io::stdin()
            .read_line(&mut guess)
            .expect("Failed to read line");

        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => continue,
        };

        println!("You guessed: {}", guess);

        match guess.cmp(&secret_number) {
            Ordering::Less => println!("Too small!"),
            Ordering::Greater => println!("Too big!"),
            Ordering::Equal => {
                println!("You win!");
                break;
            }
        }
    }
}

/// A Python module implemented in Rust. The name of this function must match
/// the `lib.name` setting in the `Cargo.toml`, else Python will not be able to
/// import the module.
#[pymodule]
fn guessing_game(m: &Bound<'_, PyModule>) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(guess_the_number, m)?)?;

    Ok(())
}
}

Thanks to pyo3, there's very little difference between this and the example in The Rust Book. All we had to do was:

  1. Include the pyo3 prelude
  2. Add #[pyfunction] to our function
  3. Add the #[pymodule] block to expose the function as part of a Python module

Refer to the pyo3 User Guide for more information on using pyo3. It can do a lot more!

Build and install the module with maturin develop

Note that this is just a Rust project at this point, and with few exceptions you can build it as you'd expect using cargo build. maturin helps with this, however, adding some platform-specific build configuration and ultimately packaging the binary results as a wheel (a .whl file, which is an archive of compiled components suitable for installation with pip, the Python package manager).

So let's use maturin to build and install in our current environment.

You can also compile a performance-optimized program by adding the -r or --release flag for speed testing.

(.venv) ferris@rustbox [~/src/rust/guessing-game] % maturin develop
🔗 Found pyo3 bindings with abi3 support for Python ≥ 3.8
🐍 Not using a specific python interpreter (With abi3, an interpreter is only required on windows)
   Compiling pyo3-build-config v0.23.1
   Compiling libc v0.2.119
   Compiling once_cell v1.10.0
   Compiling cfg-if v1.0.0
   Compiling proc-macro2 v1.0.36
   Compiling unicode-xid v0.2.2
   Compiling syn v1.0.86
   Compiling parking_lot_core v0.8.5
   Compiling smallvec v1.8.0
   Compiling scopeguard v1.1.0
   Compiling unindent v0.1.8
   Compiling ppv-lite86 v0.2.16
   Compiling instant v0.1.12
   Compiling lock_api v0.4.6
   Compiling indoc v1.0.4
   Compiling getrandom v0.2.5
   Compiling rand_core v0.6.3
   Compiling parking_lot v0.11.2
   Compiling rand_chacha v0.3.1
   Compiling rand v0.8.5
   Compiling quote v1.0.15
   Compiling pyo3-ffi v0.23.1
   Compiling pyo3 v0.23.1
   Compiling pyo3-macros-backend v0.23.1
   Compiling pyo3-macros v0.23.1
   Compiling guessing-game v0.1.0 (/Users/ferris/src/rust/guessing-game)
    Finished dev [unoptimized + debuginfo] target(s) in 13.31s

Your guessing_game module should now be available in your current virtual environment. Go ahead and play a few games!

(.venv) ferris@rustbox [~/src/rust/guessing-game] % python
Python 3.9.6 (default, Aug 25 2021, 16:04:27)
[Clang 12.0.5 (clang-1205.0.22.9)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import guessing_game
>>> guessing_game.guess_the_number()
Guess the number!
Please input your guess.
42
You guessed: 42
Too small!
Please input your guess.
80
You guessed: 80
Too big!
Please input your guess.
50
You guessed: 50
Too small!
Please input your guess.
60
You guessed: 60
Too big!
Please input your guess.
55
You guessed: 55
You win!

Create a wheel for distribution

maturin develop actually skips the wheel generation part and installs directly in the current environment. maturin build on the other hand will produce a wheel you can distribute. Note the wheel contains "tags" in its filename that correspond to supported Python versions, platforms, and/or architectures, so yours might look a little different. If you want to distribute broadly, you may need to build on multiple platforms and use a manylinux Docker container to build wheels compatible with a wide range of Linux distros.

(.venv) ferris@rustbox [~/src/rust/guessing-game] % maturin build
🔗 Found pyo3 bindings with abi3 support for Python ≥ 3.8
🐍 Not using a specific python interpreter (With abi3, an interpreter is only required on windows)
    Finished dev [unoptimized + debuginfo] target(s) in 7.32s
📦 Built wheel for abi3 Python ≥ 3.8 to /Users/ferris/src/rust/guessing-game/target/wheels/guessing_game-0.1.0-cp37-abi3-macosx_10_7_x86_64.whl

maturin can even publish wheels directly to PyPI with maturin publish!

Summary

Congratulations! You successfully created a Python module implemented entirely in Rust thanks to pyo3 and maturin.

This demonstrates how easy it is to get started with maturin, but keep reading to learn more about all the additional features.

Project Layout

Maturin expects a particular project layout depending on the contents of the package.

Pure Rust project

For a pure Rust project, the structure is as expected and what you get from cargo new:

my-rust-project/
├── Cargo.toml
├── pyproject.toml  # required for maturin configuration
└── src
    ├── lib.rs  # default for library crates
    └── main.rs  # default for binary crates

Maturin will add a necessary __init__.py to the package when building the wheel. For convenience, this file includes the following:

from .my_project import *

__doc__ = my_project.__doc__
if hasattr(my_project, "__all__"):
    __all__ = my_project.__all__

such that the module functions may be called directly with:

import my_project
my_project.foo()

rather than:

from my_project import my_project

Note: there is currently no way to tell maturin to include extra data (e.g. package_data in setuptools) for a pure Rust project. Instead, consider using the layout described below for the mixed Rust/Python project.

Mixed Rust/Python project

To create a mixed Rust/Python project, add a directory with your package name (i.e. matching lib.name in your Cargo.toml) to contain the Python source:

my-rust-and-python-project
├── Cargo.toml
├── my_project  # <<< add this directory and put Python code in here
│   ├── __init__.py
│   └── bar.py
├── pyproject.toml
├── README.md
└── src
    └── lib.rs

Note that in a mixed Rust/Python project, maturin does not modify the existing __init__.py in the root package, so now to import the rust module in Python you must use:

from my_project import my_project

You can modify __init__.py yourself (see above) if you would like to import Rust functions from a higher-level namespace.

You can specify a different python source directory in pyproject.toml by setting tool.maturin.python-source, for example

pyproject.toml

[tool.maturin]
python-source = "python"

then the project structure would look like this:

my-rust-and-python-project
├── Cargo.toml
├── python
│   └── my_project
│       ├── __init__.py
│       └── bar.py
├── pyproject.toml
├── README.md
└── src
    └── lib.rs

Note

This structure is recommended to avoid a common ImportError pitfall

Alternate Python source directory (src layout)

Having a directory with package_name in the root of the project can occasionally cause confusion as Python allows importing local packages and modules. A popular way to avoid this is with the src-layout, where the Python package is nested within a src directory. Unfortunately this interferes with the structure of a typical Rust project. Fortunately, Python is not particular about the name of the parent source directory.

maturin will detect the following src layout automatically:

my-rust-and-python-project
├── src  # put python code in src folder
│   └── my_project
│       ├── __init__.py
│       └── bar.py
├── pyproject.toml
├── README.md
└── rust # put rust code in rust folder
    |── Cargo.toml
    └── src
        └── lib.rs

Import Rust as a submodule of your project

If the Python module created by Rust has the same name as the Python package in a mixed Rust/Python project, IDEs might get confused. You might also want to discourage end users from using the Rust functions directly by giving it a different name, say '_my_project'. This can be done by adding module-name = <package name>.<rust pymodule name> to the [tool.maturin] in your pyproject.toml. For example:

[tool.maturin]
module-name = "my_project._my_project"

You can then import your Rust module inside your Python source as follows:

from my_project import _my_project

IDEs can then recognize the _my_project module as separate from your main Python source module. This allows for code completion of the types inside your Rust Python module for certain IDEs.

Adding Python type information

To distribute typing information, you need to add:

  • an empty marker file called py.typed in the root of the Python package
  • inline types in Python files and/or .pyi "stub" files

In a pure Rust project, add type stubs in a <module_name>.pyi file in the project root. Maturin will automatically include this file along with the required py.typed file for you.

my-rust-project/
├── Cargo.toml
├── my_project.pyi  # <<< add type stubs for Rust functions in the my_project module here
├── pyproject.toml
└── src
    └── lib.rs

In a mixed Rust/Python project, additional files in the Python source dir (but not in .gitignore) will be automatically included in the build outputs (source distribution and/or wheel). Type information can be therefore added to the root Python package directory as you might do in a pure Python package. This requires you to add the py.typed marker file yourself.

my-project
├── Cargo.toml
├── python
│   └── my_project
│       ├── __init__.py
│       ├── py.typed  # <<< add this empty file
│       ├── my_project.pyi  # <<< add type stubs for Rust functions in the my_project module here
│       ├── bar.pyi  # <<< add type stubs for bar.py here OR type bar.py inline
│       └── bar.py
├── pyproject.toml
├── README.md
└── src
    └── lib.rs

Data

You can add wheel data by creating a <module_name>.data folder or setting its location as data in pyproject.toml under [tool.maturin] or in Cargo.toml under [project.metadata.maturin].

The data folder may have the following subfolder:

  • data: The contents of this folder will simply be unpacked into the virtualenv
  • scripts: Treated similar to entry points, files in there are installed as standalone executable
  • headers: For .h C header files
  • purelib: This also exists, but seems to be barely used
  • platlib: This also exists, but seems to be barely used

If you add a symlink in the data directory, we'll include the actual file so you have more flexibility.

Bindings

Maturin supports several kinds of bindings, some of which are automatically detected. You can also pass -b / --bindings command line option to manually specify which bindings to use.

pyo3

pyo3 is Rust bindings for Python, including tools for creating native Python extension modules. It supports CPython, PyPy, and GraalPy.

maturin automatically detects pyo3 bindings when it's added as a dependency in Cargo.toml.

Py_LIMITED_API/abi3

pyo3 bindings has Py_LIMITED_API/abi3 support, enable the abi3 feature of the pyo3 crate to use it:

pyo3 = { version = "0.23", features = ["abi3"] }

You may additionally specify a minimum Python version by using the abi3-pyXX format for the pyo3 features, where XX is corresponds to a Python version. For example abi3-py37 will indicate a minimum Python version of 3.7.

Note: Read more about abi3 support in pyo3's documentation.

Cross Compiling

pyo3 bindings has decent cross compilation support. For manylinux support the manylinux-cross docker images can be used.

Note: Read more about cross compiling in pyo3's documentation.

cffi

Cffi wheels are compatible with all python versions including pypy. If cffi isn't installed and python is running inside a virtualenv, maturin will install it, otherwise you have to install it yourself (pip install cffi).

Maturin uses cbindgen to generate a header file for supported Rust types. The header file can be customized by configuring cbindgen through a cbindgen.toml file inside your project root. Aternatively you can use a build script that writes a header file to $PROJECT_ROOT/target/header.h, like so:

use cbindgen;
use std::env;
use std::path::Path;

fn main() {
    let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();

    let bindings = cbindgen::Builder::new()
        .with_no_includes()
        .with_language(cbindgen::Language::C)
        .with_crate(crate_dir)
        .generate()
        .unwrap();
    bindings.write_to_file(Path::new("target").join("header.h"));
}

Maturin uses the cbindgen-generated header to create a module that exposes ffi and lib objects as attributes. See the cffi docs for more information on using these ffi/lib objects to call the Rust code from Python.

Note: Maturin does not automatically detect cffi bindings. You must specify them via either command line with -b cffi or in pyproject.toml.

bin

Maturin also supports distributing binary applications written in Rust as Python packages using the bin bindings. Binaries are packaged into the wheel as "scripts" and are available on the user's PATH (e.g. in the bin directory of a virtual environment) once installed.

Note: Maturin does not automatically detect bin bindings. You must specify them via either command line with -b bin or in pyproject.toml.

Both binary and library?

Shipping both a binary and library would double the size of your wheel. Consider instead exposing a CLI function in the library and using a Python entrypoint:

#![allow(unused)]
fn main() {
#[pyfunction]
fn print_cli_args(py: Python) -> PyResult<()> {
    // This one includes python and the name of the wrapper script itself, e.g.
    // `["/home/ferris/.venv/bin/python", "/home/ferris/.venv/bin/print_cli_args", "a", "b", "c"]`
    println!("{:?}", env::args().collect::<Vec<_>>());
    // This one includes only the name of the wrapper script itself, e.g.
    // `["/home/ferris/.venv/bin/print_cli_args", "a", "b", "c"])`
    println!(
        "{:?}",
        py.import("sys")?
            .getattr("argv")?
            .extract::<Vec<String>>()?
    );
    Ok(())
}

#[pymodule]
fn my_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
    m.add_wrapped(wrap_pyfunction!(print_cli_args))?;

    Ok(())
}
}

In pyproject.toml:

[project.scripts]
print_cli_args = "my_module:print_cli_args"

uniffi

uniffi bindings use uniffi-rs to generate Python ctypes bindings from an interface definition file. uniffi wheels are compatible with all python versions including pypy.

Python Project Metadata

maturin supports PEP 621, you can specify python package metadata in pyproject.toml. maturin merges metadata from Cargo.toml and pyproject.toml, pyproject.toml takes precedence over Cargo.toml.

Here is a pyproject.toml example from PEP 621 for reference purpose:

[project]
name = "spam"
version = "2020.0.0"
description = "Lovely Spam! Wonderful Spam!"
readme = "README.rst"
requires-python = ">=3.8"
license = {file = "LICENSE.txt"}
keywords = ["egg", "bacon", "sausage", "tomatoes", "Lobster Thermidor"]
authors = [
  {email = "hi@pradyunsg.me"},
  {name = "Tzu-Ping Chung"}
]
maintainers = [
  {name = "Brett Cannon", email = "brett@python.org"}
]
classifiers = [
  "Development Status :: 4 - Beta",
  "Programming Language :: Python"
]

dependencies = [
  "httpx",
  "gidgethub[httpx]>4.0.0",
  "django>2.1; os_name != 'nt'",
  "django>2.0; os_name == 'nt'"
]

[project.optional-dependencies]
test = [
  "pytest < 5.0.0",
  "pytest-cov[all]"
]

[project.urls]
homepage = "example.com"
documentation = "readthedocs.org"
repository = "github.com"
changelog = "github.com/me/spam/blob/master/CHANGELOG.md"

[project.scripts]
spam-cli = "spam:main_cli"

[project.gui-scripts]
spam-gui = "spam:main_gui"

[project.entry-points."spam.magical"]
tomatoes = "spam:main_tomatoes"

Add Python dependencies

To specify python dependencies, add a list dependencies in a [project] section in the pyproject.toml. This list is equivalent to install_requires in setuptools:

[project]
name = "my-project"
dependencies = ["flask~=1.1.0", "toml==0.10.0"]

Add console scripts

Pip allows adding so called console scripts, which are shell commands that execute some function in you program. You can add console scripts in a section [project.scripts]. The keys are the script names while the values are the path to the function in the format some.module.path:class.function, where the class part is optional. The function is called with no arguments. Example:

[project.scripts]
get_42 = "my_project:DummyClass.get_42"

Add trove classifiers

You can also specify trove classifiers under project.classifiers:

[project]
name = "my-project"
classifiers = ["Programming Language :: Python"]

Add SPDX license expressions

A practical string value for the license key has been purposefully left out by PEP 621 to allow for a future PEP to specify support for SPDX expressions.

To use SPDX license expressions, you can specify it in Cargo.toml instead:

[package]
name = "my-project"
license = "MIT OR Apache-2.0"

Configuration

Configuration format

You can configure maturin in tool.maturin section of pyproject.toml.

Configuration keys

Cargo options

[tool.maturin]
# Build artifacts with the specified Cargo profile
profile = "release"
# List of features to activate
features = ["foo", "bar"]
# Activate all available features
all-features = false
# Do not activate the `default` feature
no-default-features = false
# Cargo manifest path
manifest-path = "Cargo.toml"
# Require Cargo.lock and cache are up to date
frozen = false
# Require Cargo.lock is up to date
locked = false
# Override a configuration value (unstable)
config = []
# Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for details
unstable-flags = []
# Extra arguments that will be passed to rustc as `cargo rustc [...] -- [...] [arg1] [arg2]`
rustc-args = []

These are cargo build options, refer Cargo documentation here.

maturin options

[tool.maturin]
# Include additional files
include = []
# Exclude files
exclude = []
# Bindings type
bindings = "pyo3"
# Control the platform tag on linux
compatibility = "manylinux2014"
# auditwheel mode, possible values are repair, check and skip
auditwheel = "repair"
# Don't check for manylinux compliance, deprecated in favor of auditwheel = "audit"
skip-auditwheel = false
# Python source directory
python-source = "src"
# Python packages to include
python-packages = ["foo", "bar"]
# Strip the library for minimum file size
strip = true
# Source distribution generator,
# supports cargo (default) and git.
sdist-generator = "cargo"

The [tool.maturin.include] and [tool.maturin.exclude] configuration are inspired by Poetry.

To specify files or globs directly:

include = ["path/**/*", "some/other/file"]

To specify a specific target format (sdist or wheel):

include = [
  { path = "path/**/*", format = "sdist" },
  { path = "all", format = ["sdist", "wheel"] },
  { path = "for/wheel/**/*", format = "wheel" }
]

The default behavior is apply these configurations to both sdist and wheel targets.

target specific maturin options

Currently only macOS deployment target SDK version can be configured for x86_64-apple-darwin and aarch64-apple-darwin targets, other targets have no options yet.

[tool.maturin.target.<triple>]
# macOS deployment target SDK version
macos-deployment-target = "11.0"

Environment Variables

Maturin reads a number of environment variables which you can use to configure the build process. Here is a list of all environment variables that are read by maturin:

Cargo environment variables

See environment variables Cargo reads

Python environment variables

  • VIRTUAL_ENV: Path to a Python virtual environment
  • CONDA_PREFIX: Path to a conda environment
  • MATURIN_PYTHON_SYSCONFIGDATA_DIR: Path to a directory containing a sysconfigdata*.py file
  • _PYTHON_SYSCONFIGDATA_NAME: Name of a sysconfigdata*.py file
  • MATURIN_PYPI_TOKEN: PyPI token for uploading wheels
  • MATURIN_PASSWORD: PyPI password for uploading wheels
  • MATURIN_PEP517_USE_BASE_PYTHON: Use base Python executable instead of venv Python executable in PEP 517 build to avoid unnecessary rebuilds, should not be set when the sdist build requires packages installed in venv.

pyo3 environment variables

  • PYO3_CROSS_PYTHON_VERSION: Python version to use for cross compilation
  • PYO3_CROSS_LIB_DIR: This variable can be set to the directory containing the target's libpython DSO and the associated _sysconfigdata*.py file for Unix-like targets, or the Python DLL import libraries for the Windows target.This variable can be set to the directory containing the target's libpython DSO and the associated _sysconfigdata*.py file for Unix-like targets, or the Python DLL import libraries for the Windows target.
  • PYO3_CONFIG_FILE: Path to a pyo3 config file

Networking environment variables

  • HTTP_PROXY / HTTPS_PROXY: Proxy to use for HTTP/HTTPS requests
  • REQUESTS_CA_BUNDLE / CURL_CA_BUNDLE: Path to a CA bundle to use for HTTPS requests

Other environment variables

  • MACOSX_DEPLOYMENT_TARGET: The minimum macOS version to target
  • SOURCE_DATE_EPOCH: The time to use for the timestamp in the wheel metadata
  • MATURIN_EMSCRIPTEN_VERSION: The version of emscripten to use for emscripten builds
  • MATURIN_NO_MISSING_BUILD_BACKEND_WARNING: Suppress missing build backend warning
  • TARGET_SYSROOT: The sysroot to use for auditwheel wheel when cross compiling
  • ARCHFLAGS: Flags to control the architecture of the build on macOS, for example you can use ARCHFLAGS="-arch x86_64 -arch arm64" to build universal2 wheels

Local Development

maturin develop command

For local development, the maturin develop command can be used to quickly build a package in debug mode by default and install it to virtualenv.

Usage: maturin develop [OPTIONS] [ARGS]...

Arguments:
  [ARGS]...
          Rustc flags

Options:
  -b, --bindings <BINDINGS>
          Which kind of bindings to use

          [possible values: pyo3, pyo3-ffi, cffi, uniffi, bin]

      --strip
          Strip the library for minimum file size

  -E, --extras <EXTRAS>
          Install extra requires aka. optional dependencies

          Use as `--extras=extra1,extra2`

      --skip-install
          Skip installation, only build the extension module inplace

          Only works with mixed Rust/Python project layout

      --pip-path <PIP_PATH>
          Use a specific pip installation instead of the default one.

          This can be used to supply the path to a pip executable when the current virtualenv does not provide one.

  -q, --quiet
          Do not print cargo log messages

      --ignore-rust-version
          Ignore `rust-version` specification in packages

  -v, --verbose...
          Use verbose output (-vv very verbose/build.rs output)

      --color <WHEN>
          Coloring: auto, always, never

      --config <KEY=VALUE>
          Override a configuration value (unstable)

  -Z <FLAG>
          Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for details

      --future-incompat-report
          Outputs a future incompatibility report at the end of the build (unstable)

      --uv
          Use `uv` to install packages instead of `pip`

  -h, --help
          Print help (see a summary with '-h')

Compilation Options:
  -r, --release
          Pass --release to cargo

  -j, --jobs <N>
          Number of parallel jobs, defaults to # of CPUs

      --profile <PROFILE-NAME>
          Build artifacts with the specified Cargo profile

      --target <TRIPLE>
          Build for the target triple

          [env: CARGO_BUILD_TARGET=]

      --target-dir <DIRECTORY>
          Directory for all generated artifacts

      --timings=<FMTS>
          Timing output formats (unstable) (comma separated): html, json

Feature Selection:
  -F, --features <FEATURES>
          Space or comma separated list of features to activate

      --all-features
          Activate all available features

      --no-default-features
          Do not activate the `default` feature

Manifest Options:
  -m, --manifest-path <PATH>
          Path to Cargo.toml

      --frozen
          Require Cargo.lock and cache are up to date

      --locked
          Require Cargo.lock is up to date

      --offline
          Run without accessing the network

PEP 660 Editable Installs

Maturin supports PEP 660 editable installs since v0.12.0. You need to add maturin to build-system section of pyproject.toml to use it:

[build-system]
requires = ["maturin>=1.0,<2.0"]
build-backend = "maturin"

Editable installs can be used with mixed Rust/Python projects so you don't have to recompile and reinstall when only Python source code changes. They can also be used with mixed and pure projects together with the import hook so that recompilation/re-installation occurs automatically when Python or Rust source code changes.

To install a package in editable mode with pip:

cd my-project
pip install -e .

or

cd my-project
maturin develop

Then Python source code changes will take effect immediately because the interpreter looks for the modules directly in the project source tree.

Import Hook

maturin_import_hook is a package that provides a python import hook to automatically rebuild maturin projects when they are imported.

This reduces friction when developing mixed python/rust codebases because changes made to rust components take effect automatically like changes to python components do.

For import statements to trigger rebuilds, the hook must to be active (by calling install() or installing site-wide) and the maturin project being imported must be installed in editable mode (eg with maturin develop or pip install -e). Rebuilds are only triggered if the source code has changed, so the overhead is small if everything is up-to-date.

The hook also adds support for importing stand-alone .rs files by creating and building temporary maturin projects for them.

Installation

Run the following commands to install the package and optionally configure the hook to activate automatically when starting the interpreter.

pip install maturin_import_hook
python -m maturin_import_hook site install

In order to use site install, you must have write access to site-packages. It is recommended to use a virtual environment instead of installing into the system interpreter.

Alternatively, instead of using site install, put calls to maturin_import_hook.install() into any script where you want to use the import hook.

Usage

If the hook is installed site-wide, no code changes are required! just import a maturin project like normal and it will rebuild when necessary.

If the hook is not installed site-wide, call install() like so:

# install the import hook with default settings.
# can be skipped if installed site-wide (see above).
# must be called before importing any maturin project.
import maturin_import_hook
maturin_import_hook.install()

# when a maturin package that is installed in editable mode is imported,
# that package will be automatically recompiled if necessary.
import my_rust_package

# when a .rs file is imported a project will be created for it in the
# maturin build cache and the resulting library will be loaded.
#
# assuming subpackage/my_rust_script.rs defines a pyo3 module:
import subpackage.my_rust_script

The maturin project importer and the rust file importer can be used separately

from maturin_import_hook import rust_file_importer
rust_file_importer.install()

from maturin_import_hook import project_importer
project_importer.install()

The import hook can be configured to control its behaviour

import maturin_import_hook
from maturin_import_hook.settings import MaturinSettings

maturin_import_hook.install(
    enable_project_importer=True,
    enable_rs_file_importer=True,
    settings=MaturinSettings(
        release=True,
        strip=True,
        # ...
    ),
    show_warnings=True,
    # ...
)

The import hook is intended for use in development environments and not for production environments, so any calls to install() should ideally be removed before reaching production. This is another reason why installing site-wide is convenient.

Features

The import hook is fairly robust and supports the following:

  • Supports all the binding types and project layouts supported by maturin.
  • Supports importing multiple maturin projects in the same script.
  • Supports importing stand-alone .rs files that use PyO3 bindings.
  • Supports importlib.reload() (currently not supported on Windows).
  • Detects source code changes of local path dependencies, not just the top-level project.
  • Can be used by multiple environments at once including with different interpreter versions. Each environment has a separate build cache.
  • Handles multiple scripts attempting to import/build packages simultaneously. Each build cache is protected with an exclusive lock. (One case where this is useful is tests using pytest-xdist).
  • Extensible (see Advanced Usage below)

CLI

The package provides a CLI interface for getting information such as the location and size of the build cache and managing the installation into sitecustomize.py. For details, run:

python -m maturin_import_hook --help
  • site (info | install | uninstall)
    • Manage import hook installation in sitecustomize.py of the active environment.
  • cache (info | clear)
    • Manage the build cache of the active environment.
  • version
    • Show version info of the import hook and associated tools. Useful for providing information to bug reports.

Environment Variables

The import hook can be disabled by setting MATURIN_IMPORT_HOOK_ENABLED=0. This can be used to disable the import hook in production if you want to leave calls to install() in place.

Build files will be stored in an appropriate place for the current system but can be overridden by setting MATURIN_BUILD_DIR. These files can be deleted without causing any issues (unless a build is in progress). The precedence for storing build files is:

  • MATURIN_BUILD_DIR
    • (Each environment will store its cache in a subdirectory of the given path).
  • <virtualenv_dir>/maturin_build_cache
  • <system_cache_dir>/maturin_build_cache
    • e.g. ~/.cache/maturin_build_cache on POSIX.

See the location being used with the CLI: python -m maturin_import_hook cache info

Logging

By default, the maturin_import_hook logger does not propagate to the root logger. This is so that INFO level messages are shown without having to configure logging (INFO level is normally not visible). The import hook also has extensive DEBUG level logging that generally would be more noise than useful. So by not propagating, DEBUG messages from the import hook are not shown even if the root logger has DEBUG level visible.

If you prefer, maturin_import_hook.reset_logger() can be called to undo the default configuration and propagate the messages as normal.

When debugging issues with the import hook, you should first call reset_logger() then configure the root logger to show DEBUG messages. You can also run with the environment variable RUST_LOG=maturin=debug to get more information from maturin.

import logging
logging.basicConfig(format='%(asctime)s %(name)s [%(levelname)s] %(message)s', level=logging.DEBUG)
import maturin_import_hook
maturin_import_hook.reset_logger()
maturin_import_hook.install()

Advanced Usage

The import hook classes can be subclassed to further customize to specific use cases. For example settings can be configured per-project or loaded from configuration files.

import sys
from pathlib import Path
from maturin_import_hook.settings import MaturinSettings
from maturin_import_hook.project_importer import MaturinProjectImporter

class CustomImporter(MaturinProjectImporter):
    def get_settings(self, module_path: str, source_path: Path) -> MaturinSettings:
        return MaturinSettings(
            release=True,
            strip=True,
            # ...
        )

sys.meta_path.insert(0, CustomImporter())

Distribution

Source Distribution

Maturin supports building through pyproject.toml. To use it, create a pyproject.toml next to your Cargo.toml with the following content:

[build-system]
requires = ["maturin>=1.0,<2.0"]
build-backend = "maturin"

If a pyproject.toml with a [build-system] entry is present, maturin can build a source distribution of your package when --sdist is specified. The source distribution will contain the same files as cargo package. To only build a source distribution, use the maturin sdist command.

You can then e.g. install your package with pip install .. With pip install . -v you can see the output of cargo and maturin.

You can use the options compatibility, skip-auditwheel, bindings, strip and common Cargo build options such as features under [tool.maturin] the same way you would when running maturin directly. The bindings key is required for cffi and bin projects as those can't be automatically detected. Currently, all builds are in release mode (see this thread for details).

For a non-manylinux build with cffi bindings you could use the following:

[build-system]
requires = ["maturin>=1.0,<2.0"]
build-backend = "maturin"

[tool.maturin]
bindings = "cffi"
compatibility = "linux"

manylinux option is also accepted as an alias of compatibility for backward compatibility with old version of maturin.

To include arbitrary files in the sdist for use during compilation specify include as an array of path globs with format set to sdist:

[tool.maturin]
include = [{ path = "path/**/*", format = "sdist" }]

Build Wheels

For portability reasons, native python modules on linux must only dynamically link a set of very few libraries which are installed basically everywhere, hence the name manylinux. The pypa offers special docker images and a tool called auditwheel to ensure compliance with the manylinux rules). If you want to publish widely usable wheels for linux pypi, you need to use a manylinux docker image or build with zig.

The Rust compiler since version 1.64 requires at least glibc 2.17, so you need to use at least manylinux2014. For publishing, we recommend enforcing the same manylinux version as the image with the manylinux flag, e.g. use --manylinux 2014 if you are building in quay.io/pypa/manylinux2014_x86_64. The PyO3/maturin-action github action already takes care of this if you set e.g. manylinux: 2014.

maturin contains a reimplementation of auditwheel automatically checks the generated library and gives the wheel the proper platform tag.

  • If your system's glibc is too new, it will assign the linux tag.
  • If you link other shared libraries, maturin will try to bundle them within the wheel, note that this requires patchelf, it can be installed along with maturin from PyPI: pip install maturin[patchelf].

You can also manually disable those checks and directly use native linux target with --manylinux off.

For full manylinux compliance you need to compile in a CentOS docker container. The pyo3/maturin image is based on the manylinux2014 image, and passes arguments to the maturin binary. You can use it like this:

docker run --rm -v $(pwd):/io ghcr.io/pyo3/maturin build --release  # or other maturin arguments

Note that this image is very basic and only contains python, maturin and stable Rust. If you need additional tools, you can run commands inside the manylinux container. See konstin/complex-manylinux-maturin-docker for a small educational example or nanoporetech/fast-ctc-decode for a real world setup.

Usage: maturin build [OPTIONS] [ARGS]...

Arguments:
  [ARGS]...
          Rustc flags

Options:
      --strip
          Strip the library for minimum file size

      --sdist
          Build a source distribution

      --compatibility [<compatibility>...]
          Control the platform tag on linux.

          Options are `manylinux` tags (for example `manylinux2014`/`manylinux_2_24`) or `musllinux` tags (for example `musllinux_1_2`) and `linux` for the native linux tag.

          Note that `manylinux1` and `manylinux2010` is unsupported by the rust compiler. Wheels with the native `linux` tag will be rejected by pypi, unless they are separately validated by `auditwheel`.

          The default is the lowest compatible `manylinux` tag, or plain `linux` if nothing matched

          This option is ignored on all non-linux platforms

  -i, --interpreter [<INTERPRETER>...]
          The python versions to build wheels for, given as the executables of interpreters such as `python3.9` or `/usr/bin/python3.8`

  -f, --find-interpreter
          Find interpreters from the host machine

  -b, --bindings <BINDINGS>
          Which kind of bindings to use

          [possible values: pyo3, pyo3-ffi, cffi, uniffi, bin]

  -o, --out <OUT>
          The directory to store the built wheels in. Defaults to a new "wheels" directory in the project's target directory

      --auditwheel <AUDITWHEEL>
          Audit wheel for manylinux compliance

          Possible values:
          - repair: Audit and repair wheel for manylinux compliance
          - check:  Check wheel for manylinux compliance, but do not repair
          - skip:   Don't check for manylinux compliance

      --zig
          For manylinux targets, use zig to ensure compliance for the chosen manylinux version

          Default to manylinux2014/manylinux_2_17 if you do not specify an `--compatibility`

          Make sure you installed zig with `pip install maturin[zig]`

  -q, --quiet
          Do not print cargo log messages

      --ignore-rust-version
          Ignore `rust-version` specification in packages

  -v, --verbose...
          Use verbose output (-vv very verbose/build.rs output)

      --color <WHEN>
          Coloring: auto, always, never

      --config <KEY=VALUE>
          Override a configuration value (unstable)

  -Z <FLAG>
          Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for details

      --future-incompat-report
          Outputs a future incompatibility report at the end of the build (unstable)

  -h, --help
          Print help (see a summary with '-h')

Compilation Options:
  -r, --release
          Build artifacts in release mode, with optimizations

  -j, --jobs <N>
          Number of parallel jobs, defaults to # of CPUs

      --profile <PROFILE-NAME>
          Build artifacts with the specified Cargo profile

      --target <TRIPLE>
          Build for the target triple

          [env: CARGO_BUILD_TARGET=]

      --target-dir <DIRECTORY>
          Directory for all generated artifacts

      --timings=<FMTS>
          Timing output formats (unstable) (comma separated): html, json

Feature Selection:
  -F, --features <FEATURES>
          Space or comma separated list of features to activate

      --all-features
          Activate all available features

      --no-default-features
          Do not activate the `default` feature

Manifest Options:
  -m, --manifest-path <PATH>
          Path to Cargo.toml

      --frozen
          Require Cargo.lock and cache are up to date

      --locked
          Require Cargo.lock is up to date

      --offline
          Run without accessing the network

Cross Compiling

Maturin has decent cross compilation support for pyo3 and bin bindings, other kind of bindings may work but aren't tested regularly.

Cross-compile to Linux/macOS

Use Docker

For manylinux support the manylinux-cross docker images can be used. And maturin-action makes it easy to do cross compilation on GitHub Actions.

Use Zig

Since v0.12.7 maturin added support for linking with zig cc, compile for Linux works and is regularly tested on CI, other platforms may also work but aren't tested regularly.

You can install zig following the official documentation, or install it from PyPI via pip install ziglang. Then pass --zig to maturin build or publish commands to use it, for example

maturin build --release --target aarch64-unknown-linux-gnu --zig

Cross-compile to Windows

Pyo3 0.16.5 added an experimental feature generate-import-lib enables the user to cross compile extension modules for Windows targets without setting the PYO3_CROSS_LIB_DIR environment variable or providing any Windows Python library files.

[dependencies]
pyo3 = { version = "0.22.0", features = ["extension-module", "generate-import-lib"] }

It uses an external python3-dll-a crate to generate import libraries for the Python DLL for MinGW-w64 and MSVC compile targets. Note: MSVC targets require LLVM binutils or MSVC build tools to be available on the host system. More specifically, python3-dll-a requires llvm-dlltool or lib.exe executable to be present in PATH when targeting *-pc-windows-msvc.

maturin integrates cargo-xwin to enable MSVC targets cross compilation support, it will download and unpack the Microsoft CRT headers and import libraries, and Windows SDK headers and import libraries needed for compiling and linking automatically.

By using this to cross compiling to Windows MSVC targets you are consented to accept the license at https://go.microsoft.com/fwlink/?LinkId=2086102. (Building on Windows natively does not apply.)

GitHub Actions

If your project uses GitHub Actions, you can use the maturin generate-ci command to generate a GitHub Actions workflow file.

mkdir -p .github/workflows
maturin generate-ci github > .github/workflows/CI.yml

There are some options to customize the generated workflow file:

Generate CI configuration

Usage: maturin generate-ci [OPTIONS] <CI>

Arguments:
  <CI>
          CI provider

          Possible values:
          - github: GitHub

Options:
  -m, --manifest-path <PATH>
          Path to Cargo.toml

  -o, --output <PATH>
          Output path

          [default: -]

      --platform <platform>...
          Platform support

          [default: linux windows macos]

          Possible values:
          - all:        All
          - linux:      Linux
          - windows:    Windows
          - macos:      macOS
          - macosarm64: macOS(Arm64)
          - emscripten: Emscripten

      --pytest
          Enable pytest

      --zig
          Use zig to do cross compilation

  -h, --help
          Print help information (use `-h` for a summary)

Using PyPI's trusted publishing

By default, the workflow provided by generate-ci will publish the release artifacts to PyPI using API token authentication. However, maturin also supports trusted publishing (OpenID Connect).

To enable it, modify the release action in the generated GitHub workflow file:

  • remove MATURIN_PYPI_TOKEN from the env section to make maturin use trusted publishing
  • add id-token: write to the action's permissions (see Configuring OpenID Connect in PyPI from GitHub's documentation).
  • if Environment name: release was set in PyPI, add environment: release

Make sure to follow the steps listed in PyPI's documentation to set up your GitHub repository as a trusted publisher in the PyPI project settings before attempting to run the workflow.

Sphinx Documentation Integration

Sphinx is a popular documentation generator in Python community. It's commonly used together with services like Read The Docs which automates documentation building, versioning, and hosting for you.

Usually in a pure Python project setting up Sphinx is easy, just follow the quick start of Sphinx documentation is enough. But it can get complicated when Rust based Python extension modules are involved.

With maturin, first you need to make sure you have added a pyproject.toml and properly configured it to build source distributions, for example a minimal configuration below:

[build-system]
requires = ["maturin>=1.0,<2.0"]
build-backend = "maturin"

With this pip install . should work when invoked in the project directory.

Read The Docs Integration

To build documentation on Read The Docs, you need to tell it to install the Rust compiler and Python interpreter in its build environment, you can do it by adding a .readthedocs.yaml in your project root:

# https://docs.readthedocs.io/en/stable/config-file/v2.html#supported-settings

version: 2

sphinx:
  builder: html

build:
  os: "ubuntu-20.04"
  tools:
    python: "3.9"
    rust: "1.55"

python:
  install:
    - method: pip
      path: .

If you're using a mixed Rust/Python project layout, make sure you didn't add the Python project path to sys.path in conf.py of Sphinx. Read The Docs doesn't install your project in editable mode, adding it to sys.path will make your project fail to import which breaks documentation generation.

If you need to install a specific version of Sphinx or adding Sphinx themes/extensions, you can change the python.install section a bit to add an extra installation step, for example:

python:
  install:
    - requirements: docs/requirements.txt
    - method: pip
      path: .

In docs/requirements.txt you can add some Python package requirements you needs build the documentation.

Netlify Integration

Netlify is another popular automated site hosting service that can be used with Sphinx and other documentation tools.

Netlify configuration can be specified in a .netlify.toml file. Assuming your Sphinx documentation files are placed in docs/ directory, a minimal configurationfor maturin based project can be:

[build]
  base = "docs"
  publish = "_build/html"
  command = "maturin develop -m ../Cargo.toml && make html"

You also need to add a rust-toolchain file at docs/rust-toolchain which netlify will use to install the specified Rust toolchain that maturin needs to compile your project.

For Sphinx which is written in Python to run you need to add a runtime.txt at docs/runtime.txt, its content should be a Python interpreter version for example 3.8. Then a requirements.txt file at docs/requirements.txt is needed to install Sphinx and its dependencies, you can generate one by:

python3 -m venv venv
source venv/bin/activate
python3 -m pip install sphinx
python3 -m pip freeze > docs/requirements.txt

Migrating from older maturin versions

This guide can help you upgrade code through breaking changes from one maturin version to the next. For a detailed list of all changes, see the CHANGELOG.

From 0.14.* to 0.15

Build with --no-default-features by default when bootstrapping from sdist

When bootstrapping maturin from sdist, maturin 0.15 will build with --no-default-features by default, which means that for distro packaging, you might want to set the environment variable MATURIN_SETUP_ARGS="--features full,rustls" to enable full features.

Remove [tool.maturin.sdist-include]

Use [tool.maturin.include] option instead.

Remove [package.metadata.maturin] from Cargo.toml

Package metadata is now specified in [tool.maturin] section of pyproject.toml instead of Cargo.toml. Note that the replacement for package.metadata.maturin.name is tool.maturin.module-name.

Require uniffi-bindgen CLI to building uniffi bindings

maturin 0.15 requires uniffi-bindgen CLI to build uniffi bindings, you can install it with pip install uniffi-bindgen.

From 0.13.* to 0.14

Remove support for specifying python package metadata in Cargo.toml

maturin 0.14 removed support for specifying python package metadata in Cargo.toml, Python package metadata should be specified in the project section of pyproject.toml instead as PEP 621 specifies.

Deprecate [tool.maturin.sdist-include]

maturin 0.14 added [tool.maturin.include] and [tool.maturin.exclude] to replace [tool.maturin.sdist-include] which was sdist only, the new options can be configured to apply to sdist and/or wheel.

macOS deployment target version defaults what rustc supports

If you don't set the MACOSX_DEPLOYMENT_TARGET environment variable, maturin 0.14 will use the default target version acquired from rustc, this may cause build issue for projects that depend on C/C++ code, usually you can fix it by setting a correct MACOSX_DEPLOYMENT_TARGET, for example

export MACOSX_DEPLOYMENT_TARGET=10.9

Deprecate python-source option in Cargo.toml

maturin 0.14 deprecated the python-source option in Cargo.toml, use [tool.maturin.python-source] option in pyproject.toml instead.

From 0.12.* to 0.13

Drop support for Python 3.6

maturin 0.13 has dropped support for Python 3.6, to support Python 3.6 you can use the old 0.12 versions.

Removed --cargo-extra-args and --rustc-extra-args

maturin 0.13 added most of the cargo rustc options so you can just use them directly, for example --cargo-extra-args="--no-default-features" becomes --no-default-features.

To pass extra arguments to rustc, add them after --, for example use maturin build -- -Clink-arg=-s instead of --rustc-extra-args="-Clink-arg=-s".

Source distributions are not built by default

maturin 0.13 replaced --no-sdist with the new --sdist option in maturin build command, source distributions are now only built when --sdist is specified.

Only build wheels for current Python interpreter in PATH by default

maturin 0.13 no longer searches for Python interpreters by default and only build wheels for the current Python interpreter (i.e. python3) in PATH.

To enable the old behavior, use the new --find-interpreter option.

--repository-url only accepts full URL now

Previously --repository-url option in maturin upload and maturin publish commands accepts both repository name and URL. maturin 0.13 changed --repository-url to only accept full URL and added a new --repository for the repository name. This new behavior matches twine upload.

Changelog

[1.7.5]

  • Improve wheel reproducibility by sorting external libraries #2261
  • Fix Readme and pyproject.toml inclusions for workspace where the bindings crate is not in the root in #2262
  • Add support for GNU/Hurd target in #2306

1.7.4

  • Fix musllinux rpath for non-cffi bindings in #2233
  • Add GitHub Actions attestation support to generate-ci in #2234

1.7.3

  • Fix upload regression to pypi/testpypi in #2229

1.7.2

  • Fix cross compilation issues for armv7l, mips64 and ppc in #2204
  • UniFFI: supports bindings generated from multiple crates in #2208
  • Enable --all-features when building source distribution in #2215
  • Fix rpath when module-name contains . in #2219

1.7.1

  • Forward cargo package --list warnings in #2186
  • In source distributions, we move the readmes of path dependencies into the respective crate to avoid collision between different readmes in #2184

1.7.0 - 2024-07-07

  • Initial iOS support in #2101
  • Remove old import hook in #2105, use maturin-import-hook instead
  • Bump MSRV to 1.74.0 in #2108
  • Add support for overriding wheel tag with _PYTHON_HOST_PLATFORM in #2122
  • Don't add files to an archive more than once #2125
  • Only use base python executable when MATURIN_PEP517_USE_BASE_PYTHON is set in #2134

1.6.0 - 2024-06-04

  • Detect compiling from Linux gnu to Linux musl as cross compiling in #2010
  • Add musllinux support to generate-ci in #2011
  • Add uv support to develop command in #2015
  • Add support for AIX target in #2030
  • Remove rust-cpython support in #2044
  • Add a global -v option in #2080
  • Detect target based on interpreter for pep517 build-wheel in #2088
  • Use base executable when possible in PEP 517 build in #2094

1.5.1 - 2024-03-21

  • Fix usage of --compatibility when run as a PEP517 backend in #1992
  • Fix upload returning malformed summary error in #2002

1.5.0 - 2024-03-05

  • Bump metadata version from 2.1 to 2.3 in #1965. Source distributions created by maturin now have reliable metadata, meaning tool such as pip, uv and poetry could skip building them for version resolution.
  • Allow identical VIRTUAL_ENV and CONDA_PREFIX env vars in #1879
  • Reject -i python when cross compiling in #1891
  • Support uniffi-bindgen in cargo workspaces in #1909
  • Add support for configuring xwin using env vars in #1961
  • Add validation for crate/package name in new/init in #1943
  • Add 32-bit RISC-V support in #1969
  • Improve import hook changes in #1958
  • Adjust cbindgen Overrides for CFFI in #1957

1.4.0 - 2023-12-02

  • Bump MSRV to 1.67.0 in #1847
  • Add support for cross compiling with cross in #1865

1.3.2 - 2023-11-14

  • Add support for uniffi library mode in #1729
  • Un-deprecate MATURIN_PEP517_ARGS env var in #1820
  • Fix missing member in Cargo.toml for sdist of nested workspace layout in #1828
  • Escape display name in email addresses of wheel metadata in #1832
  • Fix rewriting workspace Cargo.toml in sdist in #1841
  • Fix glob workspace members matching in sdist in #1846

1.3.1 - 2023-10-24

  • Use external uniffi-bindgen if no root package is configured in #1797
  • Fix wheel filename for GraalPy in #1802
  • Add unittest skeleton to mixed Python/Rust projects in #1807
  • Preserve trailing whitespace in new project files in #1808
  • Fix missing workspace.members in sdist in #1811
  • Don't set MACOSX_DEPLOYMENT_TARGET for editable builds by default in #1815

1.3.0 - 2023-10-02

  • Refactor Cargo sdist generator to avoid rewriting local dependencies in #1741
  • Added --pip-path argument to develop command in #1753
  • Ignore sdist output files when building sdist in #1756
  • Use python.exe by default in build command on Windows in #1757
  • Don't require uniffi-bindgen to be installed for uniffi bindings in #1762
  • Fix platform tag for graalpy in #1773
  • Always set minor version to 0 when major version >= 11 for macOS in #1778
  • Warning about incorrect maturin version pyproject.toml [build-system] requires in #1793

1.2.3 - 2023-08-17

  • Fix sdist build failure with workspace path dependencies by HerringtonDarkholme in #1739

1.2.2 - 2023-08-14

  • Fix non interactive mode check when username/password was supplied from cli in #1737

1.2.1 - 2023-08-14

  • Add non-interactive mode to upload command in #1722
  • Fix link-native-libraries check for emscripten target in #1724
  • Add support for ALL_PROXY to upload command in #1727
  • Handle renamed Rust dependency in sdist in #1728
  • Fix invalid TOML when rewriting workspace inherited dependencies in #1733

1.2.0 - 2023-08-06

  • Add basic support for implicit namespaces #1645
  • Add Linux mips64 and mips architecture support in #1712
  • Add x86_64h-apple-darwin target support in #1717

1.1.0 - 2023-06-10

  • Add basic support for GraalPy in #1645
  • Refactor abi tag to use EXT_SUFFIX in #1648
  • Add Linux loongarch64 architecture support in #1653
  • Add --skip-install option to maturin develop in #1654

1.0.1 - 2023-05-28

  • Add more Python 3.12 sysconfigs in #1629
  • Fix panicking when no cargo build targets are selected in #1635

1.0.0 - 2023-05-23

  • Add support for multiple --config-settings in PEP517 backend in #1624
  • Remove deprecated --universal2 cli option in #1620, use --target universal2-apple-darwin instead.

0.15.3 - 2023-05-20

  • Fix cross compile Apple universal2 wheels on non-macOS platforms by MisLink in #1613
  • Add PEP 517 config_settings support in #1619, deprecate MATURIN_PEP517_ARGS in favor of the new build-args config setting.

0.15.2 - 2023-05-16

  • When determining the python module name, use pyproject.toml project.name over Cargo.toml package.name in #1608
  • Fix rewriting dev-dependencies in sdist in #1610

0.15.1 - 2023-05-07

  • Fix finding interpreters from bundled sysconfigs in #1598

0.15.0 - 2023-05-07

  • Breaking Change: Build with --no-default-features by default when bootstrapping from sdist in #1333
  • Breaking Change: Remove deprecated sdist-include option in pyproject.toml in #1335
  • Breaking Change: Remove deprecated python-source option in Cargo.toml in #1335
  • Breaking Change: Turn patchelf version warning into a hard error in #1335
  • Breaking Change: uniffi_bindgen CLI is required for building uniffi bindings wheels in #1352
  • Add Cargo compile targets configuration for filtering multiple bin targets in #1339
  • Respect rustflags settings in cargo configuration file in #1405
  • Add support for uniffi 0.23 in #1481
  • Add support for custom TLS certificate authority bundle in #1483
  • Bump MSRV to 1.64.0 in #1528
  • Add wildcards support to publish/upload commands on Windows in #1534
  • Add support for configuring macOS deployment target version in pyproject.toml in #1536
  • Rewrite platform specific dependencies in Cargo.toml by viccie30 in #1572
  • Add trusted publisher support in #1578
  • Add new git source distribution generator in #1587

0.14.17 - 2023-04-06

  • Fix wrong EXT_SUFFIX when cross compiling musllinux wheels for Python 3.11 in #1560

0.14.16 - 2023-03-26

  • Deprecate package.metadata.maturin.name in favor of tool.maturin.module-name in pyproject.toml in #1531

0.14.15 - 2023-03-03

  • Add sdist and sccache support to generate-ci command

0.14.14 - 2023-02-24

  • Add support for Emscripten in generate-ci command in #1484
  • Add support for linking with pyo3 in abi3 debug mode on Windows in #1487
  • Use default ext_suffix for Emscripten target if not provided in PYO3_CONFIG_FILE in #1491
  • Deprecate package.metadata.maturin.data in favor of tool.maturin.data in pyproject.toml in #1492

0.14.13 - 2023-02-12

  • maturin develop now looks for a virtualenv .venv in the current or any parent directory if no virtual environment is active.
  • Add a new generate-ci command to generate CI configuration in #1456
  • Deprecate --universal2 in favor of universal2-apple-darwin target in #1457
  • Raise an error when Cargo.toml contains removed python package metadata in #1471
  • Use extension_name instead of module_name for CFFI extensions in develop mode in #1476

0.14.12 - 2023-01-31

  • Keep dev-dependencies in sdist when there are no path dependencies in #1441

0.14.11 - 2023-01-31

  • Don't package dev-only path dependencies in sdist in #1435

0.14.10 - 2023-01-13

  • Use module name specified by [package.metadata.maturin] in #1409

0.14.9 - 2023-01-10

  • Don't pass MACOSX_DEPLOYMENT_TARGET when query default value from rustc in #1395

0.14.8 - 2022-12-31

  • Add support for packaging multiple pure Python packages in #1378
  • Fallback to sysconfig interpreters for pyo3 bindings in #1381

0.14.7 - 2022-12-20

  • Add workspace lock file to sdist as a fallback in #1362

0.14.6 - 2022-12-13

  • Allow Rust crate to be placed outside of the directory containing pyproject.toml in #1347
  • Disallow uniffi bin bindings in #1353
  • Update bundled Python sysconfigs for Linux and macOS

0.14.5 - 2022-12-08

  • Support SOURCE_DATE_EPOCH when building wheels in #1334
  • Fix sdist when all Cargo workspace members are excluded in #1343

0.14.4 - 2022-12-05

  • Expanded architecture support for FreeBSD, NetBSD and OpenBSD in #1318
  • Better error message when upload failed with status code 403 in #1323

0.14.3 - 2022-12-01

  • Bump MSRV to 1.62.0 in #1297
  • Fix build error when required features of bin target isn't enabled in #1299
  • Fix wrong platform tag when building in i386 docker container on x86_64 host in #1301
  • Fix wrong platform tag when building in armv7 docker container on aarch64 host in #1303
  • Add Solaris operating system support in #1310
  • Add armv6 and armv7 target support for FreeBSD in #1312
  • Add riscv64 and powerpc target support for FreeBSD in #1313
  • Fix powerpc64 and powerpc64le Python wheel platform tag for FreeBSD in #1313

0.14.2 - 2022-11-24

  • Tighten src-layout detection logic in #1281
  • Fix generating pep517 sdist for src-layout in #1288
  • Deprecate python-source option in Cargo.toml in favor of the one in pyproject.toml in #1291
  • Fix auditwheel with read-only libraries in #1292

0.14.1 - 2022-11-20

  • Downgrade cargo_metadata to 0.15.0 to fix maturin build on old Rust versions like 1.48.0 in #1279

0.14.0 - 2022-11-19

  • Breaking Change: Remove support for specifying python package metadata in Cargo.toml in #1200. Python package metadata should be specified in the project section of pyproject.toml instead as PEP 621 specifies.
  • Initial support for shipping bin targets as wasm32-wasi binaries that are run through wasmtime in #1107. Note that wasmtime currently only support the five most popular platforms and that wasi binaries have restrictions when interacting with the host. Usage is by setting --target wasm32-wasi.
  • Add support for python first src project layout in #1185
  • Add --src option to generate src layout for mixed Python/Rust projects in #1189
  • Add Python metadata support for license-file field of Cargo.toml in #1195
  • Upgrade to clap 4.0 in #1197. This bumps MSRV to 1.61.0.
  • Remove workspace.members in Cargo.toml from sdist if there isn't any path dependency in #1227
  • Fix auditwheel libpython check on Python 3.7 and older versions in #1229
  • Use generic tags when sys.implementation.name != platform.python_implementation() in #1232. Fixes the compatibility tags for Pyston.
  • Set default macOS deployment target version if MACOSX_DEPLOYMENT_TARGET isn't specified in #1251
  • Add support for 32-bit x86 FreeBSD target in #1254
  • Add [tool.maturin.include] and [tool.maturin.exclude] and deprecate [tool.maturin.sdist-include] #1255
  • Ignore sdist tar ball instead of error out in #1259
  • Add support for uniffi bindings in #1275

0.13.7 - 2022-10-29

  • Fix macOS LC_ID_DYLIB for abi3 wheels in #1208
  • Pass --locked to Cargo when bootstrap from sdist in #1212
  • Fix build for Python 3.11 on Windows in #1222

0.13.6 - 2022-10-08

  • Fix maturin develop in Windows conda virtual environment in #1146
  • Fix build for crate using pyo3 and build.rs without cdylib crate type in #1150
  • Fix build on some 32-bit platform by downgrading indicatif in #1163
  • Include Cargo.lock by default in source distribution in #1170

0.13.5 - 2022-09-27

  • Fix resolving crate name bug in #1142

0.13.4 - 2022-09-27

  • Fix Cargo.toml in new project template in #1109
  • Fix maturin develop on Windows when using Python installed from msys2 in #1112
  • Fix duplicated Cargo.toml of local dependencies in sdist in #1114
  • Add support for Cargo workspace dependencies inheritance in #1123
  • Add support for Cargo workspace metadata inheritance in #1131
  • Use goblin instead of shelling out to patchelf to get rpath in #1139

0.13.3 - 2022-09-15

  • Allow user to override default Emscripten settings in #1059
  • Enable --crate-type cdylib on Rust 1.64.0 in #1060
  • Update MSRV to 1.59.0 in #1071
  • Fix abi3 wheel build when no Python interpreters found in #1072
  • Add zig ar support in #1073
  • Fix sdist build for optional path dependencies in #1084
  • auditwheel: find dylibs in Cargo target directory in #1092
  • Add library search paths in Cargo target directory to rpath in editable mode on Linux in #1094
  • Remove default manifest path for maturin sdist command in #1097
  • Fix sdist when pyproject.toml isn't in the same dir of Cargo.toml in #1099
  • Change readme and license paths in pyproject.toml to be relative to pyproject.toml in #1100. It's technically a breaking change, but previously it doesn't work properly.
  • Add python source files specified in pyproject.toml to sdist in #1102
  • Change sdist-include paths to be relative to pyproject.toml in #1103

0.13.2 - 2022-08-14

  • Deprecate manylinux 2010 support in #858. The manylinux project already dropped its support and the rustc compiler will drop glibc 2.12 support in 1.64.0.
  • Add Linux mips64el architecture support in #1023
  • Add Linux mipsel architecture support in #1024
  • Add Linux 32-bit powerpc architecture support in #1026
  • Add Linux sparc64 architecture support in #1027
  • Add PEP 440 local version identifier support in #1037
  • Fix inconsistent Cargo.toml and pyproject.toml path handling in #1043
  • Find python module next to pyproject.toml if pyproject.toml exists in #1044. It's technically a breaking change, but previously it doesn't work properly if the directory containing pyproject.toml isn't recognized as project root.
  • Add python-source option to [tool.maturin] section of pyproject.toml in #1046
  • Deprecate support for specifying python metadata in Cargo.toml in #1048. Please migrate to PEP 621 instead.
  • Change python-source to be relative to the file specifies it in #1049
  • Change data to be relative to the file specifies it in #1051
  • Don't reinstall dependencies in maturin develop in #1052
  • Find pyproject.toml in parent directories of Cargo.toml in #1054

0.13.1 - 2022-07-26

  • Add 64-bit RISC-V support by felixonmars in #1001
  • Add support for invoking with python3 -m maturin in #1008
  • Fix detection of optional dependencies when declaring features in pyproject.toml in #1014
  • Respect user specified Rust target in maturin develop in #1016
  • Use cargo rustc --crate-type cdylib on Rust nightly/dev channel in #1020

0.13.0 - 2022-07-09

  • Breaking Change: Drop support for python 3.6, which is end of life in #945
  • Breaking Change: Don't build source distribution by default in maturin build command in #955, --no-sdist option is replaced by --sdist
  • Breaking Change: maturin no longer search for python interpreters by default and only build for current interpreter in PATH in #964
  • Breaking Change: Removed --cargo-extra-args and --rustc-extra-args options in #972. You can now pass all common cargo build arguments directly to maturin build
  • Breaking Change: --repository-url option in upload command no longer accepts plain repository name, full url required and -r short option moved to --repository in #987
  • Add support for building with multiple binary targets in #948
  • Add a --target option to maturin list-python command in #957
  • Add support for using bundled python sysconfigs for PyPy when abi3 feature is enabled in #958
  • Add support for cross compiling PyPy wheels when abi3 feature is enabled in #963
  • Add --find-interpreter option to build and publish commands to search for python interpreters in #964
  • Infer target triple from ARCHFLAGS for macOS to be compatible with cibuildwheel in #967
  • Expose commonly used Cargo CLI options in maturin build command in #972
  • Add support for wasm32-unknown-emscripten target in #974
  • Allow overriding platform release version using env var in #975
  • Fix maturin develop for arm64 Python on M1 Mac when default toolchain is x86_64 in #980
  • Add --repository option to maturin upload command in #987
  • Only lookup bundled Python sysconfig when interpreters aren't specified as file path in #988
  • Find CPython upper to 3.12 and PyPy upper to 3.10 in #993
  • Add short alias maturin b for maturin build and maturin dev for maturin develop subcommands in #994

0.12.20 - 2022-06-15

  • Fix incompatibility with cibuildwheel for 32-bit Windows in #951
  • Don't require pip error messages to be utf-8 encoding in #953
  • Compare minimum python version requirement between requires-python and bindings crate in #954
  • Set PYO3_PYTHON env var for PyPy when abi3 is enabled in #960
  • Add sysconfigs for x64 Windows PyPy in #962
  • Add support for Linux armv6l in #966
  • Fix auditwheel bundled shared libs directory name in #969

0.12.19 - 2022-06-05

  • Fix Windows Store install detection in #949
  • Filter Python interpreters by target pointer width on Windows in #950

0.12.18 - 2022-05-29

  • Add support for building bin bindings wheels with multiple platform tags in #928
  • Skip auditwheel for non-compliant linux environment automatically in #931
  • Fix abi3 wheel build issue when no Python interpreters found on host in #933
  • Add Python 3.11 sysconfigs for Linux, macOS and Windows in #934
  • Add Python 3.11 sysconfig for arm64 Windows in #936
  • Add network proxy support to upload command in #939
  • Fix python interpreter detection on arm64 Windows in #940
  • Fallback to py -X.Y when pythonX.Y cannot be found on Windows in #943
  • Auto-detect Python Installs from Microsoft Store in #944
  • Add bindings detection to bin targets in #938

0.12.17 - 2022-05-18

  • Don't consider compile to i686 on x86_64 Windows cross compiling in #923
  • Accept -i x.y and -i python-x.y in maturin build command in #925

0.12.16 - 2022-05-16

  • Add Linux armv7l python sysconfig in #901
  • Add NetBSD python sysconfig in #903
  • Update 'replace_needed' to reduce total calls to 'patchelf' in #905
  • Add wheel data support in #906
  • Allow use python interpreters from bundled sysconfig when not cross compiling in #907
  • Use setuptools-rust for bootstrapping in #909
  • Allow setting the publish repository URL via MATURIN_REPOSITORY_URL in #913
  • Allow stubs-only mixed project layout in #914
  • Allow setting the publish user name via MATURIN_USERNAME in #915
  • Add Windows python sysconfig in #917
  • Add support for generate-import-lib feature of pyo3 in #918
  • Integrate cargo-xwin for cross compiling to Windows MSVC targets in #919

0.12.15 - 2022-05-07

  • Re-export __all__ for pure Rust projects in #886
  • Stop setting RUSTFLAGS environment variable to an empty string in #887
  • Add hardcoded well-known sysconfigs for effortless cross compiling in #896
  • Add support for PYO3_CONFIG_FILE in #899

0.12.14 - 2022-04-25

  • Fix PyPy pep517 build when abi3 feature is enabled in #883

0.12.13 - 2022-04-25

  • Stop setting PYO3_NO_PYTHON environment variable for pyo3 0.16.4 and later in #875
  • Build Windows abi3 wheels for pyo3 0.16.4 and later versions with generate-abi3-import-lib feature enabled no longer require a Python interpreter in #879

0.12.12 - 2022-04-07

  • Migrate docker image to GitHub container registry in #845
  • Change mixed rust/python template project layout for new projects in #855
  • Automatically include license files in .dist-info/license_files following PEP 639 in #862
  • Bring back multiple values support for --interpreter option in #873
  • Update the default edition to 2021 for new projects by sa- in #874
  • Drop python3.6 from ghcr.io/pyo3/maturin docker image.

0.12.11 - 2022-03-15

  • Package license files in .dist-info/license_files following PEP 639 in #837
  • Stop testing Python 3.6 on CI since it's already EOL in #840
  • Update workspace members for sdist local dependencies in #844
  • Migrate docker image to github container registry in #845
  • Remove PYO3_NO_PYTHON hack for Windows in #848
  • Remove Windows abi3 python lib link hack in #851
  • Add -r option as a short alias for --release in #854

0.12.10 - 2022-03-09

  • Add support for pyo3-ffi by ijl in #804
  • Defaults to musllinux_1_2 for musl target if it's not bin bindings in #808
  • Remove support for building only sdist via maturin build -i in #813, use maturin sdist instead.
  • Add macOS target support for --zig in #817
  • Migrate Python dependency toml to tomllib / tomli by Contextualist in #821
  • Disable auditwheel for PEP 517 build wheel process in #823
  • Lookup existing cffi header.h in workspace target directory in #833
  • Fix license line ending in wheel metadata for Windows in #836

0.12.9 - 2022-02-09

  • Don't require pyproject.toml when cargo manifest is not specified in #806

0.12.8 - 2022-02-08

  • Add missing --version flag from clap 3.0 upgrade

0.12.7 - 2022-02-08

  • Add support for using zig cc as linker for easier cross compiling and manylinux compliance in #756
  • Switch from reqwest to ureq to reduce dependencies in #767
  • Fix missing Python submodule in wheel in #772
  • Add support for specifying cargo manifest path in pyproject.toml in #781
  • Add support for passing arguments to pep517 command via MATURIN_PEP517_ARGS env var in #786
  • Fix auditwheel No such file or directory error when LD_LIBRARY_PATH contains non-existent paths in #794

0.12.6 - 2021-12-31

  • Add support for repairing cross compiled linux wheels in #754
  • Add support for manylinux_2_28 and manylinux_2_31 in #755
  • Remove existing so file first in maturin develop command to avoid triggering SIGSEV in running process in #760

0.12.5 - 2021-12-20

  • Fix docs for new and init commands in maturin --help in #734
  • Add support for x86_64 Haiku in #735
  • Fix undefined auditwheel policy panic in #740
  • Fix sdist upload for packages where the pkgname contains multiple underscores in #741
  • Implement auditwheel repair with patchelf in #742
  • Add Cargo.lock to sdist when --locked or --frozen specified in #749
  • Infer readme file if not specified in #751

0.12.4 - 2021-12-06

  • Add a maturin init command as a companion to maturin new in #719
  • Don't package non-path-dep crates in sdist for workspaces in #720
  • Build release packages with password-storage feature in #725
  • Add support for x86_64 DargonFly BSD in #727
  • Add a Python import hook in #729
  • Allow pip warnings in maturin develop command in #732

0.12.3 - 2021-11-29

  • Use platform tag from sysconfig.platform on non-portable Linux in #709
  • Consider current machine architecture when generating platform tags for abi3 wheels on linux in #709
  • Revert back to Rust 2018 edition in #710
  • Warn missing cffi package dependency in #711
  • Add support for Illumos in #712
  • Account for MACOSX_DEPLOYMENT_TARGET env var in wheel platform tag in #716

0.12.2 - 2021-11-26

  • Add support for excluding files from wheels by .gitignore in #695
  • Fix pip install maturin on OpenBSD 6.8 in #697
  • Add support for x86, x86_64 and aarch64 on NetBSD in #704
  • Add a maturin new command for bootstrapping new projects in #705

0.12.1 - 2021-11-21

  • Add support for cross compiling PyPy wheels in #687
  • Fix sysconfig.get_platform parsing for macOS in #690

0.12.0 - 2021-11-19

  • Add support for PEP 660 editable installs in #648
  • Publish musllinux_1_1 wheels for maturin in #651
  • Refactor develop command to act identical to PEP 660 editable wheels in #653
  • Upgrade to Rust 2021 edition in #655
  • Add support for powerpc64 and powerpc64le on FreeBSD by pkubaj in #656
  • Fix false positive missing pyinit warning on arm64 macOS in #673
  • Build without rustls on arm64 Windows by nsait-linaro in #674
  • Publish Windows arm64 wheels to PyPI by nsait-linaro in #675
  • Add support for building on Windows mingw platforms in #677
  • Allow building for non-abi3 pypy wheels when the abi3 feature is enabled in #678
  • Add support for cross compiling to different operating systems in #680

0.11.5 - 2021-10-13

  • Fixed module documentation missing bug of pyo3 bindings in #639
  • Fix musllinux auditwheel wrongly detects libc forbidden link in #643
  • Fix finding conda Python interpreters on Windows by RobertColton in #644
  • Fix Unicode metadata when uploading to PyPI in #645
  • Fix incorrectly folded long Summary metadata
  • Fix cross compilation for Python 3.10 in #646

0.11.4 - 2021-09-28

  • Autodetect PyPy executables in #617
  • auditwheel: add libz.so.1 to whitelisted libraries in #625
  • auditwheel: detect musl libc in #629
  • Fixed Python 3.10 and later versions detection on Windows in #630
  • Install entrypoint scripts in maturin develop command in #633 and #634
  • Add support for installing optional dependencies in maturin develop command in #635
  • Fixed build error when manylinux/compatibility options is specified in pyproject.toml in #637

0.11.3 - 2021-08-25

  • Add path option for Python source in #584
  • Add auditwheel support for musllinux in #597
  • [tool.maturin] options from pyproject.toml will be used automatically in #605
  • Skip unavailable Python interpreters from pyenv in #609

0.11.2 - 2021-07-20

  • Use UTF-8 encoding when reading pyproject.toml by domdfcoding in #588
  • Use Cargo's repository field as Source Code in project URL in #590
  • Fold long header fields in Python metadata in #594
  • Fix maturin develop for PyPy on Unix in #596

0.11.1 - 2021-07-10

  • Fix sdist error when VCS has uncommitted renamed files in #585
  • Add maturin completions <shell> command to generate shell completions in #586

0.11.0 - 2021-07-04

  • Add support for reading metadata from PEP 621 project table in pyproject.toml in #555
  • Users should migrate away from the old [package.metadata.maturin] table of Cargo.toml to this new [project] table of pyproject.toml
  • Add PEP 656 musllinux support in #543
  • --manylinux is now called --compatibility and supports musllinux
  • The pure rust install layout changed from just the shared library to a python module that reexports the shared library. This should have now observable consequences for users of the created wheel expect that my_project.my_project is now also importable (and equal to just my_project)
  • Add support for packaging type stubs in pure Rust project layout in #567
  • Support i386 on OpenBSD in #568
  • Support Aarch64 on OpenBSD in #570
  • Support Aarch64 on FreeBSD in #571
  • Cargo.toml's authors field is now optional per Rust RFC 3052 in #573
  • Allow dotted keys in Cargo.toml by switch from toml_edit to toml crate in #577
  • Fix source distribution with local path dependencies on Windows in #580

0.10.6 - 2021-05-21

  • Fix corrupted macOS binary release in #547
  • Fix build with the "upload" feature disabled by ravenexp in #548

0.10.5 - 2021-05-21

  • Add manylinux_2_27 support in #521
  • Add support for Windows arm64 target in #524
  • Always output PEP 600 platform tags in #525
  • Fix missing PyInit_<module_name> warning with Rust submodule in #528
  • Better cross compiling support for PyO3 binding on Unix in #454
  • Fix s390x architecture support in #530
  • Fix auditwheel panic with s390x wheels in #532
  • Support uploading heterogeneous wheels by ravenexp in #544
  • Warn about pyproject.toml missing maturin version constraint in #545

0.10.4 - 2021-04-28

  • Interpreter search now uses python 3.6 to 3.12 in #495
  • Consider requires-python when searching for interpreters in #495
  • Support Rust extension as a submodule in mixed Python/Rust projects in #489

0.10.3 - 2021-04-13

  • The upload command is now implemented, it is mostly similar to twine upload. #484
  • Interpreter search now uses python 3.6 to 3.12
  • Add basic support for OpenBSD in #496
  • Fix the PowerPC platform by messense in #503

0.10.2 - 2021-04-03

  • Fix --target being silently ignored

0.10.1 - 2021-04-03

  • Fix a regression in 0.10.0 that would incorrectly assume we're building for musl instead of gnu by messense in #487
  • Basic s390x support

0.10.0 - 2021-04-02

  • Change manylinux default version based on target arch by messense in #424
  • Support local path dependencies in source distribution (i.e. you can now package a workspace into an sdist)
  • Set a more reasonable LC_ID_DYLIB entry on macOS by messense #433
  • Add --skip-existing option to publish by messense #444
  • maturn develop install dependencies automatically by messense #443
  • Load credential from pypirc using repository name instead of package name by messense #445
  • Add manylinux_2_24 support in #451
  • Improve error message when auditwheel failed to find versioned offending symbols in #452
  • Add auditwheel test to CI in #455
  • Fix sdist transitive path dependencies.
  • auditwheel choose higher priority tag when possible in #456, dropped auditwheel Cargo feature.
  • develop now writes an INSTALLER file
  • develop removes an old .dist-info directory if it exists before installing the new one
  • Fix wheels for PyPy on windows containing extension modules with incorrect names. #482

0.9.4 - 2021-02-18

  • Fix building a bin with musl

0.9.3

  • CI failure

0.9.2 - 2021-02-17

  • Escape version in wheel metadata by messense in #420
  • Set executable bit on shared library by messense in #421
  • Rename classifier to classifiers for pypi compatibility. The old classifier is still available and now also works with pypi
  • Fix building for musl by automatically setting -C target-feature=-crt-static

0.9.1 - 2021-01-13

  • Error when the abi3 feature is selected but no minimum version
  • Support building universal2 wheels (x86 and aarch64 in a single file) by messense in #403
  • Recognize PYO3_CROSS_LIB_DIR for cross compiling with abi3 targeting windows.
  • package.metadata.maturin.classifier is renamed to classifiers by kngwyu in #416
  • Added more instructions to building complex manylinux setups

0.9.0 - 2021-01-10

  • Added support for building abi3 wheels with pyo3 0.13.1
  • Python 3.9 is supported (it should have worked before, but it is now tested on ci)
  • There are 64-bit and aarch64 binary builds for linux and 64-bit builds for windows, mac and freebsd-12-1
  • The auditwheel options have changed to --manylinux=[off|2010|2014] with manylinux2010 as default, and optionally --skip-auditwheel.
  • Removed Python 3.5 since it is unsupported
  • The default and minimum manylinux version is now manylinux2010
  • restructured text (rst) readmes are now supported, by clbarnes in #360
  • Allow python 3 interpreter with debuginfo use maturin by inevity in #370
  • pypirc is checked for credentials by houqp in #374
  • Added support for PowerPC by mzpqnxow and programmerjake in #366
  • project-url is now a toml dictionary instead of a toml list to conform to the standard
  • No more retry loop when the password was wrong
  • When bootstrapping, also search for cargo.exe if cargo was not found

0.8.3 - 2020-08-17

Added

  • tox is now supported due to a bugfix in the latest version of tox
  • [tool.maturin] now supports sdist-include = ["path/**/*"] to include arbitrary files in source distributions (#296).
  • Add support for PyO3 0.12's PYO3_PYTHON environment variable. #331

Fixed

  • Fix incorrectly returning full path (not basename) from PEP 517 build_sdist hook. This fixes tox support from maturin's side
  • Packages installed with maturin develop are now visible to pip and can be uninstalled with pip

0.8.2 - 2020-06-29

Added

  • Python 3.8 was added to PATH in the docker image by oconnor663 in #302

0.8.1 - 2020-04-30

Added

  • cffi is installed if it's missing and python is running inside a virtualenv.

0.8.0 - 2020-04-03

Added

  • There is now a binary wheel for aarch64
  • Warn if there are local dependencies

Fixed

  • Omit author_email if @ is not found in authors by evandrocoan in #290

0.7.9 - 2020-03-06

Fixed

  • This release includes binary wheels for mac os

0.7.8 - 2020-03-06

Added

  • Added support from arm, specifically arm7l, aarch64 by ijl in #273
  • Added support for manylinux2014 by ijl in #273

Fixed

  • Remove python 2 from tags by ijl in #254
  • 32-bit wheels didn't work on linux. This has been fixed by dae in #250
  • The path of the RECORD file on windows used a backward slash instead of a forward slash

0.7.7 - 2019-11-12

Added

  • The setup.py installer for bootstrapping maturin now checks for cargo instead of failing with a complex error message.
  • Upload errors now show the filesize

Changed

  • maturin's metadata now lists a requirement of python3.5 or later to install.

0.7.6 - 2019-09-28

Changed

  • Only --features, --no-default-features and --all-features in --cargo-extra-args are passed to cargo metadata when determining the bindings, fixing problems in the previous release with arguments supported by cargo build but by cargo metadata.

0.7.5 - 2019-09-24

Fixed

  • Fix clippy error to fix publishing from ci

0.7.4 - 2019-09-22

Fixed

  • Fix tests

0.7.3 - 2019-09-22

Fixed

  • Fix building when the bindings crate is behind a feature flag

0.7.3 - 2019-09-22

Removed

  • The manylinux docker container doesn't contain musl anymore. If you're targeting musl, there's no need to use manylinux.

0.7.2 - 2019-09-05

Added

  • Allow cross compilation with cffi and a python interpreter with the host target

Fixed

  • Renamed a folder to maturin so PEP 517 backend works again.

0.7.1 - 2019-08-31

Added

  • maturin build --interpreter/maturin publish --interpreter builds only a source distribution.

0.7.0 - 2019-08-30

With this release, the name of this project changes from pyo3-pack to maturin.

Added

  • Mixed rust/python layout
  • Added PEP 517 support
  • Added a maturin sdist command as workaround for pypa/pip#6041
  • Support settings all applicable fields from the python core metadata specification in Cargo.toml
  • Support for FreeBSD by kxepal #173

0.6.1

Fixed

  • Downgraded to structopt 0.2.16 to avoid the yanked 0.2.17

0.6.0

Added

  • Basic pypy support by ijl #105

Removed

  • Python 2 support
  • The custom progress bar was removed and cargo's output is shown instead

0.5.0

Added

  • Support for conda environments on windows by paddyhoran #52
  • maturin will generate a header for cffi crates using cbinding, which means you don't need a build.rs anymore. The option to provide your own header file using a build.rs still exists.
  • The konstin2/maturin docker image makes it easy to build fully manylinux compliant wheels. See the readme for usage details.
  • Support for manylinux2010 by ijl #70
  • The --manxlinux=[1|1-unchecked|2010|2010-unchecked|off] option allows to build for manylinux1 and manylinux2010, both with audithweel (1 or 2010) and without (1-unchecked or 2010-unchecked), but also for the native linux tag with off.

Changed

  • The --skip-auditwheel flag has been deprecated in favor of --manylinux=[1|1-unchecked|2010|2010-unchecked|off].
  • Switched to rustls. This means the upload feature can be used from the docker container and builds of maturin itself are manylinux compliant when compiled with the musl target.

0.4.2 - 2018-12-15

Fixup release because the appveyor failed to release artifacts for windows for 0.4.1.

0.4.1 - 2018-12-15

Added

  • You can now specify trove classifiers in your Cargo.toml with package.metadata.maturin.classifier. Implemented by ijl in #48. Example:
 [package.metadata.maturin]
 classifier = ["Programming Language :: Python"]

0.4.0 - 2018-11-20

Changed

  • publish defaults to release and strip, unless --debug or --no-strip are given.

Added

  • New ci script based on hyperfine which also builds debian packages.

0.3.10 - 2018-11-16

Fixed

  • Fix rust-cpython detection and compilation

0.3.9

Changed

0.3.8

Fixed

0.3.7

Fixed

  • Added cargo lock to project #9

0.3.6

With deflate and the strip options, the wheels get about 25x smaller:

wheelbaselinedeflatestrip + deflate
get_fourtytwo-2.0.1-cp36-cp36m-manylinux1_x86_64.whl2,8M771K102K
hello_world-0.1.0-py2.py3-none-manylinux1_x86_64.whl3,9M1,1M180K
points-0.1.0-py2.py3-none-manylinux1_x86_64.whl2,8M752K85K

Added

  • --strip by ijl #7

Changed

  • Renamed --bindings-crate to --bindings
  • Use deflate compression for zips by ijl #6

Fixed

  • --target is now actually used for the wheel compatibility tag

0.3.5 - 2018-09-20

Changed

  • Upgraded to reqwest 0.9

Fixed

  • "Broken Pipe" with musl builds (through the reqwest upgrade)

0.3.4 - 2018-09-18

Added

  • A --target option which behaves like cargo option of the same name

Changed

  • Musl and auditwheel compliance: Using the new musl feature combined with the musl target, you can build completely static binaries. The password-storage, which enables keyring integration, is now disabled by default. The Pypi packages are now statically linked with musl so that they are audtiwheel compliant.
  • Replaced --debug with --release. All builds are now debug by default

0.3.3 - 2018-09-17

Added

  • Builds for i686 linux and mac
  • Builds for maturin as wheel

Fixed

  • Usage with stable
  • Wrong tags in WHEEL file on non-linux platforms
  • Uploading on windows

0.3.1 - 2017-09-14

Fixed

  • Windows compilation

0.3.0 - 2017-09-14

Added

  • Packaging binaries
  • Published on pypi. You can now pip install maturin
  • A Dockerfile based on manylinux1

Fixed

  • Travis ci setup builds all types of wheels for linux and mac
  • --no-default-features --features auditwheel creates a manylinux compliant binary for maturin

Changed

  • Replaced elfkit with goblin

0.2.0 - 2018-09-03

Added

  • Cffi support
  • A develop subcommand
  • A tox example

Changed

  • Show a progress bar for cargo's compile progress

0.1.0 - 2018-08-22

  • Initial Release

Contributing

Contributions are welcome, and they are greatly appreciated!

You can contribute in many ways:

Types of Contributions

Report Bugs

Report bugs at https://github.com/PyO3/maturin/issues.

Fix Bugs

Look through the GitHub issues for bugs. Anything tagged with bug and help wanted is open to whoever wants to implement it.

Implement Features

Look through the GitHub issues for features.

Write Documentation

Maturin could always use more documentation, whether as part of the official guide, in docstrings or even on the web in blog posts, articles and such.

Submit Feedback

The best way to send feedback is to start a new discussion at https://github.com/PyO3/maturin/discussions.

Get Started!

Ready to contribute? Here's how to setup maturin for local development.

  1. Fork the maturin repository on GitHub.
  2. Clone your fork locally:
    $ git clone git@github.com:your_name_here/maturin.git
    
  3. Install a stable Rust toolchain and of course Python 3.6 or later is also required.
  4. Create a branch for local development:
    $ cd maturin
    $ git checkout -b branch-name
    
    Now you can make your changes locally.
  5. When you're done making changes, ensure the tests pass by running
    $ cargo test
    
    Note that in order to run tests you need to install virtualenv (pip install virtualenv).
  6. make sure your changes are well formatted and pass the linting checks by installing pre-commit and running
    $ pre-commit run --hook-stage manual --all
    
    running pre-commit install will enable running the checks automatically before every commit (except for the slow checks: cargo check and cargo clippy which are only run manually). You can also look at .pre-commit-config.yaml and run the individual checks yourself if you prefer.
  7. Commit your changes and push your branch to GitHub:
    $ git add .
    $ git commit
    $ git push origin branch-name
    
  8. Submit a pull request through the GitHub website.

We provide a pre-configured dev container that could be used in Github Codespaces, VSCode, JetBrains, JuptyerLab.

Open in GitHub Codespaces

Pull Request Guidelines

Before you submit a pull request, check that it meets these guidelines:

  1. The pull request should include tests if it adds or changes functionalities.
  2. Add a changelog entry.
  3. When command line interface changes, run python3 test-crates/update_readme.py to update related documentation.

Guide

To build the guide for local viewing, install mdBook and run mdbook watch guide from the repository root. The output can then be found at guide/book/index.html.

Code

The main part is the maturin library, which is completely documented and should be well integrable. The accompanying main.rs takes care username and password for the pypi upload and otherwise calls into the library.

The sysconfig folder contains the output of python -m sysconfig for different python versions and platform, which is helpful during development.

You need to install cffi and virtualenv (pip install cffi virtualenv) to run the tests.

You can set the MATURIN_TEST_PYTHON environment variable to run the tests against a specific Python version, for example MATURIN_TEST_PYTHON=python3.11 cargo test will run the tests against Python 3.11.

There are some optional hacks that can speed up the tests (over 80s to 17s on my machine).

  1. By running cargo build --release --manifest-path test-crates/cargo-mock/Cargo.toml you can activate a cargo cache avoiding to rebuild the pyo3 test crates with every python version.
  2. Delete target/test-cache to clear the cache (e.g. after changing a test crate) or remove test-crates/cargo-mock/target/release/cargo to deactivate it.
  3. By running the tests with the faster-tests feature, binaries are stripped and wheels are only stored and not compressed.

Platform Support

Being built on cargo and rustc, maturin is limited by rust's platform support.

Automated tests

On GitHub actions, windows, macOS and linux are tested, all on 64-bit x86. FreeBSD is also tested though Cirrus CI, but might get removed at some point. Since CI is very time intensive to maintain, I'd like to stick to GitHub action and these three platforms.

Releases

The following targets are built into wheels and downloadable binaries:

  • Windows: 32-bit and 64-bit x86 as well as arm64
  • Linux: x86, x86_64, armv7, aarch64 and ppc64le (musl), as well as s390x (gnu)
  • macOS: x86_64 and aarch64

Other Operating Systems

It should be possible to build maturin and for maturin to build wheels on other platforms supported by rust. To add a new os, add it in target.rs and, if it doesn't behave like the other unixes, in PythonInterpreter::get_tag. Please also submit the output of python -m sysconfig as a file in the sysconfig folder. It's ok to edit setup.py to deactivate default features so pip install works, but new platforms should not require complex workaround in compile.rs.

Architectures

All architectures included in manylinux (aarch64, armv7l, ppc64le, ppc64, i686, x86_64, s390x) are supported. I'm not sure whether it makes sense to allow architectures that aren't even supported by manylinux.

Python Support

CPython 3.8 to 3.10 are supported and tested on CI, though the entire 3.x series should work. This will be changed as new python versions are released and others have their end of life.

PyPy 3.6 and later also works, as does GraalPy 23.0 and later.

Manylinux/Musllinux

manylinux2014 and its newer versions as well as musllinux_1_1 and its newer versions are supported.

Since Rust and the manylinux project drop support for old manylinux/musllinux versions sometimes, after maturin 1.0 manylinux version bumps will be minor versions rather than major versions.