Skip to content

Fix issues around crates enabling abi3 and abi3t features#3226

Merged
messense merged 4 commits into
PyO3:mainfrom
ngoldbaum:fix-mixed-abi3-abi3t
Jun 17, 2026
Merged

Fix issues around crates enabling abi3 and abi3t features#3226
messense merged 4 commits into
PyO3:mainfrom
ngoldbaum:fix-mixed-abi3-abi3t

Conversation

@ngoldbaum

Copy link
Copy Markdown
Contributor

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_abi member 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.

@messense

Copy link
Copy Markdown
Member

PyPy tests are failing.

@ngoldbaum

Copy link
Copy Markdown
Contributor Author

Looks like pypy is fixed in the latest commit.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread guide/src/bindings.md Outdated
Comment thread guide/src/bindings.md Outdated
Comment thread guide/src/bindings.md
Comment on lines +30 to +31
maturin build --interpreter python3.10
maturin build --interpreter python3.15t

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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').

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you have those problems using the version of Maturin from this PR or the released version?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked the CI logs...

Installing 'maturin' from tag 'v1.14.0'

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@2bndy5 2bndy5 Jun 17, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.15t

and then it is passed to maturin build:

cargo run build -i $PYTHON_VERSION -m test-crates/pyo3-pure/Cargo.toml

This coincides with my observation and seems contrary to the example in question.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

PyO3/maturin-action#453

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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@messense messense merged commit 49defa4 into PyO3:main Jun 17, 2026
45 checks passed
@ngoldbaum

Copy link
Copy Markdown
Contributor Author

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

messense pushed a commit that referenced this pull request Jun 19, 2026
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.
@messense

Copy link
Copy Markdown
Member

Feel free to send release PRs, release process is mostly automated except updating changelog currently requires manual git cliff.

messense pushed a commit to PyO3/maturin-action that referenced this pull request Jun 19, 2026
* Fix stable ABI guidance to match PyO3/maturin#3226

* bump the python version used in CI
netbsd-srcmastr pushed a commit to NetBSD/pkgsrc that referenced this pull request Jun 23, 2026
## 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))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants