Skip to content

Commit

Permalink
Unify search path handling
Browse files Browse the repository at this point in the history
- 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 <[email protected]>
  • Loading branch information
LecrisUT committed Sep 3, 2024
1 parent 0cf744c commit 732eab5
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 45 deletions.
12 changes: 11 additions & 1 deletion src/scikit_build_core/_compat/importlib/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,17 @@
else:
from importlib.resources import files

__all__ = ["files"]
if sys.version_info < (3, 10):
# Readers were introduced in 3.10
from importlib_resources import readers
elif sys.version_info < (3, 11):
# In 3.10 readers were implemented in importlib.readers
from importlib import readers
else:
# From 3.11 it is accessed as importlib.resources.readers
from importlib.resources import readers

__all__ = ["files", "readers"]


def __dir__() -> list[str]:
Expand Down
108 changes: 72 additions & 36 deletions src/scikit_build_core/builder/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

if TYPE_CHECKING:
from collections.abc import Generator, Iterable, Mapping, Sequence
from typing import Any

from packaging.version import Version

Expand Down Expand Up @@ -84,6 +85,68 @@ def _filter_env_cmake_args(env_cmake_args: list[str]) -> Generator[str, None, No
yield arg


# Type-hinting for Traversable is rather hard because they were introduced in python 3.11.
# This avoids introducing importlib_resources dependency that may not be used in the actual package loader
def _sanitize_path(path: Any) -> list[Path] | None:
if isinstance(path, resources.readers.MultiplexedPath):

Check warning on line 91 in src/scikit_build_core/builder/builder.py

View check run for this annotation

Codecov / codecov/patch

src/scikit_build_core/builder/builder.py#L91

Added line #L91 was not covered by tests
# pylint: disable-next=protected-access
return path._paths
if isinstance(path, Path):
return [path]
logger.warning("Unknown path type: [{}] {}", type(path), path)
return None

Check warning on line 97 in src/scikit_build_core/builder/builder.py

View check run for this annotation

Codecov / codecov/patch

src/scikit_build_core/builder/builder.py#L93-L97

Added lines #L93 - L97 were not covered by tests


def _handle_search_paths(
entry_point: str,
settings_val: list[str] | dict[str, str] | None,
output: list[Path] | dict[str, list[Path]],
) -> None:
# Sanity checks
if isinstance(output, dict):
assert isinstance(settings_val, dict)

# Get the search paths from the entry points
search_paths_dict = {}
eps = metadata.entry_points(group=entry_point)
if eps:
logger.debug(

Check warning on line 113 in src/scikit_build_core/builder/builder.py

View check run for this annotation

Codecov / codecov/patch

src/scikit_build_core/builder/builder.py#L113

Added line #L113 was not covered by tests
"Loading search paths {} from entry-points: {}", entry_point, len(eps)
)
for ep in eps:
ep_value = _sanitize_path(resources.files(ep.load()))
logger.debug("{}: {} -> {}", ep.name, ep.value, ep_value)
if ep_value:
search_paths_dict[ep.name] = ep_value

Check warning on line 120 in src/scikit_build_core/builder/builder.py

View check run for this annotation

Codecov / codecov/patch

src/scikit_build_core/builder/builder.py#L116-L120

Added lines #L116 - L120 were not covered by tests

# Update the search paths from the settings options
if isinstance(settings_val, dict) and settings_val:
logger.debug(

Check warning on line 124 in src/scikit_build_core/builder/builder.py

View check run for this annotation

Codecov / codecov/patch

src/scikit_build_core/builder/builder.py#L124

Added line #L124 was not covered by tests
"Overriding search paths {} from config: {}", entry_point, settings_val
)
for key, val in settings_val.items():
if val:

Check warning on line 128 in src/scikit_build_core/builder/builder.py

View check run for this annotation

Codecov / codecov/patch

src/scikit_build_core/builder/builder.py#L127-L128

Added lines #L127 - L128 were not covered by tests
# TODO: Allow settings_val to be dict[str, list[str]]?
search_paths_dict[key] = [Path(val)]

Check warning on line 130 in src/scikit_build_core/builder/builder.py

View check run for this annotation

Codecov / codecov/patch

src/scikit_build_core/builder/builder.py#L130

Added line #L130 was not covered by tests
else:
search_paths_dict.pop(key)

Check warning on line 132 in src/scikit_build_core/builder/builder.py

View check run for this annotation

Codecov / codecov/patch

src/scikit_build_core/builder/builder.py#L132

Added line #L132 was not covered by tests

# Write to the output
if isinstance(output, list):
search_paths_list = [
path for ep_values in search_paths_dict.values() for path in ep_values
]
# If the settings options was a list the values are appended as-is
if isinstance(settings_val, list) and settings_val:
logger.debug(

Check warning on line 141 in src/scikit_build_core/builder/builder.py

View check run for this annotation

Codecov / codecov/patch

src/scikit_build_core/builder/builder.py#L141

Added line #L141 was not covered by tests
"Appending search paths {} with config: {}", entry_point, settings_val
)
search_paths_list += map(Path, settings_val)

Check warning on line 144 in src/scikit_build_core/builder/builder.py

View check run for this annotation

Codecov / codecov/patch

src/scikit_build_core/builder/builder.py#L144

Added line #L144 was not covered by tests
output.extend(search_paths_list)
return
output.update(search_paths_dict)


@dataclasses.dataclass
class Builder:
settings: ScikitBuildSettings
Expand Down Expand Up @@ -123,47 +186,20 @@ def configure(
}

# Add any extra CMake modules
module_dirs_dict = {
ep.name: resources.files(ep.load())
for ep in metadata.entry_points(group="cmake.module")
}
if isinstance(self.settings.search.modules, dict):
# Allow to override any entry-point definition
module_dirs_dict.update(self.settings.search.modules)
module_dirs = list(module_dirs_dict.values())
if isinstance(self.settings.search.modules, list):
# If it was a list, append to the entry-point definitions
module_dirs += self.settings.search.modules
# Remove any empty paths
module_dirs = [path for path in module_dirs if path]
self.config.module_dirs.extend(module_dirs)
_handle_search_paths(
"cmake.module", self.settings.search.modules, self.config.module_dirs
)

# Add any extra CMake prefixes
prefix_dirs_dict = {
ep.name: resources.files(ep.load())
for ep in metadata.entry_points(group="cmake.prefix")
}
if isinstance(self.settings.search.prefixes, dict):
# Allow to override any entry-point definition
prefix_dirs_dict.update(self.settings.search.prefixes)
prefix_dirs = list(prefix_dirs_dict.values())
if isinstance(self.settings.search.prefixes, list):
# If it was a list, append to the entry-point definitions
prefix_dirs += self.settings.search.prefixes
# Remove any empty paths
prefix_dirs = [path for path in prefix_dirs if path]
self.config.prefix_dirs.extend(prefix_dirs)
_handle_search_paths(
"cmake.prefix", self.settings.search.prefixes, self.config.prefix_dirs
)

# Add all CMake roots
prefix_roots = {
ep.name: resources.files(ep.load())
for ep in metadata.entry_points(group="cmake.root")
}
# TODO: Check for unique uppercase names
prefix_roots.update(self.settings.search.roots)
# Remove any empty paths
prefix_roots = {pkg: path for pkg, path in prefix_roots.items() if path}
self.config.prefix_roots.update(prefix_roots)
_handle_search_paths(
"cmake.root", self.settings.search.roots, self.config.prefix_roots
)

# Add site-packages to the prefix path for CMake
site_packages = Path(sysconfig.get_path("purelib"))
Expand Down
10 changes: 5 additions & 5 deletions src/scikit_build_core/cmake.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +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, Path] = dataclasses.field(default_factory=dict)
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 @@ -185,14 +185,14 @@ def init_cache(
f.write('set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE "BOTH" CACHE PATH "")\n')

if self.prefix_roots:
for pkg, path in self.prefix_roots.items():
path_str = str(path).replace("\\", "/")
for pkg, path_list in self.prefix_roots.items():
paths_str = ";".join(map(str, path_list)).replace("\\", "/")
f.write(

Check warning on line 190 in src/scikit_build_core/cmake.py

View check run for this annotation

Codecov / codecov/patch

src/scikit_build_core/cmake.py#L188-L190

Added lines #L188 - L190 were not covered by tests
f'set({pkg}_ROOT [===[{path_str}]===] CACHE PATH "" FORCE)\n'
f'set({pkg}_ROOT [===[{paths_str}]===] CACHE PATH "" FORCE)\n'
)
# Available since CMake 3.27 with CMP0144
f.write(

Check warning on line 194 in src/scikit_build_core/cmake.py

View check run for this annotation

Codecov / codecov/patch

src/scikit_build_core/cmake.py#L194

Added line #L194 was not covered by tests
f'set({pkg.upper()}_ROOT [===[{path_str}]===] CACHE PATH "" FORCE)\n'
f'set({pkg.upper()}_ROOT [===[{paths_str}]===] CACHE PATH "" FORCE)\n'
)

contents = self.init_cache_file.read_text(encoding="utf-8").strip()
Expand Down
6 changes: 3 additions & 3 deletions src/scikit_build_core/settings/skbuild_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,19 +93,19 @@ class SearchSettings:
Add the wheel build path to the CMake prefix paths.
"""

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

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

roots: Dict[str, Path] = dataclasses.field(default_factory=dict)
roots: Dict[str, str] = dataclasses.field(default_factory=dict)
"""
Dict of package names and prefix paths. Populates `<Pkg>_ROOT`.
"""
Expand Down

0 comments on commit 732eab5

Please sign in to comment.