Skip to content

Commit 659e0cb

Browse files
committed
Unify search path handling
- Validate the path types of the entry-points - Update the entry-points values with the settings value - Flatten the entry-points to a list or add them as a dict - Print more debug messages - Fix the settings type to string Signed-off-by: Cristian Le <cristian.le@mpsd.mpg.de>
1 parent 0cf744c commit 659e0cb

File tree

5 files changed

+106
-44
lines changed

5 files changed

+106
-44
lines changed

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,7 @@ known-local-folder = ["pathutils"]
296296
"importlib_metadata".msg = "Use scikit_build_core._compat.importlib.metadata instead."
297297
"importlib.resources".msg = "Use scikit_build_core._compat.importlib.resources instead."
298298
"importlib_resources".msg = "Use scikit_build_core._compat.importlib.resources instead."
299+
"importlib.readers".msg = "Use scikit_build_core._compat.importlib.readers instead."
299300
"pyproject_metadata".msg = "Use scikit_build_core._vendor.pyproject_metadata instead."
300301

301302

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from __future__ import annotations
2+
3+
import sys
4+
from typing import TYPE_CHECKING
5+
6+
if TYPE_CHECKING:
7+
from pathlib import Path
8+
9+
if sys.version_info < (3, 10):
10+
# Readers and MultiplexedPath were introduced in 3.10, so nothing should output a MultiplexedPath
11+
# It is also tricky because it is unclear if the `resource_loader` when calling `import` would create
12+
# either importlib.readers.MultiplexedPath or importlib_resources.MultiplexedPath.
13+
# Creating a dummy class instead so that if it fails, it fails completely (and mypy is made happy)
14+
class MultiplexedPath:
15+
_paths: list[Path]
16+
else:
17+
# From 3.11 it is accessed as importlib.resources.readers
18+
from importlib.readers import MultiplexedPath
19+
20+
__all__ = ["MultiplexedPath"]
21+
22+
23+
def __dir__() -> list[str]:
24+
return __all__

src/scikit_build_core/builder/builder.py

Lines changed: 73 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
from .. import __version__
1212
from .._compat.importlib import metadata, resources
13+
from .._compat.importlib.readers import MultiplexedPath
1314
from .._logging import logger
1415
from ..resources import find_python
1516
from .generator import set_environment_for_gen
@@ -23,6 +24,7 @@
2324

2425
if TYPE_CHECKING:
2526
from collections.abc import Generator, Iterable, Mapping, Sequence
27+
from typing import Any
2628

2729
from packaging.version import Version
2830

