diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 675831734..d7143202f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -35,6 +35,7 @@ jobs: needs: lint runs-on: ${{ matrix.os }} strategy: + fail-fast: false matrix: os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-13, macos-15] python_version: ['3.13'] diff --git a/bin/generate_schema.py b/bin/generate_schema.py index 8ca6a1a76..28538e06f 100755 --- a/bin/generate_schema.py +++ b/bin/generate_schema.py @@ -54,21 +54,23 @@ type: string_array build-frontend: default: default - description: Set the tool to use to build, either "pip" (default for now), "build", or "build[uv]" + description: Set the tool to use to build, either "pip" (default for now), "build", "build[uv]", or "uv". 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 79796a1be..a856fc42a 100644 --- a/cibuildwheel/frontend.py +++ b/cibuildwheel/frontend.py @@ -7,7 +7,7 @@ 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"] @dataclass(frozen=True) diff --git a/cibuildwheel/platforms/ios.py b/cibuildwheel/platforms/ios.py index 3473ef740..fe2c1f281 100644 --- a/cibuildwheel/platforms/ios.py +++ b/cibuildwheel/platforms/ios.py @@ -283,7 +283,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) @@ -439,7 +439,7 @@ def build(options: Options, tmp_path: Path) -> None: build_options = options.build_options(config.identifier) build_frontend = build_options.build_frontend or BuildFrontendConfig("build") # 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 4932298aa..3795a9844 100644 --- a/cibuildwheel/platforms/linux.py +++ b/cibuildwheel/platforms/linux.py @@ -204,7 +204,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 or BuildFrontendConfig("build") - 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...") @@ -303,6 +303,19 @@ def build_in_container( ], env=env, ) + elif build_frontend.name == "uv": + container.call( + [ + "uv", + "build", + "--python=python", + container_package_dir, + "--wheel", + f"--out-dir={built_wheel_dir}", + *extra_flags, + ], + env=env, + ) else: assert_never(build_frontend) diff --git a/cibuildwheel/platforms/macos.py b/cibuildwheel/platforms/macos.py index 428971263..a390061f5 100644 --- a/cibuildwheel/platforms/macos.py +++ b/cibuildwheel/platforms/macos.py @@ -199,7 +199,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] @@ -375,6 +375,17 @@ def setup_python( *constraint_flags(dependency_constraint), env=env, ) + elif build_frontend == "uv": + assert uv_path is not None + call( + uv_path, + "pip", + "install", + "--upgrade", + "delocate", + *constraint_flags(dependency_constraint), + env=env, + ) else: assert_never(build_frontend) @@ -407,7 +418,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 or BuildFrontendConfig("build") - 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" @@ -494,6 +505,17 @@ def build(options: Options, tmp_path: Path) -> None: *extra_flags, env=build_env, ) + elif build_frontend.name == "uv": + call( + "uv", + "build", + "--python=python", + build_options.package_dir, + "--wheel", + f"--out-dir={built_wheel_dir}", + *extra_flags, + env=build_env, + ) else: assert_never(build_frontend) diff --git a/cibuildwheel/platforms/windows.py b/cibuildwheel/platforms/windows.py index e429015b9..12b95e8c5 100644 --- a/cibuildwheel/platforms/windows.py +++ b/cibuildwheel/platforms/windows.py @@ -244,10 +244,10 @@ def setup_python( raise ValueError(msg) assert base_python.exists() - if build_frontend == "build[uv]" and not can_use_uv(python_configuration): + if build_frontend in {"build[uv]", "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...") @@ -357,7 +357,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 or BuildFrontendConfig("build") - 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 @@ -440,6 +440,17 @@ def build(options: Options, tmp_path: Path) -> None: *extra_flags, env=build_env, ) + elif build_frontend.name == "uv": + call( + "uv", + "build", + "--python=python", + build_options.package_dir, + "--wheel", + f"--out-dir={built_wheel_dir}", + *extra_flags, + env=build_env, + ) else: assert_never(build_frontend) diff --git a/cibuildwheel/util/packaging.py b/cibuildwheel/util/packaging.py index f6619dd92..0b7bf11a6 100644 --- a/cibuildwheel/util/packaging.py +++ b/cibuildwheel/util/packaging.py @@ -198,6 +198,6 @@ def combine_constraints( user_constraints = env.get("PIP_CONSTRAINT") - env["UV_CONSTRAINT"] = env["PIP_CONSTRAINT"] = " ".join( + env["UV_BUILD_CONSTRAINT"] = env["UV_CONSTRAINT"] = env["PIP_CONSTRAINT"] = " ".join( c for c in [our_constraints, user_constraints] if c ) diff --git a/test/conftest.py b/test/conftest.py index 8c0c9504c..30b442ab4 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -108,11 +108,13 @@ def docker_warmup_fixture( return None -@pytest.fixture(params=["pip", "build"]) +@pytest.fixture(params=["pip", "build", "uv"]) def build_frontend_env_nouv(request: pytest.FixtureRequest) -> dict[str, str]: frontend = request.param - if platform == "pyodide" and frontend == "pip": - pytest.skip("Can't use pip as build frontend for pyodide platform") + if platform == "pyodide" and frontend in {"pip", "uv"}: + pytest.skip("Can't use pip or uv as build frontend for pyodide platform") + if frontend == "uv" and find_uv() is None: + pytest.skip("Can't find uv") return {"CIBW_BUILD_FRONTEND": frontend} diff --git a/unit_test/options_test.py b/unit_test/options_test.py index fbf181754..94f5ccc3a 100644 --- a/unit_test/options_test.py +++ b/unit_test/options_test.py @@ -321,6 +321,11 @@ def test_environment_pass_references(): "build", [], ), + ( + 'build-frontend = "uv"', + "uv", + [], + ), ( 'build-frontend = {name = "build"}', "build",