diff --git a/bin/generate_schema.py b/bin/generate_schema.py index e347fce5..b2f63118 100755 --- a/bin/generate_schema.py +++ b/bin/generate_schema.py @@ -57,21 +57,23 @@ properties: type: string_array build-frontend: default: default - description: Set the tool to use to build, either "build" (default), "build[uv]", or "pip" + description: Set the tool to use to build, either "build" (default), "build[uv]", "uv", or "pip" oneOf: - - enum: [pip, build, "build[uv]", default] + - enum: [pip, build, "build[uv]", uv, default] - type: string pattern: '^pip; ?args:' - type: string pattern: '^build; ?args:' - type: string pattern: '^build\\[uv\\]; ?args:' + - type: string + pattern: '^uv; ?args:' - type: object additionalProperties: false required: [name] properties: name: - enum: [pip, build, "build[uv]"] + enum: [pip, build, "build[uv]", uv] args: type: array items: diff --git a/cibuildwheel/frontend.py b/cibuildwheel/frontend.py index 6684851f..04464f98 100644 --- a/cibuildwheel/frontend.py +++ b/cibuildwheel/frontend.py @@ -7,7 +7,7 @@ from typing import Literal, Self, get_args from .logger import log from .util.helpers import parse_key_value_string -BuildFrontendName = Literal["pip", "build", "build[uv]"] +BuildFrontendName = Literal["pip", "build", "build[uv]", "uv"] @dataclasses.dataclass(frozen=True) diff --git a/cibuildwheel/platforms/ios.py b/cibuildwheel/platforms/ios.py index d7c7f3cc..c33909bc 100644 --- a/cibuildwheel/platforms/ios.py +++ b/cibuildwheel/platforms/ios.py @@ -284,7 +284,7 @@ def setup_python( build_frontend: BuildFrontendName, xbuild_tools: Sequence[str] | None, ) -> tuple[Path, dict[str, str]]: - if build_frontend == "build[uv]": + if build_frontend == "build[uv]" or build_frontend == "uv": msg = "uv doesn't support iOS" raise errors.FatalError(msg) @@ -441,7 +441,7 @@ def build(options: Options, tmp_path: Path) -> None: build_options = options.build_options(config.identifier) build_frontend = build_options.build_frontend # uv doesn't support iOS - if build_frontend.name == "build[uv]": + if build_frontend.name == "build[uv]" or build_frontend.name == "uv": msg = "uv doesn't support iOS" raise errors.FatalError(msg) diff --git a/cibuildwheel/platforms/linux.py b/cibuildwheel/platforms/linux.py index d2f133b1..ca649619 100644 --- a/cibuildwheel/platforms/linux.py +++ b/cibuildwheel/platforms/linux.py @@ -207,7 +207,7 @@ def build_in_container( local_identifier_tmp_dir = local_tmp_dir / config.identifier build_options = options.build_options(config.identifier) build_frontend = build_options.build_frontend - use_uv = build_frontend.name == "build[uv]" + use_uv = build_frontend.name in {"build[uv]", "uv"} pip = ["uv", "pip"] if use_uv else ["pip"] log.step("Setting up build environment...") @@ -307,6 +307,19 @@ def build_in_container( ], env=env, ) + case "uv": + container.call( + [ + "uv", + "build", + "--python=python", + container_package_dir, + "--wheel", + f"--out-dir={built_wheel_dir}", + *extra_flags, + ], + env=env, + ) case _: assert_never(build_frontend) @@ -509,6 +522,7 @@ def _matches_prepared_command(error_cmd: Sequence[str], command_template: str) - def troubleshoot(options: Options, error: Exception) -> None: if isinstance(error, subprocess.CalledProcessError) and ( error.cmd[0:4] == ["python", "-m", "pip", "wheel"] + or error.cmd[0:2] == ["uv", "build"] or error.cmd[0:3] == ["python", "-m", "build"] or _matches_prepared_command( error.cmd, options.build_options(None).repair_command diff --git a/cibuildwheel/platforms/macos.py b/cibuildwheel/platforms/macos.py index 32f3d81b..0e7d0275 100644 --- a/cibuildwheel/platforms/macos.py +++ b/cibuildwheel/platforms/macos.py @@ -218,7 +218,7 @@ def setup_python( build_frontend: BuildFrontendName, ) -> tuple[Path, dict[str, str]]: uv_path = find_uv() - use_uv = build_frontend == "build[uv]" + use_uv = build_frontend in {"build[uv]", "uv"} tmp.mkdir() implementation_id = python_configuration.identifier.split("-")[0] @@ -381,6 +381,17 @@ def setup_python( *constraint_flags(dependency_constraint), env=env, ) + case "uv": + assert uv_path is not None + call( + uv_path, + "pip", + "install", + "--upgrade", + "delocate", + *constraint_flags(dependency_constraint), + env=env, + ) case _: assert_never(build_frontend) @@ -413,7 +424,7 @@ def build(options: Options, tmp_path: Path) -> None: for config in python_configurations: build_options = options.build_options(config.identifier) build_frontend = build_options.build_frontend - use_uv = build_frontend.name == "build[uv]" + use_uv = build_frontend.name in {"build[uv]", "uv"} uv_path = find_uv() if use_uv and uv_path is None: msg = "uv not found" @@ -498,6 +509,18 @@ def build(options: Options, tmp_path: Path) -> None: *extra_flags, env=build_env, ) + case "uv": + assert uv_path is not None + call( + uv_path, + "build", + "--python=python", + build_options.package_dir, + "--wheel", + f"--out-dir={built_wheel_dir}", + *extra_flags, + env=build_env, + ) case _: assert_never(build_frontend) diff --git a/cibuildwheel/platforms/windows.py b/cibuildwheel/platforms/windows.py index 7a38fd22..879ee13f 100644 --- a/cibuildwheel/platforms/windows.py +++ b/cibuildwheel/platforms/windows.py @@ -271,7 +271,7 @@ def setup_python( if build_frontend == "build[uv]" and not can_use_uv(python_configuration): build_frontend = "build" - use_uv = build_frontend == "build[uv]" + use_uv = build_frontend in {"build[uv]", "uv"} uv_path = find_uv() log.step("Setting up build environment...") @@ -386,6 +386,8 @@ def build(options: Options, tmp_path: Path) -> None: if not python_configurations: return + uv_path = find_uv() + try: before_all_options_identifier = python_configurations[0].identifier before_all_options = options.build_options(before_all_options_identifier) @@ -403,8 +405,7 @@ def build(options: Options, tmp_path: Path) -> None: for config in python_configurations: build_options = options.build_options(config.identifier) build_frontend = build_options.build_frontend - - use_uv = build_frontend.name == "build[uv]" and can_use_uv(config) + use_uv = build_frontend.name in {"build[uv]", "uv"} and can_use_uv(config) log.build_start(config.identifier) identifier_tmp_dir = tmp_path / config.identifier @@ -501,6 +502,18 @@ def build(options: Options, tmp_path: Path) -> None: *extra_flags, env=env, ) + case "uv": + assert uv_path is not None + call( + uv_path, + "build", + "--python=python", + build_options.package_dir, + "--wheel", + f"--out-dir={built_wheel_dir}", + *extra_flags, + env=env, + ) case _: assert_never(build_frontend) diff --git a/test/conftest.py b/test/conftest.py index e05ef82d..7416139f 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -163,7 +163,7 @@ def build_frontend_env_nouv(request: pytest.FixtureRequest) -> dict[str, str]: return {"CIBW_BUILD_FRONTEND": frontend} -@pytest.fixture(params=["pip", "build", "build[uv]"]) +@pytest.fixture(params=["pip", "build", "build[uv]", "uv"]) def build_frontend_env(request: pytest.FixtureRequest) -> dict[str, str]: frontend = request.param marks = {m.name for m in request.node.iter_markers()} @@ -178,10 +178,12 @@ def build_frontend_env(request: pytest.FixtureRequest) -> dict[str, str]: if platform in {"pyodide", "ios", "android"} and frontend == "pip": pytest.skip(f"Can't use pip as build frontend for {platform}") - if platform == "pyodide" and frontend == "build[uv]": + if platform == "android" and frontend == "uv": + pytest.skip(f"Can't use uv as build frontend for {platform}") + if platform == "pyodide" and frontend in {"build[uv]", "uv"}: pytest.skip("Can't use uv with pyodide yet") uv_path = find_uv() - if uv_path is None and frontend == "build[uv]": + if uv_path is None and frontend in {"build[uv]", "uv"}: pytest.skip("Can't find uv, so skipping uv tests") if uv_path is not None and frontend == "build" and platform not in {"android", "ios"}: pytest.skip("No need to check build when uv is present") diff --git a/test/test_android.py b/test/test_android.py index eb16e2b1..6d6539ab 100644 --- a/test/test_android.py +++ b/test/test_android.py @@ -128,7 +128,7 @@ def test_frontend_good(tmp_path, build_frontend_env): assert wheels == [f"spam-0.1.0-cp313-cp313-android_21_{native_arch.android_abi}.whl"] -@pytest.mark.parametrize("frontend", ["pip"]) +@pytest.mark.parametrize("frontend", ["pip", "uv"]) def test_frontend_bad(frontend, tmp_path, capfd): new_c_project().generate(tmp_path) with pytest.raises(CalledProcessError): diff --git a/unit_test/options_test.py b/unit_test/options_test.py index b9847ed5..140ad033 100644 --- a/unit_test/options_test.py +++ b/unit_test/options_test.py @@ -328,6 +328,11 @@ def test_environment_pass_references(): "build", [], ), + ( + 'build-frontend = "uv"', + "uv", + [], + ), ( 'build-frontend = {name = "build"}', "build",