Skip to content

Commit e9ab153

Browse files
authored
feat: Rework CMake search path settings (#880)
- [x] Gate the install path's site-packages by an option - [x] Add logic for `entry_points(group="cmake.root")`: - [x] Documentation - Add a new page on all the search logics - Encourage the usage of `cmake.root` over `cmake.prefix` - [x] Tests Closes #831 Closes #885 Relates to #860 (still keeping it open because it doesn't address the specific issue) --------- Signed-off-by: Cristian Le <cristian.le@mpsd.mpg.de> Signed-off-by: Cristian Le <git@lecris.dev>
1 parent 8f79c17 commit e9ab153

File tree

13 files changed

+275
-21
lines changed

13 files changed

+275
-21
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,10 @@ messages.after-failure = ""
314314
# A message to print after a successful build.
315315
messages.after-success = ""
316316

317+
# Add the python build environment site_packages folder to the CMake prefix
318+
# paths.
319+
search.site-packages = true
320+
317321
# List dynamic metadata fields and hook locations in this table.
318322
metadata = {}
319323

docs/configuration/search_paths.md

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
# Search paths
2+
3+
Scikit-build-core populates CMake search paths to take into account any other
4+
CMake project installed in the same environment. In order to take advantage of
5+
this the dependent project must populate a `cmake.*` entry-point.
6+
7+
## `<PackageName>_ROOT`
8+
9+
This is the recommended interface to be used for importing dependent packages
10+
using `find_package`. This variable is populated by the dependent project's
11+
entry-point `cmake.root`.
12+
13+
To configure the `cmake.root` entry-point to export to other projects, you can
14+
use the CMake standard install paths in you `CMakeLists.txt` if you use
15+
`wheel.install-dir` option, e.g.
16+
17+
```{code-block} cmake
18+
:caption: CMakeLists.txt
19+
:emphasize-lines: 14-16
20+
21+
include(CMakePackageConfigHelpers)
22+
include(GNUInstallDirs)
23+
write_basic_package_version_file(
24+
MyProjectConfigVersion.cmake
25+
VERSION ${PROJECT_VERSION}
26+
COMPATIBILITY SameMajorVersion
27+
)
28+
configure_package_config_file(
29+
cmake/MyProjectConfig.cmake.in
30+
MyProjectConfig.cmake
31+
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyProject
32+
)
33+
install(FILES
34+
${CMAKE_CURRENT_BINARY_DIR}/MyProjectConfigVersion.cmake
35+
${CMAKE_CURRENT_BINARY_DIR}/MyProjectConfig.cmake
36+
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyProject
37+
)
38+
```
39+
40+
```{code-block} toml
41+
:caption: pyproject.toml
42+
:emphasize-lines: 2,5
43+
44+
[tool.scikit-build]
45+
wheel.install-dir = "myproject"
46+
47+
[project.entry-points."cmake.root"]
48+
MyProject = "myproject"
49+
```
50+
51+
With this any consuming project that depends on this would automatically work
52+
with `find_package(MyProject)` as long as it is in the `build-system.requires`
53+
list.
54+
55+
````{tab} pyproject.toml
56+
57+
```toml
58+
[tool.scikit-build.search]
59+
ignore_entry_point = ["MyProject"]
60+
[tool.scikit-build.search.roots]
61+
OtherProject = "/path/to/other_project"
62+
```
63+
64+
````
65+
66+
`````{tab} config-settings
67+
68+
69+
````{tab} pip
70+
71+
```console
72+
$ pip install . -v --config-settings=search.ignore_entry_point="MyProject" --config-settings=search.roots.OtherProject="/path/to/other_project"
73+
```
74+
75+
````
76+
77+
````{tab} build
78+
79+
```console
80+
$ pipx run build --wheel -Csearch.ignore_entry_point="MyProject" -Csearch.roots.OtherProject="/path/to/other_project"
81+
```
82+
83+
````
84+
85+
````{tab} cibuildwheel
86+
87+
```toml
88+
[tool.cibuildwheel.config-settings]
89+
"search.ignore_entry_point" = ["MyProject"]
90+
"search.roots.OtherProject" = "/path/to/other_project"
91+
```
92+
93+
````
94+
95+
`````
96+
97+
````{tab} Environment
98+
99+
100+
```yaml
101+
SKBUILD_SEARCH_IGNORE_ENTRY_POINT: "MyProject"
102+
SKBUILD_SEARCH_ROOTS_OtherProject: "/path/to/other_project"
103+
```
104+
105+
````
106+
107+
## `CMAKE_PREFIX_PATH`
108+
109+
Another common search path that scikit-build-core populates is the
110+
`CMAKE_PREFIX_PATH` which is a common catch-all for all CMake search paths, e.g.
111+
`find_package`, `find_program`, `find_path`. This is populated by default with
112+
the `site-packages` folder where the project will be installed or the build
113+
isolation's `site-packages` folder. This default can be disabled by setting
114+
115+
```toml
116+
[tool.scikit-build.search]
117+
search.use-site-packages = false
118+
```
119+
120+
Additionally, scikit-build-core reads the entry-point `cmake.prefix` of the
121+
dependent projects, which is similarly export as
122+
123+
```toml
124+
[project.entry-points."cmake.prefix"]
125+
MyProject = "myproject"
126+
```
127+
128+
````{tab} pyproject.toml
129+
130+
```toml
131+
[tool.scikit-build.search]
132+
ignore_entry_point = ["MyProject"]
133+
prefixes = ["/path/to/prefixA", "/path/to/prefixB"]
134+
```
135+
136+
````
137+
138+
`````{tab} config-settings
139+
140+
141+
````{tab} pip
142+
143+
```console
144+
$ pip install . -v --config-settings=search.ignore_entry_point="MyProject" --config-settings=search.prefixes="/path/to/prefixA;/path/to/prefixB"
145+
```
146+
147+
````
148+
149+
````{tab} build
150+
151+
```console
152+
$ pipx run build --wheel -Csearch.ignore_entry_point="MyProject" -Csearch.prefixes="/path/to/prefixA;/path/to/prefixB"
153+
```
154+
155+
````
156+
157+
````{tab} cibuildwheel
158+
159+
```toml
160+
[tool.cibuildwheel.config-settings]
161+
"search.ignore_entry_point" = ["MyProject"]
162+
"search.prefixes" = ["/path/to/prefixA", "/path/to/prefixB"]
163+
```
164+
165+
````
166+
167+
`````
168+
169+
````{tab} Environment
170+
171+
172+
```yaml
173+
SKBUILD_SEARCH_IGNORE_ENTRY_POINT: "MyProject"
174+
SKBUILD_SEARCH_PREFIXES: "/path/to/prefixA;/path/to/prefixB"
175+
```
176+
177+
````
178+
179+
## `CMAKE_MODULE_PATH`
180+
181+
Scikit-build-core also populates `CMAKE_MODULE_PATH` variable used to search for
182+
CMake modules using the `include()` command (if the `.cmake` suffix is omitted).
183+
184+
[`CMAKE_PREFIX_PATH`]: #cmake-prefix-path

