From ec415785d438e860a52ed3c8151c19255508a318 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Wed, 3 Jan 2024 10:02:07 -0500 Subject: [PATCH] feat: add if.state to overrides (#600) This is the easier of the two suggestions in #591. Signed-off-by: Henry Schreiner --- docs/configuration.md | 4 ++- src/scikit_build_core/build/sdist.py | 2 +- src/scikit_build_core/build/wheel.py | 18 +++++----- .../resources/scikit-build.schema.json | 26 ++++++++++---- .../settings/skbuild_read_settings.py | 26 ++++++++++++-- .../settings/skbuild_schema.py | 36 +++++++++++++++---- tests/test_dynamic_metadata.py | 18 +++++----- tests/test_settings_overrides.py | 23 ++++++++++++ 8 files changed, 119 insertions(+), 34 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 8f06dfeb..957dbe19 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -625,7 +625,7 @@ experimental = true Scikit-build-core has an override system, similar to cibuildwheel and mypy. You specify a `tool.scikit-build.overrides` array with an `if` key. That if key can -take several values, based on [PEP 508][]: +take several values, including several based on [PEP 508][]: - `python-version`: The two-digit Python version. Takes a specifier set. - `platform-system`: The value of `sys.platform`. Takes a regex. @@ -637,6 +637,8 @@ take several values, based on [PEP 508][]: - `env`: A table of environment variables mapped to either string regexs, or booleans. Valid "truthy" environment variables are case insensitive `true`, `on`, `yes`, `y`, `t`, or a number more than 0. +- `state`: The state of the build, one of `sdist`, `wheel`, `editable`, + `metadata_wheel`, and `metadata_editable`. Takes a regex. 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 diff --git a/src/scikit_build_core/build/sdist.py b/src/scikit_build_core/build/sdist.py index 9813a7fa..eadd73de 100644 --- a/src/scikit_build_core/build/sdist.py +++ b/src/scikit_build_core/build/sdist.py @@ -100,7 +100,7 @@ def build_sdist( with Path("pyproject.toml").open("rb") as f: pyproject = tomllib.load(f) - settings_reader = SettingsReader(pyproject, config_settings or {}) + settings_reader = SettingsReader(pyproject, config_settings or {}, state="sdist") settings = settings_reader.settings setup_logging(settings.logging.level) diff --git a/src/scikit_build_core/build/wheel.py b/src/scikit_build_core/build/wheel.py index 1ff169e9..4ef5e7a3 100644 --- a/src/scikit_build_core/build/wheel.py +++ b/src/scikit_build_core/build/wheel.py @@ -11,7 +11,7 @@ from .. import __version__ from .._compat import tomllib -from .._compat.typing import assert_never +from .._compat.typing import Literal, assert_never from .._logging import logger, rich_print from .._shutil import fix_win_37_all_permissions from ..builder.builder import Builder, archs_to_tags, get_archs @@ -121,11 +121,19 @@ def _build_wheel_impl( """ Build a wheel or just prepare metadata (if wheel dir is None). Can be editable. """ + state: Literal["sdist", "wheel", "editable", "metadata_wheel", "metadata_editable"] + if exit_after_config: + state = "sdist" + elif wheel_directory is None: + state = "metadata_editable" if editable else "metadata_wheel" + else: + state = "editable" if editable else "wheel" + pyproject_path = Path("pyproject.toml") with pyproject_path.open("rb") as ft: pyproject = tomllib.load(ft) - settings_reader = SettingsReader(pyproject, config_settings or {}) + settings_reader = SettingsReader(pyproject, config_settings or {}, state=state) settings = settings_reader.settings setup_logging(settings.logging.level) @@ -139,12 +147,6 @@ def _build_wheel_impl( normalized_name = metadata.name.replace("-", "_").replace(".", "_") - state = "editable" if editable else "wheel" - if wheel_directory is None: - state = f"metadata_{state}" - if exit_after_config: - state = "sdist" - if settings.wheel.cmake: cmake = CMake.default_search(minimum_version=settings.cmake.minimum_version) cmake_msg = [f"using [blue]CMake {cmake.version}[/blue]"] diff --git a/src/scikit_build_core/resources/scikit-build.schema.json b/src/scikit_build_core/resources/scikit-build.schema.json index bf007080..568efa88 100644 --- a/src/scikit_build_core/resources/scikit-build.schema.json +++ b/src/scikit_build_core/resources/scikit-build.schema.json @@ -366,6 +366,7 @@ }, "overrides": { "type": "array", + "description": "A list of overrides to apply to the settings, based on the `if` selector.", "items": { "type": "object", "required": [ @@ -457,22 +458,32 @@ "additionalProperties": false, "properties": { "python-version": { - "type": "string" + "type": "string", + "description": "The two-digit Python version. Takes a specifier set." }, "implementation-name": { - "type": "string" + "type": "string", + "description": "The value of `sys.implementation.name`. Takes a regex" }, "implementation-version": { - "type": "string" + "type": "string", + "description": "Derived from `sys.implementation.version`, following PEP 508. Takes a specifier set." }, "platform-system": { - "type": "string" + "type": "string", + "description": "The value of `sys.platform`. Takes a regex." }, "platform-machine": { - "type": "string" + "type": "string", + "description": "The value of `platform.machine()`. Takes a regex." }, "platform-node": { - "type": "string" + "type": "string", + "description": "The value of `platform.node()`. Takes a regex." + }, + "state": { + "type": "string", + "description": "The state of the build, one of `sdist`, `wheel`, `editable`, `metadata_wheel`, and `metadata_editable`. Takes a regex." }, "env": { "type": "object", @@ -489,7 +500,8 @@ } }, "additionalProperties": false, - "minProperties": 1 + "minProperties": 1, + "description": "A table of environment variables mapped to either string regexs, or booleans. Valid 'truthy' environment variables are case insensitive `true`, `on`, `yes`, `y`, `t`, or a number more than 0." } } } diff --git a/src/scikit_build_core/settings/skbuild_read_settings.py b/src/scikit_build_core/settings/skbuild_read_settings.py index 810e64b4..3b6e4669 100644 --- a/src/scikit_build_core/settings/skbuild_read_settings.py +++ b/src/scikit_build_core/settings/skbuild_read_settings.py @@ -22,6 +22,8 @@ if TYPE_CHECKING: from collections.abc import Generator, Mapping + from .._compat.typing import Literal + __all__ = ["SettingsReader"] @@ -60,6 +62,9 @@ def override_match( *, match_all: bool, current_env: Mapping[str, str] | None, + current_state: Literal[ + "sdist", "wheel", "editable", "metadata_wheel", "metadata_editable" + ], python_version: str | None = None, implementation_name: str | None = None, implementation_version: str | None = None, @@ -67,6 +72,7 @@ def override_match( platform_machine: str | None = None, platform_node: str | None = None, env: dict[str, str] | None = None, + state: str | None = None, ) -> bool: matches = [] if current_env is None: @@ -108,6 +114,10 @@ def override_match( match_msg = regex_match(current_platform_node, platform_node) matches.append(match_msg) + if state is not None: + match_msg = regex_match(current_state, state) + matches.append(match_msg) + if env: for key, value in env.items(): if isinstance(value, bool): @@ -144,6 +154,9 @@ def __init__( pyproject: dict[str, Any], config_settings: Mapping[str, str | list[str]], *, + state: Literal[ + "sdist", "wheel", "editable", "metadata_wheel", "metadata_editable" + ], verify_conf: bool = True, env: Mapping[str, str] | None = None, ) -> None: @@ -163,11 +176,13 @@ def __init__( if "any" in if_override: any_override = if_override.pop("any") select = {k.replace("-", "_"): v for k, v in any_override.items()} - matched = override_match(match_all=False, current_env=env, **select) + matched = override_match( + match_all=False, current_env=env, current_state=state, **select + ) select = {k.replace("-", "_"): v for k, v in if_override.items()} if select: matched = matched and override_match( - match_all=True, current_env=env, **select + match_all=True, current_env=env, current_state=state, **select ) if matched: for key, value in override.items(): @@ -301,9 +316,14 @@ def from_file( pyproject_path: os.PathLike[str] | str, config_settings: Mapping[str, str | list[str]] | None, *, + state: Literal[ + "sdist", "wheel", "editable", "metadata_wheel", "metadata_editable" + ] = "sdist", verify_conf: bool = True, ) -> SettingsReader: with Path(pyproject_path).open("rb") as f: pyproject = tomllib.load(f) - return cls(pyproject, config_settings or {}, verify_conf=verify_conf) + return cls( + pyproject, config_settings or {}, verify_conf=verify_conf, state=state + ) diff --git a/src/scikit_build_core/settings/skbuild_schema.py b/src/scikit_build_core/settings/skbuild_schema.py index b388c316..53d67eba 100644 --- a/src/scikit_build_core/settings/skbuild_schema.py +++ b/src/scikit_build_core/settings/skbuild_schema.py @@ -88,12 +88,34 @@ def generate_skbuild_schema(tool_name: str = "scikit-build") -> dict[str, Any]: "minProperties": 1, "additionalProperties": False, "properties": { - "python-version": {"type": "string"}, - "implementation-name": {"type": "string"}, - "implementation-version": {"type": "string"}, - "platform-system": {"type": "string"}, - "platform-machine": {"type": "string"}, - "platform-node": {"type": "string"}, + "python-version": { + "type": "string", + "description": "The two-digit Python version. Takes a specifier set.", + }, + "implementation-name": { + "type": "string", + "description": "The value of `sys.implementation.name`. Takes a regex", + }, + "implementation-version": { + "type": "string", + "description": "Derived from `sys.implementation.version`, following PEP 508. Takes a specifier set.", + }, + "platform-system": { + "type": "string", + "description": "The value of `sys.platform`. Takes a regex.", + }, + "platform-machine": { + "type": "string", + "description": "The value of `platform.machine()`. Takes a regex.", + }, + "platform-node": { + "type": "string", + "description": "The value of `platform.node()`. Takes a regex.", + }, + "state": { + "type": "string", + "description": "The state of the build, one of `sdist`, `wheel`, `editable`, `metadata_wheel`, and `metadata_editable`. Takes a regex.", + }, "env": { "type": "object", "patternProperties": { @@ -101,11 +123,13 @@ def generate_skbuild_schema(tool_name: str = "scikit-build") -> dict[str, Any]: }, "additionalProperties": False, "minProperties": 1, + "description": "A table of environment variables mapped to either string regexs, or booleans. Valid 'truthy' environment variables are case insensitive `true`, `on`, `yes`, `y`, `t`, or a number more than 0.", }, }, } schema["properties"]["overrides"] = { "type": "array", + "description": "A list of overrides to apply to the settings, based on the `if` selector.", "items": { "type": "object", "required": ["if"], diff --git a/tests/test_dynamic_metadata.py b/tests/test_dynamic_metadata.py index 1359a309..99a3efc9 100644 --- a/tests/test_dynamic_metadata.py +++ b/tests/test_dynamic_metadata.py @@ -97,7 +97,7 @@ def mock_entry_points(monkeypatch): def test_dynamic_metadata(): with Path("pyproject.toml").open("rb") as ft: pyproject = tomllib.load(ft) - settings_reader = SettingsReader(pyproject, {}) + settings_reader = SettingsReader(pyproject, {}, state="metadata_wheel") settings = settings_reader.settings settings_reader.validate_may_exit() @@ -131,7 +131,7 @@ def test_plugin_metadata(): with Path("pyproject.toml").open("rb") as ft: pyproject = tomllib.load(ft) - settings_reader = SettingsReader(pyproject, {}) + settings_reader = SettingsReader(pyproject, {}, state="metadata_wheel") settings = settings_reader.settings settings_reader.validate_may_exit() @@ -153,7 +153,7 @@ def test_plugin_metadata(): def test_faulty_metadata(): with Path("faulty_project.toml").open("rb") as ft: pyproject = tomllib.load(ft) - settings_reader = SettingsReader(pyproject, {}) + settings_reader = SettingsReader(pyproject, {}, state="metadata_wheel") settings = settings_reader.settings settings_reader.validate_may_exit() @@ -166,7 +166,7 @@ def test_faulty_metadata(): def test_local_plugin_metadata(): with Path("local_pyproject.toml").open("rb") as ft: pyproject = tomllib.load(ft) - settings_reader = SettingsReader(pyproject, {}) + settings_reader = SettingsReader(pyproject, {}, state="metadata_wheel") settings = settings_reader.settings settings_reader.validate_may_exit() @@ -179,7 +179,7 @@ def test_local_plugin_metadata(): def test_warn_metadata(): with Path("warn_project.toml").open("rb") as ft: pyproject = tomllib.load(ft) - settings_reader = SettingsReader(pyproject, {}) + settings_reader = SettingsReader(pyproject, {}, state="metadata_wheel") settings = settings_reader.settings settings_reader.validate_may_exit() @@ -192,7 +192,9 @@ def test_warn_metadata(): def test_fail_experimental_metadata(): with Path("warn_project.toml").open("rb") as ft: pyproject = tomllib.load(ft) - settings_reader = SettingsReader(pyproject, {"experimental": "false"}) + settings_reader = SettingsReader( + pyproject, {"experimental": "false"}, state="metadata_wheel" + ) with pytest.raises(SystemExit) as exc: settings_reader.validate_may_exit() @@ -204,7 +206,7 @@ def test_fail_experimental_metadata(): def test_dual_metadata(): with Path("dual_project.toml").open("rb") as ft: pyproject = tomllib.load(ft) - settings_reader = SettingsReader(pyproject, {}) + settings_reader = SettingsReader(pyproject, {}, state="metadata_wheel") settings = settings_reader.settings settings_reader.validate_may_exit() @@ -215,7 +217,7 @@ def test_dual_metadata(): with Path("faulty_dual_project.toml").open("rb") as ft: pyproject = tomllib.load(ft) - settings_reader = SettingsReader(pyproject, {}) + settings_reader = SettingsReader(pyproject, {}, state="metadata_wheel") settings = settings_reader.settings settings_reader.validate_may_exit() diff --git a/tests/test_settings_overrides.py b/tests/test_settings_overrides.py index 53988ee8..36e133c5 100644 --- a/tests/test_settings_overrides.py +++ b/tests/test_settings_overrides.py @@ -402,3 +402,26 @@ def test_skbuild_env_bool_all_any( assert settings.sdist.cmake else: assert not settings.sdist.cmake + + +@pytest.mark.parametrize("state", ["wheel", "sdist"]) +def test_skbuild_overrides_state(state: str, tmp_path: Path): + pyproject_toml = tmp_path / "pyproject.toml" + pyproject_toml.write_text( + dedent( + f"""\ + [[tool.scikit-build.overrides]] + if.state = "{state}" + experimental = true + """ + ), + encoding="utf-8", + ) + + settings_reader = SettingsReader.from_file(pyproject_toml, {}, state="wheel") + settings = settings_reader.settings + + if state == "wheel": + assert settings.experimental + else: + assert not settings.experimental