From 5b05319dc1d40de5dd1435bb8b3fc1d5c5357eed Mon Sep 17 00:00:00 2001 From: Cristian Le Date: Mon, 17 Feb 2025 16:37:47 +0100 Subject: [PATCH 1/3] Initial support for `build.build-requires` injection Signed-off-by: Cristian Le --- README.md | 4 ++ src/scikit_build_core/builder/get_requires.py | 2 + .../resources/scikit-build.schema.json | 10 +++++ .../settings/skbuild_model.py | 6 +++ .../build_requires_project.toml | 13 ++++++ tests/test_dynamic_metadata.py | 40 ++++++++++++++++++- 6 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 tests/packages/dynamic_metadata/build_requires_project.toml diff --git a/README.md b/README.md index b6a978865..cdd561aa5 100644 --- a/README.md +++ b/README.md @@ -281,6 +281,10 @@ build.targets = [] # Verbose printout when building. build.verbose = false +# Additional ``build-system.requires``. Intended to be used in combination with +# ``overrides``. +build.requires = [] + # The components to install. If empty, all default components are installed. install.components = [] diff --git a/src/scikit_build_core/builder/get_requires.py b/src/scikit_build_core/builder/get_requires.py index be22448de..3a6242b13 100644 --- a/src/scikit_build_core/builder/get_requires.py +++ b/src/scikit_build_core/builder/get_requires.py @@ -140,6 +140,8 @@ def dynamic_metadata(self) -> Generator[str, None, None]: if self.settings.fail: return + yield from self.settings.build.requires + for dynamic_metadata in self.settings.metadata.values(): if "provider" in dynamic_metadata: config = dynamic_metadata.copy() diff --git a/src/scikit_build_core/resources/scikit-build.schema.json b/src/scikit_build_core/resources/scikit-build.schema.json index d8e87df88..4518f03ee 100644 --- a/src/scikit_build_core/resources/scikit-build.schema.json +++ b/src/scikit_build_core/resources/scikit-build.schema.json @@ -299,6 +299,13 @@ "type": "boolean", "default": false, "description": "Verbose printout when building." + }, + "requires": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Additional ``build-system.requires``. Intended to be used in combination with ``overrides``." } } }, @@ -560,6 +567,9 @@ }, "targets": { "$ref": "#/$defs/inherit" + }, + "requires": { + "$ref": "#/$defs/inherit" } } }, diff --git a/src/scikit_build_core/settings/skbuild_model.py b/src/scikit_build_core/settings/skbuild_model.py index af1c7c4f2..9f8c8ddf2 100644 --- a/src/scikit_build_core/settings/skbuild_model.py +++ b/src/scikit_build_core/settings/skbuild_model.py @@ -277,6 +277,12 @@ class BuildSettings: Verbose printout when building. """ + requires: List[str] = dataclasses.field(default_factory=list) + """ + Additional ``build-system.requires``. Intended to be used in combination + with ``overrides``. + """ + @dataclasses.dataclass class InstallSettings: diff --git a/tests/packages/dynamic_metadata/build_requires_project.toml b/tests/packages/dynamic_metadata/build_requires_project.toml new file mode 100644 index 000000000..cf9956a2a --- /dev/null +++ b/tests/packages/dynamic_metadata/build_requires_project.toml @@ -0,0 +1,13 @@ +[build-system] +requires = ["scikit-build-core"] +build-backend = "scikit_build_core.build" + +[project] +name = "more_build_requires" + +[tool.scikit-build] +build.requires = ["foo"] + +[[tool.scikit-build.overrides]] +if.env.LOCAL_FOO = true +build.requires = ["foo @ {root:uri}/foo"] diff --git a/tests/test_dynamic_metadata.py b/tests/test_dynamic_metadata.py index de0f0054e..57ce38112 100644 --- a/tests/test_dynamic_metadata.py +++ b/tests/test_dynamic_metadata.py @@ -7,7 +7,7 @@ import types import zipfile from pathlib import Path -from typing import Any +from typing import TYPE_CHECKING, Any import pytest from packaging.version import Version @@ -22,6 +22,9 @@ from pathutils import contained +if TYPE_CHECKING: + from typing import Literal + # these are mock plugins returning known results # it turns out to be easier to create EntryPoint objects pointing to real @@ -345,3 +348,38 @@ def test_regex_remove( ) assert version == ("1.2.3dev1" if dev else "1.2.3") + + +@pytest.mark.usefixtures("package_dynamic_metadata") +@pytest.mark.parametrize("override", [None, "env", "sdist"]) +def test_build_requires_field(override, monkeypatch) -> None: + shutil.copy("build_requires_project.toml", "pyproject.toml") + + if override == "env": + monkeypatch.setenv("LOCAL_FOO", "True") + else: + monkeypatch.delenv("LOCAL_FOO", raising=False) + + with Path("pyproject.toml").open("rb") as ft: + pyproject = tomllib.load(ft) + state: Literal["sdist", "metadata_wheel"] = ( + "sdist" if override == "sdist" else "metadata_wheel" + ) + settings_reader = SettingsReader(pyproject, {}, state=state) + + settings_reader.validate_may_exit() + + if override is None: + assert set(GetRequires().dynamic_metadata()) == { + "foo", + } + elif override == "env": + assert set(GetRequires().dynamic_metadata()) == { + # TODO: This should be resolved to actual path + "foo @ {root:uri}/foo", + } + elif override == "sdist": + assert set(GetRequires().dynamic_metadata()) == { + # TODO: Check if special handling should be done for sdist + "foo", + } From 86ddf86010d5307c465d6afc20ca412cab7070eb Mon Sep 17 00:00:00 2001 From: Cristian Le Date: Wed, 19 Feb 2025 18:58:54 +0100 Subject: [PATCH 2/3] Support `{root:uri}` expansion similar to `hatchling` Signed-off-by: Cristian Le --- src/scikit_build_core/builder/get_requires.py | 26 ++++++++++++++++++- .../build_requires_project.toml | 2 +- tests/test_dynamic_metadata.py | 9 ++++--- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/scikit_build_core/builder/get_requires.py b/src/scikit_build_core/builder/get_requires.py index 3a6242b13..26b6d7c4b 100644 --- a/src/scikit_build_core/builder/get_requires.py +++ b/src/scikit_build_core/builder/get_requires.py @@ -5,6 +5,7 @@ import importlib.util import os import sysconfig +from pathlib import Path from typing import TYPE_CHECKING, Literal from packaging.tags import sys_tags @@ -66,6 +67,28 @@ def _load_scikit_build_settings( return SettingsReader.from_file("pyproject.toml", config_settings).settings +@dataclasses.dataclass() +class RootPathResolver: + """Handle ``{root:uri}`` like formatting similar to ``hatchling``.""" + + path: Path = dataclasses.field(default_factory=Path) + + def __post_init__(self) -> None: + self.path = self.path.resolve() + + def __format__(self, fmt: str) -> str: + command, _, rest = fmt.partition(":") + if command == "parent": + parent = RootPathResolver(self.path.parent) + return parent.__format__(rest) + if command == "uri" and rest == "": + return self.path.as_uri() + if command == "" and rest == "": + return str(self) + msg = f"Could not handle format: {fmt}" + raise ValueError(msg) + + @dataclasses.dataclass(frozen=True) class GetRequires: settings: ScikitBuildSettings = dataclasses.field( @@ -140,7 +163,8 @@ def dynamic_metadata(self) -> Generator[str, None, None]: if self.settings.fail: return - yield from self.settings.build.requires + for build_require in self.settings.build.requires: + yield build_require.format(root=RootPathResolver()) for dynamic_metadata in self.settings.metadata.values(): if "provider" in dynamic_metadata: diff --git a/tests/packages/dynamic_metadata/build_requires_project.toml b/tests/packages/dynamic_metadata/build_requires_project.toml index cf9956a2a..9bf36232e 100644 --- a/tests/packages/dynamic_metadata/build_requires_project.toml +++ b/tests/packages/dynamic_metadata/build_requires_project.toml @@ -10,4 +10,4 @@ build.requires = ["foo"] [[tool.scikit-build.overrides]] if.env.LOCAL_FOO = true -build.requires = ["foo @ {root:uri}/foo"] +build.requires = ["foo @ {root:parent:uri}/foo"] diff --git a/tests/test_dynamic_metadata.py b/tests/test_dynamic_metadata.py index 57ce38112..842c14875 100644 --- a/tests/test_dynamic_metadata.py +++ b/tests/test_dynamic_metadata.py @@ -360,7 +360,8 @@ def test_build_requires_field(override, monkeypatch) -> None: else: monkeypatch.delenv("LOCAL_FOO", raising=False) - with Path("pyproject.toml").open("rb") as ft: + pyproject_path = Path("pyproject.toml") + with pyproject_path.open("rb") as ft: pyproject = tomllib.load(ft) state: Literal["sdist", "metadata_wheel"] = ( "sdist" if override == "sdist" else "metadata_wheel" @@ -374,9 +375,11 @@ def test_build_requires_field(override, monkeypatch) -> None: "foo", } elif override == "env": + # evaluate ../foo as uri + foo_path = pyproject_path.absolute().parent.parent / "foo" + foo_path = foo_path.absolute() assert set(GetRequires().dynamic_metadata()) == { - # TODO: This should be resolved to actual path - "foo @ {root:uri}/foo", + f"foo @ {foo_path.as_uri()}", } elif override == "sdist": assert set(GetRequires().dynamic_metadata()) == { From aae3cb113e9f8c357295f24a8fca76dc870c4cbc Mon Sep 17 00:00:00 2001 From: Cristian Le Date: Fri, 21 Feb 2025 15:09:13 +0100 Subject: [PATCH 3/3] Document `build.requires` feature Signed-off-by: Cristian Le --- docs/configuration/dynamic.md | 39 +++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/docs/configuration/dynamic.md b/docs/configuration/dynamic.md index 49c8a4931..f46d44bd9 100644 --- a/docs/configuration/dynamic.md +++ b/docs/configuration/dynamic.md @@ -112,6 +112,45 @@ metadata.readme.provider = "scikit_build_core.metadata.fancy_pypi_readme" # tool.hatch.metadata.hooks.fancy-pypi-readme options here ``` +## `build-system.requires`: Scikit-build-core's `build.requires` + +If you need to inject and manipulate additional `build-system.requires`, you can +use the `build.requires`. This is intended to be used in combination with +[](./overrides.md). + +This is not technically a dynamic metadata and thus does not have to have the +`dynamic` field defined, and it is not defined under the `metadata` table, but +similar to the other dynamic metadata it injects the additional +`build-system.requires`. + +```toml +[package] +name = "mypackage" + +[tool.scikit-build] +build.requires = ["foo"] + +[[tool.scikit-build.overrides]] +if.from-sdist = false +build.requires = ["foo @ {root:uri}/foo"] +``` + +This example shows a common use-case where the package has a default +`build-system.requires` pointing to the package `foo` in the PyPI index, but +when built from the original git checkout or equivalent, the local folder is +used as dependency instead by resolving the `{root:uri}` to a file uri pointing +to the folder where the `pyproject.toml` is located. + +```{note} +In order to be compliant with the package index, when building from `sdist`, the +`build.requires` **MUST NOT** have any `@` redirects. This rule may be later +enforced explicitly. +``` + +```{versionadded} 0.11 + +``` + ## Generate files with dynamic metadata You can write out metadata to file(s) as well. Other info might become available