Repository: scikit-build/scikit-build-core
Pull request #825 (2024-07-25 15:37:28)

feat: add messages config for failure/success
See #112.

This includes `.format()` so that items can be added here later in a backward-compatible way.


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/scikit-build/scikit-build-core/pull/825?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scikit-build) Report
Attention: Patch coverage is `61.53846%` with `15 lines` in your changes missing coverage. Please review.
> Project coverage is 83.51%. Comparing base [(`d977a08`)](https://app.codecov.io/gh/scikit-build/scikit-build-core/commit/d977a084ff2716bd66e84ac2c5248618b069d6b5?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scikit-build) to head [(`7a71736`)](https://app.codecov.io/gh/scikit-build/scikit-build-core/commit/7a7173608fde78d4bad897dd090bdc1c6eb29117?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scikit-build).

| [Files](https://app.codecov.io/gh/scikit-build/scikit-build-core/pull/825?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scikit-build) | Patch % | Lines |
|---|---|---|
| [src/scikit\_build\_core/build/\_\_init\_\_.py](https://app.codecov.io/gh/scikit-build/scikit-build-core/pull/825?src=pr&el=tree&filepath=src%2Fscikit_build_core%2Fbuild%2F__init__.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scikit-build#diff-c3JjL3NjaWtpdF9idWlsZF9jb3JlL2J1aWxkL19faW5pdF9fLnB5) | 28.57% | [10 Missing :warning: ](https://app.codecov.io/gh/scikit-build/scikit-build-core/pull/825?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scikit-build) |
| [src/scikit\_build\_core/build/wheel.py](https://app.codecov.io/gh/scikit-build/scikit-build-core/pull/825?src=pr&el=tree&filepath=src%2Fscikit_build_core%2Fbuild%2Fwheel.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scikit-build#diff-c3JjL3NjaWtpdF9idWlsZF9jb3JlL2J1aWxkL3doZWVsLnB5) | 66.66% | [5 Missing :warning: ](https://app.codecov.io/gh/scikit-build/scikit-build-core/pull/825?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scikit-build) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #825      +/-   ##
==========================================
- Coverage   83.76%   83.51%   -0.26%     
==========================================
  Files          74       74              
  Lines        4139     4173      +34     
==========================================
+ Hits         3467     3485      +18     
- Misses        672      688      +16     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/scikit-build/scikit-build-core/pull/825?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scikit-build).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scikit-build).


Patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index f663418..db73127 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -42,7 +42,7 @@ repos:
     rev: 1.18.0
     hooks:
       - id: blacken-docs
-        additional_dependencies: [black==23.*]
+        additional_dependencies: [black==24.*]
 
   - repo: https://github.com/cheshirekow/cmake-format-precommit
     rev: v0.6.13
@@ -84,6 +84,7 @@ repos:
           - nox
           - packaging
           - pytest<8
+          - pytest-subprocess
           - rich
           - setuptools-scm
           - tomli
diff --git a/README.md b/README.md
index a4d9d12..845632c 100644
--- a/README.md
+++ b/README.md
@@ -299,6 +299,12 @@ generate[].template-path = ""
 # files & remember to gitignore the file.
 generate[].location = "install"
 
+# A message to print after a build failure.
+messages.after-failure = ""
+
+# A message to print after a successful build.
+messages.after-success = ""
+
 # List dynamic metadata fields and hook locations in this table.
 metadata = {}
 
diff --git a/docs/configuration.md b/docs/configuration.md
index f961e50..7107659 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -672,6 +672,29 @@ On the command line, you can pass `-Ceditable.mode=inplace` to enable this mode.
 
 :::
 
+## Messages
+
+You can add a message to be printed after a successful or failed build. For example:
+
+```toml
+[tool.scikit-build]
+messages.after-sucesss = "[green]Wheel successfully built"
+messages.after-failure = """
+[red bold]Sorry[/bold], build failed.
+"""
+```
+
+This will be run through Python's formatter, so escape curly brackets. There
+currently are no items provided in the format call, but some may be added in the
+future if requested.
+
+A small mini-language for colorization using `[]` is also provided somewhat
+similar to [Rich](https://rich.readthedocs.io). Supported keywords inside the
+square brackets are `red`, `green`, `yellow`, `blue`, `magenta`, `cyan`, and
+`bold`.  These can be reset with a proceeding `/`. `reset` is also supported.
+Unsupported options will be ignored, but keep in mind other keywords from Rich
+might be added in the future.
+
 ## Other options
 
 You can select a custom build dir; by default scikit-build-core will use a
diff --git a/docs/overrides.md b/docs/overrides.md
index 835a97f..6afd649 100644
--- a/docs/overrides.md
+++ b/docs/overrides.md
@@ -132,6 +132,38 @@ SDist.
 This will be true if the `PKG-INFO` file exists, that is, if this is coming
 from an SDist. Takes a bool.
 
+### `system-cmake` (version)
+
+This will match if there's a system CMake matching this version specification.
+
+```toml
+[[tool.scikit-build.overrides]]
+if.system-cmake = ">=3.15"
+cmake.version = ""
+message.after-success = "Built using a system CMake, not a wheel"
+```
+
+### `cmake-wheel` (bool)
+
+This matches true if a wheel is known to be provided for this platform, and
+false otherwise.  This is useful for specifying a pure Python fallback on
+systems that don't have provided CMake wheels. Ninja wheels are available on all
+platforms CMake is, so a separate override for Ninja isn't needed. Often
+combined with `system-cmake`.
+
+For example, this would be an optional build only on systems with CMake or
+supported by wheels:
+
+```toml
+[tool.scikit-build]
+wheel.cmake = false
+
+[[tool.scikit-build.overrides]]
+if.any.system-cmake = ">=3.15"
+if.any.cmake-wheel = true
+wheel.cmake = true
+```
+
 ### `failed` (bool)
 
 This override is a bit special. If a build fails, scikit-build-core will check
diff --git a/src/scikit_build_core/build/__init__.py b/src/scikit_build_core/build/__init__.py
index ba477aa..7b75fa7 100644
--- a/src/scikit_build_core/build/__init__.py
+++ b/src/scikit_build_core/build/__init__.py
@@ -37,6 +37,8 @@ def build_wheel(
     except FailedLiveProcessError as err:
         sys.stdout.flush()
         rich_print(f"\n[red bold]*** {' '.join(err.args)}", file=sys.stderr)
+        if err.msg:
+            rich_print(err.msg)
         raise SystemExit(1) from None
 
 
@@ -59,6 +61,8 @@ def build_editable(
     except FailedLiveProcessError as err:
         sys.stdout.flush()
         rich_print(f"\n[red bold]*** {' '.join(err.args)}", file=sys.stderr)
+        if err.msg:
+            rich_print(err.msg)
         raise SystemExit(1) from None
 
 
@@ -91,9 +95,18 @@ def build_sdist(
     sdist_directory: str,
     config_settings: dict[str, list[str] | str] | None = None,
 ) -> str:
+    from .._logging import rich_print
+    from ..errors import FailedLiveProcessError
     from .sdist import build_sdist as skbuild_build_sdist
 
-    return skbuild_build_sdist(sdist_directory, config_settings)
+    try:
+        return skbuild_build_sdist(sdist_directory, config_settings)
+    except FailedLiveProcessError as err:
+        sys.stdout.flush()
+        rich_print(f"\n[red bold]*** {' '.join(err.args)}", file=sys.stderr)
+        if err.msg:
+            rich_print(err.msg)
+        raise SystemExit(1) from None
 
 
 def get_requires_for_build_sdist(
diff --git a/src/scikit_build_core/build/wheel.py b/src/scikit_build_core/build/wheel.py
index 510a64f..d993a1d 100644
--- a/src/scikit_build_core/build/wheel.py
+++ b/src/scikit_build_core/build/wheel.py
@@ -111,6 +111,7 @@ def _get_packages(
 @dataclasses.dataclass
 class WheelImplReturn:
     wheel_filename: str
+    settings: ScikitBuildSettings
     mapping: dict[str, str] = dataclasses.field(default_factory=dict)
 
 
@@ -146,6 +147,9 @@ def _build_wheel_impl(
     settings_reader.validate_may_exit()
 
     if settings_reader.settings.fail:
+        if settings_reader.settings.messages.after_failure:
+            rich_print(settings_reader.settings.messages.after_failure.format())
+            raise SystemExit(7)
         rich_error("scikit-build-core's fail setting was enabled. Exiting immediately.")
 
     # Warn if cmake or ninja is in build-system.requires
@@ -177,6 +181,7 @@ def _build_wheel_impl(
             pyproject, config_settings or {}, state=state, retry=True
         )
         if "failed" not in settings_reader.overrides:
+            err.msg = settings_reader.settings.messages.after_failure.format()
             raise
 
         rich_print(
@@ -187,15 +192,19 @@ def _build_wheel_impl(
 
         settings_reader.validate_may_exit()
 
-        return _build_wheel_impl_impl(
-            wheel_directory,
-            metadata_directory,
-            exit_after_config=exit_after_config,
-            editable=editable,
-            state=state,
-            settings=settings_reader.settings,
-            pyproject=pyproject,
-        )
+        try:
+            return _build_wheel_impl_impl(
+                wheel_directory,
+                metadata_directory,
+                exit_after_config=exit_after_config,
+                editable=editable,
+                state=state,
+                settings=settings_reader.settings,
+                pyproject=pyproject,
+            )
+        except FailedLiveProcessError as err2:
+            err2.msg = settings_reader.settings.messages.after_failure.format()
+            raise
 
 
 def _build_wheel_impl_impl(
@@ -343,7 +352,7 @@ def _build_wheel_impl_impl(
                 if not path.parent.is_dir():
                     path.parent.mkdir(exist_ok=True, parents=True)
                 path.write_bytes(data)
-            return WheelImplReturn(wheel_filename=dist_info.name)
+            return WheelImplReturn(wheel_filename=dist_info.name, settings=settings)
 
         for gen in settings.generate:
             contents = generate_file_contents(gen, metadata)
@@ -390,7 +399,7 @@ def _build_wheel_impl_impl(
             )
 
             if exit_after_config:
-                return WheelImplReturn("")
+                return WheelImplReturn("", settings=settings)
 
             default_gen = (
                 "MSVC"
@@ -486,4 +495,8 @@ def _build_wheel_impl_impl(
 
     wheel_filename: str = wheel.wheelpath.name
     rich_print(f"[green]***[/green] [bold]Created[/bold] {wheel_filename}...")
-    return WheelImplReturn(wheel_filename=wheel_filename, mapping=mapping)
+    if settings.messages.after_success:
+        rich_print(settings.messages.after_success.format())
+    return WheelImplReturn(
+        wheel_filename=wheel_filename, mapping=mapping, settings=settings
+    )
diff --git a/src/scikit_build_core/errors.py b/src/scikit_build_core/errors.py
index 06469e5..b3cfd4d 100644
--- a/src/scikit_build_core/errors.py
+++ b/src/scikit_build_core/errors.py
@@ -76,6 +76,10 @@ class FailedLiveProcessError(Exception):
     Exception for when output was not being redirected.
     """
 
+    def __init__(self, *args: object, msg: str = "") -> None:
+        super().__init__(*args)
+        self.msg = msg
+
 
 class CMakeAccessError(FailedProcessError):
     """
diff --git a/src/scikit_build_core/resources/known_wheels.toml b/src/scikit_build_core/resources/known_wheels.toml
index ba4f5cf..1f0165a 100644
--- a/src/scikit_build_core/resources/known_wheels.toml
+++ b/src/scikit_build_core/resources/known_wheels.toml
@@ -14,8 +14,8 @@ known-wheels = [
     "manylinux_2_17_aarch64",
     "manylinux_2_17_x86_64",
     "manylinux_2_17_i686",
-    "manylinux_2_5_x86_64",
-    "manylinux_2_5_i686",
+    "manylinux_2_12_x86_64",
+    "manylinux_2_12_i686",
 ]
 
 [tool.scikit-build.ninja]
diff --git a/src/scikit_build_core/resources/scikit-build.schema.json b/src/scikit_build_core/resources/scikit-build.schema.json
index ead6a26..427a030 100644
--- a/src/scikit_build_core/resources/scikit-build.schema.json
+++ b/src/scikit_build_core/resources/scikit-build.schema.json
@@ -360,6 +360,22 @@
         ]
       }
     },
+    "messages": {
+      "type": "object",
+      "additionalProperties": false,
+      "properties": {
+        "after-failure": {
+          "type": "string",
+          "default": "",
+          "description": "A message to print after a build failure."
+        },
+        "after-success": {
+          "type": "string",
+          "default": "",
+          "description": "A message to print after a successful build."
+        }
+      }
+    },
     "metadata": {
       "type": "object",
       "description": "List dynamic metadata fields and hook locations in this table.",
@@ -565,6 +581,9 @@
           "generate": {
             "$ref": "#/properties/generate"
           },
+          "messages": {
+            "$ref": "#/properties/messages"
+          },
           "metadata": {
             "$ref": "#/properties/metadata"
           },
@@ -640,6 +659,14 @@
           "type": "boolean",
           "description": "Matches if the build fails. A build will be retried if there is at least one matching override with this set to true."
         },
+        "system-cmake": {
+          "type": "string",
+          "description": "The version of CMake found on the system. Takes a specifier set."
+        },
+        "cmake-wheel": {
+          "type": "boolean",
+          "description": "Whether a cmake wheel is known to be provided for this system."
+        },
         "env": {
           "type": "object",
           "patternProperties": {
diff --git a/src/scikit_build_core/settings/skbuild_model.py b/src/scikit_build_core/settings/skbuild_model.py
index 1e2af51..6c18cdc 100644
--- a/src/scikit_build_core/settings/skbuild_model.py
+++ b/src/scikit_build_core/settings/skbuild_model.py
@@ -9,12 +9,13 @@ from .._compat.typing import Annotated, Literal
 
 __all__ = [
     "BackportSettings",
+    "BuildSettings",
     "CMakeSettings",
     "EditableSettings",
     "GenerateSettings",
     "InstallSettings",
-    "BuildSettings",
     "LoggingSettings",
+    "MessagesSettings",
     "NinjaSettings",
     "SDistSettings",
     "ScikitBuildSettings",
@@ -293,6 +294,23 @@ class GenerateSettings:
     """
 
 
+@dataclasses.dataclass
+class MessagesSettings:
+    """
+    Settings for messages.
+    """
+
+    after_failure: str = ""
+    """
+    A message to print after a build failure.
+    """
+
+    after_success: str = ""
+    """
+    A message to print after a successful build.
+    """
+
+
 @dataclasses.dataclass
 class ScikitBuildSettings:
     cmake: CMakeSettings = dataclasses.field(default_factory=CMakeSettings)
@@ -305,6 +323,7 @@ class ScikitBuildSettings:
     build: BuildSettings = dataclasses.field(default_factory=BuildSettings)
     install: InstallSettings = dataclasses.field(default_factory=InstallSettings)
     generate: List[GenerateSettings] = dataclasses.field(default_factory=list)
+    messages: MessagesSettings = dataclasses.field(default_factory=MessagesSettings)
 
     metadata: Dict[str, Dict[str, Any]] = dataclasses.field(default_factory=dict)
     """
diff --git a/src/scikit_build_core/settings/skbuild_overrides.py b/src/scikit_build_core/settings/skbuild_overrides.py
index 47e1f10..88d11dd 100644
--- a/src/scikit_build_core/settings/skbuild_overrides.py
+++ b/src/scikit_build_core/settings/skbuild_overrides.py
@@ -7,9 +7,14 @@ import sys
 from pathlib import Path
 from typing import TYPE_CHECKING, Any
 
+import packaging.tags
 from packaging.specifiers import SpecifierSet
 
+from .._compat import tomllib
 from .._logging import logger
+from ..cmake import CMake
+from ..errors import CMakeNotFoundError
+from ..resources import resources
 
 __all__ = ["process_overides", "regex_match"]
 
@@ -69,6 +74,8 @@ def override_match(
     state: str | None = None,
     from_sdist: bool | None = None,
     failed: bool | None = None,
+    system_cmake: str | None = None,
+    cmake_wheel: bool | None = None,
 ) -> tuple[dict[str, str], set[str]]:
     """
     Check if the current environment matches the overrides. Returns a dict
@@ -161,6 +168,29 @@ def override_match(
         else:
             failed_set.add("from-sdist")
 
+    if system_cmake is not None:
+        try:
+            cmake = CMake.default_search(
+                version=SpecifierSet(system_cmake), module=False, env=current_env
+            )
+            passed_dict["system-cmake"] = (
+                f"system cmake {cmake.version} found at {cmake.cmake_path} passing {system_cmake}"
+            )
+        except CMakeNotFoundError:
+            failed_set.add("system-cmake")
+
+    if cmake_wheel is not None:
+        with resources.joinpath("known_wheels.toml").open("rb") as f:
+            known_wheels_toml = tomllib.load(f)
+        known_cmake_wheels = set(
+            known_wheels_toml["tool"]["scikit-build"]["cmake"]["known-wheels"]
+        )
+        cmake_plat = known_cmake_wheels.intersection(packaging.tags.sys_tags())
+        if cmake_plat:
+            passed_dict["cmake-wheel"] = f"cmake wheel available on {cmake_plat}"
+        else:
+            failed_set.add("cmake-wheel")
+
     if env:
         for key, value in env.items():
             if key not in current_env:
diff --git a/src/scikit_build_core/settings/skbuild_schema.py b/src/scikit_build_core/settings/skbuild_schema.py
index 797b336..1aaa7eb 100644
--- a/src/scikit_build_core/settings/skbuild_schema.py
+++ b/src/scikit_build_core/settings/skbuild_schema.py
@@ -124,6 +124,14 @@ def generate_skbuild_schema(tool_name: str = "scikit-build") -> dict[str, Any]:
                 "type": "boolean",
                 "description": "Matches if the build fails. A build will be retried if there is at least one matching override with this set to true.",
             },
+            "system-cmake": {
+                "type": "string",
+                "description": "The version of CMake found on the system. Takes a specifier set.",
+            },
+            "cmake-wheel": {
+                "type": "boolean",
+                "description": "Whether a cmake wheel is known to be provided for this system.",
+            },
             "env": {
                 "type": "object",
                 "patternProperties": {


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
index 6a44567..0755e51 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -9,6 +9,7 @@ import subprocess
 import sys
 import sysconfig
 from pathlib import Path
+from typing import Any
 
 import virtualenv as _virtualenv
 
@@ -333,6 +334,34 @@ def package_simple_purelib_package(
     return package
 
 
+def which_mock(name: str) -> str | None:
+    if name in {"ninja", "ninja-build", "cmake3", "samu", "gmake", "make"}:
+        return None
+    if name == "cmake":
+        return "cmake/path"
+    return None
+
+
+@pytest.fixture()
+def protect_get_requires(fp, monkeypatch):
+    """
+    Protect get_requires from actually calling anything variable during tests.
+    """
+    # This needs to be passed due to packaging.tags 22 extra checks if macos 10.16 is reported
+    fp.pass_command([sys.executable, fp.any()])
+    monkeypatch.setattr(shutil, "which", which_mock)
+    monkeypatch.delenv("CMAKE_GENERATOR", raising=False)
+
+    orig_find_spec = importlib.util.find_spec
+
+    def find_spec(name: str, package: str | None = None) -> Any:
+        if name in {"cmake", "ninja"}:
+            return None
+        return orig_find_spec(name, package)
+
+    monkeypatch.setattr(importlib.util, "find_spec", find_spec)
+
+
 def pytest_collection_modifyitems(items: list[pytest.Item]) -> None:
     for item in items:
         # Ensure all tests using virtualenv are marked as such
diff --git a/tests/packages/abi3_pyproject_ext/pyproject.toml b/tests/packages/abi3_pyproject_ext/pyproject.toml
index 93f045e..c62f98b 100644
--- a/tests/packages/abi3_pyproject_ext/pyproject.toml
+++ b/tests/packages/abi3_pyproject_ext/pyproject.toml
@@ -8,3 +8,4 @@ version = "0.0.1"
 
 [tool.scikit-build]
 wheel.py-api = "cp37"
+messages.after-success = "This is a message after success"
diff --git a/tests/test_broken_fallback.py b/tests/test_broken_fallback.py
index 15512cd..01b957f 100644
--- a/tests/test_broken_fallback.py
+++ b/tests/test_broken_fallback.py
@@ -50,3 +50,22 @@ def test_fail_setting(
     assert exc.value.code == 7
     out, _ = capsys.readouterr()
     assert "fail setting was enabled" in out
+
+
+@pytest.mark.usefixtures("broken_fallback")
+def test_fail_setting_msg(
+    monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
+):
+    monkeypatch.setenv("FAIL_NOW", "1")
+    monkeypatch.setenv(
+        "SKBUILD_MESSAGES_AFTER_FAILURE", "This is a test failure message"
+    )
+
+    assert get_requires_for_build_wheel({}) == []
+    with pytest.raises(SystemExit) as exc:
+        build_wheel("dist")
+
+    assert exc.value.code == 7
+    out, _ = capsys.readouterr()
+    assert "This is a test failure message" in out
+    assert "fail setting was enabled" not in out
diff --git a/tests/test_get_requires.py b/tests/test_get_requires.py
index cd431ed..df51581 100644
--- a/tests/test_get_requires.py
+++ b/tests/test_get_requires.py
@@ -1,11 +1,8 @@
 from __future__ import annotations
 
-import importlib.util
-import shutil
-import sys
 import sysconfig
 from pathlib import Path
-from typing import Any
+from typing import TYPE_CHECKING
 
 import pytest
 
@@ -16,38 +13,20 @@ from scikit_build_core.build import (
 )
 from scikit_build_core.builder.get_requires import GetRequires
 
-ninja = [] if sysconfig.get_platform().startswith("win") else ["ninja>=1.5"]
-
+if TYPE_CHECKING:
+    from pytest_subprocess import FakeProcess
 
-def which_mock(name: str) -> str | None:
-    if name in {"ninja", "ninja-build", "cmake3", "samu", "gmake", "make"}:
-        return None
-    if name == "cmake":
-        return "cmake/path"
-    return None
+ninja = [] if sysconfig.get_platform().startswith("win") else ["ninja>=1.5"]
 
 
 @pytest.fixture(autouse=True)
-def protect_get_requires(fp, monkeypatch):
+def protect_get_requires_autouse(protect_get_requires: None):  # noqa: ARG001
     """
-    Protect get_requires from actually calling anything variable during tests.
+    Autouse this fixture in this test.
     """
-    # This needs to be passed due to packaging.tags 22 extra checks if macos 10.16 is reported
-    fp.pass_command([sys.executable, fp.any()])
-    monkeypatch.setattr(shutil, "which", which_mock)
-    monkeypatch.delenv("CMAKE_GENERATOR", raising=False)
-
-    orig_find_spec = importlib.util.find_spec
-
-    def find_spec(name: str, package: str | None = None) -> Any:
-        if name in {"cmake", "ninja"}:
-            return None
-        return orig_find_spec(name, package)
-
-    monkeypatch.setattr(importlib.util, "find_spec", find_spec)
 
 
-def test_get_requires_parts(fp):
+def test_get_requires_parts(fp: FakeProcess):
     fp.register(
         [Path("cmake/path"), "-E", "capabilities"],
         stdout='{"version":{"string":"3.14.0"}}',
@@ -56,7 +35,7 @@ def test_get_requires_parts(fp):
     assert set(GetRequires().ninja()) == {*ninja}
 
 
-def test_get_requires_parts_uneeded(fp):
+def test_get_requires_parts_uneeded(fp: FakeProcess):
     fp.register(
         [Path("cmake/path"), "-E", "capabilities"],
         stdout='{"version":{"string":"3.18.0"}}',
@@ -65,7 +44,7 @@ def test_get_requires_parts_uneeded(fp):
     assert set(GetRequires().ninja()) == {*ninja}
 
 
-def test_get_requires_parts_settings(fp):
+def test_get_requires_parts_settings(fp: FakeProcess):
     fp.register(
         [Path("cmake/path"), "-E", "capabilities"],
         stdout='{"version":{"string":"3.18.0"}}',
@@ -75,7 +54,9 @@ def test_get_requires_parts_settings(fp):
     assert set(GetRequires.from_config_settings(config).ninja()) == {*ninja}
 
 
-def test_get_requires_parts_pyproject(fp, monkeypatch, tmp_path):
+def test_get_requires_parts_pyproject(
+    fp: FakeProcess, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
+):
     monkeypatch.chdir(tmp_path)
     tmp_path.joinpath("pyproject.toml").write_text(
         """
@@ -92,7 +73,9 @@ def test_get_requires_parts_pyproject(fp, monkeypatch, tmp_path):
     assert set(GetRequires().ninja()) == {*ninja}
 
 
-def test_get_requires_parts_pyproject_old(fp, monkeypatch, tmp_path):
+def test_get_requires_parts_pyproject_old(
+    fp: FakeProcess, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
+):
     monkeypatch.chdir(tmp_path)
     tmp_path.joinpath("pyproject.toml").write_text(
         """
@@ -111,7 +94,7 @@ def test_get_requires_parts_pyproject_old(fp, monkeypatch, tmp_path):
     assert set(GetRequires().ninja()) == {*ninja}
 
 
-def test_get_requires_for_build_sdist(fp):
+def test_get_requires_for_build_sdist(fp: FakeProcess):
     fp.register(
         [Path("cmake/path"), "-E", "capabilities"],
         stdout='{"version":{"string":"3.14.0"}}',
@@ -119,7 +102,7 @@ def test_get_requires_for_build_sdist(fp):
     assert set(get_requires_for_build_sdist({})) == set()
 
 
-def test_get_requires_for_build_sdist_cmake(fp):
+def test_get_requires_for_build_sdist_cmake(fp: FakeProcess):
     expected = {"cmake>=3.15", *ninja}
     fp.register(
         [Path("cmake/path"), "-E", "capabilities"],
@@ -128,7 +111,7 @@ def test_get_requires_for_build_sdist_cmake(fp):
     assert set(get_requires_for_build_sdist({"sdist.cmake": "True"})) == expected
 
 
-def test_get_requires_for_build_wheel(fp):
+def test_get_requires_for_build_wheel(fp: FakeProcess):
     expected = {"cmake>=3.15", *ninja}
     fp.register(
         [Path("cmake/path"), "-E", "capabilities"],
@@ -137,7 +120,7 @@ def test_get_requires_for_build_wheel(fp):
     assert set(get_requires_for_build_wheel({})) == expected
 
 
-def test_get_requires_for_build_wheel_pure(fp):
+def test_get_requires_for_build_wheel_pure(fp: FakeProcess):
     fp.register(
         [Path("cmake/path"), "-E", "capabilities"],
         stdout='{"version":{"string":"3.14.0"}}',
@@ -145,7 +128,7 @@ def test_get_requires_for_build_wheel_pure(fp):
     assert set(get_requires_for_build_wheel({"wheel.cmake": "False"})) == set()
 
 
-def test_get_requires_for_build_editable(fp):
+def test_get_requires_for_build_editable(fp: FakeProcess):
     expected = {"cmake>=3.15", *ninja}
     fp.register(
         [Path("cmake/path"), "-E", "capabilities"],
@@ -154,7 +137,7 @@ def test_get_requires_for_build_editable(fp):
     assert set(get_requires_for_build_editable({})) == expected
 
 
-def test_get_requires_for_build_editable_pure(fp):
+def test_get_requires_for_build_editable_pure(fp: FakeProcess):
     fp.register(
         [Path("cmake/path"), "-E", "capabilities"],
         stdout='{"version":{"string":"3.14.0"}}',
diff --git a/tests/test_pyproject_abi3.py b/tests/test_pyproject_abi3.py
index 92339e0..17e843a 100644
--- a/tests/test_pyproject_abi3.py
+++ b/tests/test_pyproject_abi3.py
@@ -19,7 +19,7 @@ SYSCONFIGPLAT = sysconfig.get_platform()
     sysconfig.get_platform().startswith(("msys", "mingw")),
     reason="abi3 FindPython on MSYS/MinGW reports not found",
 )
-def test_abi3_wheel(tmp_path, monkeypatch, virtualenv):
+def test_abi3_wheel(tmp_path, monkeypatch, virtualenv, capsys):
     dist = tmp_path / "dist"
     dist.mkdir()
     monkeypatch.chdir(ABI_PKG)
@@ -29,6 +29,8 @@ def test_abi3_wheel(tmp_path, monkeypatch, virtualenv):
         shutil.rmtree("build")
 
     out = build_wheel(str(dist))
+    stdout, stderr = capsys.readouterr()
+    assert "This is a message after success" in stdout
     (wheel,) = dist.glob("abi3_example-0.0.1-*.whl")
     assert wheel == dist / out
     abi3 = sys.implementation.name == "cpython" and not sysconfig.get_config_var(
diff --git a/tests/test_settings_overrides.py b/tests/test_settings_overrides.py
index 406ab8a..bd49222 100644
--- a/tests/test_settings_overrides.py
+++ b/tests/test_settings_overrides.py
@@ -1,6 +1,7 @@
 from __future__ import annotations
 
 import typing
+from pathlib import Path
 from textwrap import dedent
 
 import pytest
@@ -9,7 +10,7 @@ from scikit_build_core.settings.skbuild_overrides import regex_match
 from scikit_build_core.settings.skbuild_read_settings import SettingsReader
 
 if typing.TYPE_CHECKING:
-    from pathlib import Path
+    from pytest_subprocess import FakeProcess
 
 
 class VersionInfo(typing.NamedTuple):
@@ -603,3 +604,65 @@ def test_failed_retry(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
     settings = settings_reader.settings
     assert settings.wheel.cmake
     assert not settings.sdist.cmake
+
+
+@pytest.mark.parametrize("sys_tag", ["win32", "linux"])
+def test_wheel_platform(tmp_path: Path, monkeypatch: pytest.MonkeyPatch, sys_tag: str):
+    pyproject_toml = tmp_path / "pyproject.toml"
+    pyproject_toml.write_text(
+        dedent(
+            """\
+            [tool.scikit-build]
+            wheel.cmake = false
+
+            [[tool.scikit-build.overrides]]
+            if.cmake-wheel = true
+            wheel.cmake = true
+            """
+        ),
+        encoding="utf-8",
+    )
+
+    monkeypatch.chdir(tmp_path)
+
+    monkeypatch.setattr("packaging.tags.sys_tags", lambda: [sys_tag])
+
+    settings_reader = SettingsReader.from_file(pyproject_toml, retry=False)
+    settings = settings_reader.settings
+    assert settings.wheel.cmake == (sys_tag == "win32")
+
+
+@pytest.mark.parametrize("cmake_version", ["3.21", "3.27"])
+@pytest.mark.usefixtures("protect_get_requires")
+def test_system_cmake(
+    tmp_path: Path,
+    monkeypatch: pytest.MonkeyPatch,
+    cmake_version: str | None,
+    fp: FakeProcess,
+) -> None:
+    if cmake_version:
+        fp.register(
+            [Path("cmake/path"), "-E", "capabilities"],
+            stdout=f'{{"version":{{"string": "{cmake_version}"}}}}',
+        )
+
+    pyproject_toml = tmp_path / "pyproject.toml"
+    pyproject_toml.write_text(
+        dedent(
+            """\
+            [tool.scikit-build]
+            wheel.cmake = false
+
+            [[tool.scikit-build.overrides]]
+            if.system-cmake = ">=3.24"
+            wheel.cmake = true
+            """
+        ),
+        encoding="utf-8",
+    )
+
+    monkeypatch.chdir(tmp_path)
+
+    settings_reader = SettingsReader.from_file(pyproject_toml, retry=False)
+    settings = settings_reader.settings
+    assert settings.wheel.cmake == (cmake_version == "3.27")
diff --git a/tests/test_skbuild_settings.py b/tests/test_skbuild_settings.py
index 2da6e9b..b7bda68 100644
--- a/tests/test_skbuild_settings.py
+++ b/tests/test_skbuild_settings.py
@@ -68,6 +68,8 @@ def test_skbuild_settings_default(tmp_path: Path):
     assert settings.install.strip
     assert settings.generate == []
     assert not settings.fail
+    assert settings.messages.after_failure == ""
+    assert settings.messages.after_success == ""
 
 
 def test_skbuild_settings_envvar(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
@@ -106,6 +108,12 @@ def test_skbuild_settings_envvar(tmp_path: Path, monkeypatch: pytest.MonkeyPatch
     monkeypatch.setenv("SKBUILD_INSTALL_COMPONENTS", "a;b;c")
     monkeypatch.setenv("SKBUILD_INSTALL_STRIP", "False")
     monkeypatch.setenv("SKBUILD_FAIL", "1")
+    monkeypatch.setenv(
+        "SKBUILD_MESSAGES_AFTER_FAILURE", "This is a test failure message"
+    )
+    monkeypatch.setenv(
+        "SKBUILD_MESSAGES_AFTER_SUCCESS", "This is a test success message"
+    )
 
     pyproject_toml = tmp_path / "pyproject.toml"
     pyproject_toml.write_text("", encoding="utf-8")
@@ -149,6 +157,8 @@ def test_skbuild_settings_envvar(tmp_path: Path, monkeypatch: pytest.MonkeyPatch
     assert settings.install.components == ["a", "b", "c"]
     assert not settings.install.strip
     assert settings.fail
+    assert settings.messages.after_failure == "This is a test failure message"
+    assert settings.messages.after_success == "This is a test success message"
 
 
 @pytest.mark.parametrize("prefix", [True, False], ids=["skbuild", "noprefix"])
@@ -196,6 +206,8 @@ def test_skbuild_settings_config_settings(
         "install.components": ["a", "b", "c"],
         "install.strip": "True",
         "fail": "1",
+        "messages.after-failure": "This is a test failure message",
+        "messages.after-success": "This is a test success message",
     }
 
     if prefix:
@@ -238,6 +250,8 @@ def test_skbuild_settings_config_settings(
     assert settings.install.components == ["a", "b", "c"]
     assert settings.install.strip
     assert settings.fail
+    assert settings.messages.after_failure == "This is a test failure message"
+    assert settings.messages.after_success == "This is a test success message"
 
 
 def test_skbuild_settings_pyproject_toml(
@@ -284,6 +298,8 @@ def test_skbuild_settings_pyproject_toml(
             install.components = ["a", "b", "c"]
             install.strip = true
             fail = true
+            messages.after-failure = "This is a test failure message"
+            messages.after-success = "This is a test success message"
             [[tool.scikit-build.generate]]
             path = "a/b/c"
             template = "hello"
@@ -341,6 +357,8 @@ def test_skbuild_settings_pyproject_toml(
         ),
     ]
     assert settings.fail
+    assert settings.messages.after_failure == "This is a test failure message"
+    assert settings.messages.after_success == "This is a test success message"
 
 
 def test_skbuild_settings_pyproject_toml_broken(