Building and distributing abi3t extensions#
Starting with CPython 3.15, native extensions can target the free-threaded
Stable ABI, abi3t.
On each platform, one wheel tagged cp315-abi3.abi3t can run on both the
GIL-enabled and free-threaded builds of CPython 3.15 and later.
Publishing an abi3t wheel is optional; it is not required to support
free-threaded Python. The ABI also does not make an extension safe to use
without the GIL. First port and test the extension for
free-threading, then decide whether publishing fewer
wheels is worth the restrictions of the Stable ABI.
Supporting older Python versions#
abi3t wheels support only Python 3.15 and newer. For a project supporting
Python 3.11 and later, we suggest publishing these three wheels on each
platform:
| Wheel tag | Compatible CPython builds |
|---|---|
cp311-abi3 |
GIL-enabled 3.11 and later |
cp314-cp314t |
Free-threaded 3.14 only |
cp315-abi3.abi3t |
GIL-enabled and free-threaded 3.15 and later |
Build the ordinary abi3 wheel with the oldest Python version your project
supports. The examples below use Python 3.11.
Free-threaded 3.14 predates abi3t, so it still needs a version-specific
cp314-cp314t wheel.
Build tools#
Build-tool support varies:
| Build path | Status |
|---|---|
| CMake with scikit-build-core | Released in CMake 4.4 and scikit-build-core 1.0 |
| Maturin with PyO3 | Initial support in Maturin 1.14.0; use 1.14.1+ with both ABI families |
| meson-python | Released in meson-python 0.21.0; abi3t builds need a free-threaded interpreter |
| setuptools | Under development |
CMake 4.4 supports abi3t, and scikit-build-core 1.0+ generates the
corresponding wheel tag. To build the three wheels listed above, use
cp311 for GIL-enabled interpreters and an
override
for free-threaded Python 3.15 and newer. Add the following to your existing
pyproject.toml:
[build-system]
requires = ["scikit-build-core>=1.0"]
build-backend = "scikit_build_core.build"
[tool.scikit-build]
wheel.py-api = "cp311"
[[tool.scikit-build.overrides]]
if.abi-flags = "t"
if.python-version = ">=3.15"
wheel.py-api = "cp315.cp315t"
A minimal CMakeLists.txt fragment looks like this:
cmake_minimum_required(VERSION 4.4)
project(example LANGUAGES C)
find_package(
Python 3.11 REQUIRED COMPONENTS
Interpreter Development.Module ${SKBUILD_SABI_COMPONENT})
if(SKBUILD_SABI_VERSION)
Python_add_library(
_core MODULE src/_core.c USE_SABI ${SKBUILD_SABI_VERSION} WITH_SOABI)
else()
Python_add_library(_core MODULE src/_core.c WITH_SOABI)
endif()
install(TARGETS _core DESTINATION example)
Build separately with GIL-enabled Python 3.11, free-threaded Python 3.14,
and free-threaded Python 3.15 to produce cp311-abi3, cp314-cp314t, and
cp315-abi3.abi3t, respectively. Scikit-build-core leaves
SKBUILD_SABI_VERSION empty on free-threaded Python 3.14, which has no
Stable ABI. On the other builds, it selects 3.11 or 3.15 to match the
wheel tag. The extension source must support each selected API.
Setting wheel.py-api = "cp315.cp315t" unconditionally targets Python
3.15 and later; it does not also produce an abi3 wheel for Python 3.11.
See the complete scikit-build-core abi3t
example
and CMake's FindPython
reference.
The scikit-build-core example also includes a fallback for CMake versions
older than 4.4.
meson-python 0.21.0
added abi3t support. Its Limited API build
guide
recommends making Stable ABI builds opt-in. Add the following to your
existing pyproject.toml:
[build-system]
requires = ["meson-python>=0.21.0", "meson>=1.7.0"]
build-backend = "mesonpy"
[tool.meson-python]
limited-api = true
In meson.build, select the API version as described in the
abi3t instructions:
project(
'example', 'c',
meson_version: '>=1.7.0',
default_options: ['python.allow_limited_api=false'],
)
py = import('python').find_installation(pure: false)
api_version = '3.11'
if py.language_version().version_compare('>=3.15')
if py.get_variable('Py_GIL_DISABLED') == 1
api_version = '3.15'
endif
endif
py.extension_module(
'_core', 'src/_core.c',
limited_api: api_version,
subdir: 'example',
install: true,
)
Enable the Limited API for the two Stable ABI builds; leave it disabled for free-threaded Python 3.14:
python3.11 -m build --wheel -Csetup-args=-Dpython.allow_limited_api=true
python3.14t -m build --wheel
python3.15t -m build --wheel -Csetup-args=-Dpython.allow_limited_api=true
These commands produce cp311-abi3, cp314-cp314t, and
cp315-abi3.abi3t wheels, respectively. Meson-python takes the wheel's
Python version tag from the build interpreter, regardless of
limited_api, so build the first wheel with Python 3.11. Building for
abi3t currently requires a free-threaded interpreter.
No released version supports abi3t yet. Released versions reject
py_limited_api on free-threaded builds. Follow setuptools pull request
5193 and the tracking
issue for the proposed
support.
Until that work is released, use version-specific free-threaded wheels. The setuptools extension module documentation remains the reference for its general C/C++ build configuration.
Extension APIs, bindings, and code generators#
The extension API, language binding, or source generator you use must support
abi3t. The build backend must also assign the correct extension and wheel
tags. Check both layers before publishing.
CPython C API#
For extensions written directly against the CPython C API, follow CPython's
abi3t migration
HOWTO. The ordinary
Limited API documentation does not cover the abi3t-specific changes. The
HOWTO explains the new module initialization convention and the major C API
restrictions.
If your build tool does not define the target selector, define it before
including Python.h:
#define Py_TARGET_ABI3T 0x030f0000
#include <Python.h>
The selector controls only the API available at compile time. On platforms
that use .so extensions, the built extension must also have an .abi3t.so
suffix. Python 3.15 permits Stable ABI filenames with a multiarch
tuple,
such as .abi3t-x86_64-linux-gnu.so. Windows keeps the .pyd suffix. You must
also give the wheel the correct tag, so prefer a build backend with native
support. See the Stable ABI
reference for the precise
contract.
PyO3 and Maturin#
PyO3 0.29+ and Maturin
1.14.0+ support abi3t. Use Maturin 1.14.1 or
newer when enabling both Stable ABI families:
pyo3 = { version = "0.29", features = ["abi3-py311", "abi3t-py315"] }
Build each family with the corresponding interpreter:
maturin build --interpreter python3.11
maturin build --interpreter python3.15t
Each maturin build command selects one ABI family. See the PyO3 build and
distribution guide for
the complete workflow. Free-threaded 3.14 still needs its own version-specific
wheel.
CFFI#
CFFI 2.1.1+ can
generate extension sources that target abi3t when compiled for CPython 3.15.
The upstream cffi-gen-src
documentation
already provides complete Meson and setuptools integration examples, so use
those instead of duplicating their source-generation setup here.
CFFI does not assign the final wheel tag by itself. Pair it with an
abi3t-capable backend, such as the CMake with scikit-build-core or
meson-python paths above. The upstream Meson example does not target the
Stable ABI; add the limited_api argument and the limited-api setting from
the meson-python tab. Until setuptools support is released, CFFI projects using
setuptools should publish version-specific free-threaded wheels.
Cython#
Cython does not yet provide released abi3t support. You can experiment with
the upstream
freethreading-limited-api-preview
branch, but expect bugs and unsupported Cython features.
nanobind#
Current nanobind releases support version-specific free-threaded extensions
and ordinary abi3, but not abi3t. The split-mode
documentation
describes a preview implementation with an abi3t frontend.
Verify and test the wheel#
Before publishing an abi3t wheel:
- Configure the build to target
abi3t. Some backends, including CMake with scikit-build-core and meson-python, require building with a free-threaded CPython 3.15 interpreter; others can target it from a GIL-enabled build. - If you target Python 3.15, confirm that the wheel name contains
cp315-abi3.abi3tand, on Unix-like systems, that the extension filename uses anabi3tsuffix such as.abi3t.soor.abi3t-<multiarch>.so. Windows extension filenames continue to end in.pyd. - Test the wheel with both GIL-enabled and free-threaded builds of every
supported Python version, following CPython's
abi3ttesting guidance. The worked example below shows how to cross-test one wheel. For general CI setup and testing strategies, see Setting up CI and Validating thread safety with testing. - Document the minimum supported installer version. pip 26.1 or newer recognizes the combined tag.
Worked GitHub Actions cross-test#
Use cibuildwheel to build and test with free-threaded Python, then pass the wheel to a separate job that tests with GIL-enabled Python. This example covers Linux, macOS, and Windows, with one wheel per platform.
The project must already be configured to produce abi3t wheels. For the
opt-in meson-python example above, uncomment CIBW_CONFIG_SETTINGS below.
Replace pytest and tests with your project's test dependencies and test
suite as needed.
The workflow disables cibuildwheel's default abi3audit check because
abi3t auditing is not yet supported.
Released abi3audit rejects the PyModExport_* entry point required by
abi3t. Wheel repair and the tests on both interpreter builds still run.
name: abi3t cross-test
on:
pull_request:
push:
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-15, windows-latest]
steps:
- uses: actions/checkout@v7
- uses: pypa/cibuildwheel@v4.2.1
env:
CIBW_BUILD: cp315t-*
CIBW_SKIP: '*-musllinux_*'
CIBW_ARCHS: auto64
CIBW_AUDIT_COMMAND: ''
CIBW_TEST_REQUIRES: pytest
CIBW_TEST_COMMAND: python -m pytest {project}/tests
# For the opt-in meson-python configuration above:
# CIBW_CONFIG_SETTINGS: setup-args=-Dpython.allow_limited_api=true
- uses: actions/upload-artifact@v7
with:
name: abi3t-${{ matrix.os }}
path: wheelhouse/*.whl
test-gil:
needs: build
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-15, windows-latest]
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: '3.15'
allow-prereleases: true
- uses: actions/download-artifact@v8
with:
name: abi3t-${{ matrix.os }}
path: wheelhouse
- name: Install and test the same wheel
shell: bash
run: |
python -m pip install --upgrade 'pip>=26.1'
python -m pip install wheelhouse/*.whl pytest
python -Im pytest tests
Cibuildwheel tests the installed wheel in a separate environment using the
free-threaded interpreter selected by CIBW_BUILD. The second job downloads
that wheel and tests it with GIL-enabled Python 3.15. On Linux, cibuildwheel
builds manylinux wheels; musllinux wheels are excluded because the second job
runs on Ubuntu. In production, pin third-party actions to commit SHAs.
Python 3.15 is the first release with abi3t. Add GIL-enabled and free-threaded
tests for every later Python version you support.