From 74af63819180ad05a9d8e08948ceaa8d95697f5f Mon Sep 17 00:00:00 2001 From: Cristian Le Date: Tue, 4 Mar 2025 19:05:54 +0100 Subject: [PATCH 1/5] Parse the CMake file-api during configuration Signed-off-by: Cristian Le --- src/scikit_build_core/cmake.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/scikit_build_core/cmake.py b/src/scikit_build_core/cmake.py index 5a14b52cd..c613075f9 100644 --- a/src/scikit_build_core/cmake.py +++ b/src/scikit_build_core/cmake.py @@ -13,9 +13,12 @@ from typing import TYPE_CHECKING, Any from . import __version__ +from ._compat.builtins import ExceptionGroup from ._logging import logger from ._shutil import Run from .errors import CMakeConfigError, CMakeNotFoundError, 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 if TYPE_CHECKING: @@ -25,6 +28,7 @@ from packaging.version import Version from ._compat.typing import Self + from .file_api.model.index import Index __all__ = ["CMake", "CMaker"] @@ -83,6 +87,8 @@ class CMaker: init_cache_file: Path = dataclasses.field(init=False, default=Path()) env: dict[str, str] = dataclasses.field(init=False, default_factory=os.environ.copy) single_config: bool = not sysconfig.get_platform().startswith("win") + file_api: Index | None = None + _file_api_query: Path = dataclasses.field(init=False) def __post_init__(self) -> None: self.init_cache_file = self.build_dir / "CMakeInit.txt" @@ -97,6 +103,8 @@ def __post_init__(self) -> None: msg = f"build directory {self.build_dir} must be a (creatable) directory" raise CMakeConfigError(msg) + # TODO: This could be stateful instead + self._file_api_query = stateless_query(self.build_dir) skbuild_info = self.build_dir / ".skbuild-info.json" stale = False @@ -253,6 +261,13 @@ def configure( msg = "CMake configuration failed" raise FailedLiveProcessError(msg) from None + try: + if self._file_api_query.exists(): + self.file_api = load_reply_dir(self._file_api_query) + except ExceptionGroup as exc: + logger.warning("Could not parse CMake file-api") + logger.debug(str(exc)) + def _compute_build_args( self, *, From 56fbb2f51a6663974d702fa1002b1276bf700dbe Mon Sep 17 00:00:00 2001 From: Cristian Le Date: Thu, 6 Mar 2025 13:10:23 +0100 Subject: [PATCH 2/5] Load directory file-api objects Signed-off-by: Cristian Le --- src/scikit_build_core/file_api/_cattrs_converter.py | 7 +++++++ src/scikit_build_core/file_api/reply.py | 7 ++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/scikit_build_core/file_api/_cattrs_converter.py b/src/scikit_build_core/file_api/_cattrs_converter.py index 0a273f071..49ded5414 100644 --- a/src/scikit_build_core/file_api/_cattrs_converter.py +++ b/src/scikit_build_core/file_api/_cattrs_converter.py @@ -11,6 +11,8 @@ from .model.cache import Cache from .model.cmakefiles import CMakeFiles from .model.codemodel import CodeModel, Target +from .model.codemodel import Directory as CodeModelDirectory +from .model.directory import Directory from .model.index import Index, Reply T = TypeVar("T") @@ -37,16 +39,21 @@ def make_converter(base_dir: Path) -> cattr.preconf.json.JsonConverter: converter.register_structure_hook(Reply, st_hook) def from_json_file(with_path: Dict[str, Any], t: Type[T]) -> T: + if "jsonFile" not in with_path and t is CodeModelDirectory: + return converter.structure_attrs_fromdict(with_path, t) if with_path["jsonFile"] is None: return converter.structure_attrs_fromdict({}, t) path = base_dir / Path(with_path["jsonFile"]) raw = json.loads(path.read_text(encoding="utf-8")) + if t is CodeModelDirectory: + t = Directory # type: ignore[assignment] return converter.structure_attrs_fromdict(raw, t) converter.register_structure_hook(CodeModel, from_json_file) converter.register_structure_hook(Target, from_json_file) converter.register_structure_hook(Cache, from_json_file) converter.register_structure_hook(CMakeFiles, from_json_file) + converter.register_structure_hook(CodeModelDirectory, from_json_file) return converter diff --git a/src/scikit_build_core/file_api/reply.py b/src/scikit_build_core/file_api/reply.py index b488a3e39..074abb6ce 100644 --- a/src/scikit_build_core/file_api/reply.py +++ b/src/scikit_build_core/file_api/reply.py @@ -11,6 +11,7 @@ from .model.cache import Cache from .model.cmakefiles import CMakeFiles from .model.codemodel import CodeModel, Target +from .model.codemodel import Directory as CodeModelDirectory from .model.directory import Directory from .model.index import Index @@ -51,10 +52,14 @@ def make_class(self, data: InputDict, target: Type[T]) -> T: Convert a dict to a dataclass. Automatically load a few nested jsonFile classes. """ if ( - target in {CodeModel, Target, Cache, CMakeFiles, Directory} + target in {CodeModel, Target, Cache, CMakeFiles, CodeModelDirectory} and "jsonFile" in data and data["jsonFile"] is not None ): + # Transform CodeModelDirectory to Directory object + # TODO: inherit the fields from CodeModel counterparts + if target is CodeModelDirectory: + target = Directory # type: ignore[assignment] return self._load_from_json(Path(data["jsonFile"]), target) input_dict: Dict[str, Type[Any]] = {} From 1194b0b98afe583f4c4a58a00aba626b45d9198b Mon Sep 17 00:00:00 2001 From: Cristian Le Date: Tue, 11 Mar 2025 15:44:50 +0100 Subject: [PATCH 3/5] commandFragments is optional Signed-off-by: Cristian Le --- src/scikit_build_core/file_api/model/codemodel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scikit_build_core/file_api/model/codemodel.py b/src/scikit_build_core/file_api/model/codemodel.py index 958d3b4a2..f51ce1350 100644 --- a/src/scikit_build_core/file_api/model/codemodel.py +++ b/src/scikit_build_core/file_api/model/codemodel.py @@ -94,7 +94,7 @@ class Sysroot: @dataclasses.dataclass(frozen=True) class Link: language: str - commandFragments: List[CommandFragment] + commandFragments: List[CommandFragment] = dataclasses.field(default_factory=list) lto: Optional[bool] = None sysroot: Optional[Sysroot] = None From 1c611fcd579b87b3f0d9c15eaf360d799efd64bf Mon Sep 17 00:00:00 2001 From: Cristian Le Date: Tue, 11 Mar 2025 16:48:49 +0100 Subject: [PATCH 4/5] Workaround for cattrs<23.2.0 Signed-off-by: Cristian Le --- .../file_api/_cattrs_converter.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/scikit_build_core/file_api/_cattrs_converter.py b/src/scikit_build_core/file_api/_cattrs_converter.py index 49ded5414..3f9ed7fbf 100644 --- a/src/scikit_build_core/file_api/_cattrs_converter.py +++ b/src/scikit_build_core/file_api/_cattrs_converter.py @@ -2,16 +2,21 @@ import builtins import json +from importlib.metadata import version from pathlib import Path -from typing import Any, Callable, Dict, Type, TypeVar # noqa: TID251 +from typing import Any, Callable, Dict, Type, TypeVar, Union # noqa: TID251 import cattr import cattr.preconf.json +from cattrs import ClassValidationError +from packaging.version import Version +from .._compat.typing import get_args from .model.cache import Cache from .model.cmakefiles import CMakeFiles from .model.codemodel import CodeModel, Target from .model.codemodel import Directory as CodeModelDirectory +from .model.common import Paths from .model.directory import Directory from .model.index import Index, Reply @@ -49,11 +54,23 @@ def from_json_file(with_path: Dict[str, Any], t: Type[T]) -> T: t = Directory # type: ignore[assignment] return converter.structure_attrs_fromdict(raw, t) + def from_union(obj: Dict[str, Any], t: Type[T]) -> T: + for try_type in get_args(t): + try: + return converter.structure(obj, try_type) # type: ignore[no-any-return] + except ClassValidationError: # noqa: PERF203 + continue + msg = f"Could not convert {obj} into {t}" + raise TypeError(msg) + converter.register_structure_hook(CodeModel, from_json_file) converter.register_structure_hook(Target, from_json_file) converter.register_structure_hook(Cache, from_json_file) converter.register_structure_hook(CMakeFiles, from_json_file) converter.register_structure_hook(CodeModelDirectory, from_json_file) + # Workaround for cattrs < 23.2.0 not handling Union with dataclass properly + if Version(version("cattrs")) < Version("23.2.0"): + converter.register_structure_hook(Union[str, Paths], from_union) return converter From 2bdd65a588e00c6f830b98588273b8b766f5f3dc Mon Sep 17 00:00:00 2001 From: Cristian Le Date: Thu, 6 Mar 2025 13:23:15 +0100 Subject: [PATCH 5/5] [DEBUG] Make file-api failure an error Signed-off-by: Cristian Le --- src/scikit_build_core/cmake.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/scikit_build_core/cmake.py b/src/scikit_build_core/cmake.py index c613075f9..818677ce7 100644 --- a/src/scikit_build_core/cmake.py +++ b/src/scikit_build_core/cmake.py @@ -264,9 +264,9 @@ def configure( try: if self._file_api_query.exists(): self.file_api = load_reply_dir(self._file_api_query) - except ExceptionGroup as exc: - logger.warning("Could not parse CMake file-api") - logger.debug(str(exc)) + except ExceptionGroup: + logger.error("Could not parse CMake file-api") + raise def _compute_build_args( self,