diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index 999fdbc3..a9481b91 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -46,16 +46,21 @@ jobs: if: ${{ github.event_name }} == pull_request shell: bash # - On PPs, omit musllinux for speed - # - On PRs, run just oldest and newest Python versions + # - On PRs, run just oldest and newest Python versions (3.11 is the oldest abi3 target) run: | - CIBW_SKIP="cp310-win_arm64 cp311-* cp312-* cp313-* *musllinux*" + CIBW_SKIP="cp310-win_arm64 *musllinux*" echo "CIBW_SKIP=$CIBW_SKIP" >> $GITHUB_ENV echo "Setting CIBW_SKIP=$CIBW_SKIP" + CIBW_TEST_SKIP="cp312-* cp313-*" + echo "CIBW_TEST_SKIP=$CIBW_TEST_SKIP" >> $GITHUB_ENV + echo "Setting CIBW_TEST_SKIP=$CIBW_TEST_SKIP" + - name: "Building ${{ matrix.os }} (${{ matrix.arch }}) wheels" uses: pypa/cibuildwheel@v3.3.1 env: CIBW_SKIP: ${{ env.CIBW_SKIP }} + CIBW_TEST_SKIP: ${{ env.CIBW_TEST_SKIP }} CIBW_ARCHS: ${{ matrix.arch }} - uses: actions/upload-artifact@v6 diff --git a/pyproject.toml b/pyproject.toml index ae720e47..1f452b64 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -127,3 +127,4 @@ test-command = [ ] manylinux-x86_64-image = "manylinux2014" manylinux-aarch64-image = "manylinux2014" +environment = { CFTIME_LIMITED_API = "1" } diff --git a/setup.py b/setup.py index eed7048f..48371c37 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,6 @@ import os import sys +import sysconfig import numpy from Cython.Build import cythonize @@ -29,6 +30,24 @@ CFTIME_DIR = os.path.join(SRCDIR, NAME) CYTHON_FNAME = os.path.join(CFTIME_DIR, '_{}.pyx'.format(NAME)) +USE_PY_LIMITED_API = ( + # require opt-in (builds are specialized by default) + os.getenv('CFTIME_LIMITED_API', '0') == '1' + # Cython + numpy + limited API de facto requires Python >=3.11 + and sys.version_info >= (3, 11) + # as of Python 3.14t, free-threaded builds don't support the limited API + and not sysconfig.get_config_var("Py_GIL_DISABLED") +) +ABI3_TARGET_VERSION = "".join(str(_) for _ in sys.version_info[:2]) +ABI3_TARGET_HEX = hex(sys.hexversion & 0xFFFF00F0) + +if USE_PY_LIMITED_API: + DEFINE_MACROS += [(("Py_LIMITED_API", ABI3_TARGET_HEX))] + +if USE_PY_LIMITED_API: + SETUP_OPTIONS = {"bdist_wheel": {"py_limited_api": f"cp{ABI3_TARGET_VERSION}"}} +else: + SETUP_OPTIONS = {} class CleanCython(Command): description = 'Purge artifacts built by Cython' @@ -60,6 +79,7 @@ def run(self): } DEFINE_MACROS += [('CYTHON_TRACE', '1'), ('CYTHON_TRACE_NOGIL', '1')] + if FLAG_COVERAGE in sys.argv: sys.argv.remove(FLAG_COVERAGE) print('enable: "linetrace" Cython compiler directive') @@ -71,7 +91,8 @@ def run(self): extension = Extension('{}._{}'.format(NAME, NAME), sources=[os.path.relpath(CYTHON_FNAME, BASEDIR)], define_macros=DEFINE_MACROS, - include_dirs=[numpy.get_include(),]) + include_dirs=[numpy.get_include()], + py_limited_api=USE_PY_LIMITED_API) ext_modules = cythonize( extension, @@ -82,4 +103,5 @@ def run(self): setup( cmdclass={'clean_cython': CleanCython}, ext_modules=ext_modules, + options=SETUP_OPTIONS, )