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

Issue:
Please add a possibility to configure builder (`cmake --build`) arguments
FWICS there is currently no way to pass additional arguments to the builder, i.e. the equivalent of:

    cmake --build . -- -j12 -l13

Could you please add a configuration knob for that? We really want to respect user job count and load average options in Gentoo.

Pull request description:
feat: build.tool-args
Close #722.


Patch:
diff --git a/README.md b/README.md
index 886059b..fe014c6 100644
--- a/README.md
+++ b/README.md
@@ -272,6 +272,9 @@ editable.verbose = true
 # set.
 editable.rebuild = false
 
+# Extra args to pass directly to the builder in the build step.
+build.tool-args = []
+
 # The components to install. If empty, all default components are installed.
 install.components = []
 
diff --git a/src/scikit_build_core/builder/builder.py b/src/scikit_build_core/builder/builder.py
index 2751f7a..19d46a1 100644
--- a/src/scikit_build_core/builder/builder.py
+++ b/src/scikit_build_core/builder/builder.py
@@ -227,7 +227,11 @@ class Builder:
             cmake_args=[*self.get_cmake_args(), *configure_args],
         )
 
-    def build(self, build_args: list[str]) -> None:
+    def build(self, build_args: Sequence[str]) -> None:
+        build_tool_args = self.settings.build.tool_args
+        if build_tool_args:
+            build_args = [*build_args, "--", *build_tool_args]
+
         self.config.build(
             build_args=build_args,
             targets=self.settings.cmake.targets,
diff --git a/src/scikit_build_core/resources/scikit-build.schema.json b/src/scikit_build_core/resources/scikit-build.schema.json
index b9edc5e..d4dfb8e 100644
--- a/src/scikit_build_core/resources/scikit-build.schema.json
+++ b/src/scikit_build_core/resources/scikit-build.schema.json
@@ -253,6 +253,19 @@
         }
       }
     },
+    "build": {
+      "type": "object",
+      "additionalProperties": false,
+      "properties": {
+        "tool-args": {
+          "type": "array",
+          "items": {
+            "type": "string"
+          },
+          "description": "Extra args to pass directly to the builder in the build step."
+        }
+      }
+    },
     "install": {
       "type": "object",
       "additionalProperties": false,
@@ -481,6 +494,15 @@
                   }
                 }
               },
