Fix issues around crates enabling abi3 and abi3t features#3226
Conversation
|
PyPy tests are failing. |
|
Looks like pypy is fixed in the latest commit. |
There was a problem hiding this comment.
Pull request overview
This PR fixes stable-ABI (abi3/abi3t) selection bugs when crates enable both feature families, by deferring selection until after interpreter resolution and ensuring maturin emits at most one stable-ABI wheel per build (with version-specific fallback wheels for non-matching interpreters). It also adds new unit/integration tests and a fixture crate to cover mixed abi3/abi3t configurations, and updates the user guide to reflect the actual behavior.
Changes:
- Move stable-ABI selection to after interpreter resolution and introduce per-interpreter stable-ABI compatibility (
stable_abi_for_interpreter) to drive wheel/tag decisions. - Update wheel tag generation and PyO3 config-file generation to support “one stable ABI + fallbacks” behavior cleanly.
- Add new fixture crate plus unit/integration tests for mixed abi3/abi3t builds; update documentation for abi3t behavior and limitations.
Reviewed changes
Copilot reviewed 16 out of 17 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tests/run/integration.rs | Adds integration test entry points for combined abi3/abi3t wheel selection. |
| tests/common/other.rs | Implements the new integration test helper that builds the fixture and asserts produced wheel tags. |
| test-crates/pyo3-abi3-and-abi3t/src/lib.rs | New PyO3 fixture module used to exercise mixed abi3/abi3t feature sets. |
| test-crates/pyo3-abi3-and-abi3t/README.md | Documents the purpose of the new fixture crate. |
| test-crates/pyo3-abi3-and-abi3t/pyproject.toml | Adds Python packaging metadata for the fixture crate. |
| test-crates/pyo3-abi3-and-abi3t/Cargo.toml | Defines mixed abi3/abi3t feature families used by the tests. |
| test-crates/pyo3-abi3-and-abi3t/Cargo.lock | Locks dependencies for the new fixture crate. |
| src/python_interpreter/resolver.rs | Adjusts interpreter resolution behavior for stable ABI detection (abi3-specific fixed version handling). |
| src/python_interpreter/mod.rs | Adds explicit abi3/abi3t capability checks and routes has_stable_api by stable-ABI kind. |
| src/python_interpreter/config.rs | Refactors PyO3 config generation to take Option<StableAbi> and correctly encode target ABI/version. |
| src/compile.rs | Updates PyO3 env/config-file logic to support “forced target_abi config” for stable-ABI builds where appropriate. |
| src/build_orchestrator.rs | Updates tag computation and stable-ABI wheel building to emit one stable-ABI wheel plus version-specific fallbacks. |
| src/build_options.rs | Adds unit tests covering mixed abi3/abi3t bridge detection and post-resolution stable-ABI selection. |
| src/build_context/builder.rs | Ensures stable-ABI upgrade/selection always happens after interpreter resolution (and respects CLI feature overrides). |
| src/bridge/mod.rs | Introduces ABI3T_MINIMUM_PYTHON_MINOR and adds stable_abi_for_interpreter helper for per-interpreter stable-ABI decisions. |
| src/bridge/detection.rs | Updates stable-ABI detection/selection to consider resolved interpreters and pick a single stable-ABI family. |
| guide/src/bindings.md | Updates documentation to describe the “one stable ABI per build” behavior and fallback wheels. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| maturin build --interpreter python3.10 | ||
| maturin build --interpreter python3.15t |
There was a problem hiding this comment.
I had problems with this --interpreter value/format. The example in question simply does not work for Windows builds. I found it worked better (cross-platform -- windows-latest, ubuntu-latest, macos-15-intel, macos-latest) when I used that same version syntax passed to the actions/setup-python action:
strategy:
matrix:
# note, '3.11' is min available build on `windows-11-arm` runner
include:
- python-version: '3.10'
pyo3-feature: 'pyo3/abi3-py310'
- python-version: '3.15t'
pyo3-feature: 'pyo3/abi3t-py315'
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v7
with:
python-version: ${{ matrix.python-version }}
# 3.15 is not stable yet, so ...
allow-prereleases: ${{ startsWith(matrix.python-version, '3.15') }}
- uses: pyo3/maturin-action@v1
with:
args: >-
--release
--out dist
--interpreter ${{ matrix.python-version }}
--features ${{ matrix.pyo3-feature }}FYI, passing the ${{ steps.python-setup.outputs.python-path }} is a bad idea for Linux builds (if pyo3/maturin-action manylinux input is not 'off').
There was a problem hiding this comment.
Did you have those problems using the version of Maturin from this PR or the released version?
There was a problem hiding this comment.
I checked the CI logs...
Installing 'maturin' from tag 'v1.14.0'
There was a problem hiding this comment.
So then I think this PR will fix things for you, there is Windows CI running on this PR and the invocation in the docs is what the integration test I added here does.
There was a problem hiding this comment.
But the windows CI logs show:
python_version=$(echo 3.15t-dev | sed -e s/-dev//)
echo "PYTHON_VERSION=$python_version" >> "${GITHUB_ENV}"where that output is
python_version=3.15t
echo PYTHON_VERSION=3.15tand then it is passed to maturin build:
cargo run build -i $PYTHON_VERSION -m test-crates/pyo3-pure/Cargo.tomlThis coincides with my observation and seems contrary to the example in question.
There was a problem hiding this comment.
Ah, I see what you're getting at now. Yes, the CI over here doesn't set allow-prereleases but I don't think that matters for the question you have? The maturin integration tests don't know about setup-python or its naming conventions.
So the place to handle the problem you're having is probably over in maturin-action. Maybe in the PR I have open over there?
Also with this PR merged you won't need to explicitly pass in abi3 and abi3t features at build time anymore. You can just enable both features, like in the test crates in this PR.
I'm going to bed now so I won't deal with subsequent replies here until tomorrow.
There was a problem hiding this comment.
Ah, I didn't think there was any pre-processing in pyo3/maturin-action's args input. I'll look at that PR too.
Also with this PR merged you won't need to explicitly pass in abi3 and abi3t features at build time anymore. You can just enable both features, like in the test crates in this PR.
The way this patch reads, I think I still need to specify each feature separately if I want a abi3 wheel and a abi3t wheel. My understanding is that it prevents building abi3t wheels for any version of python prior to 3.15t.
Anyway, I bid you well-deserved sweet dreams! Thanks again.
There was a problem hiding this comment.
The way this patch reads, I think I still need to specify each feature separately if I want a abi3 wheel and a abi3t wheel. My understanding is that it prevents building abi3t wheels for any version of python prior to 3.15t.
One last comment:
PyO3 is perfectly happy to let you enable both features.
If you enable, say abi3-py310 and abi3t-py315, the wheel you get out of maturin depends on the Python version used to build the wheel:
- 3.14 or older Gil-enabled build -> abi3 wheel targeting the Python 3.10 limited API
- 3.14t -> cp314t wheel
- 3.15 or newer, both builds -> abi3.abi3t wheel targeting the 3.15 limited API
The idea is you get a working build no matter what, with abi3t preferred and falling back to abi3 on the GIL-enabled build where abi3t can't work and a version-specific wheel on 3.14t whhere neither stable ABI is supported.
Please try it yourself by checking out this PR locally, building and installing it in your Python environment, and trying it with a test crate you come up with or one of the test crates I added in this PR.
|
Thanks for merging! Out of curiosity, when are you planning a release? I'm working on wheels for cryptography and needed to pin to maturin's git repo: pyca/cryptography#15063 |
The `&& !force_target_abi` was [introduced incorrectly by me](https://github.com/PyO3/maturin/blame/main/src/compile.rs#L898) in #3226 and hasn't yet shown up in a release. I hit this testing abi3t builds for cryptography with from-source builds of maturin: pyca/cryptography#15063. To better help Maturin's CI catch issues that are load-bearing for the cryptography project, I also added a test crate that uses CFFI like cryptography does: cooperatively with PyO3 and using PYO3_PYTHON to shell out and run a CFFI script. I manually verified that the new integration test fails on maturin's current `main` branch.
|
Feel free to send release PRs, release process is mostly automated except updating changelog currently requires manual |
* Fix stable ABI guidance to match PyO3/maturin#3226 * bump the python version used in CI
## 1.14.1 * Bump uraimo/run-on-arch-action to v3 to fix pytest job ([#3221](PyO3/maturin#3221)) * Fix platform tag logic to generate the same as cpython on AIX ([#3220](PyO3/maturin#3220)) * Bump pyo3-introspection ([#3227](PyO3/maturin#3227)) * Upgrade cargo-zigbuild & cargo-xwin ([#3228](PyO3/maturin#3228)) * Fix issues around crates enabling abi3 and abi3t features ([#3226](PyO3/maturin#3226)) * Add PEP 740 publish attestations to PyPI releases ([#3230](PyO3/maturin#3230)) * Set PYO3_PYTHON to run scripts for stable ABI builds ([#3233](PyO3/maturin#3233)) * Fix shell quoting in CI scripts ([#3231](PyO3/maturin#3231)) ## 1.14.0 * Support parent-relative pyproject metadata in sdists ([#3182](PyO3/maturin#3182)) * Update PyPI platform tag validation ([#3187](PyO3/maturin#3187)) * Maint: update setup emsdk action in generate-ci ([#3194](PyO3/maturin#3194)) * Fix: only shim bin wheels during auditwheel repair ([#3197](PyO3/maturin#3197)) * Fix: avoid editable ELF truncation from stale hardlinks ([#3199](PyO3/maturin#3199)) * Fix Pyodide Emscripten platform tags ([#3191](PyO3/maturin#3191)) * Use pax instead of GNU headers for tar ([#3203](PyO3/maturin#3203)) * Feat: add default exclude `__pycache__` and `*.pyc` files ([#3202](PyO3/maturin#3202)) * Add support for finding free-threaded interpreters for `--find-interpreters` ([#3206](PyO3/maturin#3206)) * Stubs: also generate them for mixed PyO3 projects ([#3211](PyO3/maturin#3211)) * Don't depend on CFFI on PyPy ([#3213](PyO3/maturin#3213)) * Support pyo3 abi3t features on Python3.15 and PyO3 0.29 ([#3113](PyO3/maturin#3113))
This fixes issues reported by @2bndy5 over at PyO3/maturin-action#368 (comment).
Unfortunately, I completely skipped adding tests here for crates that mix abi3 and abi3t features and introduced some pretty serious bugs.
This PR makes it structurally impossible to generate abi3.abi3t wheels targeting ABIs before Python 3.15. Stable ABI selection now happens after interpreter resolution and chooses at most one stable ABI family per build: abi3t only when a compatible CPython 3.15+ interpreter is present, otherwise abi3 when available. Interpreters that do not support the selected stable ABI family fall back to version-specific wheels, e.g. CPython 3.14t builds cp314-cp314t instead of cp314-abi3.abi3t.
Updates the docs I added for abi3t support to describe the actual behavior of Maturin. I considered making it possible to build two stable ABI wheels in the same call using a GIL-enabled interpreter but decided that required too many internal code changes to change the
stable_abimember of the PyO3 bridge struct from an Option wrapping a single value into a vec! that might contain multiple values.The bulk of the PR is new tests, including new unit tests and integration tests. The current python versions used in CI capture interesting behaviors in this test: Python 3.9 should produce a py38-abi3 wheel, Python 3.15 and 3.15t should produce an abi3t wheel.