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')
0 commit comments