Skip to content

Commit fa956ec

Browse files
committed
feat: support uv with iOS
Signed-off-by: Henry Schreiner <[email protected]>
1 parent 3a52416 commit fa956ec

File tree

2 files changed

+100
-50
lines changed

2 files changed

+100
-50
lines changed

cibuildwheel/platforms/ios.py

Lines changed: 95 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
find_compatible_wheel,
3838
get_pip_version,
3939
)
40-
from ..venv import constraint_flags, virtualenv
40+
from ..venv import constraint_flags, find_uv, virtualenv
4141
from .macos import install_cpython as install_build_cpython
4242

4343

@@ -151,6 +151,7 @@ def cross_virtualenv(
151151
venv_path: Path,
152152
dependency_constraint: Path | None,
153153
xbuild_tools: Sequence[str] | None,
154+
build_frontend: str,
154155
) -> dict[str, str]:
155156
"""Create a cross-compilation virtual environment.
156157
@@ -181,13 +182,15 @@ def cross_virtualenv(
181182
:param xbuild_tools: A list of executable names (without paths) that are
182183
on the path, but must be preserved in the cross environment.
183184
"""
185+
use_uv = build_frontend == "build[uv]"
186+
184187
# Create an initial macOS virtual environment
185188
env = virtualenv(
186189
py_version,
187190
build_python,
188191
venv_path,
189192
dependency_constraint,
190-
use_uv=False,
193+
use_uv=use_uv,
191194
)
192195

193196
# Convert the macOS virtual environment into an iOS virtual environment
@@ -282,9 +285,12 @@ def setup_python(
282285
build_frontend: BuildFrontendName,
283286
xbuild_tools: Sequence[str] | None,
284287
) -> tuple[Path, dict[str, str]]:
285-
if build_frontend == "build[uv]":
286-
msg = "uv doesn't support iOS"
287-
raise errors.FatalError(msg)
288+
use_uv = build_frontend == "build[uv]"
289+
uv_path = find_uv()
290+
if use_uv and uv_path is None:
291+
msg = "uv not found"
292+
raise AssertionError(msg)
293+
pip = ["pip"] if not use_uv else [str(uv_path), "pip"]
288294

289295
# An iOS environment requires 2 python installs - one for the build machine
290296
# (macOS), and one for the target (iOS). We'll only ever interact with the
@@ -334,6 +340,7 @@ def setup_python(
334340
venv_path=venv_path,
335341
dependency_constraint=dependency_constraint,
336342
xbuild_tools=xbuild_tools,
343+
build_frontend=build_frontend,
337344
)
338345
venv_bin_path = venv_path / "bin"
339346
assert venv_bin_path.exists()
@@ -343,16 +350,17 @@ def setup_python(
343350

344351
# upgrade pip to the version matching our constraints
345352
# if necessary, reinstall it to ensure that it's available on PATH as 'pip'
346-
pip = ["python", "-m", "pip"]
347-
call(
348-
*pip,
349-
"install",
350-
"--upgrade",
351-
"pip",
352-
*constraint_flags(dependency_constraint),
353-
env=env,
354-
cwd=venv_path,
355-
)
353+
if not use_uv:
354+
pip = ["python", "-m", "pip"]
355+
call(
356+
*pip,
357+
"install",
358+
"--upgrade",
359+
"pip",
360+
*constraint_flags(dependency_constraint),
361+
env=env,
362+
cwd=venv_path,
363+
)
356364

357365
# Apply our environment after pip is ready
358366
env = environment.as_dictionary(prev_environment=env)
@@ -370,17 +378,18 @@ def setup_python(
370378
call("python", "--version", env=env)
371379

372380
# Check what pip version we're on
373-
assert (venv_bin_path / "pip").exists()
374-
which_pip = call("which", "pip", env=env, capture_stdout=True).strip()
375-
print(which_pip)
376-
if which_pip != str(venv_bin_path / "pip"):
377-
msg = (
378-
"cibuildwheel: pip available on PATH doesn't match our installed instance. "
379-
"If you have modified PATH, ensure that you don't overwrite cibuildwheel's "
380-
"entry or insert pip above it."
381-
)
382-
raise errors.FatalError(msg)
383-
call("pip", "--version", env=env)
381+
if not use_uv:
382+
assert (venv_bin_path / "pip").exists()
383+
which_pip = call("which", "pip", env=env, capture_stdout=True).strip()
384+
print(which_pip)
385+
if which_pip != str(venv_bin_path / "pip"):
386+
msg = (
387+
"cibuildwheel: pip available on PATH doesn't match our installed instance. "
388+
"If you have modified PATH, ensure that you don't overwrite cibuildwheel's "
389+
"entry or insert pip above it."
390+
)
391+
raise errors.FatalError(msg)
392+
call("pip", "--version", env=env)
384393

385394
# Ensure that IPHONEOS_DEPLOYMENT_TARGET is set in the environment
386395
env.setdefault("IPHONEOS_DEPLOYMENT_TARGET", "13.0")
@@ -392,13 +401,22 @@ def setup_python(
392401
pass
393402
case "build":
394403
call(
395-
"pip",
404+
*pip,
396405
"install",
397406
"--upgrade",
398407
"build[virtualenv]",
399408
*constraint_flags(dependency_constraint),
400409
env=env,
401410
)
411+
case "build[uv]":
412+
call(
413+
*pip,
414+
"install",
415+
"--upgrade",
416+
"build",
417+
*constraint_flags(dependency_constraint),
418+
env=env,
419+
)
402420
case _:
403421
assert_never(build_frontend)
404422

@@ -438,10 +456,12 @@ def build(options: Options, tmp_path: Path) -> None:
438456
for config in python_configurations:
439457
build_options = options.build_options(config.identifier)
440458
build_frontend = build_options.build_frontend
441-
# uv doesn't support iOS
442-
if build_frontend.name == "build[uv]":
443-
msg = "uv doesn't support iOS"
444-
raise errors.FatalError(msg)
459+
use_uv = build_frontend.name == "build[uv]"
460+
uv_path = find_uv()
461+
if use_uv and uv_path is None:
462+
msg = "uv not found"
463+
raise AssertionError(msg)
464+
pip = ["pip"] if not use_uv else [str(uv_path), "pip"]
445465

446466
log.build_start(config.identifier)
447467

@@ -461,7 +481,6 @@ def build(options: Options, tmp_path: Path) -> None:
461481
build_frontend=build_frontend.name,
462482
xbuild_tools=build_options.xbuild_tools,
463483
)
464-
pip_version = get_pip_version(env)
465484

466485
compatible_wheel = find_compatible_wheel(built_wheels, config.identifier)
467486
if compatible_wheel:
@@ -490,7 +509,9 @@ def build(options: Options, tmp_path: Path) -> None:
490509
)
491510

492511
build_env = env.copy()
493-
build_env["VIRTUALENV_PIP"] = pip_version
512+
if not use_uv:
513+
pip_version = get_pip_version(env)
514+
build_env["VIRTUALENV_PIP"] = pip_version
494515
if constraints_path:
495516
combine_constraints(build_env, constraints_path, None)
496517

@@ -521,6 +542,18 @@ def build(options: Options, tmp_path: Path) -> None:
521542
*extra_flags,
522543
env=build_env,
523544
)
545+
case "build[uv]":
546+
call(
547+
"python",
548+
"-m",
549+
"build",
550+
build_options.package_dir,
551+
"--wheel",
552+
"--installer=uv",
553+
f"--outdir={built_wheel_dir}",
554+
*extra_flags,
555+
env=build_env,
556+
)
524557
case _:
525558
assert_never(build_frontend)
526559

