Repository: scikit-build/scikit-build-core
Pull request #813 (2024-07-16 03:03:25)

chore: nicer error/warning printer
Clean up warning/error printout. Warnings are not repeated.


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/scikit-build/scikit-build-core/pull/813?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 `57.14286%` with `12 lines` in your changes missing coverage. Please review.
> Project coverage is 83.13%. Comparing base [(`c299548`)](https://app.codecov.io/gh/scikit-build/scikit-build-core/commit/c299548569567e76ecc663bef2fa40e396ce6d1c?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scikit-build) to head [(`2439b23`)](https://app.codecov.io/gh/scikit-build/scikit-build-core/commit/2439b23f5f3c6ecd8945876f97ff6136efa5bc55?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/813?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 |
|---|---|---|
| [...cikit\_build\_core/settings/skbuild\_read\_settings.py](https://app.codecov.io/gh/scikit-build/scikit-build-core/pull/813?src=pr&el=tree&filepath=src%2Fscikit_build_core%2Fsettings%2Fskbuild_read_settings.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scikit-build#diff-c3JjL3NjaWtpdF9idWlsZF9jb3JlL3NldHRpbmdzL3NrYnVpbGRfcmVhZF9zZXR0aW5ncy5weQ==) | 36.84% | [12 Missing :warning: ](https://app.codecov.io/gh/scikit-build/scikit-build-core/pull/813?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     #813      +/-   ##
==========================================
+ Coverage   82.98%   83.13%   +0.14%     
==========================================
  Files          73       73              
  Lines        4074     4068       -6     
==========================================
+ Hits         3381     3382       +1     
+ Misses        693      686       -7     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/scikit-build/scikit-build-core/pull/813?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/docs/projects.md b/docs/projects.md
index 3e3ce6a..e57078b 100644
--- a/docs/projects.md
+++ b/docs/projects.md
@@ -49,7 +49,7 @@ In addition, most of the [RAPIDSAI](https://github.com/rapidsai) projects use sc
 
 * CuDF ([source](https://github.com/rapidsai/cudf/blob/HEAD/python/cudf/pyproject.toml))
 * CuGraph ([source](https://github.com/rapidsai/cugraph/blob/HEAD/python/cugraph/pyproject.toml))
-* CuML ([source](https://github.com/rapidsai/cuml/blob/HEAD/python/pyproject.toml))
+* CuML ([source](https://github.com/rapidsai/cuml/blob/HEAD/python/cuml/pyproject.toml))
 * 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))
diff --git a/src/scikit_build_core/_logging.py b/src/scikit_build_core/_logging.py
index 941510e..c60b420 100644
--- a/src/scikit_build_core/_logging.py
+++ b/src/scikit_build_core/_logging.py
@@ -1,13 +1,22 @@
 from __future__ import annotations
 
 import contextlib
+import functools
 import logging
 import os
 import re
 import sys
-from typing import Any
+from typing import Any, NoReturn
 
-__all__ = ["ScikitBuildLogger", "logger", "raw_logger", "rich_print", "LEVEL_VALUE"]
+__all__ = [
+    "ScikitBuildLogger",
+    "logger",
+    "raw_logger",
+    "rich_print",
+    "rich_warning",
+    "rich_error",
+    "LEVEL_VALUE",
+]
 
 
 def __dir__() -> list[str]:
@@ -147,3 +156,13 @@ def rich_print(*args: object, **kwargs: object) -> None:
     if args != args_2:
         args_2 = (*args_2[:-1], args_2[-1] + colors()["reset"])
     print(*args_2, **kwargs, flush=True)  # type: ignore[call-overload] # noqa: T201
+
+
+@functools.lru_cache(maxsize=None)
+def rich_warning(*args: object, **kwargs: object) -> None:
+    rich_print("[red][yellow]WARNING:[/bold]", *args, **kwargs)
+
+
+def rich_error(*args: object, **kwargs: object) -> NoReturn:
+    rich_print("[red][bold]ERROR:[/bold]", *args, **kwargs)
+    raise SystemExit(7)
diff --git a/src/scikit_build_core/settings/skbuild_read_settings.py b/src/scikit_build_core/settings/skbuild_read_settings.py
index ea6e526..15bb694 100644
--- a/src/scikit_build_core/settings/skbuild_read_settings.py
+++ b/src/scikit_build_core/settings/skbuild_read_settings.py
@@ -15,7 +15,7 @@ from packaging.version import Version
 
 from .. import __version__
 from .._compat import tomllib
-from .._logging import logger, rich_print
+from .._logging import logger, rich_error, rich_print, rich_warning
 from ..errors import CMakeConfigError
 from .auto_cmake_version import find_min_cmake_version
 from .auto_requires import get_min_requires
@@ -180,25 +180,22 @@ def _handle_minimum_version(
         and minimum_version is not None
         and minimum_version < Version("0.8")
     ):
-        rich_print(
-            f"[red][bold]ERROR:[/bold] Cannot set {name}.version if minimum-version is set to less than 0.8 (which is where it was introduced)"
+        rich_error(
+            f"Cannot set {name}.version if minimum-version is set to less than 0.8 (which is where it was introduced)"
         )
-        raise SystemExit(7)
 
     # Backwards compatibility for minimum_version
     if dc.minimum_version is not None:
         msg = f"Use {name}.version instead of {name}.minimum-version with scikit-build-core >= 0.8"
         if minimum_version is None:
-            rich_print(f"[yellow][bold]WARNING:[/bold] {msg}")
+            rich_warning(msg)
         elif minimum_version >= Version("0.8"):
-            rich_print(f"[red][bold]ERROR:[/bold] {msg}")
-            raise SystemExit(7)
+            rich_error(msg)
 
         if dc.version != version_default:
-            rich_print(
-                f"[red][bold]ERROR:[/bold] Cannot set both {name}.minimum_version and {name}.version; use version only for scikit-build-core >= 0.8."
+            rich_error(
+                f"Cannot set both {name}.minimum_version and {name}.version; use version only for scikit-build-core >= 0.8."
             )
-            raise SystemExit(7)
 
         dc.version = SpecifierSet(f">={dc.minimum_version}")
 
@@ -216,33 +213,28 @@ def _handle_move(
     """
 
     if after and minimum_version is not None and minimum_version < introduced_in:
-        rich_print(
-            f"[red][bold]ERROR:[/bold] Cannot set {after_name} if minimum-version is set to less than {introduced_in} (which is where it was introduced)"
+        rich_error(
+            f"Cannot set {after_name} if minimum-version is set to less than {introduced_in} (which is where it was introduced)"
         )
-        raise SystemExit(7)
 
     if (
         before is not None
         and minimum_version is not None
         and minimum_version >= introduced_in
     ):
-        rich_print(
-            f"[red][bold]ERROR:[/bold] Cannot set {before_name} if minimum-version is set to {introduced_in} or higher"
+        rich_error(
+            f"Cannot set {before_name} if minimum-version is set to {introduced_in} or higher"
         )
-        raise SystemExit(7)
 
     if before is not None and after:
-        rich_print(
-            f"[red][bold]ERROR:[/bold] Cannot set {before_name} and {after_name} at the same time"
-        )
-        raise SystemExit(7)
+        rich_error(f"Cannot set {before_name} and {after_name} at the same time")
 
     if before is None:
         return after
 
     if minimum_version is None:
-        rich_print(
-            f"[yellow][bold]WARNING:[/bold] Use {after_name} instead of {before_name} for scikit-build-core >= {introduced_in}"
+        rich_warning(
+            f"Use {after_name} instead of {before_name} for scikit-build-core >= {introduced_in}"
         )
 
     return before
@@ -350,11 +342,10 @@ class SettingsReader:
             reqlist = pyproject["build-system"]["requires"]
             min_v = get_min_requires("scikit-build-core", reqlist)
             if min_v is None:
-                rich_print(
-                    "[red][bold]ERROR:[/bold] scikit-build-core needs a min version in "
+                rich_error(
+                    "scikit-build-core needs a min version in "
                     "build-system.requires to use minimum-version='build-system.requires'"
                 )
-                raise SystemExit(7)
             pyproject["tool"]["scikit-build"]["minimum-version"] = str(min_v)
         toml_srcs = [TOMLSource("tool", "scikit-build", settings=pyproject)]
 
@@ -401,16 +392,10 @@ class SettingsReader:
 
         if self.settings.editable.rebuild:
             if self.settings.editable.mode == "inplace":
-                rich_print(
-                    "[red][bold]ERROR:[/bold] editable rebuild is incompatible with inplace mode"
-                )
-                raise SystemExit(7)
+                rich_error("editable rebuild is incompatible with inplace mode")
 
             if not self.settings.build_dir:
-                rich_print(
-                    "[red][bold]ERROR:[/bold] editable mode with rebuild requires build-dir"
-                )
-                raise SystemExit(7)
+                rich_error("editable mode with rebuild requires build-dir")
 
         install_policy = (
             self.settings.minimum_version is None
@@ -435,29 +420,27 @@ class SettingsReader:
                     new_min_cmake = find_min_cmake_version(f.read())
             except FileNotFoundError:
                 new_min_cmake = "3.15"
-                rich_print(
-                    "[red][bold]WARNING:[/bold] CMakeLists.txt not found when looking for minimum CMake version. "
+                rich_warning(
+                    "CMakeLists.txt not found when looking for minimum CMake version. "
                     "Report this or (and) set manually to avoid this warning. Using 3.15 as a fall-back."
                 )
 
             if new_min_cmake is None:
                 if force_auto_cmake:
-                    rich_print(
-                        "[red][bold]ERROR:[/bold] Minimum CMake version set as "
+                    rich_error(
+                        "Minimum CMake version set as "
                         "'CMakeLists.txt' wasn't able to find minimum version setting. "
                         "If the CMakeLists.txt is valid, this might be a bug in our search algorithm."
                     )
-                    raise SystemExit(7)
-                rich_print(
-                    "[red][bold]WARNING:[/bold] Minimum CMake version not found in CMakeLists.txt. "
+                rich_warning(
+                    "Minimum CMake version not found in CMakeLists.txt. "
                     "If the CMakeLists.txt is valid, this might be a bug in our search algorithm. Report "
                     "this or (and) set manually to avoid this warning."
                 )
                 new_min_cmake = "3.15"
             if Version(new_min_cmake) < Version("3.15"):
-                rich_print(
-                    "[red][bold]WARNING:[/bold] Minimum CMake version set as "
-                    "'CMakeLists.txt' is less than 3.15. "
+                rich_warning(
+                    "Minimum CMake version set as 'CMakeLists.txt' is less than 3.15. "
                     "This is not supported by scikit-build-core; set manually or increase to avoid this warning."
                 )
                 new_min_cmake = "3.15"
@@ -528,27 +511,20 @@ class SettingsReader:
         for key, value in self.settings.metadata.items():
             if "provider" not in value:
                 sys.stdout.flush()
-                rich_print(
-                    f"[red][bold]ERROR:[/bold] provider= must be provided in {key!r}:"
-                )
-                raise SystemExit(7)
+                rich_error(f"provider= must be provided in {key!r}:")
             if not self.settings.experimental and (
                 "provider-path" in value
                 or not value["provider"].startswith("scikit_build_core.")
             ):
                 sys.stdout.flush()
-                rich_print(
-                    "[red][bold]ERROR:[/bold] experimental must be enabled currently to use plugins not provided by scikit-build-core"
+                rich_error(
+                    "experimental must be enabled currently to use plugins not provided by scikit-build-core"
                 )
-                raise SystemExit(7)
 
         for gen in self.settings.generate:
             if not gen.template and not gen.template_path:
                 sys.stdout.flush()
-                rich_print(
-                    "[red][bold]ERROR:[/bold] template= or template-path= must be provided in generate"
-                )
-                raise SystemExit(7)
+                rich_error("template= or template-path= must be provided in generate")
 
     @classmethod
     def from_file(


Test patch:
diff --git a/tests/test_skbuild_settings.py b/tests/test_skbuild_settings.py
index 2d76947..da4a758 100644
--- a/tests/test_skbuild_settings.py
+++ b/tests/test_skbuild_settings.py
@@ -11,6 +11,7 @@ import pytest
 from packaging.specifiers import SpecifierSet
 from packaging.version import Version
 
+import scikit_build_core._logging
 import scikit_build_core.settings.skbuild_read_settings
 from scikit_build_core.settings.skbuild_model import GenerateSettings
 from scikit_build_core.settings.skbuild_read_settings import SettingsReader
@@ -684,6 +685,7 @@ def test_skbuild_settings_auto_cmake_warning(
     monkeypatch.setattr(
         scikit_build_core.settings.skbuild_read_settings, "__version__", "0.10.0"
     )
+    scikit_build_core._logging.rich_warning.cache_clear()
     pyproject_toml = tmp_path / "pyproject.toml"
     pyproject_toml.write_text(
         textwrap.dedent(