From af8ffe4bd05dda681086be5d3f5314418236ff32 Mon Sep 17 00:00:00 2001 From: James Hilliard Date: Wed, 21 Oct 2026 12:03:14 -0600 Subject: [PATCH 1/2] Fix ABI3T feature selection for free-threaded Python --- CHANGELOG.md | 3 +++ docs/building_wheels.md | 6 ++++- setuptools_rust/build.py | 7 ++--- tests/test_build.py | 56 +++++++++++++++++++++++++++++++++++++++- 4 files changed, 67 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 901972e6..743c2354 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # Changelog ## Unreleased +### Fixed +- Select PyO3's ABI3T features for limited-API builds targeting free-threaded Python. + ### Packaging - Drop support for Python 3.9. - Bump `setuptools` minimum version to 83.0. diff --git a/docs/building_wheels.md b/docs/building_wheels.md index dbe702b3..7fe2e4c9 100644 --- a/docs/building_wheels.md +++ b/docs/building_wheels.md @@ -8,7 +8,7 @@ A key choice to make is whether to upload [PEP 384](https://www.python.org/dev/p This chapter covers each of these options below. -## Building for ABI3 +## Building for ABI3 and ABI3T `setuptools-rust` will automatically configure for the limited API when this is set in the `[bdist_wheel]` configuration section of [`setup.cfg`](https://setuptools.pypa.io/en/latest/deprecated/distutils/configfile.html#writing-the-setup-configuration-file): @@ -17,6 +17,10 @@ This chapter covers each of these options below. py_limited_api=cp37 # replace with desired minimum Python version ``` +When targeting free-threaded Python 3.15 or newer, this selects PyO3's ABI3T +features instead of ABI3. Set `py_limited_api=cp315` or newer for these builds, +as PyO3's ABI3T support requires Python 3.15 or newer. + If using a `pyproject.toml`-based build, then save the above in a file and use the `DIST_EXTRA_CONFIG` environment variable to instruct `setuptools` to pick up this extra configuration. (`DIST_EXTRA_CONFIG` is documented [on this page](https://setuptools.pypa.io/en/latest/deprecated/distutils/configfile.html#writing-the-setup-configuration-file) of the `setuptools` docs.) It is also possible to pass this setting via the command line, e.g. diff --git a/setuptools_rust/build.py b/setuptools_rust/build.py index 0cbf64ca..784685e7 100644 --- a/setuptools_rust/build.py +++ b/setuptools_rust/build.py @@ -789,11 +789,12 @@ def _binding_features( elif ext.binding is Binding.PyO3: features = {"pyo3/extension-module"} if ext.py_limited_api == "auto": + abi = "abi3t" if get_config_var("Py_GIL_DISABLED") else "abi3" if isinstance(py_limited_api, str): python_version = py_limited_api[2:] - features.add(f"pyo3/abi3-py{python_version}") + features.add(f"pyo3/{abi}-py{python_version}") elif py_limited_api: - features.add("pyo3/abi3") + features.add(f"pyo3/{abi}") return features elif ext.binding is Binding.RustCPython: return {"cpython/python3-sys", "cpython/extension-module"} @@ -801,7 +802,7 @@ def _binding_features( raise PlatformError(f"unknown Rust binding: '{ext.binding}'") -_PyLimitedApi = Literal["cp37", "cp38", "cp39", "cp310", "cp311", "cp312", True, False] +_PyLimitedApi = str | bool def _override_cargo_default_target(plat_name: str, env: Env) -> str | _Platform: diff --git a/tests/test_build.py b/tests/test_build.py index 49d831b1..59eb215f 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -1,7 +1,10 @@ from unittest import mock +import pytest + from setuptools_rust._utils import Env -from setuptools_rust.build import _override_cargo_default_target +from setuptools_rust.build import _binding_features, _override_cargo_default_target +from setuptools_rust.extension import Binding, RustExtension NO_ENV = Env(None) @@ -35,3 +38,54 @@ def test_adjusted_local_rust_target_macos(): assert ( _override_cargo_default_target("macosx-", NO_ENV) == "x86_64-apple-darwin" ) + + +@pytest.mark.parametrize( + "gil_disabled,py_limited_api,feature", + [ + (None, "cp315", "abi3-py315"), + (0, False, None), + (0, True, "abi3"), + (0, "cp39", "abi3-py39"), + (0, "cp315", "abi3-py315"), + (1, False, None), + (1, True, "abi3t"), + (1, "cp315", "abi3t-py315"), + (1, "cp316", "abi3t-py316"), + ], +) +def test_binding_features_target_abi(gil_disabled, py_limited_api, feature): + ext = RustExtension("test") + with mock.patch( + "setuptools_rust.build.get_config_var", return_value=gil_disabled + ) as get_config_var: + features = _binding_features(ext, py_limited_api) + get_config_var.assert_called_once_with("Py_GIL_DISABLED") + expected = {"pyo3/extension-module"} + if feature is not None: + expected.add(f"pyo3/{feature}") + assert features == expected + + +@pytest.mark.parametrize("py_limited_api", [True, False]) +def test_binding_features_explicit_limited_api(py_limited_api): + with pytest.warns(DeprecationWarning, match="RustExtension.py_limited_api"): + ext = RustExtension("test", py_limited_api=py_limited_api) + with mock.patch("setuptools_rust.build.get_config_var") as get_config_var: + assert _binding_features(ext, "cp315") == {"pyo3/extension-module"} + get_config_var.assert_not_called() + + +@pytest.mark.parametrize( + "binding,expected", + [ + (Binding.NoBinding, set()), + (Binding.Exec, set()), + (Binding.RustCPython, {"cpython/python3-sys", "cpython/extension-module"}), + ], +) +def test_binding_features_other_bindings(binding, expected): + ext = RustExtension("test", binding=binding) + with mock.patch("setuptools_rust.build.get_config_var") as get_config_var: + assert _binding_features(ext, "cp315") == expected + get_config_var.assert_not_called() From 36361cfe6a13b84c5a1660c5bd1b2eb7a6f04a2d Mon Sep 17 00:00:00 2001 From: James Hilliard Date: Wed, 21 Oct 2026 12:24:51 -0600 Subject: [PATCH 2/2] Fix CI interpreter and container setup Build the ABI3 example on Python 3.11 and test it on the latest Python. Building on 3.15 produces platform-qualified suffixes that Python 3.11 cannot import. Use the stdlib venv backend for GraalPy on Windows, where virtualenv fails to import IO_REPARSE_TAG_MOUNT_POINT from stat. Test the Zig-built wheel in the Python 3.10 container using the existing QEMU setup action. This avoids installing Ubuntu packages under emulation, where the libc-bin post-installation step crashes. --- .github/workflows/ci.yml | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 935e109e..46d4b251 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -134,6 +134,7 @@ jobs: env: SETUPTOOLS_RUST_CARGO_PROFILE: dev + NOX_DEFAULT_VENV_BACKEND: ${{ matrix.platform.os == 'windows-latest' && startsWith(matrix.python-version, 'graalpy') && 'venv' || 'virtualenv' }} steps: - uses: actions/checkout@v7 @@ -207,7 +208,7 @@ jobs: - name: Setup python uses: actions/setup-python@v7 with: - python-version: "3.x" + python-version: "3.11" - uses: dtolnay/rust-toolchain@stable @@ -232,7 +233,7 @@ jobs: - name: Setup python uses: actions/setup-python@v7 with: - python-version: "3.11" + python-version: "3.x" - name: Install abi3 wheel and run tests shell: bash @@ -293,7 +294,7 @@ jobs: - name: Setup python uses: actions/setup-python@v7 with: - python-version: "3.10" # must match the ubuntu version in the install, and also the version copied out of the docker image below + python-version: "3.10" # must match the build and test containers below - uses: dtolnay/rust-toolchain@stable with: targets: aarch64-unknown-linux-gnu @@ -318,22 +319,16 @@ jobs: python -m build --no-isolation ls -la dist/ unzip -l dist/*.whl # debug all files inside wheel file - - uses: uraimo/run-on-arch-action@v3.2.1 - name: Install built wheel - with: - arch: aarch64 - distro: ubuntu22.04 - dockerRunArgs: | - --volume "${PWD}/examples/namespace_package:/io" - install: | - apt-get update - apt-get install -y --no-install-recommends python3 python3-venv python3-pip - run: | - python3 -m venv .venv - source .venv/bin/activate + - uses: docker/setup-qemu-action@v4 + - name: Install built wheel + run: | + docker run --rm --platform linux/arm64 \ + -v "$PWD/examples/namespace_package:/io" \ + python:3.10 bash -euxc ' pip install namespace_package --no-index --find-links /io/dist/ --force-reinstall python -c "from namespace_package import rust; assert rust.rust_func() == 14" python -c "from namespace_package import python; assert python.python_func() == 15" + ' test-cibuildwheel: runs-on: macos-latest