Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 25 additions & 33 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@ runs:
from subprocess import run

EXTRAS = set(e.strip() for e in "${{ inputs.extras }}".split(",") if e.strip())
if sys.platform == "linux":
EXTRAS.discard("uv")


class EnvBuilder(venv.EnvBuilder):
Expand All @@ -73,47 +71,41 @@ runs:
shutil.rmtree(venv_path)
builder = EnvBuilder()
builder.create(venv_path)
exposed_binaries = {"cibuildwheel"}
if "uv" in EXTRAS:
exposed_binaries.add("uv")
clean_bin_path = builder.bin_path.parent / f"{builder.bin_path.name}.clean"
clean_bin_path.mkdir()
for path in list(builder.bin_path.iterdir()):
if path.stem in exposed_binaries:
try:
os.symlink(path, clean_bin_path / path.name)
except OSError:
import shutil
cibw_bin = [p for p in builder.bin_path.glob("cibuildwheel*") if p.stem == "cibuildwheel"][0]

shutil.copy2(path, clean_bin_path / path.name)
full_path = f"{clean_bin_path}{os.pathsep}{os.environ['PATH']}"
with open(os.environ["GITHUB_OUTPUT"], "at") as f:
f.write(f"updated-path={full_path}\n")
f.write(f"cibw-bin={cibw_bin}\n")
f.write(f"prepend-path={builder.bin_path if 'uv' in EXTRAS else ''}\n")

print("::endgroup::")
EOF
shell: bash

# Redirecting stderr to stdout to fix interleaving issue in Actions.
- run: >
cibuildwheel
"${{ inputs.package-dir }}"
${{ inputs.output-dir != '' && format('--output-dir "{0}"', inputs.output-dir) || ''}}
${{ inputs.config-file != '' && format('--config-file "{0}"', inputs.config-file) || ''}}
${{ inputs.only != '' && format('--only "{0}"', inputs.only) || ''}}
2>&1
env:
PATH: "${{ steps.cibw.outputs.updated-path }}"
- run: |
prepend_path="${{ steps.cibw.outputs.prepend-path }}"
if [ -n "$prepend_path" ]; then
export PATH="$prepend_path:$PATH"
fi
"${{ steps.cibw.outputs.cibw-bin }}" \
"${{ inputs.package-dir }}" \
${{ inputs.output-dir != '' && format('--output-dir "{0}"', inputs.output-dir) || ''}} \
${{ inputs.config-file != '' && format('--config-file "{0}"', inputs.config-file) || ''}} \
${{ inputs.only != '' && format('--only "{0}"', inputs.only) || ''}} \
2>&1
shell: bash
if: runner.os != 'Windows'

# Windows needs powershell to interact nicely with Meson
- run: >
cibuildwheel
"${{ inputs.package-dir }}"
${{ inputs.output-dir != '' && format('--output-dir "{0}"', inputs.output-dir) || ''}}
${{ inputs.config-file != '' && format('--config-file "{0}"', inputs.config-file) || ''}}
${{ inputs.only != '' && format('--only "{0}"', inputs.only) || ''}}
env:
PATH: "${{ steps.cibw.outputs.updated-path }}"
- run: |
$PrependPath = "${{ steps.cibw.outputs.prepend-path }}"
if ($PrependPath) {
$env:PATH = "$PrependPath;$env:PATH"
}
& "${{ steps.cibw.outputs.cibw-bin }}" `
"${{ inputs.package-dir }}" `
${{ inputs.output-dir != '' && format('--output-dir "{0}"', inputs.output-dir) || ''}} `
${{ inputs.config-file != '' && format('--config-file "{0}"', inputs.config-file) || ''}} `
${{ inputs.only != '' && format('--only "{0}"', inputs.only) || ''}}
shell: pwsh
if: runner.os == 'Windows'
18 changes: 7 additions & 11 deletions cibuildwheel/platforms/android.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
from ..util.helpers import prepare_command
from ..util.packaging import find_compatible_wheel
from ..util.python_build_standalone import create_python_build_standalone_environment
from ..venv import constraint_flags, find_uv, virtualenv
from ..venv import constraint_flags, ensure_uv, virtualenv

