The goal is to compile scikit-image extension modules targeting Python's stable ABI (PEP 384, aka "abi3"), so that a single compiled wheel works across Python 3.12+ without per-version recompilation. This is done via Meson's limited_api keyword argument on py3.extension_module() calls, which:
- Defines
Py_LIMITED_API=0x030C0000and injectsCYTHON_LIMITED_APIfor Cython sources - Names resulting shared libraries
module.abi3.so(Linux/macOS) instead ofmodule.cpython-3XX-...so
Prerequisites already met:
- Meson
>= 1.5.0required (limited_api available since 1.3.0) - meson-python
>= 0.16required (abi3 support since 0.14.0) - NumPy
>= 2.1required (stable ABI support added in NumPy 2.0)
Known limitation: Pythran-generated modules (brief_cy, _hessian_det_appx) cannot use limited_api and must be excluded. They will remain versioned .so files.
Key risk: cimport numpy with CYTHON_LIMITED_API — requires Cython ≥ 3.1.0 (currently min is 3.0.10) and NumPy 2.0+. Both are satisfied here.
Create /Users/stevensilvester/workspace/scikit-image/meson.options:
option('limited_api',
type: 'boolean',
value: true,
description: 'Build extension modules targeting Python\'s limited/stable ABI (abi3)',
)Default true on this branch; can be set to false to compare against the standard build.
After the Cython version check for freethreading (if cy.version().version_compare('>=3.1.0')), add:
# Limited/stable ABI (abi3) support
if get_option('limited_api')
if not cy.version().version_compare('>=3.1.0')
error('Building with limited_api requires Cython >= 3.1.0, found ' + cy.version())
endif
_limited_api = '3.12'
else
_limited_api = ''
endifThe _limited_api variable is available in all subdir() children. When '', Meson treats it as "no limited API" (same as today's behavior).
The existing np_dep / numpy_nodepr_api (-DNPY_NO_DEPRECATED_API=NPY_1_24_API_VERSION) stays unchanged — NumPy's own API versioning is orthogonal to Python's limited API.
Every py3.extension_module() call that uses cython_gen or cython_gen_cpp gets one new line. Pythran modules are excluded (they use the full C API).
Files to modify and their modules:
| File | Modules | Change |
|---|---|---|
src/skimage/draw/meson.build |
_draw |
add limited_api: _limited_api |
src/skimage/filters/meson.build |
_multiotsu |
add limited_api: _limited_api |
src/skimage/filters/rank/meson.build |
bilateral_cy, core_cy, core_cy_3d, generic_cy, percentile_cy |
add limited_api: _limited_api |
src/skimage/graph/meson.build |
_mcp, _ncut_cy, _spath, heap |
add limited_api: _limited_api |
src/skimage/measure/meson.build |
_ccomp, _find_contours_cy, _marching_cubes_lewiner_cy, _moments_cy, _pnpoly |
add limited_api: _limited_api |
src/skimage/morphology/meson.build |
foreach loop (7 C) + _skeletonize_lee_cy (C++) |
add limited_api: _limited_api to both calls |
src/skimage/restoration/meson.build |
_denoise_cy, _inpaint, _nl_means_denoising, _rolling_ball_cy, _unwrap_1d/2d/3d |
add limited_api: _limited_api |
src/skimage/segmentation/meson.build |
_felzenszwalb_cy, _quickshift_cy, _slic, _watershed_cy |
add limited_api: _limited_api |
src/skimage/transform/meson.build |
_hough_transform, _radon_transform, _warps_cy |
add limited_api: _limited_api |
src/skimage/util/meson.build |
_remap |
add limited_api: _limited_api |
src/skimage/feature/meson.build |
Cython modules only: corner_cy, censure_cy, orb_cy, _texture, _hoghistogram, _sift, _cascade, _haar |
add limited_api: _limited_api; leave brief_cy and _hessian_det_appx unchanged |
src/_skimage2/_shared/meson.build |
transform, fast_exp, geometry, interpolation |
add limited_api: _limited_api to each call |
src/_skimage2/feature/meson.build |
_canny_cy |
add limited_api: _limited_api |
For foreach-loop patterns, add limited_api: _limited_api as a new line inside the py3.extension_module() call body.
-
Bump Cython minimum (needed for limited API Cython support):
'Cython>=3.0.10,!=3.2.0b1' → 'Cython>=3.1.0'Update in both
[project.optional-dependencies]build section AND[build-system]requires. -
cibuildwheel: With abi3, free-threaded builds (
cp3??t-*) do NOT support limited API (meson-python refuses). Since those overrides exist already, no change is needed — free-threaded builds will fail unless we handle the meson option. Consider adding a cibuildwheel override to disable limited_api for free-threaded:[[tool.cibuildwheel.overrides]] select = "cp3??t-*" config-settings."setup-args=-Dlimited_api" = "false"
meson.options(create new)src/meson.build(lines 115–142 — after cython generators)- All 13 module-level
meson.buildfiles listed above pyproject.toml(line 51 Cython dep)
# Build with limited API enabled (default on this branch)
spin build
# Check that .so files have abi3 suffix
find build-install/ -name "*.so" | head -20
# Expected: module.abi3.so (not module.cpython-312-darwin.so)
# Build without limited API (regression check)
spin build -- -Dlimited_api=false
find build-install/ -name "*.so" | head -5
# Expected: module.cpython-312-darwin.so (original naming)
# Run tests
spin test
# Check Pythran modules are NOT abi3
find build-install/ -name "brief_cy*" -o -name "_hessian_det_appx*"
# Expected: versioned .so (brief_cy.cpython-312-darwin.so), NOT abi3