Skip to content

Commit 0e67e57

Browse files
authored
feat: auto min version (#798)
First part of #777. --------- Signed-off-by: Henry Schreiner <[email protected]>
1 parent 3287d2c commit 0e67e57

File tree

7 files changed

+145
-0
lines changed

7 files changed

+145
-0
lines changed

docs/api/scikit_build_core.settings.rst

+8
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@ scikit\_build\_core.settings package
99
Submodules
1010
----------
1111

12+
scikit\_build\_core.settings.auto\_requires module
13+
--------------------------------------------------
14+
15+
.. automodule:: scikit_build_core.settings.auto_requires
16+
:members:
17+
:undoc-members:
18+
:show-inheritance:
19+
1220
scikit\_build\_core.settings.documentation module
1321
-------------------------------------------------
1422

docs/configuration.md

+15
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,19 @@ probably keep in sync with your build-system requirements.
101101
minimum-version = "0.2"
102102
```
103103

104+
In your `pyproject.toml`, you can specify the special string
105+
`"build-system.requires"`, which will read the minimum version from your
106+
build-system requirements directly; you must specify a minimum there to use this
107+
automatic feature.
108+
109+
```toml
110+
[build-system]
111+
requires = ["scikit-build-core>=0.10"]
112+
113+
[tool.scikit-build]
114+
minimum-version = "build-system.requires"
115+
```
116+
104117
:::{warning}
105118

106119
The following behaviors are affected by `minimum-version`:
@@ -110,6 +123,8 @@ The following behaviors are affected by `minimum-version`:
110123
- `minimum-version` 0.5+ (or unset) strips binaries by default.
111124
- `minimum-version` 0.8+ (or unset) `cmake.minimum-version` and
112125
`ninja.minimum-version` are replaced with `cmake.version` and `ninja.version`.
126+
- `minimum-version` 0.10+ (or unset) `cmake.targets` and
127+
`cmake.verbose` are replaced with `build.targets` and `build.verbose`.
113128

114129
:::
115130

pyproject.toml

+2
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,8 @@ report.exclude_lines = [
203203
'pragma: no cover',
204204
'\.\.\.',
205205
'if typing.TYPE_CHECKING:',
206+
'if TYPE_CHECKING:',
207+
'def __repr__',
206208
]
207209

208210

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
from __future__ import annotations
2+
3+
from typing import TYPE_CHECKING
4+
5+
from packaging.requirements import Requirement
6+
from packaging.utils import canonicalize_name
7+
from packaging.version import Version
8+
9+
if TYPE_CHECKING:
10+
from collections.abc import Iterable
11+
12+
from packaging.specifiers import Specifier
13+
14+
__all__ = ["get_min_requires"]
15+
16+
17+
def __dir__() -> list[str]:
18+
return __all__
19+
20+
21+
def get_min_requires(package: str, reqlist: Iterable[str]) -> Version | None:
22+
"""
23+
Read the build requirements from a pyproject.toml file
24+
and return the minimum version required for the given package.
25+
Returns None if no minimum version is discovered.
26+
"""
27+
28+
norm_package = canonicalize_name(package)
29+
30+
requires = [Requirement(req) for req in reqlist]
31+
32+
for req in requires:
33+
if canonicalize_name(req.name) == norm_package:
34+
specset = req.specifier
35+
versions = (min_from_spec(v) for v in specset)
36+
return min((v for v in versions if v is not None), default=None)
37+
38+
return None
39+
40+
41+
def min_from_spec(spec: Specifier) -> Version | None:
42+
"""
43+
Return the minimum version from a specifier.
44+
45+
Returns None if no minimum version is discovered.
46+
">" technically doesn't include the minimum, but any
47+
larger version is acceptable, so it is treated as the
48+
minimum.
49+
"""
50+
51+
if spec.operator in {">=", ">", "==", "~="}:
52+
return Version(spec.version)
53+
return None

src/scikit_build_core/settings/skbuild_read_settings.py

+18
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from .._compat import tomllib
1818
from .._logging import logger, rich_print
1919
from ..errors import CMakeConfigError
20+
from .auto_requires import get_min_requires
2021
from .skbuild_model import CMakeSettings, NinjaSettings, ScikitBuildSettings
2122
from .sources import ConfSource, EnvSource, SourceChain, TOMLSource
2223

@@ -327,8 +328,25 @@ def __init__(
327328
) -> None:
328329
self.state = state
329330

331+
# Handle overrides
330332
pyproject = copy.deepcopy(pyproject)
331333
process_overides(pyproject.get("tool", {}).get("scikit-build", {}), state, env)
334+
335+
# Support for minimum-version='build-system.requires'
336+
tmp_min_v = (
337+
pyproject.get("tool", {})
338+
.get("scikit-build", {})
339+
.get("minimum-version", None)
340+
)
341+
if tmp_min_v == "build-system.requires":
342+
reqlist = pyproject["build-system"]["requires"]
343+
min_v = get_min_requires("scikit-build-core", reqlist)
344+
if min_v is None:
345+
rich_print(
346+
"[red][bold]ERROR:[/bold] scikit-build-core needs a min version in build-system.requires to use minimum-version='build-system.requires'"
347+
)
348+
raise SystemExit(7)
349+
pyproject["tool"]["scikit-build"]["minimum-version"] = str(min_v)
332350
toml_srcs = [TOMLSource("tool", "scikit-build", settings=pyproject)]
333351

334352
if extra_settings is not None:

tests/test_auto.py

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import pytest
2+
from packaging.version import Version
3+
4+
from scikit_build_core.settings.auto_requires import get_min_requires
5+
6+
7+
def test_auto_requires_pkg_no_spec():
8+
reqlist = ["scikit-build-core"]
9+
10+
assert get_min_requires("scikit-build-core", reqlist) is None
11+
assert get_min_requires("other", reqlist) is None
12+
13+
14+
@pytest.mark.parametrize(
15+
("spec", "version"),
16+
[
17+
("==1.0", Version("1.0")),
18+
(">=1.0", Version("1.0")),
19+
(">1.0", Version("1.0")),
20+
("~=1.0", Version("1.0")),
21+
(">=0.3,<0.4", Version("0.3")),
22+
("", None),
23+
],
24+
)
25+
def test_auto_requires_pkg_version(spec: str, version: Version):
26+
reqlist = [f"scikit_build_core{spec}"]
27+
assert get_min_requires("scikit-build-core", reqlist) == version

tests/test_skbuild_settings.py

+22
Original file line numberDiff line numberDiff line change
@@ -577,3 +577,25 @@ def test_backcompat_cmake_build_both_specified(
577577

578578
with pytest.raises(SystemExit):
579579
SettingsReader.from_file(pyproject_toml, {"build.verbose": "1"})
580+
581+
582+
def test_auto_minimum_version(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
583+
monkeypatch.setattr(
584+
scikit_build_core.settings.skbuild_read_settings, "__version__", "0.10.0"
585+
)
586+
pyproject_toml = tmp_path / "pyproject.toml"
587+
pyproject_toml.write_text(
588+
textwrap.dedent(
589+
"""\
590+
[build-system]
591+
requires = ["scikit-build-core>=0.8"]
592+
593+
[tool.scikit-build]
594+
minimum-version = "build-system.requires"
595+
"""
596+
),
597+
encoding="utf-8",
598+
)
599+
600+
reader = SettingsReader.from_file(pyproject_toml, {})
601+
assert reader.settings.minimum_version == Version("0.8")

0 commit comments

Comments
 (0)