Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 11 additions & 16 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
6 changes: 5 additions & 1 deletion docs/building_wheels.md
Original file line number Diff line number Diff line change
Expand Up @@ -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):

Expand All @@ -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.
Expand Down
7 changes: 4 additions & 3 deletions setuptools_rust/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -789,19 +789,20 @@ 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"}
else:
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:
Expand Down
56 changes: 55 additions & 1 deletion tests/test_build.py
Original file line number Diff line number Diff line change
@@ -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)

Expand Down Expand Up @@ -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()
Loading