From d07f6499d9e62e69479683a88f9764001c03e4ae Mon Sep 17 00:00:00 2001 From: Cristian Le <git@lecris.dev> Date: Mon, 17 Feb 2025 19:05:58 +0100 Subject: [PATCH] Initial support for `cmake.preset` Signed-off-by: Cristian Le <git@lecris.dev> --- README.md | 8 ++++++ src/scikit_build_core/builder/builder.py | 1 + src/scikit_build_core/builder/generator.py | 1 + src/scikit_build_core/cmake.py | 17 ++++++++++-- .../resources/scikit-build.schema.json | 4 +++ .../settings/skbuild_model.py | 9 +++++++ .../settings/skbuild_read_settings.py | 6 ++++- tests/packages/cmake_defines/CMakeLists.txt | 9 +++++++ .../packages/cmake_defines/CMakePresets.json | 14 ++++++++++ tests/packages/cmake_defines/pyproject.toml | 5 ++++ tests/test_cmake_config.py | 26 ++++++++++++++++++- tests/test_skbuild_settings.py | 1 + 12 files changed, 97 insertions(+), 4 deletions(-) create mode 100644 tests/packages/cmake_defines/CMakePresets.json diff --git a/README.md b/README.md index b6a97886..3bacc704 100644 --- a/README.md +++ b/README.md @@ -181,6 +181,14 @@ cmake.source-dir = "." # DEPRECATED in 0.10; use build.targets instead. cmake.targets = "" +# Configure preset to use. ``cmake.source-dir`` must still be appropriately +# defined and it must contain a ``CMake(User)Presets.json``. The preset's +# ``binaryDir`` is ignored and is always overwritten by the ``build-dir`` +# defined by scikit-build-core. ``cmake.define``, generator values are still +# passed if defined and take precedence over preset's value according to CMake +# logic. +cmake.preset = "" + # The versions of Ninja to allow. If Ninja is not present on the system or does # not pass this specifier, it will be downloaded via PyPI if possible. An empty # string will disable this check. diff --git a/src/scikit_build_core/builder/builder.py b/src/scikit_build_core/builder/builder.py index b19173fd..88e89d9b 100644 --- a/src/scikit_build_core/builder/builder.py +++ b/src/scikit_build_core/builder/builder.py @@ -256,6 +256,7 @@ def configure( cmake_defines.update(self.settings.cmake.define) self.config.configure( + preset=self.settings.cmake.preset, defines=cmake_defines, cmake_args=[*self.get_cmake_args(), *configure_args], ) diff --git a/src/scikit_build_core/builder/generator.py b/src/scikit_build_core/builder/generator.py index 9c914864..fcfaf875 100644 --- a/src/scikit_build_core/builder/generator.py +++ b/src/scikit_build_core/builder/generator.py @@ -90,6 +90,7 @@ def set_environment_for_gen( If gen is not None, then that will be the target generator. """ + # TODO: How does make_fallback interact when `preset` is set? allow_make_fallback = ninja_settings.make_fallback if generator: diff --git a/src/scikit_build_core/cmake.py b/src/scikit_build_core/cmake.py index aac19f0d..7d6c0bd2 100644 --- a/src/scikit_build_core/cmake.py +++ b/src/scikit_build_core/cmake.py @@ -12,17 +12,23 @@ from pathlib import Path from typing import TYPE_CHECKING, Any +from packaging.version import Version + from . import __version__ from ._logging import logger from ._shutil import Run -from .errors import CMakeConfigError, CMakeNotFoundError, FailedLiveProcessError +from .errors import ( + CMakeConfigError, + CMakeNotFoundError, + CMakeVersionError, + FailedLiveProcessError, +) from .program_search import Program, best_program, get_cmake_program, get_cmake_programs if TYPE_CHECKING: from collections.abc import Generator, Iterable, Mapping, Sequence from packaging.specifiers import SpecifierSet - from packaging.version import Version from ._compat.typing import Self @@ -222,12 +228,19 @@ def get_generator(self, *args: str) -> str | None: def configure( self, *, + preset: str | None = None, defines: Mapping[str, str | os.PathLike[str] | bool] | None = None, cmake_args: Sequence[str] = (), ) -> None: _cmake_args = self._compute_cmake_args(defines or {}) all_args = [*_cmake_args, *cmake_args] + if preset: + if self.cmake.version < Version("3.19"): + msg = f"CMake version ({self.cmake.version}) is too old to support presets." + raise CMakeVersionError(msg) + all_args.append(f"--preset={preset}") + gen = self.get_generator(*all_args) if gen: self.single_config = gen == "Ninja" or "Makefiles" in gen diff --git a/src/scikit_build_core/resources/scikit-build.schema.json b/src/scikit_build_core/resources/scikit-build.schema.json index d8e87df8..feb43e9c 100644 --- a/src/scikit_build_core/resources/scikit-build.schema.json +++ b/src/scikit_build_core/resources/scikit-build.schema.json @@ -102,6 +102,10 @@ }, "description": "DEPRECATED in 0.10; use build.targets instead.", "deprecated": true + }, + "preset": { + "type": "string", + "description": "Configure preset to use. ``cmake.source-dir`` must still be appropriately defined and it must contain a ``CMake(User)Presets.json``. The preset's ``binaryDir`` is ignored and is always overwritten by the ``build-dir`` defined by scikit-build-core. ``cmake.define``, generator values are still passed if defined and take precedence over preset's value according to CMake logic." } } }, diff --git a/src/scikit_build_core/settings/skbuild_model.py b/src/scikit_build_core/settings/skbuild_model.py index af1c7c4f..fb4c03a3 100644 --- a/src/scikit_build_core/settings/skbuild_model.py +++ b/src/scikit_build_core/settings/skbuild_model.py @@ -102,6 +102,15 @@ class CMakeSettings: DEPRECATED in 0.10; use build.targets instead. """ + preset: Optional[str] = None + """ + Configure preset to use. ``cmake.source-dir`` must still be appropriately defined + and it must contain a ``CMake(User)Presets.json``. The preset's ``binaryDir`` is + ignored and is always overwritten by the ``build-dir`` defined by scikit-build-core. + ``cmake.define``, generator values are still passed if defined and take precedence + over preset's value according to CMake logic. + """ + @dataclasses.dataclass class NinjaSettings: diff --git a/src/scikit_build_core/settings/skbuild_read_settings.py b/src/scikit_build_core/settings/skbuild_read_settings.py index a3bd624f..eb6a07c1 100644 --- a/src/scikit_build_core/settings/skbuild_read_settings.py +++ b/src/scikit_build_core/settings/skbuild_read_settings.py @@ -273,8 +273,12 @@ def __init__( new_min_cmake = "3.15" self.settings.cmake.version = SpecifierSet(f">={new_min_cmake}") + default_cmake_minimum = "3.15" + if self.settings.cmake.preset: + default_cmake_minimum = "3.19" + _handle_minimum_version( - self.settings.cmake, self.settings.minimum_version, "3.15" + self.settings.cmake, self.settings.minimum_version, default_cmake_minimum ) _handle_minimum_version(self.settings.ninja, self.settings.minimum_version) diff --git a/tests/packages/cmake_defines/CMakeLists.txt b/tests/packages/cmake_defines/CMakeLists.txt index e4867404..a0a593e7 100644 --- a/tests/packages/cmake_defines/CMakeLists.txt +++ b/tests/packages/cmake_defines/CMakeLists.txt @@ -7,10 +7,19 @@ set(ONE_LEVEL_LIST set(NESTED_LIST "" CACHE STRING "") +set(PRESET_ONLY_VAR + "" + CACHE STRING "") +set(OVERWRITTEN_VAR + "" + CACHE STRING "") set(out_file "${CMAKE_CURRENT_BINARY_DIR}/log.txt") file(WRITE "${out_file}" "") +file(APPEND "${out_file}" "PRESET_ONLY_VAR=${PRESET_ONLY_VAR}\n") +file(APPEND "${out_file}" "OVERWRITTEN_VAR=${OVERWRITTEN_VAR}\n") + foreach(list IN ITEMS ONE_LEVEL_LIST NESTED_LIST) list(LENGTH ${list} length) file(APPEND "${out_file}" "${list}.LENGTH = ${length}\n") diff --git a/tests/packages/cmake_defines/CMakePresets.json b/tests/packages/cmake_defines/CMakePresets.json new file mode 100644 index 00000000..fe37b6b2 --- /dev/null +++ b/tests/packages/cmake_defines/CMakePresets.json @@ -0,0 +1,14 @@ +{ + "version": 1, + "configurePresets": [ + { + "name": "scikit", + "cacheVariables": { + "PRESET_ONLY_VAR": "defined", + "OVERWRITTEN_VAR": "original" + }, + "binaryDir": "/dev/null", + "generator": "Ninja" + } + ] +} diff --git a/tests/packages/cmake_defines/pyproject.toml b/tests/packages/cmake_defines/pyproject.toml index 32e9bc15..ab3f0ce6 100644 --- a/tests/packages/cmake_defines/pyproject.toml +++ b/tests/packages/cmake_defines/pyproject.toml @@ -9,3 +9,8 @@ ONE_LEVEL_LIST = [ "Baz", ] NESTED_LIST = [ "Apple", "Lemon;Lime", "Banana" ] +OVERWRITTEN_VAR = "overwritten" + +[[tool.scikit-build.overrides]] +if.env.WITH_PRESET = true +cmake.preset = "scikit" diff --git a/tests/test_cmake_config.py b/tests/test_cmake_config.py index 6a86e1ba..19b40561 100644 --- a/tests/test_cmake_config.py +++ b/tests/test_cmake_config.py @@ -14,12 +14,14 @@ from scikit_build_core.builder.builder import Builder from scikit_build_core.cmake import CMake, CMaker from scikit_build_core.errors import CMakeNotFoundError +from scikit_build_core.program_search import best_program, get_cmake_programs from scikit_build_core.settings.skbuild_read_settings import SettingsReader if TYPE_CHECKING: from collections.abc import Generator DIR = Path(__file__).parent.resolve() +cmake_preset_info = best_program(get_cmake_programs(), version=SpecifierSet(">=3.19")) def single_config(param: None | str) -> bool: @@ -204,10 +206,26 @@ def test_cmake_paths( assert len(fp.calls) == 2 +@pytest.mark.parametrize( + "with_preset", + [ + pytest.param( + True, + marks=pytest.mark.skipif( + cmake_preset_info is None, + reason="CMake version does not support presets.", + ), + ), + False, + ], +) @pytest.mark.configure def test_cmake_defines( + monkeypatch, tmp_path: Path, + with_preset: bool, ): + monkeypatch.setenv("WITH_PRESET", f"{with_preset}") source_dir = DIR / "packages" / "cmake_defines" binary_dir = tmp_path / "build" @@ -224,8 +242,14 @@ def test_cmake_defines( builder.configure(defines={}) configure_log = Path.read_text(binary_dir / "log.txt") + + # This var is always overwritten + overwritten_var = "overwritten" + preset_only_var = "defined" if with_preset else "" assert configure_log == dedent( - """\ + f"""\ + PRESET_ONLY_VAR={preset_only_var} + OVERWRITTEN_VAR={overwritten_var} ONE_LEVEL_LIST.LENGTH = 4 Foo Bar diff --git a/tests/test_skbuild_settings.py b/tests/test_skbuild_settings.py index 90d4e429..86957ece 100644 --- a/tests/test_skbuild_settings.py +++ b/tests/test_skbuild_settings.py @@ -764,4 +764,5 @@ def test_skbuild_settings_cmake_define_list(): assert settings.cmake.define == { "NESTED_LIST": r"Apple;Lemon\;Lime;Banana", "ONE_LEVEL_LIST": "Foo;Bar;ExceptionallyLargeListEntryThatWouldOverflowTheLine;Baz", + "OVERWRITTEN_VAR": "overwritten", }