+              "build": {
+                "type": "object",
+                "additionalProperties": false,
+                "properties": {
+                  "tool-args": {
+                    "$ref": "#/$defs/inherit"
+                  }
+                }
+              },
               "install": {
                 "type": "object",
                 "additionalProperties": false,
@@ -514,6 +536,9 @@
           "editable": {
             "$ref": "#/properties/editable"
           },
+          "build": {
+            "$ref": "#/properties/build"
+          },
           "install": {
             "$ref": "#/properties/install"
           },
diff --git a/src/scikit_build_core/settings/skbuild_model.py b/src/scikit_build_core/settings/skbuild_model.py
index da56e5c..bc52a42 100644
--- a/src/scikit_build_core/settings/skbuild_model.py
+++ b/src/scikit_build_core/settings/skbuild_model.py
@@ -13,6 +13,7 @@ __all__ = [
     "EditableSettings",
     "GenerateSettings",
     "InstallSettings",
+    "BuildSettings",
     "LoggingSettings",
     "NinjaSettings",
     "SDistSettings",
@@ -232,6 +233,14 @@ class EditableSettings:
     """
 
 
+@dataclasses.dataclass
+class BuildSettings:
+    tool_args: List[str] = dataclasses.field(default_factory=list)
+    """
+    Extra args to pass directly to the builder in the build step.
+    """
+
+
 @dataclasses.dataclass
 class InstallSettings:
     components: List[str] = dataclasses.field(default_factory=list)
@@ -281,6 +290,7 @@ class ScikitBuildSettings:
     wheel: WheelSettings = dataclasses.field(default_factory=WheelSettings)
     backport: BackportSettings = dataclasses.field(default_factory=BackportSettings)
     editable: EditableSettings = dataclasses.field(default_factory=EditableSettings)
+    build: BuildSettings = dataclasses.field(default_factory=BuildSettings)
     install: InstallSettings = dataclasses.field(default_factory=InstallSettings)
     generate: List[GenerateSettings] = dataclasses.field(default_factory=list)
 


Test patch:
diff --git a/tests/test_builder.py b/tests/test_builder.py
index e5b1546..4681bd1 100644
--- a/tests/test_builder.py
+++ b/tests/test_builder.py
@@ -4,6 +4,7 @@ import pprint
 import sys
 import sysconfig
 import typing
+import unittest.mock
 from pathlib import Path
 from types import SimpleNamespace
 
@@ -17,7 +18,11 @@ from scikit_build_core.builder.sysconfig import (
 )
 from scikit_build_core.builder.wheel_tag import WheelTag
 from scikit_build_core.cmake import CMaker
-from scikit_build_core.settings.skbuild_model import ScikitBuildSettings, WheelSettings
+from scikit_build_core.settings.skbuild_model import (
+    BuildSettings,
+    ScikitBuildSettings,
+    WheelSettings,
+)
 
 
 # The envvar_higher case shouldn't happen, but the compiler should cause the
@@ -128,6 +133,19 @@ def test_builder_get_cmake_args(monkeypatch, cmake_args, answer):
     assert tmpbuilder.get_cmake_args() == answer
 
 
+def test_build_tool_args():
+    config = unittest.mock.create_autospec(CMaker)
+    settings = ScikitBuildSettings(build=BuildSettings(tool_args=["b"]))
+    tmpbuilder = Builder(
+        settings=settings,
+        config=typing.cast(CMaker, config),
+    )
+    tmpbuilder.build(["a"])
+    config.build.assert_called_once_with(
+        build_args=["a", "--", "b"], targets=[], verbose=settings.cmake.verbose
+    )
+
+
 @pytest.mark.parametrize(
     ("minver", "archs", "answer"),
     [
diff --git a/tests/test_skbuild_settings.py b/tests/test_skbuild_settings.py
index 30f0111..6d84306 100644
--- a/tests/test_skbuild_settings.py
+++ b/tests/test_skbuild_settings.py
@@ -62,6 +62,7 @@ def test_skbuild_settings_default(tmp_path: Path):
     assert settings.editable.mode == "redirect"
     assert not settings.editable.rebuild
     assert settings.editable.verbose
+    assert settings.build.tool_args == []
     assert settings.install.components == []
     assert settings.install.strip
     assert settings.generate == []
@@ -99,6 +100,7 @@ def test_skbuild_settings_envvar(tmp_path: Path, monkeypatch: pytest.MonkeyPatch
     monkeypatch.setenv("SKBUILD_BUILD_DIR", "a/b/c")
     monkeypatch.setenv("SKBUILD_EDITABLE_REBUILD", "True")
     monkeypatch.setenv("SKBUILD_EDITABLE_VERBOSE", "False")
+    monkeypatch.setenv("SKBUILD_BUILD_TOOL_ARGS", "a;b")
     monkeypatch.setenv("SKBUILD_INSTALL_COMPONENTS", "a;b;c")
     monkeypatch.setenv("SKBUILD_INSTALL_STRIP", "False")
 
@@ -140,6 +142,7 @@ def test_skbuild_settings_envvar(tmp_path: Path, monkeypatch: pytest.MonkeyPatch
     assert settings.editable.mode == "redirect"
     assert settings.editable.rebuild
     assert not settings.editable.verbose
+    assert settings.build.tool_args == ["a", "b"]
     assert settings.install.components == ["a", "b", "c"]
     assert not settings.install.strip
 
@@ -185,6 +188,7 @@ def test_skbuild_settings_config_settings(
         "editable.mode": "redirect",
         "editable.rebuild": "True",
         "editable.verbose": "False",
+        "build.tool-args": ["a", "b"],
         "install.components": ["a", "b", "c"],
         "install.strip": "True",
     }
@@ -225,6 +229,7 @@ def test_skbuild_settings_config_settings(
     assert settings.editable.mode == "redirect"
     assert settings.editable.rebuild
     assert not settings.editable.verbose
+    assert settings.build.tool_args == ["a", "b"]
     assert settings.install.components == ["a", "b", "c"]
     assert settings.install.strip
 
@@ -269,6 +274,7 @@ def test_skbuild_settings_pyproject_toml(
             editable.mode = "redirect"
             editable.rebuild = true
             editable.verbose = false
+            build.tool-args = ["a", "b"]
             install.components = ["a", "b", "c"]
             install.strip = true
             [[tool.scikit-build.generate]]
@@ -318,6 +324,7 @@ def test_skbuild_settings_pyproject_toml(
     assert settings.editable.mode == "redirect"
     assert settings.editable.rebuild
     assert not settings.editable.verbose
+    assert settings.build.tool_args == ["a", "b"]
     assert settings.install.components == ["a", "b", "c"]
     assert settings.install.strip
     assert settings.generate == [