diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cdf6a5c0b..402c969f6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -175,6 +175,25 @@ jobs: - name: Test min package run: pytest -ra --showlocals -Wdefault + manylinux: + name: Manylinux on 🐍 3.13 • Free-threaded + runs-on: ubuntu-latest + timeout-minutes: 40 + container: quay.io/pypa/musllinux_1_2_x86_64:latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Prepare venv + run: python3.13t -m venv /venv + + - name: Install deps + run: /venv/bin/pip install -e .[test] ninja + + - name: Test package + run: /venv/bin/pytest + cygwin: name: Tests on 🐍 3.9 • cygwin runs-on: windows-latest diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 452ca84f4..5806d3b64 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -119,3 +119,17 @@ repos: language: pygrep entry: tool\.cmake exclude: .pre-commit-config.yaml + + - repo: https://github.com/henryiii/validate-pyproject-schema-store + rev: 2024.04.29 + hooks: + - id: validate-pyproject + + - repo: https://github.com/python-jsonschema/check-jsonschema + rev: 0.28.3 + hooks: + - id: check-dependabot + - id: check-github-workflows + - id: check-readthedocs + - id: check-metaschema + files: \.schema\.json diff --git a/docs/cmakelists.md b/docs/cmakelists.md index 4e1bbf2b3..ace6e3e13 100644 --- a/docs/cmakelists.md +++ b/docs/cmakelists.md @@ -132,14 +132,17 @@ When defining your module, if you only support the Stable ABI after some point, you should use (for example for 3.11): ```cmake -if(Python_VERSION VERSION_GREATER_EQUAL 3.11 AND Python_INTERPRETER_ID STREQUAL Python) - python_add_library(some_ext MODULE USE_SABI 3.11 ...) +if(NOT "${SKBUILD_SABI_COMPONENT}" STREQUAL "") + python_add_library(some_ext MODULE WITH_SOABI USE_SABI 3.11 ...) else() python_add_library(some_ext MODULE WITH_SOABI ...) endif() ``` -This will define `Py_LIMITED_API` for you. +This will define `Py_LIMITED_API` for you. If you want to support building +directly from CMake, you need to protect this for Python version, +`Python_INTERPRETER_ID STREQUAL Python`, and free-threading Python 3.13+ doesn't +support ABI3 either. If you are using `nanobind`'s `nanobind_add_module`, the `STABLE_ABI` flag does this automatically for you for 3.12+. diff --git a/pyproject.toml b/pyproject.toml index 60fd719b1..ea032a6b6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,6 +26,7 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Development Status :: 4 - Beta", "Typing :: Typed", ] @@ -51,7 +52,8 @@ pyproject = [ test = [ "build >=0.8", "cattrs >=22.2.0", - "pip >=22", + "pip >=22; python_version<'3.13'", + "pip >=24.1b1; python_version>='3.13'", "pybind11 >=2.11", "pytest >=7.0", # 7.2+ recommended for better tracebacks with ExceptionGroup "pytest-subprocess >=1.5", diff --git a/src/scikit_build_core/builder/builder.py b/src/scikit_build_core/builder/builder.py index 2751f7ac1..9a131d690 100644 --- a/src/scikit_build_core/builder/builder.py +++ b/src/scikit_build_core/builder/builder.py @@ -114,7 +114,7 @@ def configure( cache_entries: Mapping[str, str | Path] | None = None, name: str | None = None, version: Version | None = None, - limited_abi: bool | None = None, + limited_api: bool | None = None, configure_args: Iterable[str] = (), ) -> None: cmake_defines = { @@ -162,16 +162,26 @@ def configure( cache_config["SKBUILD_PROJECT_VERSION"] = version.base_version cache_config["SKBUILD_PROJECT_VERSION_FULL"] = str(version) - if limited_abi is None: + if limited_api is None: if self.settings.wheel.py_api.startswith("cp3"): target_minor_version = int(self.settings.wheel.py_api[3:]) - limited_abi = target_minor_version <= sys.version_info.minor + limited_api = target_minor_version <= sys.version_info.minor else: - limited_abi = False + limited_api = False + + if limited_api and sys.implementation.name != "cpython": + limited_api = False + logger.info("PyPy doesn't support the Limited API, ignoring") + + if limited_api and sysconfig.get_config_var("Py_GIL_DISABLED"): + limited_api = False + logger.info( + "Free-threaded Python doesn't support the Limited API currently, ignoring" + ) python_library = get_python_library(self.config.env, abi3=False) python_sabi_library = ( - get_python_library(self.config.env, abi3=True) if limited_abi else None + get_python_library(self.config.env, abi3=True) if limited_api else None ) python_include_dir = get_python_include_dir() numpy_include_dir = get_numpy_include_dir() @@ -196,11 +206,11 @@ def configure( if numpy_include_dir: cache_config[f"{prefix}_NumPy_INCLUDE_DIR"] = numpy_include_dir - cache_config["SKBUILD_SOABI"] = get_soabi(self.config.env, abi3=limited_abi) + cache_config["SKBUILD_SOABI"] = get_soabi(self.config.env, abi3=limited_api) # Allow CMakeLists to detect this is supposed to be a limited ABI build cache_config["SKBUILD_SABI_COMPONENT"] = ( - "Development.SABIModule" if limited_abi else "" + "Development.SABIModule" if limited_api else "" ) if cache_entries: diff --git a/src/scikit_build_core/builder/wheel_tag.py b/src/scikit_build_core/builder/wheel_tag.py index ad1b03b61..5e6605d97 100644 --- a/src/scikit_build_core/builder/wheel_tag.py +++ b/src/scikit_build_core/builder/wheel_tag.py @@ -3,6 +3,7 @@ import dataclasses import itertools import sys +import sysconfig from typing import TYPE_CHECKING import packaging.tags @@ -104,11 +105,12 @@ def compute_best( if ( sys.implementation.name == "cpython" and minor <= sys.version_info.minor + and not sysconfig.get_config_var("Py_GIL_DISABLED") ): pyvers = pyvers_new abi = "abi3" else: - msg = "Ignoring py-api, not a CPython interpreter ({}) or version (3.{}) is too high" + msg = "Ignoring py-api, not a CPython interpreter ({}) or version (3.{}) is too high or free-threaded" logger.debug(msg, sys.implementation.name, minor) elif all(x.startswith("py") and x[2:].isdecimal() for x in pyvers_new): pyvers = pyvers_new diff --git a/src/scikit_build_core/setuptools/build_cmake.py b/src/scikit_build_core/setuptools/build_cmake.py index 00d22ab20..a7efd007e 100644 --- a/src/scikit_build_core/setuptools/build_cmake.py +++ b/src/scikit_build_core/setuptools/build_cmake.py @@ -141,7 +141,7 @@ def run(self) -> None: name=dist.get_name(), version=Version(dist.get_version()), defines={}, - limited_abi=limited_api, + limited_api=limited_api, configure_args=configure_args, ) diff --git a/tests/conftest.py b/tests/conftest.py index 81acbd668..a8caa39b4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -52,7 +52,8 @@ def pep518_wheelhouse(tmp_path_factory: pytest.TempPathFactory) -> Path: "build", "cython", "hatchling", - "pip>=23", + "pip>=23; python_version<'3.13'", + "pip>=24.1b1; python_version>='3.13'", "pybind11", "setuptools", "virtualenv", @@ -94,6 +95,8 @@ def __init__(self, env_dir: Path, *, wheelhouse: Path | None = None) -> None: self.purelib = Path( self.execute("import sysconfig; print(sysconfig.get_path('purelib'))") ) + if sys.version_info >= (3, 13): + self.run("pip", "install", "-U", "pip>=24.1b1") @overload def run(self, *args: str, capture: Literal[True]) -> str: ... diff --git a/tests/packages/abi3_pyproject_ext/CMakeLists.txt b/tests/packages/abi3_pyproject_ext/CMakeLists.txt index 3456c488b..9deb08e6c 100644 --- a/tests/packages/abi3_pyproject_ext/CMakeLists.txt +++ b/tests/packages/abi3_pyproject_ext/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.15...3.26) +cmake_minimum_required(VERSION 3.15...3.29) project( ${SKBUILD_PROJECT_NAME} @@ -7,9 +7,13 @@ project( find_package( Python - COMPONENTS Interpreter Development.SABIModule + COMPONENTS Interpreter Development.Module ${SKBUILD_SABI_COMPONENT} REQUIRED) -python_add_library(abi3_example MODULE abi3_example.c WITH_SOABI USE_SABI 3.7) +if(NOT "${SKBUILD_SABI_COMPONENT}" STREQUAL "") + python_add_library(abi3_example MODULE abi3_example.c WITH_SOABI USE_SABI 3.7) +else() + python_add_library(abi3_example MODULE abi3_example.c WITH_SOABI) +endif() install(TARGETS abi3_example DESTINATION .) diff --git a/tests/packages/cython_pxd_editable/pkg1/CMakeLists.txt b/tests/packages/cython_pxd_editable/pkg1/CMakeLists.txt index 44546e523..93441f753 100644 --- a/tests/packages/cython_pxd_editable/pkg1/CMakeLists.txt +++ b/tests/packages/cython_pxd_editable/pkg1/CMakeLists.txt @@ -7,8 +7,8 @@ find_package( REQUIRED) add_custom_command( - OUTPUT src/pkg1/one.c - DEPENDS src/pkg1/one.pyx + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/src/pkg1/one.c" + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/pkg1/one.pyx" VERBATIM COMMAND Python::Interpreter -m cython diff --git a/tests/packages/cython_pxd_editable/pkg1/pyproject.toml b/tests/packages/cython_pxd_editable/pkg1/pyproject.toml index d7338b412..f15f05e12 100644 --- a/tests/packages/cython_pxd_editable/pkg1/pyproject.toml +++ b/tests/packages/cython_pxd_editable/pkg1/pyproject.toml @@ -10,4 +10,4 @@ name = "pkg1" version = "1.0.0" [tool.scikit-build] -wheel.packages = ["src/pkg1"] +ninja.make-fallback = false diff --git a/tests/packages/cython_pxd_editable/pkg2/pyproject.toml b/tests/packages/cython_pxd_editable/pkg2/pyproject.toml index c2a8caec0..a6d3ce544 100644 --- a/tests/packages/cython_pxd_editable/pkg2/pyproject.toml +++ b/tests/packages/cython_pxd_editable/pkg2/pyproject.toml @@ -10,4 +10,4 @@ name = "pkg2" version = "1.0.0" [tool.scikit-build] -wheel.packages = ["src/pkg2"] +ninja.make-fallback = false diff --git a/tests/test_builder.py b/tests/test_builder.py index e5b15462b..e4b9ec171 100644 --- a/tests/test_builder.py +++ b/tests/test_builder.py @@ -139,6 +139,12 @@ def test_builder_get_cmake_args(monkeypatch, cmake_args, answer): ], ) def test_wheel_tag(monkeypatch, minver, archs, answer): + get_config_var = sysconfig.get_config_var + monkeypatch.setattr( + sysconfig, + "get_config_var", + lambda x: None if x == "Py_GIL_DISABLED" else get_config_var(x), + ) monkeypatch.setattr(sys, "platform", "darwin") monkeypatch.setenv("MACOSX_DEPLOYMENT_TARGET", minver) monkeypatch.setattr(platform, "mac_ver", lambda: ("10.9.2", "", "")) @@ -150,6 +156,12 @@ def test_wheel_tag(monkeypatch, minver, archs, answer): @pytest.mark.parametrize("archs", ["x86_64" "arm64" "universal2"]) def test_wheel_build_tag(monkeypatch, archs): + get_config_var = sysconfig.get_config_var + monkeypatch.setattr( + sysconfig, + "get_config_var", + lambda x: None if x == "Py_GIL_DISABLED" else get_config_var(x), + ) monkeypatch.setattr(sys, "platform", "darwin") monkeypatch.setenv("MACOSX_DEPLOYMENT_TARGET", "10.12") monkeypatch.setattr(platform, "mac_ver", lambda: ("10.9.2", "", "")) @@ -160,6 +172,12 @@ def test_wheel_build_tag(monkeypatch, archs): def test_wheel_tag_expand(monkeypatch): + get_config_var = sysconfig.get_config_var + monkeypatch.setattr( + sysconfig, + "get_config_var", + lambda x: None if x == "Py_GIL_DISABLED" else get_config_var(x), + ) monkeypatch.setattr(sys, "platform", "darwin") monkeypatch.setenv("MACOSX_DEPLOYMENT_TARGET", "10.10") monkeypatch.setattr(platform, "mac_ver", lambda: ("10.9.2", "", "")) @@ -177,6 +195,12 @@ def test_wheel_tag_expand(monkeypatch): def test_wheel_tag_expand_11(monkeypatch): + get_config_var = sysconfig.get_config_var + monkeypatch.setattr( + sysconfig, + "get_config_var", + lambda x: None if x == "Py_GIL_DISABLED" else get_config_var(x), + ) monkeypatch.setattr(sys, "platform", "darwin") monkeypatch.setenv("MACOSX_DEPLOYMENT_TARGET", "11.2") monkeypatch.setattr(platform, "mac_ver", lambda: ("10.9.2", "", "")) @@ -191,6 +215,12 @@ def test_wheel_tag_expand_11(monkeypatch): def test_wheel_tag_with_abi_darwin(monkeypatch): + get_config_var = sysconfig.get_config_var + monkeypatch.setattr( + sysconfig, + "get_config_var", + lambda x: None if x == "Py_GIL_DISABLED" else get_config_var(x), + ) monkeypatch.setattr(sys, "platform", "darwin") monkeypatch.setenv("MACOSX_DEPLOYMENT_TARGET", "10.10") monkeypatch.setattr(platform, "mac_ver", lambda: ("10.9.2", "", "")) diff --git a/tests/test_editable.py b/tests/test_editable.py index 41ce2c8e9..f0d382d65 100644 --- a/tests/test_editable.py +++ b/tests/test_editable.py @@ -73,7 +73,14 @@ def test_cython_pxd(monkeypatch, tmp_path, editable, editable_mode, isolated): tmp_path1.mkdir() process_package(package1, tmp_path1, monkeypatch) - isolated.install("pip>23", "cython", "scikit-build-core") + ninja = [ + "ninja" for f in isolated.wheelhouse.iterdir() if f.name.startswith("ninja-") + ] + cmake = [ + "cmake" for f in isolated.wheelhouse.iterdir() if f.name.startswith("cmake-") + ] + + isolated.install("pip>23", "cython", "scikit-build-core", *ninja, *cmake) isolated.install( "-v", diff --git a/tests/test_pyproject_abi3.py b/tests/test_pyproject_abi3.py index 632d3d80e..260f2fc0e 100644 --- a/tests/test_pyproject_abi3.py +++ b/tests/test_pyproject_abi3.py @@ -15,9 +15,6 @@ @pytest.mark.compile() @pytest.mark.configure() -@pytest.mark.skipif( - sys.implementation.name == "pypy", reason="pypy does not support abi3" -) @pytest.mark.skipif( sysconfig.get_platform().startswith(("msys", "mingw")), reason="abi3 FindPython on MSYS/MinGW reports not found", @@ -34,7 +31,14 @@ def test_abi3_wheel(tmp_path, monkeypatch, virtualenv): out = build_wheel(str(dist)) (wheel,) = dist.glob("abi3_example-0.0.1-*.whl") assert wheel == dist / out - assert "-cp37-abi3-" in out + abi3 = sys.implementation.name == "cpython" and not sysconfig.get_config_var( + "Py_GIL_DISABLED" + ) + + if abi3: + assert "-cp37-abi3-" in out + else: + assert "-cp37-abi3-" not in out if sys.version_info >= (3, 8): with wheel.open("rb") as f: @@ -47,11 +51,19 @@ def test_abi3_wheel(tmp_path, monkeypatch, virtualenv): (so_file,) = file_names if sysconfig.get_platform().startswith("win"): - assert so_file == "abi3_example.pyd" + if sys.implementation.name == "cpython": + assert so_file == "abi3_example.pyd" + else: + assert so_file.endswith(".pyd") elif sys.platform.startswith("cygwin"): - assert so_file == "abi3_example.abi3.dll" - else: + if abi3: + assert so_file == "abi3_example.abi3.dll" + else: + assert so_file != "abi3_example.abi3.dll" + elif abi3: assert so_file == "abi3_example.abi3.so" + else: + assert so_file != "abi3_example.abi3.so" virtualenv.install(wheel) diff --git a/tests/test_setuptools_abi3.py b/tests/test_setuptools_abi3.py index 92ae409cb..9ceb65f64 100644 --- a/tests/test_setuptools_abi3.py +++ b/tests/test_setuptools_abi3.py @@ -20,6 +20,10 @@ @pytest.mark.skipif( sys.implementation.name == "pypy", reason="pypy does not support abi3" ) +@pytest.mark.skipif( + sysconfig.get_config_var("Py_GIL_DISABLED"), + reason="Free-threaded Python does not support abi3", +) @pytest.mark.skipif( SYSCONFIGPLAT.startswith(("msys", "mingw")), reason="abi3 FindPython on MSYS/MinGW reports not found",