Skip to content

Commit 62b1b66

Browse files
committed
build: one abi3 wheel per platform, a wheel per free-threaded CPython
cffi compiles the module against the limited API, and abi3audit finds nothing beyond 3.10's; the wheel was nevertheless tagged for one CPython, so a platform needed a wheel per version. setup.py now tags a wheel built under the GIL cp310-abi3; cibuildwheel builds it on 3.10 and tests it on each later CPython, and the hash tables of wheels.yml run it under every one of them. Free-threaded CPython has no stable ABI and keeps a wheel per version.
1 parent 315bef8 commit 62b1b66

5 files changed

Lines changed: 46 additions & 19 deletions

File tree

‎.github/workflows/wheels.yml‎

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
name: Wheels
22

33
# Every wheel a release has, built and tested by cibuildwheel (configuration in
4-
# pyproject.toml, which also says which platforms get wheels). The code itself is
5-
# tested on every platform by ci.yml; what only a wheel build exercises is the build:
6-
# the manylinux images, the free-threaded builds, the repair of each wheel and the
7-
# cibuildwheel configuration. Beyond each wheel's test suite, every wheel writes the
8-
# hashes of every fixture, and the tables of all platforms must be the same byte for
9-
# byte; and libphash configured without libjpeg-turbo, decoding JPEG with stb_image,
10-
# passes the suite with its own golden hashes. Every wheel and the sdist pass
11-
# tools/audit.py (exports, dependencies, licenses, SBOM, contents) and twine's checks;
12-
# the sdist is built twice and must be the same bytes, and a wheel is built from it
13-
# without an index. So the workflow runs when a file the build reads changes, by hand,
14-
# and from release.yml, which publishes the artifacts it uploads.
4+
# pyproject.toml, which also says which platforms get wheels): per platform one abi3
5+
# wheel, built on the oldest CPython and tested on each later one, and a wheel per
6+
# free-threaded CPython. The code itself is tested on every platform by ci.yml; what
7+
# only a wheel build exercises is the build: the manylinux images, the free-threaded
8+
# builds, the repair of each wheel and the cibuildwheel configuration. Beyond each
9+
# wheel's test suite, every wheel writes the hashes of every fixture under each Python
10+
# it is for, and the tables of all platforms must be the same byte for byte; and
11+
# libphash configured without libjpeg-turbo, decoding JPEG with stb_image, passes the
12+
# suite with its own golden hashes. Every wheel and the sdist pass tools/audit.py
13+
# (exports, dependencies, licenses, SBOM, contents) and twine's checks, and abi3audit
14+
# holds the abi3 wheel to the limited API of 3.10; the sdist is built twice and must be
15+
# the same bytes, and a wheel is built from it without an index. So the workflow runs
16+
# when a file the build reads changes, by hand, and from release.yml, which publishes
17+
# the artifacts it uploads.
1518
on:
1619
push:
1720
branches: [ main, 'release/**' ]
@@ -79,6 +82,7 @@ jobs:
7982
shell: bash
8083
run: |
8184
uv run --no-project --with lief --with 'setuptools>=77' python tools/audit.py wheelhouse/*.whl
85+
uvx abi3audit --strict --verbose wheelhouse/*-abi3-*.whl
8286
uvx twine check --strict wheelhouse/*.whl
8387
uvx check-wheel-contents wheelhouse/*.whl
8488
@@ -88,18 +92,26 @@ jobs:
8892
path: wheelhouse/*.whl
8993
if-no-files-found: error
9094

91-
# Each wheel with the Python it is for, installed by uv on the runner itself: every
95+
# Each wheel with each Python it is for, installed by uv on the runner itself: every
9296
# wheel is for the runner's own architecture, and a manylinux wheel runs on Ubuntu.
93-
# The ABI tag names the Python (cp312 -> 3.12, cp314t -> 3.14t).
97+
# The abi3 wheel serves every CPython with the GIL from 3.10 on; a free-threaded
98+
# wheel's ABI tag names its Python (cp314t -> 3.14t).
9499
- name: Hash tables of the wheels
95100
shell: bash
96101
run: |
97102
mkdir tables
98103
for wheel in wheelhouse/*.whl; do
99104
name=$(basename "$wheel" .whl)
100105
abi=$(echo "$name" | cut -d- -f4)
101-
uv run --no-project --python "${abi:2:1}.${abi:3}" --with "$wheel" \
102-
python tools/hash_table.py native/libphash/tests/data > "tables/$name.txt"
106+
if [ "$abi" = abi3 ]; then
107+
pythons="3.10 3.11 3.12 3.13 3.14 3.15"
108+
else
109+
pythons="${abi:2:1}.${abi:3}"
110+
fi
111+
for python in $pythons; do
112+
uv run --no-project --python "$python" --with "$wheel" \
113+
python tools/hash_table.py native/libphash/tests/data > "tables/$name-$python.txt"
114+
done
103115
done
104116
105117
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1

‎CHANGELOG.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,8 @@ read [`MIGRATION.md`](https://github.com/gudoshnikovn/python-libphash/blob/main/
112112
- Releases are published with PEP 740 attestations on PyPI and build provenance on the
113113
GitHub Release (`SECURITY.md`, "Verifying a release"); the sdist is reproducible.
114114
- Wheels for Linux arm64 and Windows x86-64, and for the free-threaded builds of
115-
CPython 3.14 and later. Paths with any characters, Cyrillic, CJK and emoji included, load on Windows.
115+
CPython 3.14 and later. A wheel per platform for CPython with the GIL, tagged `abi3`:
116+
it installs on every CPython from 3.10 on, a later one included. Paths with any characters, Cyrillic, CJK and emoji included, load on Windows.
116117

117118
### Changed
118119

‎CLAUDE.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ How the extension is built:
5252
- **`MANIFEST.in`** — the sdist carries only what libphash's CMake build reads (`CMakeLists.txt`, `cmake/`, `include/`, `src/`, `vendor/`, licenses), enough to build offline; not the tests, which need libphash's fixtures.
5353
- **Licenses** — `license` in `pyproject.toml` is the SPDX expression of everything the wheel links, and `license-files` carries each library's license into the wheel.
5454

55-
Wheels are built for the platforms libphash's own CI supports: Linux x86-64 and arm64 (manylinux), macOS arm64, Windows x86-64 (`[tool.cibuildwheel]` in `pyproject.toml`). Elsewhere pip builds the sdist; `tools/native.py` still honours `ARCHFLAGS` on macOS and the Python's architecture on Windows. Platforms are verified in CI on native runners, never under emulation.
55+
Wheels are built for the platforms libphash's own CI supports: Linux x86-64 and arm64 (manylinux), macOS arm64, Windows x86-64 (`[tool.cibuildwheel]` in `pyproject.toml`). The module uses only the limited API (cffi), so `setup.py` tags a wheel built under the GIL `cp310-abi3`: cibuildwheel builds it on 3.10 and tests it on each later CPython; each free-threaded CPython, which has no stable ABI, gets its own wheel. Elsewhere pip builds the sdist; `tools/native.py` still honours `ARCHFLAGS` on macOS and the Python's architecture on Windows. Platforms are verified in CI on native runners, never under emulation.
5656

5757
## Architecture
5858

‎docs/guide/install.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ Wheels exist for the platforms libphash's own CI builds and tests:
1515
| macOS arm64 (11 or later) | `macosx_11_0_arm64` |
1616
| Windows x86-64 | `win_amd64` |
1717

18-
for every CPython from 3.10 on, and for the free-threaded builds from 3.14t on. Each
18+
for every CPython from 3.10 on, and for the free-threaded builds from 3.14t on: one
19+
`abi3` wheel per platform serves every CPython with the GIL, a later release of Python
20+
included, and each free-threaded version has a wheel of its own. Each
1921
wheel links libphash and its decoders statically (libjpeg-turbo, libpng, libwebp,
2022
zlib-ng, stb_image), and needs only the system's C library: no system libpng or libjpeg
2123
is used, and the decoders inside cannot clash with another copy of the same libraries in

‎setup.py‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Build the CFFI extension, with libphash built by its own CMake project first."""
22

33
import sys
4+
import sysconfig
45
from pathlib import Path
56

67
from setuptools import setup
@@ -35,7 +36,18 @@ def run(self) -> None:
3536

3637

3738
class BdistWheel(bdist_wheel):
38-
"""Add the SBOM of the statically linked libraries to the wheel (PEP 770)."""
39+
"""Tag the wheel abi3 and add the SBOM of the linked libraries to it (PEP 770).
40+
41+
cffi compiles the module against the limited API wherever the interpreter has one,
42+
so one wheel built on 3.10 serves every later CPython with the GIL. A free-threaded
43+
interpreter has no stable ABI: its wheel keeps the tag of its version (cp314t).
44+
"""
45+
46+
def finalize_options(self) -> None:
47+
"""Ask for the abi3 tag, from the oldest supported Python, under the GIL."""
48+
if not sysconfig.get_config_var("Py_GIL_DISABLED"):
49+
self.py_limited_api = "cp310"
50+
super().finalize_options()
3951

4052
def egg2dist(self, egginfo_path: str, distinfo_path: str) -> None:
4153
"""Write the .dist-info directory, then its sboms/ entry."""

0 commit comments

Comments
 (0)