Skip to content

Instantly share code, notes, and snippets.

@blink1073
Created May 19, 2026 12:29
Show Gist options
  • Select an option

  • Save blink1073/4607587a9652c1e1695c8e0f79c2d611 to your computer and use it in GitHub Desktop.

Select an option

Save blink1073/4607587a9652c1e1695c8e0f79c2d611 to your computer and use it in GitHub Desktop.
scikit-image limited ABI (abi3) implementation plan

Plan: Python Limited ABI (abi3) Support for scikit-image

Context

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=0x030C0000 and injects CYTHON_LIMITED_API for Cython sources
  • Names resulting shared libraries module.abi3.so (Linux/macOS) instead of module.cpython-3XX-...so

Prerequisites already met:

  • Meson >= 1.5.0 required (limited_api available since 1.3.0)
  • meson-python >= 0.16 required (abi3 support since 0.14.0)
  • NumPy >= 2.1 required (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.

Implementation Plan

Step 1 — Add meson.options with a limited_api option

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.

Step 2 — Update src/meson.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 = ''
endif

The _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.

Step 3 — Add limited_api: _limited_api to all Cython extension_module calls

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.

Step 4 — Update pyproject.toml

  1. 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.

  2. 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"

Critical Files

  • meson.options (create new)
  • src/meson.build (lines 115–142 — after cython generators)
  • All 13 module-level meson.build files listed above
  • pyproject.toml (line 51 Cython dep)

Verification

# 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment