From 8183b7091fd4e179178e969e2cf2817be1b0f828 Mon Sep 17 00:00:00 2001 From: Cristian Le Date: Thu, 29 Aug 2024 14:18:22 +0200 Subject: [PATCH] Read `cmake.root` entry_points and unify search path handling Signed-off-by: Cristian Le --- README.md | 14 ++++ src/scikit_build_core/builder/builder.py | 68 +++++++++++++++++-- src/scikit_build_core/cmake.py | 12 ++++ .../resources/scikit-build.schema.json | 45 ++++++++++++ .../settings/skbuild_model.py | 22 ++++++ 5 files changed, 155 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 1048637e..ec1041d0 100644 --- a/README.md +++ b/README.md @@ -312,6 +312,20 @@ messages.after-success = "" # paths. use-site-packages = true +# Entry points to ignore. Any entry-point in `cmake.module`, `cmake.prefix`, +# `cmake.root` with a key value matching a value in this list will be ignored +# when building the search paths. +ignore_entry_point = [] + +# List of additional CMake module search paths. Populates `CMAKE_MODULE_PATH`. +search.modules = [] + +# List of additional CMake prefix search paths. Populates `CMAKE_PREFIX_PATH`. +search.prefixes = [] + +# Dict of package names and prefix paths. Populates `_ROOT`. +search.roots = {} + # List dynamic metadata fields and hook locations in this table. metadata = {} diff --git a/src/scikit_build_core/builder/builder.py b/src/scikit_build_core/builder/builder.py index cc52773a..4e50bf55 100644 --- a/src/scikit_build_core/builder/builder.py +++ b/src/scikit_build_core/builder/builder.py @@ -97,6 +97,32 @@ def _sanitize_path(path: os.PathLike[str]) -> list[Path]: return [Path(os.fspath(path))] +def _merge_search_paths( + entry_point_search_path: dict[str, list[Path]], + settings_val: list[str] | dict[str, str], + output: list[Path] | dict[str, list[Path]], +) -> None: + if isinstance(settings_val, dict): + # if the settings and output search paths are dicts, just override them + # and update the output + assert isinstance(output, dict) + # No need to clone this because the search paths are not used anywhere else. + # Just renaming for readability + search_paths_dict = entry_point_search_path + for key, val in settings_val.items(): + search_paths_dict[key] = [Path(val)] + output.update(search_paths_dict) + return + # Otherwise the settings and outputs are lists. + # We flatten out the dict into a list and append the settings values. + assert isinstance(output, list) + search_paths_list = [ + path for ep_values in entry_point_search_path.values() for path in ep_values + ] + search_paths_list += map(Path, settings_val) + output.extend(search_paths_list) + + @dataclasses.dataclass class Builder: settings: ScikitBuildSettings @@ -120,6 +146,23 @@ def get_cmake_args(self) -> list[str]: def get_generator(self, *args: str) -> str | None: return self.config.get_generator(*self.get_cmake_args(), *args) + def _get_entry_point_search_path(self, entry_point: str) -> dict[str, list[Path]]: + """Get the search path dict from the entry points""" + search_paths = {} + eps = metadata.entry_points(group=entry_point) + if eps: + logger.debug( + "Loading search paths {} from entry-points: {}", entry_point, len(eps) + ) + for ep in eps: + if ep.name in self.settings.search.ignore_entry_point: + logger.debug("Ignoring entry-point: {}", ep.name) + ep_value = _sanitize_path(resources.files(ep.load())) + logger.debug("{}: {} -> {}", ep.name, ep.value, ep_value) + if ep_value: + search_paths[ep.name] = ep_value + return search_paths + def configure( self, *, @@ -136,16 +179,29 @@ def configure( } # Add any extra CMake modules - eps = metadata.entry_points(group="cmake.module") - self.config.module_dirs.extend( - p for ep in eps for p in _sanitize_path(resources.files(ep.load())) + _merge_search_paths( + self._get_entry_point_search_path("cmake.module"), + self.settings.search.modules, + self.config.module_dirs, ) + logger.debug("cmake.modules: {}", self.config.module_dirs) # Add any extra CMake prefixes - eps = metadata.entry_points(group="cmake.prefix") - self.config.prefix_dirs.extend( - p for ep in eps for p in _sanitize_path(resources.files(ep.load())) + _merge_search_paths( + self._get_entry_point_search_path("cmake.prefix"), + self.settings.search.prefixes, + self.config.prefix_dirs, + ) + logger.debug("cmake.prefix: {}", self.config.prefix_dirs) + + # Add all CMake roots + # TODO: Check for unique uppercase names + _merge_search_paths( + self._get_entry_point_search_path("cmake.root"), + self.settings.search.roots, + self.config.prefix_roots, ) + logger.debug("cmake.root: {}", self.config.prefix_roots) # Add site-packages to the prefix path for CMake site_packages = Path(sysconfig.get_path("purelib")) diff --git a/src/scikit_build_core/cmake.py b/src/scikit_build_core/cmake.py index aac19f0d..5a14b52c 100644 --- a/src/scikit_build_core/cmake.py +++ b/src/scikit_build_core/cmake.py @@ -79,6 +79,7 @@ class CMaker: build_type: str module_dirs: list[Path] = dataclasses.field(default_factory=list) prefix_dirs: list[Path] = dataclasses.field(default_factory=list) + prefix_roots: dict[str, list[Path]] = dataclasses.field(default_factory=dict) 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") @@ -183,6 +184,17 @@ def init_cache( ) f.write('set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE "BOTH" CACHE PATH "")\n') + if self.prefix_roots: + for pkg, path_list in self.prefix_roots.items(): + paths_str = ";".join(map(str, path_list)).replace("\\", "/") + f.write( + f'set({pkg}_ROOT [===[{paths_str}]===] CACHE PATH "" FORCE)\n' + ) + # Available since CMake 3.27 with CMP0144 + f.write( + f'set({pkg.upper()}_ROOT [===[{paths_str}]===] CACHE PATH "" FORCE)\n' + ) + contents = self.init_cache_file.read_text(encoding="utf-8").strip() logger.debug( "{}:\n{}", diff --git a/src/scikit_build_core/resources/scikit-build.schema.json b/src/scikit_build_core/resources/scikit-build.schema.json index 4dcb2b00..4658e731 100644 --- a/src/scikit_build_core/resources/scikit-build.schema.json +++ b/src/scikit_build_core/resources/scikit-build.schema.json @@ -96,6 +96,36 @@ "search": { "additionalProperties": false, "properties": { + "modules": { + "description": "List of additional CMake module search paths. Populates `CMAKE_MODULE_PATH`.", + "type": "array", + "items": { + "type": "string" + } + }, + "prefixes": { + "description": "List of additional CMake prefix search paths. Populates `CMAKE_PREFIX_PATH`.", + "type": "array", + "items": { + "type": "string" + } + }, + "roots": { + "description": "Dict of package names and prefix paths. Populates `_ROOT`.", + "type": "object", + "patternProperties": { + ".+": { + "type": "string" + } + } + }, + "ignore_entry_point": { + "description": "Entry points to ignore. Any entry-point in `cmake.module`, `cmake.prefix`, `cmake.root` with a key value matching a value in this list will be ignored when building the search paths.", + "type": "array", + "items": { + "type": "string" + } + }, "use-site-packages": { "default": true, "description": "Add the install (or build isolation) site_packages folder to the CMake prefix paths.", @@ -535,6 +565,21 @@ } } }, + "search": { + "type": "object", + "additionalProperties": false, + "properties": { + "modules": { + "$ref": "#/$defs/inherit" + }, + "prefixes": { + "$ref": "#/$defs/inherit" + }, + "roots": { + "$ref": "#/$defs/inherit" + } + } + }, "wheel": { "type": "object", "additionalProperties": false, diff --git a/src/scikit_build_core/settings/skbuild_model.py b/src/scikit_build_core/settings/skbuild_model.py index 16fd8fc6..f0462339 100644 --- a/src/scikit_build_core/settings/skbuild_model.py +++ b/src/scikit_build_core/settings/skbuild_model.py @@ -89,6 +89,28 @@ class SearchSettings: paths. """ + ignore_entry_point: List[str] = dataclasses.field(default_factory=list) + """ + Entry points to ignore. Any entry-point in `cmake.module`, `cmake.prefix`, + `cmake.root` with a key value matching a value in this list will be ignored + when building the search paths. + """ + + modules: List[str] = dataclasses.field(default_factory=list) + """ + List of additional CMake module search paths. Populates `CMAKE_MODULE_PATH`. + """ + + prefixes: List[str] = dataclasses.field(default_factory=list) + """ + List of additional CMake prefix search paths. Populates `CMAKE_PREFIX_PATH`. + """ + + roots: Dict[str, str] = dataclasses.field(default_factory=dict) + """ + Dict of package names and prefix paths. Populates `_ROOT`. + """ + @dataclasses.dataclass class NinjaSettings: