Repository: scikit-build/scikit-build-core (python)

Issue:
CMake SDist only could skip sdist step
Currently I don't think it's useful to use CMake only in the SDist only step, as if someone builds a wheel from source, the SDist step won't run. We should consider this use case, and see if there's a way (maybe with overrides) to add controls for Source vs. from SDist builds.

I think `if.from-sdist` would do it?

Pull request:
feat: from-sdist for overrides
Close #766.

Will make sure it works for something like pybind11 ~~first~~. Will need one more thing, so making this a followup.


Patch:
diff --git a/docs/configuration.md b/docs/configuration.md
index 982e44b..ed758f3 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -561,10 +561,23 @@ provider = "scikit_build_core.metadata.regex"
 input = "src/mypackage/__init__.py"
 ```
 
-You can set a custom regex with `regex=`; use `(?P<value>...)` to capture the
-value you want to use. By default when targeting version, you get a reasonable
-regex for python files,
+You can set a custom regex with `regex=`. By default when targeting version, you
+get a reasonable regex for python files,
 `'(?i)^(__version__|VERSION)(?: ?\: ?str)? *= *([\'"])v?(?P<value>.+?)\2'`.
+You can set `result` to a format string to process the matches; the default is
+`"{value}"`. A more complex example:
+
+```toml
+[tool.scikit-build.metadata.version]
+provider = "scikit_build_core.metadata.regex"
+input = "src/mypackage/version.hpp"
+regex = '''(?sx)
+\#define \w+ VERSION_MAJOR \w+ (?P<major>\d+) .*?
+\#define \w+ VERSION_MINOR \w+ (?P<minor>\d+) .*?
+\#define \w+ VERSION_PATCH \w+ (?P<patch>\d+)
+'''
+result = "{major}.{minor}.{patch}"
+```
 
 ```{versionadded} 0.5
 
diff --git a/docs/overrides.md b/docs/overrides.md
index 6a1775a..585d310 100644
--- a/docs/overrides.md
+++ b/docs/overrides.md
@@ -127,6 +127,11 @@ and `metadata_editable`. Takes a regex.
 Note that you can build directly to wheel; you don't have to go through an
 SDist.
 
+### `from-sdist` (bool)
+
+This will be true if the `PKG-INFO` file exists, that is, if this is coming
+from an SDist. Takes a bool.
+
 ## Any matching condition
 
 If you use `if.any` instead of `if`, then the override is true if any one of the
diff --git a/docs/projects.md b/docs/projects.md
index e57078b..b0c42a0 100644
--- a/docs/projects.md
+++ b/docs/projects.md
@@ -53,3 +53,10 @@ In addition, most of the [RAPIDSAI](https://github.com/rapidsai) projects use sc
 * CuSpatial ([source](https://github.com/rapidsai/cuspatial/blob/HEAD/python/cuspatial/pyproject.toml))
 * RMM ([source](https://github.com/rapidsai/rmm/blob/HEAD/python/rmm/pyproject.toml))
 * Raft ([source](https://github.com/rapidsai/raft/blob/HEAD/python/pylibraft/pyproject.toml))
+
+The [Insight Toolkit (ITK)](https://docs.itk.org), the initial target project
+for scikit-build classic, has [transitioned to
+sckit-build-core](https://github.com/InsightSoftwareConsortium/ITKPythonPackage/blob/master/scripts/pyproject.toml.in).
+ITK currently provides one example of a production SWIG-based deployment. In
+addition, dozens of [ITK-based extension packages are configured with
+scikit-build-core](https://github.com/topics/itk-module).
diff --git a/src/scikit_build_core/metadata/regex.py b/src/scikit_build_core/metadata/regex.py
index cc10851..6963498 100644
--- a/src/scikit_build_core/metadata/regex.py
+++ b/src/scikit_build_core/metadata/regex.py
@@ -14,6 +14,9 @@ def __dir__() -> list[str]:
     return __all__
 
 
+KEYS = {"input", "regex", "result"}
+
+
 def dynamic_metadata(
     field: str,
     settings: Mapping[str, Any],
@@ -22,8 +25,8 @@ def dynamic_metadata(
     if field not in {"version", "description", "requires-python"}:
         msg = "Only string fields supported by this plugin"
         raise RuntimeError(msg)
-    if settings.keys() > {"input", "regex"}:
-        msg = "Only 'input' and 'regex' settings allowed by this plugin"
+    if settings.keys() > KEYS:
+        msg = f"Only {KEYS} settings allowed by this plugin"
         raise RuntimeError(msg)
     if "input" not in settings:
         msg = "Must contain the 'input' setting to perform a regex on"
@@ -31,15 +34,18 @@ def dynamic_metadata(
     if field != "version" and "regex" not in settings:
         msg = "Must contain the 'regex' setting if not getting version"
         raise RuntimeError(msg)
-    if not all(isinstance(x, str) for x in settings.values()):
-        msg = "Must set 'input' and/or 'regex' to strings"
-        raise RuntimeError(msg)
+    for key in KEYS:
+        if key in settings and not isinstance(settings[key], str):
+            msg = f"Setting {key!r} must be a string"
+            raise RuntimeError(msg)
 
     input_filename = settings["input"]
     regex = settings.get(
         "regex",
         r'(?i)^(__version__|VERSION)(?: ?\: ?str)? *= *([\'"])v?(?P<value>.+?)\2',
     )
+    result = settings.get("result", "{value}")
+    assert isinstance(result, str)
 
     with Path(input_filename).open(encoding="utf-8") as f:
         match = re.search(regex, f.read(), re.MULTILINE)
@@ -48,4 +54,4 @@ def dynamic_metadata(
         msg = f"Couldn't find {regex!r} in {input_filename}"
         raise RuntimeError(msg)
 
-    return match.group("value")
+    return result.format(*match.groups(), **match.groupdict())
diff --git a/src/scikit_build_core/resources/scikit-build.schema.json b/src/scikit_build_core/resources/scikit-build.schema.json
index 4fab59f..25c48e9 100644
--- a/src/scikit_build_core/resources/scikit-build.schema.json
+++ b/src/scikit_build_core/resources/scikit-build.schema.json
@@ -624,6 +624,10 @@
           "type": "string",
           "description": "The state of the build, one of `sdist`, `wheel`, `editable`, `metadata_wheel`, and `metadata_editable`. Takes a regex."
         },
+        "from-sdist": {
+          "type": "boolean",
+          "description": "Whether the build is from an sdist."
+        },
         "env": {
           "type": "object",
           "patternProperties": {
diff --git a/src/scikit_build_core/settings/skbuild_read_settings.py b/src/scikit_build_core/settings/skbuild_read_settings.py
index 15bb694..f59697e 100644
--- a/src/scikit_build_core/settings/skbuild_read_settings.py
+++ b/src/scikit_build_core/settings/skbuild_read_settings.py
@@ -71,6 +71,7 @@ def override_match(
     current_state: Literal[
         "sdist", "wheel", "editable", "metadata_wheel", "metadata_editable"
     ],
+    has_dist_info: bool,
     python_version: str | None = None,
     implementation_name: str | None = None,
     implementation_version: str | None = None,
@@ -79,8 +80,14 @@ def override_match(
     platform_node: str | None = None,
     env: dict[str, str] | None = None,
     state: str | None = None,
+    from_sdist: bool | None = None,
 ) -> bool:
+    # This makes a matches list. A match is a string; if it's an empty string,
+    # then it did not match. If it's not empty, it did match, and the string
+    # will be printed in the logs - it's the match reason. If match_all is True,
+    # then all must match, otherwise any match will count.
     matches = []
+
     if current_env is None:
         current_env = os.environ
 
@@ -124,6 +131,12 @@ def override_match(
         match_msg = regex_match(current_state, state)
         matches.append(match_msg)
 
+    if from_sdist is not None:
+        if has_dist_info:
+            matches.append("from sdist due to PKG-INFO" if from_sdist else "")
+        else:
+            matches.append("" if from_sdist else "not from sdist, no PKG-INFO")
+
     if env:
         for key, value in env.items():
             if isinstance(value, bool):
@@ -264,8 +277,10 @@ def process_overides(
     env: Mapping[str, str] | None = None,
 ) -> None:
     """
-    Process overrides into the main dictionary if they match. Modifies the input dictionary.
+    Process overrides into the main dictionary if they match. Modifies the input
+    dictionary. Must be run from the package directory.
     """
+    has_dist_info = Path("PKG-INFO").is_file()
 
     for override in tool_skb.pop("overrides", []):
         matched = True
@@ -280,7 +295,11 @@ def process_overides(
             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, current_state=state, **select
+                match_all=False,
+                current_env=env,
+                current_state=state,
+                has_dist_info=has_dist_info,
+                **select,
             )
 
         inherit_override = override.pop("inherit", {})
@@ -291,7 +310,11 @@ def process_overides(
         select = {k.replace("-", "_"): v for k, v in if_override.items()}
         if select:
             matched = matched and override_match(
-                match_all=True, current_env=env, current_state=state, **select
+                match_all=True,
+                current_env=env,
+                current_state=state,
+                has_dist_info=has_dist_info,
+                **select,
             )
         if matched:
             for key, value in override.items():
diff --git a/src/scikit_build_core/settings/skbuild_schema.py b/src/scikit_build_core/settings/skbuild_schema.py
index 2c86a1c..6062d77 100644
--- a/src/scikit_build_core/settings/skbuild_schema.py
+++ b/src/scikit_build_core/settings/skbuild_schema.py
@@ -116,6 +116,10 @@ def generate_skbuild_schema(tool_name: str = "scikit-build") -> dict[str, Any]:
                 "type": "string",
                 "description": "The state of the build, one of `sdist`, `wheel`, `editable`, `metadata_wheel`, and `metadata_editable`. Takes a regex.",
             },
+            "from-sdist": {
+                "type": "boolean",
+                "description": "Whether the build is from an sdist.",
+            },
             "env": {
                 "type": "object",
                 "patternProperties": {


Test patch:
diff --git a/tests/test_dynamic_metadata.py b/tests/test_dynamic_metadata.py
index e23c26a..c3bc9ab 100644
--- a/tests/test_dynamic_metadata.py
+++ b/tests/test_dynamic_metadata.py
@@ -4,6 +4,7 @@ import importlib
 import shutil
 import subprocess
 import sys
+import textwrap
 import types
 import zipfile
 from pathlib import Path
@@ -274,3 +275,36 @@ def test_regex_errors() -> None:
         regex.dynamic_metadata("version", {})
     with pytest.raises(RuntimeError, match="Only string fields supported"):
         regex.dynamic_metadata("author", {"input": "x", "regex": "x"})
+
+
+def test_multipart_regex(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
+    d = tmp_path / "test_multi_regex"
+    d.mkdir()
+    monkeypatch.chdir(d)
+
+    with Path("version.hpp").open("w") as f:
+        f.write(
+            textwrap.dedent(
+                """\
+            #define VERSION_MAJOR 1
+            // Comment
+            #define VERSION_MINOR 2
+            #define VERSION_PATCH 3dev1
+            """
+            )
+        )
+
+    version = regex.dynamic_metadata(
+        "version",
+        {
+            "input": "version.hpp",
+            "regex": r"""(?sx)
+            \#define \s+ VERSION_MAJOR \s+ (?P<major>\d+) .*?
+            \#define \s+ VERSION_MINOR \s+ (?P<minor>\d+) .*?
+            \#define \s+ VERSION_PATCH \s+ (?P<patch>\w+)
+            """,
+            "result": "{major}.{minor}.{patch}",
+        },
+    )
+
+    assert version == "1.2.3dev1"
diff --git a/tests/test_settings_overrides.py b/tests/test_settings_overrides.py
index d17ef22..8e93dfe 100644
--- a/tests/test_settings_overrides.py
+++ b/tests/test_settings_overrides.py
@@ -490,3 +490,43 @@ def test_skbuild_overrides_inherit(inherit: str, tmp_path: Path):
         assert settings.wheel.exclude == ["xx", "yy", "x", "y"]
         assert settings.install.components == ["c", "d", "a", "b"]
         assert settings.cmake.define == {"a": "A", "b": "B", "c": "C"}
+
+
+@pytest.mark.parametrize("from_sdist", [True, False])
+def test_skbuild_overrides_from_sdist(
+    tmp_path: Path, monkeypatch: pytest.MonkeyPatch, from_sdist: bool
+):
+    pyproject_toml = (
+        tmp_path / ("from_sdist" if from_sdist else "not_from_sdist") / "pyproject.toml"
+    )
+    pyproject_toml.parent.mkdir(exist_ok=True)
+    pyproject_toml.write_text(
+        dedent(
+            """\
+            [tool.scikit-build]
+            cmake.version = ">=3.15"
+            wheel.cmake = false
+            sdist.cmake = false
+
+            [[tool.scikit-build.overrides]]
+            if.from-sdist = false
+            wheel.cmake = true
+
+            [[tool.scikit-build.overrides]]
+            if.from-sdist = true
+            sdist.cmake = true
+            """
+        ),
+        encoding="utf-8",
+    )
+
+    if from_sdist:
+        pyproject_toml.parent.joinpath("PKG-INFO").touch(exist_ok=True)
+
+    monkeypatch.chdir(pyproject_toml.parent)
+
+    settings_reader = SettingsReader.from_file(pyproject_toml, state="wheel")
+    settings = settings_reader.settings
+
+    assert settings.wheel.cmake != from_sdist
+    assert settings.sdist.cmake == from_sdist