Skip to content

Commit

Permalink
Read cmake.root entry_points and unify search path handling
Browse files Browse the repository at this point in the history
Signed-off-by: Cristian Le <[email protected]>
  • Loading branch information
LecrisUT committed Sep 24, 2024
1 parent a09eb91 commit 8183b70
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 6 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<Pkg>_ROOT`.
search.roots = {}

# List dynamic metadata fields and hook locations in this table.
metadata = {}

Expand Down
68 changes: 62 additions & 6 deletions src/scikit_build_core/builder/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
*,
Expand All @@ -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"))
Expand Down
12 changes: 12 additions & 0 deletions src/scikit_build_core/cmake.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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{}",
Expand Down
45 changes: 45 additions & 0 deletions src/scikit_build_core/resources/scikit-build.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<Pkg>_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.",
Expand Down Expand Up @@ -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,
Expand Down
22 changes: 22 additions & 0 deletions src/scikit_build_core/settings/skbuild_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<Pkg>_ROOT`.
"""


@dataclasses.dataclass
class NinjaSettings:
Expand Down

0 comments on commit 8183b70

Please sign in to comment.