docs/guide/cmakelists.md

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -77,15 +77,13 @@ succeed.
7777

7878
## Finding other packages
7979

80-
Scikit-build-core includes the site-packages directory in CMake's search path,
81-
so packages can provide a find package config with a name matching the package
82-
name - such as the `pybind11` package.
83-
84-
Third party packages can declare entry-points `cmake.module` and `cmake.prefix`,
85-
and the specified module will be added to `CMAKE_MODULE_PATH` and
86-
`CMAKE_PREFIX_PATH`, respectively. Currently, the key is not used, but
87-
eventually there might be a way to request or exclude certain entry-points by
88-
key.
80+
Scikit-build-core includes various pythonic paths to the CMake search paths by
81+
default so that usually you only need to include the dependent project inside
82+
the `build-system.requires` section. Note that `cmake` and `ninja` should not be
83+
included in that section.
84+
85+
See [search paths section](../configuration/search_paths.md) for more details on
86+
how the search paths are constructed.
8987

9088
## Install directories
9189

docs/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ configuration/index
4444
configuration/overrides
4545
configuration/dynamic
4646
configuration/formatted
47+
configuration/search_paths
4748
```
4849

4950
```{toctree}

src/scikit_build_core/builder/builder.py

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,21 @@ def get_cmake_args(self) -> list[str]:
120120
def get_generator(self, *args: str) -> str | None:
121121
return self.config.get_generator(*self.get_cmake_args(), *args)
122122

