Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: if.scikit-build-version #851

Merged
merged 3 commits into from
Aug 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ New features:
* Adding `if.system-cmake` and `if.cmake-wheel` by @henryiii in #826
* Add `if.from-sdist` for overrides by @henryiii in #812
* Add `if.failed` (retry) by @henryiii in #820
* Add `if.scikit-build-version` by @henryiii in #851
* Packages can also be specified via a table by @henryiii in #841
* Move `cmake.targets` and `cmake.verbose` to `build.targets` and `build.verbose` by @henryiii in #793
* Support multipart regex by @henryiii in #818
Expand Down
10 changes: 10 additions & 0 deletions docs/overrides.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@ At least one must be provided. Then you can specify any collection of valid
options, and those will override if all the items in the `if` are true. They
will match top to bottom, overriding previous matches.

If an override does not match, it's contents are ignored, including invalid
options. Combined with the `if.scikit-build-version` override, this allows using overrides to
support a range of scikit-build-core versions that added settings you want to
use.

### `scikit-build-version` (version)

The version of scikit-build-core itself. Takes a specifier set. If this is
provided, unknown overrides will not be validated unless it's a match.

### `python-version` (version)

The two-digit Python version. Takes a specifier set.
Expand Down
4 changes: 4 additions & 0 deletions src/scikit_build_core/resources/scikit-build.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,10 @@
"minProperties": 1,
"additionalProperties": false,
"properties": {
"scikit-build-version": {
"type": "string",
"description": "The version of scikit-build-version. Takes a specifier set."
},
"python-version": {
"type": "string",
"description": "The two-digit Python version. Takes a specifier set."
Expand Down
15 changes: 8 additions & 7 deletions src/scikit_build_core/settings/auto_requires.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,14 @@ def get_min_requires(package: str, reqlist: Iterable[str]) -> Version | None:

requires = [Requirement(req) for req in reqlist]

for req in requires:
if canonicalize_name(req.name) == norm_package:
specset = req.specifier
versions = (min_from_spec(v) for v in specset)
return min((v for v in versions if v is not None), default=None)

return None
versions = (
min_from_spec(v)
for req in requires
if canonicalize_name(req.name) == norm_package
and (req.marker is None or req.marker.evaluate())
for v in req.specifier
)
return min((v for v in versions if v is not None), default=None)


def min_from_spec(spec: Specifier) -> Version | None:
Expand Down
50 changes: 41 additions & 9 deletions src/scikit_build_core/settings/skbuild_overrides.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import packaging.tags
from packaging.specifiers import SpecifierSet

from .. import __version__
from .._compat import tomllib
from .._logging import logger
from ..builder.sysconfig import get_abi_flags
Expand Down Expand Up @@ -78,10 +79,12 @@
system_cmake: str | None = None,
cmake_wheel: bool | None = None,
abi_flags: str | None = None,
) -> tuple[dict[str, str], set[str]]:
scikit_build_version: str | None = None,
**unknown: Any,
) -> tuple[dict[str, str], set[str], dict[str, Any]]:
"""
Check if the current environment matches the overrides. Returns a dict
of passed matches, with reasons for values, and a set of non-matches.
Check if the current environment matches the overrides. Returns a dict of
passed matches, with reasons for values, and a set of non-matches.
"""

passed_dict = {}
Expand All @@ -90,6 +93,16 @@
if current_env is None:
current_env = os.environ

if scikit_build_version is not None:
current_version = __version__
match_msg = version_match(
current_version, scikit_build_version, "scikit-build-core"
)
if match_msg:
passed_dict["scikit-build-version"] = match_msg
else:
failed_set.add("scikit-build-version")

if python_version is not None:
current_python_version = ".".join(str(x) for x in sys.version_info[:2])
match_msg = version_match(current_python_version, python_version, "Python")
Expand Down Expand Up @@ -219,11 +232,11 @@
else:
failed_set.add(f"env.{key}")

if not passed_dict and not failed_set:
if len(passed_dict) + len(failed_set) + len(unknown) < 1:
msg = "At least one override must be provided"
raise ValueError(msg)

return passed_dict, failed_set
return passed_dict, failed_set, unknown


