diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index db972b25..c7602893 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -135,9 +135,10 @@ jobs: fi mkdir -p "artifacts/whl/${{ needs.pre-commit.outputs.dist-base }}" mv dist/* "artifacts/whl/${{ needs.pre-commit.outputs.dist-base }}" - # from system builds, save version-specific wheels and oldest abi3 wheel + # from system builds, save version-specific wheels, oldest abi3 wheel, + # and oldest abi3.abi3t wheel python -c 'import sys, sysconfig - if "${{ matrix.openslide }}" == "system" and (sys.version_info < (3, 12) or sysconfig.get_config_var("Py_GIL_DISABLED")): + if "${{ matrix.openslide }}" == "system" and (sys.version_info < (3, 12) or (sys.version_info < (3, 16) and sysconfig.get_config_var("Py_GIL_DISABLED"))): print("archive_wheel=1")' >> $GITHUB_ENV - name: Install run: pip install artifacts/whl/${{ needs.pre-commit.outputs.dist-base }}/*.whl @@ -210,9 +211,10 @@ jobs: python -m build -w mkdir -p "artifacts/whl/${{ needs.pre-commit.outputs.dist-base }}" mv dist/*.whl "artifacts/whl/${{ needs.pre-commit.outputs.dist-base }}" - # from zip builds, save version-specific wheels and oldest abi3 wheel + # from zip builds, save version-specific wheels, oldest abi3 wheel, + # and oldest abi3.abi3t wheel python -c 'import sys, sysconfig - if "${{ matrix.openslide }}" == "zip" and (sys.version_info < (3, 12) or sysconfig.get_config_var("Py_GIL_DISABLED")): + if "${{ matrix.openslide }}" == "zip" and (sys.version_info < (3, 12) or (sys.version_info < (3, 16) and sysconfig.get_config_var("Py_GIL_DISABLED"))): print("archive_wheel=1")' >> $GITHUB_ENV - name: Install run: pip install artifacts/whl/${{ needs.pre-commit.outputs.dist-base }}/*.whl diff --git a/openslide/_convert.c b/openslide/_convert.c index 4427c72a..8f5f1767 100644 --- a/openslide/_convert.c +++ b/openslide/_convert.c @@ -102,9 +102,44 @@ static PyMethodDef _convert_methods[] = { {NULL, NULL, 0, NULL} }; +#ifdef Py_TARGET_ABI3T +PyABIInfo_VAR(abi_info); + +static PySlot _convert_slot_array[] = { + PySlot_STATIC_DATA(Py_mod_abi, &abi_info), + PySlot_STATIC_DATA(Py_mod_name, "_convert"), + PySlot_STATIC_DATA(Py_mod_methods, _convert_methods), + PySlot_DATA(Py_mod_gil, Py_MOD_GIL_NOT_USED), + PySlot_DATA(Py_mod_multiple_interpreters, + Py_MOD_PER_INTERPRETER_GIL_SUPPORTED), + PySlot_END +}; + +PyMODEXPORT_FUNC +PyModExport__convert(void) +{ + return _convert_slot_array; +} + +#ifdef _WIN32 +// distutils insists on exporting a PyInit_ function on Windows +// https://github.com/pypa/distutils/issues/387 +// https://github.com/python/cpython/issues/158098 +PyMODINIT_FUNC +PyInit__convert(void) +{ + PyErr_SetString(PyExc_SystemError, + "PyInit_* called for module with PyModExport_*"); + return NULL; +} +#endif + +#else + static PyModuleDef_Slot _convert_slots[] = { #if PY_VERSION_HEX >= 0x030D0000 && !defined(Py_LIMITED_API) {Py_mod_gil, Py_MOD_GIL_NOT_USED}, + {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, #endif {0, NULL} }; @@ -123,3 +158,4 @@ PyInit__convert(void) { return PyModuleDef_Init(&_convert_module); } +#endif diff --git a/setup.py b/setup.py index 3a89c30d..dbdca38d 100644 --- a/setup.py +++ b/setup.py @@ -3,14 +3,24 @@ import sysconfig from setuptools import Extension, setup +from setuptools import __version__ as _setuptools_ver # Load version string with open(Path(__file__).parent / 'openslide/_version.py') as _fh: exec(_fh.read()) # instantiates __version__ # noqa: S102 -# use the Limited API on Python 3.11+ on GIL builds; build release-specific -# wheels on older or free-threaded Python -_abi3 = sys.version_info >= (3, 11) and not sysconfig.get_config_var('Py_GIL_DISABLED') +# Use the free-threaded Stable ABI (abi3t) only on free-threaded Python +# 3.15+ with setuptools 84.2+; Python supports building abi3t from +# GIL-enabled interpreters but setuptools doesn't yet. Use the +# non-free-threaded Stable API (abi3) on Python 3.11+ with GIL enabled, and +# release-specific wheels in all other cases. +_freethread = sysconfig.get_config_var('Py_GIL_DISABLED') +_abi3t = ( + sys.version_info >= (3, 15) + and _freethread + and tuple(int(c) for c in _setuptools_ver.split('.')) >= (84, 2) +) +_abi3 = sys.version_info >= (3, 11) and not _freethread setup( ext_modules=[ @@ -18,14 +28,22 @@ 'openslide._convert', ['openslide/_convert.c'], # hide symbols that aren't in the Limited API - define_macros=[('Py_LIMITED_API', '0x030b0000')] if _abi3 else [], - # tag extension module for Limited API - py_limited_api=_abi3, + define_macros=[('Py_TARGET_ABI3T', '0x030f0000')] + if _abi3t + else [('Py_LIMITED_API', '0x030b0000')] + if _abi3 + else [], + # tag extension module for Stable ABI + py_limited_api=_abi3 or _abi3t, ), ], options={ - # tag wheel for Limited API - 'bdist_wheel': {'py_limited_api': 'cp311'} if _abi3 else {}, + # tag wheel for Stable ABI + 'bdist_wheel': {'py_limited_api': 'cp315'} + if _abi3t + else {'py_limited_api': 'cp311'} + if _abi3 + else {}, }, # # setuptools < 61 compatibility for distro packages building from source