123+
def _get_entry_point_search_path(self, entry_point: str) -> dict[str, list[Path]]:
124+
"""Get the search path dict from the entry points"""
125+
search_paths = {}
126+
eps = metadata.entry_points(group=entry_point)
127+
if eps:
128+
logger.debug(
129+
"Loading search paths {} from entry-points: {}", entry_point, len(eps)
130+
)
131+
for ep in eps:
132+
ep_value = _sanitize_path(resources.files(ep.load()))
133+
logger.debug("{}: {} -> {}", ep.name, ep.value, ep_value)
134+
if ep_value:
135+
search_paths[ep.name] = ep_value
136+
return search_paths
137+
123138
def configure(
124139
self,
125140
*,
@@ -136,25 +151,35 @@ def configure(
136151
}
137152

138153
# Add any extra CMake modules
139-
eps = metadata.entry_points(group="cmake.module")
140154
self.config.module_dirs.extend(
141-
p for ep in eps for p in _sanitize_path(resources.files(ep.load()))
155+
p
156+
for ep_paths in self._get_entry_point_search_path("cmake.module").values()
157+
for p in ep_paths
142158
)
159+
logger.debug("cmake.modules: {}", self.config.module_dirs)
143160

144161
# Add any extra CMake prefixes
145-
eps = metadata.entry_points(group="cmake.prefix")
146162
self.config.prefix_dirs.extend(
147-
p for ep in eps for p in _sanitize_path(resources.files(ep.load()))
163+
p
164+
for ep_paths in self._get_entry_point_search_path("cmake.prefix").values()
165+
for p in ep_paths
148166
)
167+
logger.debug("cmake.prefix: {}", self.config.prefix_dirs)
168+
169+
# Add all CMake roots
170+
# TODO: Check for unique uppercase names
171+
self.config.prefix_roots.update(self._get_entry_point_search_path("cmake.root"))
172+
logger.debug("cmake.root: {}", self.config.prefix_roots)
149173

150174
# Add site-packages to the prefix path for CMake
151175
site_packages = Path(sysconfig.get_path("purelib"))
152-
self.config.prefix_dirs.append(site_packages)
153-
logger.debug("SITE_PACKAGES: {}", site_packages)
154-
if site_packages != DIR.parent.parent:
155-
self.config.prefix_dirs.append(DIR.parent.parent)
156-
logger.debug("Extra SITE_PACKAGES: {}", DIR.parent.parent)
157-
logger.debug("PATH: {}", sys.path)
176+
if self.settings.search.site_packages:
177+
self.config.prefix_dirs.append(site_packages)
178+
logger.debug("SITE_PACKAGES: {}", site_packages)
179+
if site_packages != DIR.parent.parent:
180+
self.config.prefix_dirs.append(DIR.parent.parent)
181+
logger.debug("Extra SITE_PACKAGES: {}", DIR.parent.parent)
182+
logger.debug("PATH: {}", sys.path)
158183

159184
# Add the FindPython backport if needed
160185
if self.config.cmake.version < self.settings.backport.find_python:

src/scikit_build_core/cmake.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +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, list[Path]] = dataclasses.field(default_factory=dict)
8283
init_cache_file: Path = dataclasses.field(init=False, default=Path())
8384
env: dict[str, str] = dataclasses.field(init=False, default_factory=os.environ.copy)
8485
single_config: bool = not sysconfig.get_platform().startswith("win")
@@ -183,6 +184,17 @@ def init_cache(
183184
)
184185
f.write('set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE "BOTH" CACHE PATH "")\n')
185186

187+
if self.prefix_roots:
188+
for pkg, path_list in self.prefix_roots.items():
189+
paths_str = ";".join(map(str, path_list)).replace("\\", "/")
190+
f.write(
191+
f'set({pkg}_ROOT [===[{paths_str}]===] CACHE PATH "" FORCE)\n'
192+
)
193+
# Available since CMake 3.27 with CMP0144
194+
f.write(
195+
f'set({pkg.upper()}_ROOT [===[{paths_str}]===] CACHE PATH "" FORCE)\n'
196+
)
197+
186198
contents = self.init_cache_file.read_text(encoding="utf-8").strip()
187199
logger.debug(
188200
"{}:\n{}",

src/scikit_build_core/resources/scikit-build.schema.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,17 @@
407407
}
408408
}
409409
},
410+
"search": {
411+
"type": "object",
412+
"additionalProperties": false,
413+
"properties": {
414+
"site-packages": {
415+
"type": "boolean",
416+
"default": true,
417+
"description": "Add the python build environment site_packages folder to the CMake prefix paths."
418+
}
419+
}
420+
},
410421
"metadata": {
411422
"type": "object",
412423
"description": "List dynamic metadata fields and hook locations in this table.",
@@ -618,6 +629,9 @@
618629
"messages": {
619630
"$ref": "#/properties/messages"
620631
},
632+
"search": {
633+
"$ref": "#/properties/search"
634+
},
621635
"metadata": {
622636
"$ref": "#/properties/metadata"
623637
},

src/scikit_build_core/settings/skbuild_model.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"NinjaSettings",
2121
"SDistSettings",
2222
"ScikitBuildSettings",
23+
"SearchSettings",
2324
"WheelSettings",
2425
]
2526

@@ -103,6 +104,14 @@ class CMakeSettings:
103104
"""
104105

105106

107+
@dataclasses.dataclass
108+
class SearchSettings:
109+
site_packages: bool = True
110+
"""
111+
Add the python build environment site_packages folder to the CMake prefix paths.
112+
"""
113+
114+
106115
@dataclasses.dataclass
107116
class NinjaSettings:
108117
minimum_version: Optional[Version] = None
@@ -355,6 +364,7 @@ class ScikitBuildSettings:
355364
install: InstallSettings = dataclasses.field(default_factory=InstallSettings)
356365
generate: List[GenerateSettings] = dataclasses.field(default_factory=list)
357366
messages: MessagesSettings = dataclasses.field(default_factory=MessagesSettings)
367+
search: SearchSettings = dataclasses.field(default_factory=SearchSettings)
358368

359369
metadata: Dict[str, Dict[str, Any]] = dataclasses.field(default_factory=dict)
360370
"""

tests/packages/custom_cmake/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ project(
66
VERSION 2.3.4)
77

88
find_package(ExamplePkg REQUIRED)
9-
9+
find_package(ExampleRoot REQUIRED)
1010
include(ExampleInclude)
1111

1212
if(NOT EXAMPLE_INCLUDE_FOUND)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
set(ExampleRoot_FOUND
2+
TRUE
3+
CACHE BOOL "ExampleRoot found" FORCE)

0 commit comments

Comments
 (0)