From edcada3107110430b3bbe90ad15e300d30176455 Mon Sep 17 00:00:00 2001 From: Cristian Le Date: Mon, 17 Feb 2025 19:05:58 +0100 Subject: [PATCH] Initial support for `cmake.preset` Signed-off-by: Cristian Le --- README.md | 3 +++ docs/reference/configs.md | 11 ++++++++ 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 + 13 files changed, 103 insertions(+), 4 deletions(-) create mode 100644 tests/packages/cmake_defines/CMakePresets.json diff --git a/README.md b/README.md index ba1626045..ac84b2158 100644 --- a/README.md +++ b/README.md @@ -172,6 +172,9 @@ cmake.build-type = "Release" # The source directory to use when building the project. cmake.source-dir = "." +# Configure preset to use. ``cmake.source-dir`` must still be appropriately defined +cmake.preset = "" + # The versions of Ninja to allow. ninja.version = ">=1.5" diff --git a/docs/reference/configs.md b/docs/reference/configs.md index 8c6e8b857..d9310d24c 100644 --- a/docs/reference/configs.md +++ b/docs/reference/configs.md @@ -199,6 +199,17 @@ print(mk_skbuild_docs()) DEPRECATED in 0.8; use version instead. ``` +```{eval-rst} +.. confval:: cmake.preset + :type: ``str`` + + 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. +``` + ```{eval-rst} .. confval:: cmake.source-dir :type: ``Path`` diff --git a/src/scikit_build_core/builder/builder.py b/src/scikit_build_core/builder/builder.py index dde36ddab..860fbacf9 100644 --- a/src/scikit_build_core/builder/builder.py +++ b/src/scikit_build_core/builder/builder.py @@ -292,6 +292,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 9c9148646..fcfaf8755 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 c613075f9..ac5c5659e 100644 --- a/src/scikit_build_core/cmake.py +++ b/src/scikit_build_core/cmake.py @@ -12,11 +12,18 @@ from pathlib import Path from typing import TYPE_CHECKING, Any +from packaging.version import Version + from . import __version__ from ._compat.builtins import ExceptionGroup from ._logging import logger from ._shutil import Run -from .errors import CMakeConfigError, CMakeNotFoundError, FailedLiveProcessError +from .errors import ( + CMakeConfigError, + CMakeNotFoundError, + CMakeVersionError, + FailedLiveProcessError, +) from .file_api.query import stateless_query from .file_api.reply import load_reply_dir from .program_search import Program, best_program, get_cmake_program, get_cmake_programs @@ -25,7 +32,6 @@ from collections.abc import Generator, Iterable, Mapping, Sequence from packaging.specifiers import SpecifierSet - from packaging.version import Version from ._compat.typing import Self from .file_api.model.index import Index @@ -242,12 +248,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 5065b4153..b129f8a40 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" } } }, diff --git a/src/scikit_build_core/settings/skbuild_model.py b/src/scikit_build_core/settings/skbuild_model.py index a83abd2f8..5f2ed1523 100644 --- a/src/scikit_build_core/settings/skbuild_model.py +++ b/src/scikit_build_core/settings/skbuild_model.py @@ -133,6 +133,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 SearchSettings: diff --git a/src/scikit_build_core/settings/skbuild_read_settings.py b/src/scikit_build_core/settings/skbuild_read_settings.py index 33e4dfba4..e3e358a9c 100644 --- a/src/scikit_build_core/settings/skbuild_read_settings.py +++ b/src/scikit_build_core/settings/skbuild_read_settings.py @@ -285,8 +285,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 e48674042..a0a593e79 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 000000000..fe37b6b22 --- /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 32e9bc15b..ab3f0ce6a 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 6a86e1ba8..19b40561d 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 4f92b62dc..f1fe0eebc 100644 --- a/tests/test_skbuild_settings.py +++ b/tests/test_skbuild_settings.py @@ -790,6 +790,7 @@ 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", }