def inherit_join(
Expand Down Expand Up @@ -261,7 +274,9 @@
for override in tool_skb.pop("overrides", []):
passed_any: dict[str, str] | None = None
passed_all: dict[str, str] | None = None
failed: set[str] = set()
unknown: set[str] = set()
failed_any: set[str] = set()
failed_all: set[str] = set()
if_override = override.pop("if", None)
if not if_override:
msg = "At least one 'if' override must be provided"
Expand All @@ -272,13 +287,14 @@
if "any" in if_override:
any_override = if_override.pop("any")
select = {k.replace("-", "_"): v for k, v in any_override.items()}
passed_any, _ = override_match(
passed_any, failed_any, unknown_any = override_match(
current_env=env,
current_state=state,
has_dist_info=has_dist_info,
retry=retry,
**select,
)
unknown |= set(unknown_any)

inherit_override = override.pop("inherit", {})
if not isinstance(inherit_override, dict):
Expand All @@ -287,20 +303,32 @@

select = {k.replace("-", "_"): v for k, v in if_override.items()}
if select:
passed_all, failed = override_match(
passed_all, failed_all, unknown_all = override_match(
current_env=env,
current_state=state,
has_dist_info=has_dist_info,
retry=retry,
**select,
)
unknown |= set(unknown_all)

# Verify no unknown options are present unless scikit-build-version is specified
passed_or_failed = {
*(passed_all or {}),
*(passed_any or {}),
*failed_all,
*failed_any,
}
if "scikit-build-version" not in passed_or_failed and unknown:
msg = f"Unknown overrides: {', '.join(unknown)}"
raise TypeError(msg)

Check warning on line 324 in src/scikit_build_core/settings/skbuild_overrides.py

View check run for this annotation

Codecov / codecov/patch

src/scikit_build_core/settings/skbuild_overrides.py#L323-L324

Added lines #L323 - L324 were not covered by tests

# If no overrides are passed, do nothing
if passed_any is None and passed_all is None:
continue

# If normal overrides are passed and one or more fails, do nothing
if passed_all is not None and failed:
if passed_all is not None and failed_all:
continue

# If any is passed, at least one always needs to pass.
Expand All @@ -310,6 +338,10 @@
local_matched = set(passed_any or []) | set(passed_all or [])
global_matched |= local_matched
if local_matched:
if unknown:
msg = f"Unknown overrides: {', '.join(unknown)}"
raise TypeError(msg)

all_str = " and ".join(
[
*(passed_all or {}).values(),
Expand Down
4 changes: 4 additions & 0 deletions src/scikit_build_core/settings/skbuild_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ def generate_skbuild_schema(tool_name: str = "scikit-build") -> dict[str, Any]:
"minProperties": 1,
"additionalProperties": False,
"properties": {
"scikit-build-version": {
"type": "string",
"description": "The version of scikit-build-version. Takes a specifier set.",
},
"python-version": {
"type": "string",
"description": "The two-digit Python version. Takes a specifier set.",
Expand Down
9 changes: 9 additions & 0 deletions tests/test_auto.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ def test_auto_requires_pkg_version(spec: str, version: Version):
assert get_min_requires("scikit-build-core", reqlist) == version


def test_auto_requires_with_marker():
reqlist = [
"scikit_build_core>=0.1; python_version < '3.7'",
"scikit_build_core>=0.2; python_version >= '3.7'",
]

assert get_min_requires("scikit-build-core", reqlist) == Version("0.2")


@pytest.mark.parametrize(
("expr", "answer"),
[
Expand Down
165 changes: 165 additions & 0 deletions tests/test_settings_overrides.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import pytest

import scikit_build_core.settings.skbuild_overrides
from scikit_build_core.settings.skbuild_overrides import regex_match
from scikit_build_core.settings.skbuild_read_settings import SettingsReader

Expand Down Expand Up @@ -688,3 +689,167 @@ def test_free_threaded_override(tmp_path: Path):
settings_reader = SettingsReader.from_file(pyproject_toml, state="wheel")
settings = settings_reader.settings
assert settings.wheel.cmake == bool(sysconfig.get_config_var("Py_GIL_DISABLED"))


@pytest.mark.parametrize("version", ["0.9", "0.10"])
def test_skbuild_overrides_version(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, version: str
):
monkeypatch.setattr(
scikit_build_core.settings.skbuild_overrides, "__version__", version
)
pyproject_toml = tmp_path / "pyproject.toml"
pyproject_toml.write_text(
dedent(
"""\
[tool.scikit-build]
wheel.cmake = false

[[tool.scikit-build.overrides]]
if.scikit-build-version = ">=0.10"
wheel.cmake = true
"""
)
)

settings_reader = SettingsReader.from_file(pyproject_toml, state="wheel")
settings = settings_reader.settings
if version == "0.10":
assert settings.wheel.cmake
else:
assert not settings.wheel.cmake


def test_skbuild_overrides_unmatched_version(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
):
monkeypatch.setattr(
scikit_build_core.settings.skbuild_overrides, "__version__", "0.10"
)
pyproject_toml = tmp_path / "pyproject.toml"
pyproject_toml.write_text(
dedent(
"""\
[[tool.scikit-build.overrides]]
if.scikit-build-version = "<0.10"
if.is-not-real = true
also-not-real = true
"""
)
)

settings = SettingsReader.from_file(pyproject_toml)
settings.validate_may_exit()


def test_skbuild_overrides_matched_version_if(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
):
monkeypatch.setattr(
scikit_build_core.settings.skbuild_overrides, "__version__", "0.10"
)
pyproject_toml = tmp_path / "pyproject.toml"
pyproject_toml.write_text(
dedent(
"""\
[[tool.scikit-build.overrides]]
if.scikit-build-version = ">=0.10"
if.is-not-real = true
"""
)
)

with pytest.raises(TypeError, match="is_not_real"):
SettingsReader.from_file(pyproject_toml)


def test_skbuild_overrides_matched_version_extra(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
):
monkeypatch.setattr(
scikit_build_core.settings.skbuild_overrides, "__version__", "0.10"
)
pyproject_toml = tmp_path / "pyproject.toml"
pyproject_toml.write_text(
dedent(
"""\
[[tool.scikit-build.overrides]]
if.scikit-build-version = ">=0.10"
not-real = true
"""
)
)

settings = SettingsReader.from_file(pyproject_toml)
with pytest.raises(SystemExit):
settings.validate_may_exit()

assert "not-real" in capsys.readouterr().out


def test_skbuild_overrides_matched_version_if_any(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
):
monkeypatch.setattr(
scikit_build_core.settings.skbuild_overrides, "__version__", "0.9"
)
pyproject_toml = tmp_path / "pyproject.toml"
pyproject_toml.write_text(
dedent(
"""\
[[tool.scikit-build.overrides]]
if.any.scikit-build-version = ">=0.10"
if.any.not-real = true
also-not-real = true
"""
)
)

settings = SettingsReader.from_file(pyproject_toml)
settings.validate_may_exit()


def test_skbuild_overrides_matched_version_if_any_dual(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
):
monkeypatch.setattr(
scikit_build_core.settings.skbuild_overrides, "__version__", "0.9"
)
pyproject_toml = tmp_path / "pyproject.toml"
pyproject_toml.write_text(
dedent(
"""\
[[tool.scikit-build.overrides]]
if.scikit-build-version = ">=0.10"
if.any.not-real = true
if.any.python-version = ">=3.7"
also-not-real = true
"""
)
)

settings = SettingsReader.from_file(pyproject_toml)
settings.validate_may_exit()


def test_skbuild_overrides_matched_version_if_any_match(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
):
monkeypatch.setattr(
scikit_build_core.settings.skbuild_overrides, "__version__", "0.10"
)
pyproject_toml = tmp_path / "pyproject.toml"
pyproject_toml.write_text(
dedent(
"""\
[[tool.scikit-build.overrides]]
if.any.scikit-build-version = ">=0.10"
if.any.not-real = true
if.python-version = ">=3.7"
experimental = true
"""
)
)

with pytest.raises(TypeError, match="not_real"):
SettingsReader.from_file(pyproject_toml)
Loading