Skip to content

Commit d28e022

Browse files
Merge pull request #697 from Blosc/abi3
Support for abi3 and forthcoming abi3t
2 parents 5cc0c03 + 3c39ac4 commit d28e022

3 files changed

Lines changed: 139 additions & 11 deletions

File tree

.github/workflows/cibuildwheels.yml

Lines changed: 103 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ env:
2121
# x86_64 wheels yet, so `pip install` of the test venv would cross-compile
2222
# them from the arm64 runner -- which lacks the x86_64-apple-darwin Rust
2323
# target. The wheel itself builds fine, so ship it and skip only its test.
24-
CIBW_TEST_SKIP: "cp315-macosx_x86_64"
24+
# (`cp315*` so the free-threaded cp315t build is skipped for the same reason)
25+
CIBW_TEST_SKIP: "cp315*-macosx_x86_64"
2526
# Use explicit generator/compiler env vars; CMAKE_ARGS with spaces is not split on Windows.
2627
CIBW_ENVIRONMENT_WINDOWS: >-
2728
CMAKE_GENERATOR=Ninja
@@ -42,31 +43,50 @@ jobs:
4243
strategy:
4344
fail-fast: false
4445
matrix:
46+
# Three builds per platform, not one per Python version:
47+
#
48+
# cp311 -> a single `cp311-abi3` wheel serving CPython 3.11+, via
49+
# `wheel.py-api` in pyproject.toml. Building it on any
50+
# other version would emit the *same* filename, so only
51+
# the floor version is built.
52+
# cp314t -> free-threaded 3.14. Cannot use abi3: the free-threaded
53+
# cp315t ABI is distinct, and its stable variant (abi3t, PEP 803)
54+
# starts at 3.15 and needs a PEP 793 module export hook
55+
# that Cython does not emit yet. These stay
56+
# version-specific until that lands upstream.
57+
#
58+
# cp313t is deliberately absent: 3.13's free-threaded build was still
59+
# experimental (PEP 703 phase I) with a large single-thread penalty.
60+
# cibuildwheel keeps it behind CIBW_ENABLE: cpython-freethreading.
61+
#
62+
# NOTE: cibuildwheel only tests a wheel on the interpreter that built
63+
# it, so the abi3 wheel is exercised on 3.11 alone here. The
64+
# test_abi3_matrix job below installs it on every supported version.
4565
include:
4666
# Linux x86_64 builds
4767
- os: ubuntu-latest
4868
arch: x86_64
49-
cibw_pattern: "cp3{11,12,13,14,15}-manylinux*"
69+
cibw_pattern: "cp311-manylinux* cp314t-manylinux* cp315t-manylinux*"
5070
artifact_name: "linux-x86_64"
5171

5272
# Linux ARM64 builds (native runners)
5373
- os: ubuntu-24.04-arm
5474
arch: aarch64
55-
cibw_pattern: "cp3{11,12,13,14,15}-manylinux*"
75+
cibw_pattern: "cp311-manylinux* cp314t-manylinux* cp315t-manylinux*"
5676
artifact_name: "linux-aarch64"
5777
# Don't use native runners for now (looks like wait times are too long)
5878
#runs-on: ["ubuntu-latest", "arm64"]
5979

6080
# Windows builds
6181
- os: windows-latest
6282
arch: x86_64
63-
cibw_pattern: "cp3{11,12,13,14,15}-win_amd64"
83+
cibw_pattern: "cp311-win_amd64 cp314t-win_amd64 cp315t-win_amd64"
6484
artifact_name: "windows-x86_64"
6585