@@ -582,20 +615,35 @@ def build(options: Options, tmp_path: Path) -> None:
582615
ios_version = test_env["IPHONEOS_DEPLOYMENT_TARGET"]
583616
platform_tag = f"ios_{ios_version.replace('.', '_')}_{config.arch}_{config.sdk}"
584617

585-
call(
586-
"python",
587-
"-m",
588-
"pip",
589-
"install",
590-
"--only-binary=:all:",
591-
"--platform",
592-
platform_tag,
593-
"--target",
594-
testbed_path / "iOSTestbed" / "app_packages",
595-
f"{test_wheel}{build_options.test_extras}",
596-
*build_options.test_requires,
597-
env=test_env,
598-
)
618+
if use_uv:
619+
call(
620+
*pip,
621+
"install",
622+
"--only-binary=:all:",
623+
"--platform",
624+
platform_tag,
625+
"--target",
626+
testbed_path / "iOSTestbed" / "app_packages",
627+
f"{test_wheel}{build_options.test_extras}",
628+
*build_options.test_requires,
629+
env=test_env,
630+
)
631+
632+
else:
633+
call(
634+
"python",
635+
"-m",
636+
"pip",
637+
"install",
638+
"--only-binary=:all:",
639+
"--platform",
640+
platform_tag,
641+
"--target",
642+
testbed_path / "iOSTestbed" / "app_packages",
643+
f"{test_wheel}{build_options.test_extras}",
644+
*build_options.test_requires,
645+
env=test_env,
646+
)
599647

600648
log.step("Running test suite...")
601649

test/test_ios.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,12 @@ def skip_if_ios_testing_not_supported() -> None:
5555
@pytest.mark.parametrize(
5656
"build_config",
5757
[
58-
# Default to the pip build frontend
58+
# Check the default build frontend
5959
{"CIBW_PLATFORM": "ios"},
60-
# Also check the build frontend
61-
{"CIBW_PLATFORM": "ios", "CIBW_BUILD_FRONTEND": "build"},
60+
# Check the build[uv] frontend
61+
{"CIBW_PLATFORM": "ios", "CIBW_BUILD_FRONTEND": "build[[uv]"},
62+
# Check the pip frontend
63+
{"CIBW_PLATFORM": "ios", "CIBW_BUILD_FRONTEND": "pip"},
6264
],
6365
)
6466
def test_ios_platforms(tmp_path, build_config, monkeypatch, capfd):

0 commit comments

Comments
 (0)