diff --git a/.github/workflows/CI-wheels.yaml b/.github/workflows/CI-wheels.yaml index c89bc1f2..0609c507 100644 --- a/.github/workflows/CI-wheels.yaml +++ b/.github/workflows/CI-wheels.yaml @@ -18,6 +18,7 @@ jobs: name: Build wheels on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: + fail-fast: false matrix: os: [macos-26, windows-2025, ubuntu-26.04] diff --git a/CHANGELOG.md b/CHANGELOG.md index d9621761..ddd1713a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add a CI job building the documentation on every PR/push so this kind of breakage is caught automatically (#279) - Add support for Python 3.15 (#281) +- Build wheels against the stable ABI (abi3) so a single wheel per platform covers all supported CPython versions (#263) ## [3.13.1] - 2026-10-05 diff --git a/setup.py b/setup.py index 04c7080b..3198a0aa 100755 --- a/setup.py +++ b/setup.py @@ -383,6 +383,11 @@ def can_sign_apple(self) -> bool: ] ) + @property + def can_use_limited_api(self) -> bool: + """whether runtime can use ABIv3 (free-threaded cannot)""" + return not self.profiling and not sysconfig.get_config_var("Py_GIL_DISABLED") + config = Config() @@ -403,6 +408,8 @@ def get_cython_extension() -> list[Extension]: ("CYTHON_USE_SYS_MONITORING", "0"), ] compiler_directives.update(linetrace="true") + elif config.can_use_limited_api: + define_macros += [("Py_LIMITED_API", "0x030B0000")] include_dirs: list[str] = [] library_dirs: list[str] = [] @@ -458,11 +465,30 @@ def get_cython_extension() -> list[Extension]: extra_compile_args=extra_compile_args, language="c++", define_macros=define_macros, + py_limited_api=config.can_use_limited_api, ) return cythonize([wrapper_extension], compiler_directives=compiler_directives) class LibzimBuildExt(build_ext): + + def get_ext_filename(self, ext_name): + # set proper filename for abi3 usage on all Python versions + filename = super().get_ext_filename(ext_name) + + if not config.can_use_limited_api: + return filename + + if config.platform == "Windows": + ext_path = Path(*ext_name.split(".")) + return str(ext_path.with_suffix(".pyd")) + else: + ext_suffix = sysconfig.get_config_var("EXT_SUFFIX") + filename = ( + filename[: -len(ext_suffix)] + ".abi3" + os.path.splitext(ext_suffix)[1] + ) + return filename + def finalize_options(self): """Workaround for rpath bug in distutils for macOS""" super().finalize_options() @@ -637,4 +663,9 @@ def run(self): "repair_win_wheel": RepairWindowsWheel, }, ext_modules=ext_modules, + options=( + {"bdist_wheel": {"py_limited_api": "cp311"}} + if config.can_use_limited_api + else {} + ), )