6686
# macOS builds (universal2)
6787
- os: macos-latest
6888
arch: x86_64
69-
cibw_pattern: "cp3{11,12,13,14,15}-macosx*"
89+
cibw_pattern: "cp311-macosx* cp314t-macosx* cp315t-macosx*"
7090
artifact_name: "macos-universal2"
7191
steps:
7292
- name: Checkout repo
@@ -187,8 +207,85 @@ jobs:
187207
path: ./wheelhouse/*.whl
188208

189209

210+
# The abi3 wheel is built once, on 3.11, and cibuildwheel only tests a wheel
211+
# on the interpreter that produced it. Every other supported version would
212+
# therefore ship untested, so install the one wheel on each of them and run a
213+
# slice of the suite. This is also the job that would catch a CPython release
214+
# breaking the stable ABI -- the failure mode abi3 trades away rebuilds for.
215+
test_abi3_matrix:
216+
name: Test abi3 wheel on ${{ matrix.python-version }} (${{ matrix.os }})
217+
needs: [ build_wheels ]
218+
runs-on: ${{ matrix.os }}
219+
strategy:
220+
fail-fast: false
221+
matrix:
222+
# Both are real matrix dimensions so they cross-product (15 short jobs).
223+
# The `include` entries key on `os`, which is an existing dimension, so
224+
# they attach artifact_name to the matching combinations rather than
225+
# being appended as extra jobs.
226+
os: [ubuntu-latest, windows-latest, macos-latest]
227+
python-version: ["3.11", "3.12", "3.13", "3.14", "3.15"]
228+
include:
229+
- os: ubuntu-latest
230+
artifact_name: "linux-x86_64"
231+
- os: windows-latest
232+
artifact_name: "windows-x86_64"
233+
- os: macos-latest
234+
artifact_name: "macos-universal2"
235+
steps:
236+
- uses: actions/checkout@v7
237+
238+
- uses: actions/setup-python@v7
239+
with:
240+
python-version: ${{ matrix.python-version }}
241+
# 3.15 is still a pre-release until October
242+
allow-prereleases: true
243+
244+
- uses: actions/download-artifact@v8
245+
with:
246+
name: ${{ matrix.artifact_name }}
247+
path: ./wheelhouse
248+
249+
# Pin the exact version so pip resolves blosc2 from ./wheelhouse (picking
250+
# the arch-compatible file) while still pulling dependencies from PyPI.
251+
- name: Install the abi3 wheel
252+
shell: bash
253+
run: |
254+
WHL=$(ls wheelhouse/blosc2-*abi3*.whl | head -1)
255+
echo "Installing $WHL on $(python -V)"
256+
python -m pip install --upgrade pip
257+
python -m pip install --find-links ./wheelhouse "blosc2==$(basename "$WHL" | cut -d- -f2)"
258+
259+
# Check that the module is not version-tagged, rather than looking for a
260+
# literal ".abi3." -- that infix is a POSIX convention. On Windows a
261+
# limited-API module is just `blosc2_ext.pyd`, versus
262+
# `blosc2_ext.cp314-win_amd64.pyd` for a version-specific build.
263+
# EXTENSION_SUFFIXES[0] is the version-tagged suffix on every platform,
264+
# so "does not end with it" is the portable way to say "this is abi3".
265+
- name: Confirm the abi3 module was the one loaded
266+
shell: bash
267+
run: |
268+
python -c "
269+
import importlib.machinery as machinery, os
270+
import blosc2, blosc2.blosc2_ext as ext
271+
name = os.path.basename(ext.__file__)
272+
tagged = machinery.EXTENSION_SUFFIXES[0]
273+
print('module:', name, '| version-tagged suffix:', tagged)
274+
assert not name.endswith(tagged), f'version-specific module: {name}'
275+
print('OK: loaded a version-independent (abi3) module')
276+
blosc2.print_versions()
277+
"
278+
279+
- name: Run a slice of the test suite
280+
shell: bash
281+
run: |
282+
python -m pip install pytest
283+
python -m pytest tests/test_open.py tests/test_vlmeta.py \
284+
tests/ndarray/test_evaluate.py -q -p no:cacheprovider
285+
286+
190287
upload_pypi:
191-
needs: [ build_wheels, build_wheels_wasm ]
288+
needs: [ build_wheels, build_wheels_wasm, test_abi3_matrix ]
192289
runs-on: ubuntu-latest
193290
# Only upload wheels when tagging (typically a release)
194291
if: startsWith(github.event.ref, 'refs/tags')

CMakeLists.txt

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,27 @@ endif()
2727
# does not include a python symbolic link to python3.
2828
# find_package(Python 3.12 COMPONENTS Interpreter NumPy Development.Module REQUIRED)
2929
# IMO, this would need to be solved in Fedora, so we can just use the following line:
30-
find_package(Python COMPONENTS Interpreter NumPy Development.Module REQUIRED)
30+
find_package(Python COMPONENTS Interpreter NumPy Development.Module
31+
${SKBUILD_SABI_COMPONENT} REQUIRED)
32+
33+
# abi3 (Stable ABI) support. scikit-build-core sets SKBUILD_SABI_COMPONENT to
34+
# "Development.SABIModule" and SKBUILD_SABI_VERSION to e.g. "3.11" when
35+
# `wheel.py-api` requests a limited-API build; both are empty otherwise, so a
36+
# plain CMake invocation keeps the version-specific behaviour. Passing
37+
# USE_SABI to Python_add_library defines Py_LIMITED_API for the target (which
38+
# is what flips Cython into CYTHON_LIMITED_API mode) and, together with
39+
# WITH_SOABI, names the module `<name>.abi3.so` instead of
40+
# `<name>.cpython-3XY-<plat>.so`.
41+
#
42+
# Note scikit-build-core silently ignores py-api on free-threaded
43+
# interpreters, so cp3XXt builds fall back to version-specific wheels on their
44+
# own -- abi3t (PEP 803) is a separate ABI that Cython cannot emit yet.
45+
if(SKBUILD_SABI_VERSION)
46+
set(B2_SABI USE_SABI ${SKBUILD_SABI_VERSION})
47+
message(STATUS "Building limited-API (abi3) modules for Python ${SKBUILD_SABI_VERSION}+")
48+
else()
49+
set(B2_SABI "")
50+
endif()
3151

3252
# Add custom command to generate the version file
3353
add_custom_command(
@@ -68,15 +88,15 @@ add_custom_command(
6888
VERBATIM)
6989

7090
# ...and add it to the target
71-
Python_add_library(blosc2_ext MODULE blosc2_ext.c WITH_SOABI)
91+
Python_add_library(blosc2_ext MODULE ${B2_SABI} WITH_SOABI blosc2_ext.c)
7292
target_sources(blosc2_ext PRIVATE src/blosc2/matmul_kernels.c)
7393
target_include_directories(blosc2_ext PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/blosc2)
7494
if(UNIX)
7595
target_link_libraries(blosc2_ext PRIVATE ${CMAKE_DL_LIBS})
7696
endif()
77-
Python_add_library(indexing_ext MODULE indexing_ext.c WITH_SOABI)
78-
Python_add_library(groupby_ext MODULE groupby_ext.c WITH_SOABI)
79-
Python_add_library(utf8_ext MODULE utf8_ext.c WITH_SOABI)
97+
Python_add_library(indexing_ext MODULE ${B2_SABI} WITH_SOABI indexing_ext.c)
98+
Python_add_library(groupby_ext MODULE ${B2_SABI} WITH_SOABI groupby_ext.c)
99+
Python_add_library(utf8_ext MODULE ${B2_SABI} WITH_SOABI utf8_ext.c)
80100
# NpyString_pack() and friends are part of NumPy's 2.0 C API; opt in
81101
# explicitly since numpy/*.h otherwise targets an older API version by
82102
# default for source compatibility.

pyproject.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,17 @@ test-command = "python -c \"import blosc2; blosc2.print_versions()\""
119119
manylinux-x86_64-image = "manylinux_2_28"
120120
manylinux-aarch64-image = "manylinux_2_28"
121121

122+
[tool.scikit-build]
123+
# Build a single Stable ABI (abi3) wheel covering CPython 3.11+ instead of one
124+
# wheel per minor version. CMakeLists.txt turns the SKBUILD_SABI_* variables
125+
# this produces into USE_SABI on each extension target.
126+
#
127+
# On free-threaded interpreters scikit-build-core ignores this and falls back to
128+
# a version-specific wheel, because abi3t (PEP 803) is a distinct ABI that
129+
# starts at 3.15 and that Cython cannot emit yet. So cp314t/cp315t keep
130+
# building as normal cp3XXt wheels with no extra configuration.
131+
wheel.py-api = "cp311"
132+
122133
[tool.scikit-build.sdist]
123134
exclude = ["bench*", ".github*"]
124135

0 commit comments

Comments
 (0)