ANDROID_TRIPLET = {
"arm64_v8a": "aarch64-linux-android",
Expand Down Expand Up @@ -191,11 +191,9 @@ def setup_env(
log.step("Setting up build environment...")
build_frontend = build_options.build_frontend.name
use_uv = build_frontend == "build[uv]"
uv_path = find_uv()
if use_uv and uv_path is None:
msg = "uv not found"
raise AssertionError(msg)
pip = ["pip"] if not use_uv else [str(uv_path), "pip"]
if use_uv:
ensure_uv()
pip = ["pip"] if not use_uv else ["uv", "pip"]

# Create virtual environment
python_exe = create_python_build_standalone_environment(
Expand Down Expand Up @@ -578,11 +576,9 @@ def test_wheel(state: BuildState, wheel: Path, *, build_frontend: str) -> None:

log.step("Testing wheel...")
use_uv = build_frontend == "build[uv]"
uv_path = find_uv()
if use_uv and uv_path is None:
msg = "uv not found"
raise AssertionError(msg)
pip = ["pip"] if not use_uv else [str(uv_path), "pip"]
if use_uv:
ensure_uv()
pip = ["pip"] if not use_uv else ["uv", "pip"]

native_arch = arch_synonym(platform.machine(), platforms.native_platform(), "android")
if state.config.arch != native_arch:
Expand Down
18 changes: 8 additions & 10 deletions cibuildwheel/platforms/macos.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
)
from ..util.helpers import prepare_command, unwrap
from ..util.packaging import find_compatible_wheel, get_pip_version
from ..venv import constraint_flags, find_uv, virtualenv
from ..venv import constraint_flags, ensure_uv, virtualenv


@functools.cache
Expand Down Expand Up @@ -217,8 +217,9 @@ def setup_python(
environment: ParsedEnvironment,
build_frontend: BuildFrontendName,
) -> tuple[Path, dict[str, str]]:
uv_path = find_uv()
use_uv = build_frontend == "build[uv]"
if use_uv:
ensure_uv()

tmp.mkdir()
implementation_id = python_configuration.identifier.split("-")[0]
Expand Down Expand Up @@ -370,14 +371,13 @@ def setup_python(
env=env,
)
case "build[uv]":
assert uv_path is not None
call(
uv_path,
"uv",
"pip",
"install",
"--upgrade",
"delocate",
"build[virtualenv, uv]",
"build",
*constraint_flags(dependency_constraint),
env=env,
)
Expand Down Expand Up @@ -414,11 +414,9 @@ def build(options: Options, tmp_path: Path) -> None:
build_options = options.build_options(config.identifier)
build_frontend = build_options.build_frontend
use_uv = build_frontend.name == "build[uv]"
uv_path = find_uv()
if use_uv and uv_path is None:
msg = "uv not found"
raise AssertionError(msg)
pip = ["pip"] if not use_uv else [str(uv_path), "pip"]
if use_uv:
ensure_uv()
pip = ["pip"] if not use_uv else ["uv", "pip"]
log.build_start(config.identifier)

identifier_tmp_dir = tmp_path / config.identifier
Expand Down
10 changes: 5 additions & 5 deletions cibuildwheel/platforms/windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from ..util.file import CIBW_CACHE_PATH, copy_test_sources, download, extract_zip, move_file
from ..util.helpers import prepare_command, unwrap
from ..util.packaging import find_compatible_wheel, get_pip_version
from ..venv import constraint_flags, find_uv, virtualenv
from ..venv import constraint_flags, ensure_uv, virtualenv


def get_nuget_args(
Expand Down Expand Up @@ -272,7 +272,8 @@ def setup_python(
build_frontend = "build"

use_uv = build_frontend == "build[uv]"
uv_path = find_uv()
if use_uv:
ensure_uv()

log.step("Setting up build environment...")
venv_path = tmp / "venv"
Expand Down Expand Up @@ -323,13 +324,12 @@ def setup_python(
env=env,
)
case "build[uv]":
assert uv_path is not None
call(
uv_path,
"uv",
"pip",
"install",
"--upgrade",
"build[virtualenv]",
"build",
*constraint_flags(dependency_constraint),
env=env,
)
Expand Down
23 changes: 14 additions & 9 deletions cibuildwheel/venv.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import contextlib
import functools
import os
import shutil
Expand Down Expand Up @@ -108,6 +107,7 @@ def virtualenv(
assert python.exists()

if use_uv:
ensure_uv()
call("uv", "venv", venv_path, "--python", python)
else:
virtualenv_app, virtualenv_version = _ensure_virtualenv(version)
Expand Down Expand Up @@ -156,12 +156,17 @@ def virtualenv(
return venv_env


def find_uv() -> Path | None:
# Prefer uv in our environment
with contextlib.suppress(ImportError, FileNotFoundError):
from uv import find_uv_bin # noqa: PLC0415

return Path(find_uv_bin())
def ensure_uv() -> None:
"""
Ensures uv is available on PATH. Raises an error with a helpful message if not found.

uv_on_path = shutil.which("uv")
return Path(uv_on_path) if uv_on_path else None
When using build-frontend=build[uv], callers must ensure uv is available
on PATH before invoking cibuildwheel.
"""
if shutil.which("uv") is None:
msg = (
"uv not found on PATH. When using build-frontend=build[uv], "
"ensure uv is installed and available on PATH. "
"You can install it with 'pip install uv' or see https://docs.astral.sh/uv/getting-started/installation/"
)
raise FileNotFoundError(msg)
2 changes: 1 addition & 1 deletion docs/ci-services.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ To build Linux, macOS, and Windows wheels using GitHub Actions, create a `.githu
`package-dir: .`, `output-dir: wheelhouse` and `config-file: ''`
locations (those values are the defaults). You can also pass a
comma-separated list of extras to install additional packages.
For example, `extras: "uv"` to install UV into the virtual environment.
For example, `extras: "uv"` to make uv available for cibuildwheel to use.

!!! tab "pipx"
The GitHub Actions runners have pipx installed, so you can easily build in
Expand Down
20 changes: 13 additions & 7 deletions docs/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -470,13 +470,19 @@ Default: `build`

Choose which build frontend to use.

You can use "build\[uv\]", which will use an external [uv][] everywhere
possible, both through `--installer=uv` passed to build, as well as when making
all build and test environments. This will generally speed up cibuildwheel.
Make sure you have an external uv on Windows and macOS, either by
pre-installing it, or installing cibuildwheel with the `uv` extra, which is
possible by manually passing `cibuildwheel[uv]` to installers or by using the
`extras` option in the [cibuildwheel action](ci-services.md#github-actions).
You can use "build\[uv\]", which will use [uv][] everywhere possible, both
through `--installer=uv` passed to build, as well as when making all build and
test environments. This will generally speed up cibuildwheel.

When using `build[uv]`, ensure uv is available on PATH. You can do this by:

- Pre-installing uv (e.g., via `pip install uv` or using `astral-sh/setup-uv`
in GitHub Actions)
- Installing cibuildwheel with the `uv` extra by manually passing
`cibuildwheel[uv]` to installers
- Using the `extras: "uv"` option in the [cibuildwheel action](ci-services.md#github-actions),
which installs uv in an isolated environment

uv currently does not support iOS or musllinux on s390x, ppc64le and riscv64.

On Android and Pyodide, the "pip" frontend is not supported.
Expand Down
8 changes: 4 additions & 4 deletions test/conftest.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import json
import os
import shutil
import subprocess
from collections.abc import Generator

Expand All @@ -11,7 +12,6 @@
from cibuildwheel.options import CommandLineArguments, Options
from cibuildwheel.selector import EnableGroup
from cibuildwheel.typing import PLATFORMS
from cibuildwheel.venv import find_uv

from .utils import DEFAULT_CIBW_ENABLE, EMULATED_ARCHS, get_platform

Expand Down Expand Up @@ -180,10 +180,10 @@ def build_frontend_env(request: pytest.FixtureRequest) -> dict[str, str]:
pytest.skip(f"Can't use pip as build frontend for {platform}")
if platform == "pyodide" and frontend == "build[uv]":
pytest.skip("Can't use uv with pyodide yet")
uv_path = find_uv()
if uv_path is None and frontend == "build[uv]":
uv_available = shutil.which("uv") is not None
if not uv_available and frontend == "build[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"}:
if uv_available and frontend == "build" and platform not in {"android", "ios"}:
pytest.skip("No need to check build when uv is present")

return {"CIBW_BUILD_FRONTEND": frontend}
Expand Down
Loading