@@ -84,6 +86,68 @@ def _filter_env_cmake_args(env_cmake_args: list[str]) -> Generator[str, None, No
8486
yield arg
8587

8688

89+
# Type-hinting for Traversable is rather hard because they were introduced in python 3.11.
90+
# This avoids introducing importlib_resources dependency that may not be used in the actual package loader
91+
def _sanitize_path(path: Any) -> list[Path] | None:
92+
if isinstance(path, MultiplexedPath):
93+
# pylint: disable-next=protected-access
94+
return path._paths
95+
if isinstance(path, Path):
96+
return [path]
97+
logger.warning("Unknown path type: [{}] {}", type(path), path)
98+
return None
99+
100+
101+
def _handle_search_paths(
102+
entry_point: str,
103+
settings_val: list[str] | dict[str, str] | None,
104+
output: list[Path] | dict[str, list[Path]],
105+
) -> None:
106+
# Sanity checks
107+
if isinstance(output, dict):
108+
assert isinstance(settings_val, dict)
109+
110+
# Get the search paths from the entry points
111+
search_paths_dict = {}
112+
eps = metadata.entry_points(group=entry_point)
113+
if eps:
114+
logger.debug(
115+
"Loading search paths {} from entry-points: {}", entry_point, len(eps)
116+
)
117+
for ep in eps:
118+
ep_value = _sanitize_path(resources.files(ep.load()))
119+
logger.debug("{}: {} -> {}", ep.name, ep.value, ep_value)
120+
if ep_value:
121+
search_paths_dict[ep.name] = ep_value
122+
123+
# Update the search paths from the settings options
124+
if isinstance(settings_val, dict) and settings_val:
125+
logger.debug(
126+
"Overriding search paths {} from config: {}", entry_point, settings_val
127+
)
128+
for key, val in settings_val.items():
129+
if val:
130+
# TODO: Allow settings_val to be dict[str, list[str]]?
131+
search_paths_dict[key] = [Path(val)]
132+
else:
133+
search_paths_dict.pop(key)
134+
135+
# Write to the output
136+
if isinstance(output, list):
137+
search_paths_list = [
138+
path for ep_values in search_paths_dict.values() for path in ep_values
139+
]
140+
# If the settings options was a list the values are appended as-is
141+
if isinstance(settings_val, list) and settings_val:
142+
logger.debug(
143+
"Appending search paths {} with config: {}", entry_point, settings_val
144+
)
145+
search_paths_list += map(Path, settings_val)
146+
output.extend(search_paths_list)
147+
return
148+
output.update(search_paths_dict)
149+
150+
87151
@dataclasses.dataclass
88152
class Builder:
89153
settings: ScikitBuildSettings
@@ -123,47 +187,20 @@ def configure(
123187
}
124188

125189
# Add any extra CMake modules
126-
module_dirs_dict = {
127-
ep.name: resources.files(ep.load())
128-
for ep in metadata.entry_points(group="cmake.module")
129-
}
130-
if isinstance(self.settings.search.modules, dict):
131-
# Allow to override any entry-point definition
132-
module_dirs_dict.update(self.settings.search.modules)
133-
module_dirs = list(module_dirs_dict.values())
134-
if isinstance(self.settings.search.modules, list):
135-
# If it was a list, append to the entry-point definitions
136-
module_dirs += self.settings.search.modules
137-
# Remove any empty paths
138-
module_dirs = [path for path in module_dirs if path]
139-
self.config.module_dirs.extend(module_dirs)
190+
_handle_search_paths(
191+
"cmake.module", self.settings.search.modules, self.config.module_dirs
192+
)
140193

141194
# Add any extra CMake prefixes
142-
prefix_dirs_dict = {
143-
ep.name: resources.files(ep.load())
144-
for ep in metadata.entry_points(group="cmake.prefix")
145-
}
146-
if isinstance(self.settings.search.prefixes, dict):
147-
# Allow to override any entry-point definition
148-
prefix_dirs_dict.update(self.settings.search.prefixes)
149-
prefix_dirs = list(prefix_dirs_dict.values())
150-
if isinstance(self.settings.search.prefixes, list):
151-
# If it was a list, append to the entry-point definitions
152-
prefix_dirs += self.settings.search.prefixes
153-
# Remove any empty paths
154-
prefix_dirs = [path for path in prefix_dirs if path]
155-
self.config.prefix_dirs.extend(prefix_dirs)
195+
_handle_search_paths(
196+
"cmake.prefix", self.settings.search.prefixes, self.config.prefix_dirs
197+
)
156198

157199
# Add all CMake roots
158-
prefix_roots = {
159-
ep.name: resources.files(ep.load())
160-
for ep in metadata.entry_points(group="cmake.root")
161-
}
162200
# TODO: Check for unique uppercase names
163-
prefix_roots.update(self.settings.search.roots)
164-
# Remove any empty paths
165-
prefix_roots = {pkg: path for pkg, path in prefix_roots.items() if path}
166-
self.config.prefix_roots.update(prefix_roots)
201+
_handle_search_paths(
202+
"cmake.root", self.settings.search.roots, self.config.prefix_roots
203+
)
167204

168205
# Add site-packages to the prefix path for CMake
169206
site_packages = Path(sysconfig.get_path("purelib"))

src/scikit_build_core/cmake.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ class CMaker:
7979
build_type: str
8080
module_dirs: list[Path] = dataclasses.field(default_factory=list)
8181
prefix_dirs: list[Path] = dataclasses.field(default_factory=list)
82-
prefix_roots: dict[str, Path] = dataclasses.field(default_factory=dict)
82+
prefix_roots: dict[str, list[Path]] = dataclasses.field(default_factory=dict)
8383
init_cache_file: Path = dataclasses.field(init=False, default=Path())
8484
env: dict[str, str] = dataclasses.field(init=False, default_factory=os.environ.copy)
8585
single_config: bool = not sysconfig.get_platform().startswith("win")
@@ -185,14 +185,14 @@ def init_cache(
185185
f.write('set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE "BOTH" CACHE PATH "")\n')
186186

187187
if self.prefix_roots:
188-
for pkg, path in self.prefix_roots.items():
189-
path_str = str(path).replace("\\", "/")
188+
for pkg, path_list in self.prefix_roots.items():
189+
paths_str = ";".join(map(str, path_list)).replace("\\", "/")
190190
f.write(
191-
f'set({pkg}_ROOT [===[{path_str}]===] CACHE PATH "" FORCE)\n'
191+
f'set({pkg}_ROOT [===[{paths_str}]===] CACHE PATH "" FORCE)\n'
192192
)
193193
# Available since CMake 3.27 with CMP0144
194194
f.write(
195-
f'set({pkg.upper()}_ROOT [===[{path_str}]===] CACHE PATH "" FORCE)\n'
195+
f'set({pkg.upper()}_ROOT [===[{paths_str}]===] CACHE PATH "" FORCE)\n'
196196
)
197197

198198
contents = self.init_cache_file.read_text(encoding="utf-8").strip()

src/scikit_build_core/settings/skbuild_model.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,19 +93,19 @@ class SearchSettings:
9393
Add the wheel build path to the CMake prefix paths.
9494
"""
9595

96-
modules: Optional[Union[List[Path], Dict[str, Path]]] = None
96+
modules: Optional[Union[List[str], Dict[str, str]]] = None
9797
"""
9898
List or dict of CMake module search paths. Dict from is used to override
9999
another package's entry-point definition. Populates `CMAKE_MODULE_PATH`.
100100
"""
101101

102-
prefixes: Optional[Union[List[Path], Dict[str, Path]]] = None
102+
prefixes: Optional[Union[List[str], Dict[str, str]]] = None
103103
"""
104104
List or dict of CMake prefix search paths. Dict from is used to override
105105
another package's entry-point definition. Populates `CMAKE_PREFIX_PATH`.
106106
"""
107107

108-
roots: Dict[str, Path] = dataclasses.field(default_factory=dict)
108+
roots: Dict[str, str] = dataclasses.field(default_factory=dict)
109109
"""
110110
Dict of package names and prefix paths. Populates `<Pkg>_ROOT`.
111111
"""

0 commit comments

Comments
 (0)