Skip to content

Commit c67339b

Browse files
authored
feat: support cmake-less runs (for overrides) (#550)
This supports skipping the CMake build. This can be used with overrides to make packages that have a pure Python implementation (#112). Signed-off-by: Henry Schreiner <[email protected]>
1 parent 860e8e5 commit c67339b

File tree

13 files changed

+187
-66
lines changed

13 files changed

+187
-66
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,13 @@ wheel.install-dir = ""
226226
# A list of license files to include in the wheel. Supports glob patterns.
227227
wheel.license-files = ["LICEN[CS]E*", "COPYING*", "NOTICE*", "AUTHORS*"]
228228

229+
# If set to True (the default), CMake will be run before building the wheel.
230+
wheel.cmake = true
231+
232+
# Target the platlib or the purelib. If not set, the default is to target the
233+
# platlib if wheel.cmake is true, and the purelib otherwise.
234+
wheel.platlib = ""
235+
229236
# If CMake is less than this value, backport a copy of FindPython. Set to 0
230237
# disable this, or the empty string.
231238
backport.find-python = "3.26.1"

docs/configuration.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,16 @@ Globbing patterns are supported.
251251
wheel.license-files = ["LICENSE"]
252252
```
253253

254+
:::{note}
255+
256+
There are two more settings that are primarily intended for `overrides` (see
257+
below). `wheel.cmake` defaults to `true`, and this enables/disables building
258+
with CMake. It also changes the default of `wheel.platlib` unless it's set
259+
explicitly; CMake builds assume `wheel.platlib = true`, and CMake-less builds
260+
assume `wheel.platlib = false` (purelib targeted instead).
261+
262+
:::
263+
254264
## Customizing the output wheel
255265

256266
The python API tags for your wheel will be correct assuming you are building a

src/scikit_build_core/build/__init__.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -123,11 +123,15 @@ def get_requires_for_build_wheel(
123123

124124
requires = GetRequires(config_settings)
125125

126+
# These are only injected if cmake is required for the wheel step
127+
cmake_requires = (
128+
[*requires.cmake(), *requires.ninja()] if requires.settings.wheel.cmake else []
129+
)
130+
126131
return [
127132
"pathspec",
128133
"pyproject_metadata",
129-
*requires.cmake(),
130-
*requires.ninja(),
134+
*cmake_requires,
131135
*requires.dynamic_metadata(),
132136
]
133137

@@ -139,10 +143,14 @@ def get_requires_for_build_editable(
139143

140144
requires = GetRequires(config_settings)
141145

146+
# These are only injected if cmake is required for the wheel step
147+
cmake_requires = (
148+
[*requires.cmake(), *requires.ninja()] if requires.settings.wheel.cmake else []
149+
)
150+
142151
return [
143152
"pathspec",
144153
"pyproject_metadata",
145-
*requires.cmake(),
146-
*requires.ninja(),
154+
*cmake_requires,
147155
*requires.dynamic_metadata(),
148156
]

src/scikit_build_core/build/_editable.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import os
4+
import typing
45
from pathlib import Path
56

67
from ..resources import resources
@@ -10,6 +11,9 @@
1011
scantree,
1112
)
1213

14+
if typing.TYPE_CHECKING:
15+
from collections.abc import Sequence
16+
1317
__all__ = ["editable_redirect", "libdir_to_installed", "mapping_to_modules"]
1418

1519

@@ -24,8 +28,8 @@ def editable_redirect(
2428
reload_dir: Path | None,
2529
rebuild: bool,
2630
verbose: bool,
27-
build_options: list[str],
28-
install_options: list[str],
31+
build_options: Sequence[str],
32+
install_options: Sequence[str],
2933
) -> str:
3034
"""
3135
Prepare the contents of the _editable_redirect.py file.

src/scikit_build_core/build/_wheelfile.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -142,12 +142,11 @@ def dist_info_contents(self) -> dict[str, bytes]:
142142
}
143143

144144
def build(self, wheel_dirs: dict[str, Path]) -> None:
145-
assert "platlib" in wheel_dirs
146-
assert "purelib" not in wheel_dirs
147-
assert {"platlib", "data", "headers", "scripts", "null"} >= wheel_dirs.keys()
145+
(targetlib,) = {"platlib", "purelib"} & set(wheel_dirs)
146+
assert {targetlib, "data", "headers", "scripts", "null"} >= wheel_dirs.keys()
148147

149-
# The "main" directory (platlib for us) will be handled specially below
150-
plans = {"": wheel_dirs["platlib"]}
148+
# The "main" directory (platlib usually for us) will be handled specially below
149+
plans = {"": wheel_dirs[targetlib]}
151150
data_dir = f"{self.name_ver}.data"
152151

153152
for key in sorted({"data", "headers", "scripts"} & wheel_dirs.keys()):

src/scikit_build_core/build/wheel.py

Lines changed: 72 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ def __dir__() -> list[str]:
4242

4343
def _make_editable(
4444
*,
45-
builder: Builder,
45+
build_options: Sequence[str] = (),
46+
install_options: Sequence[str] = (),
4647
libdir: Path,
4748
mapping: dict[str, str],
4849
name: str,
@@ -53,19 +54,14 @@ def _make_editable(
5354
modules = mapping_to_modules(mapping, libdir)
5455
installed = libdir_to_installed(libdir)
5556

56-
options = []
57-
if not builder.config.single_config and builder.config.build_type:
58-
options += ["--config", builder.config.build_type]
59-
ext_build_opts = ["-v"] if builder.settings.cmake.verbose else []
60-
6157
editable_txt = editable_redirect(
6258
modules=modules,
6359
installed=installed,
6460
reload_dir=reload_dir,
6561
rebuild=settings.editable.rebuild,
6662
verbose=settings.editable.verbose,
67-
build_options=options + ext_build_opts,
68-
install_options=options,
63+
build_options=build_options,
64+
install_options=install_options,
6965
)
7066

7167
wheel.writestr(
@@ -143,11 +139,21 @@ def _build_wheel_impl(
143139
if exit_after_config:
144140
state = "sdist"
145141

146-
cmake = CMake.default_search(minimum_version=settings.cmake.minimum_version)
142+
if settings.wheel.cmake:
143+
cmake = CMake.default_search(minimum_version=settings.cmake.minimum_version)
144+
cmake_msg = [f"using [blue]CMake {cmake.version}[/blue]"]
145+
else:
146+
cmake = None
147+
cmake_msg = []
148+
149+
if settings.wheel.platlib is None:
150+
targetlib = "platlib" if settings.wheel.cmake else "purelib"
151+
else:
152+
targetlib = "platlib" if settings.wheel.platlib else "purelib"
147153

148154
rich_print(
149155
f"[green]***[/green] [bold][green]scikit-build-core {__version__}[/green]",
150-
f"using [blue]CMake {cmake.version}[/blue]",
156+
*cmake_msg,
151157
f"[red]({state})[/red]",
152158
)
153159

@@ -177,7 +183,7 @@ def _build_wheel_impl(
177183
logger.info("Build directory: {}", build_dir.resolve())
178184

179185
wheel_dirs = {
180-
"platlib": wheel_dir / "platlib",
186+
targetlib: wheel_dir / targetlib,
181187
"data": wheel_dir / "data",
182188
"headers": wheel_dir / "headers",
183189
"scripts": wheel_dir / "scripts",
@@ -199,7 +205,7 @@ def _build_wheel_impl(
199205
raise AssertionError(msg)
200206
install_dir = wheel_dir / settings.wheel.install_dir[1:]
201207
else:
202-
install_dir = wheel_dirs["platlib"] / settings.wheel.install_dir
208+
install_dir = wheel_dirs[targetlib] / settings.wheel.install_dir
203209

204210
license_files = {
205211
x: x.read_bytes()
@@ -217,18 +223,6 @@ def _build_wheel_impl(
217223
gen.path.write_text(contents)
218224
settings.sdist.include.append(str(gen.path))
219225

220-
config = CMaker(
221-
cmake,
222-
source_dir=settings.cmake.source_dir,
223-
build_dir=build_dir,
224-
build_type=settings.cmake.build_type,
225-
)
226-
227-
builder = Builder(
228-
settings=settings,
229-
config=config,
230-
)
231-
232226
if wheel_directory is None and not exit_after_config:
233227
if metadata_directory is None:
234228
msg = "metadata_directory must be specified if wheel_directory is None"
@@ -262,38 +256,60 @@ def _build_wheel_impl(
262256
path.parent.mkdir(parents=True, exist_ok=True)
263257
path.write_text(contents, encoding="utf-8")
264258

265-
rich_print("[green]***[/green] [bold]Configuring CMake...")
266-
defines: dict[str, str] = {}
267-
cache_entries: dict[str, str | Path] = {
268-
f"SKBUILD_{k.upper()}_DIR": v for k, v in wheel_dirs.items()
269-
}
270-
cache_entries["SKBUILD_STATE"] = state
271-
builder.configure(
272-
defines=defines,
273-
cache_entries=cache_entries,
274-
name=metadata.name,
275-
version=metadata.version,
276-
)
259+
build_options = []
260+
install_options = []
277261

278-
if exit_after_config:
279-
return WheelImplReturn("")
262+
if cmake is not None:
263+
config = CMaker(
264+
cmake,
265+
source_dir=settings.cmake.source_dir,
266+
build_dir=build_dir,
267+
build_type=settings.cmake.build_type,
268+
)
280269

281-
assert wheel_directory is not None
270+
builder = Builder(
271+
settings=settings,
272+
config=config,
273+
)
282274

283-
default_gen = (
284-
"MSVC"
285-
if sysconfig.get_platform().startswith("win")
286-
else "Default Generator"
287-
)
288-
generator = builder.get_generator() or default_gen
289-
rich_print(
290-
f"[green]***[/green] [bold]Building project with [blue]{generator}[/blue]..."
291-
)
292-
build_args: list[str] = []
293-
builder.build(build_args=build_args)
275+
rich_print("[green]***[/green] [bold]Configuring CMake...")
276+
defines: dict[str, str] = {}
277+
cache_entries: dict[str, str | Path] = {
278+
f"SKBUILD_{k.upper()}_DIR": v for k, v in wheel_dirs.items()
279+
}
280+
cache_entries["SKBUILD_STATE"] = state
281+
builder.configure(
282+
defines=defines,
283+
cache_entries=cache_entries,
284+
name=metadata.name,
285+
version=metadata.version,
286+
)
287+
288+
if exit_after_config:
289+
return WheelImplReturn("")
294290

295-
rich_print("[green]***[/green] [bold]Installing project into wheel...")
296-
builder.install(install_dir)
291+
default_gen = (
292+
"MSVC"
293+
if sysconfig.get_platform().startswith("win")
294+
else "Default Generator"
295+
)
296+
generator = builder.get_generator() or default_gen
297+
rich_print(
298+
f"[green]***[/green] [bold]Building project with [blue]{generator}[/blue]..."
299+
)
300+
build_args: list[str] = []
301+
builder.build(build_args=build_args)
302+
303+
rich_print("[green]***[/green] [bold]Installing project into wheel...")
304+
builder.install(install_dir)
305+
306+
if not builder.config.single_config and builder.config.build_type:
307+
build_options += ["--config", builder.config.build_type]
308+
install_options += ["--config", builder.config.build_type]
309+
if builder.settings.cmake.verbose:
310+
build_options.append("-v")
311+
312+
assert wheel_directory is not None
297313

298314
rich_print(f"[green]***[/green] [bold]Making {state}...")
299315
packages = _get_packages(
@@ -302,7 +318,7 @@ def _build_wheel_impl(
302318
)
303319
mapping = packages_to_file_mapping(
304320
packages=packages,
305-
platlib_dir=wheel_dirs["platlib"],
321+
platlib_dir=wheel_dirs[targetlib],
306322
include=settings.sdist.include,
307323
exclude=settings.sdist.exclude,
308324
)
@@ -326,10 +342,11 @@ def _build_wheel_impl(
326342
reload_dir = build_dir.resolve() if settings.build_dir else None
327343

328344
_make_editable(
329-
libdir=wheel_dirs["platlib"],
345+
build_options=build_options,
346+
install_options=install_options,
347+
libdir=wheel_dirs[targetlib],
330348
mapping=mapping,
331349
reload_dir=reload_dir,
332-
builder=builder,
333350
settings=settings,
334351
wheel=wheel,
335352
name=normalized_name,

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,15 @@
157157
"type": "string"
158158
},
159159
"description": "A list of license files to include in the wheel. Supports glob patterns."
160+
},
161+
"cmake": {
162+
"type": "boolean",
163+
"default": true,
164+
"description": "If set to True (the default), CMake will be run before building the wheel."
165+
},
166+
"platlib": {
167+
"type": "boolean",
168+
"description": "Target the platlib or the purelib. If not set, the default is to target the platlib if wheel.cmake is true, and the purelib otherwise."
160169
}
161170
}
162171
},

src/scikit_build_core/settings/skbuild_model.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,17 @@ class WheelSettings:
166166
A list of license files to include in the wheel. Supports glob patterns.
167167
"""
168168

169+
cmake: bool = True
170+
"""
171+
If set to True (the default), CMake will be run before building the wheel.
172+
"""
173+
174+
platlib: Optional[bool] = None
175+
"""
176+
Target the platlib or the purelib. If not set, the default is to target the
177+
platlib if wheel.cmake is true, and the purelib otherwise.
178+
"""
179+
169180

170181
@dataclasses.dataclass
171182
class BackportSettings:

tests/conftest.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,17 @@ def package_sdist_config(
294294
return package
295295

296296

297+
@pytest.fixture()
298+
def package_simple_purelib_package(
299+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
300+
) -> PackageInfo:
301+
package = PackageInfo(
302+
"simple_purelib_package",
303+
)
304+
process_package(package, tmp_path, monkeypatch)
305+
return package
306+
307+
297308
def pytest_collection_modifyitems(items: list[pytest.Item]) -> None:
298309
for item in items:
299310
# Ensure all tests using virtualenv are marked as such
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[build-system]
2+
requires = ["scikit-build-core[pyproject]"]
3+
build-backend = "scikit_build_core.build"
4+
5+
[project]
6+
name = "purelib_example"
7+
version = "0.0.1"
8+
9+
[tool.scikit-build]
10+
wheel.cmake = false

0 commit comments

Comments
 (0)