feat: option to build directly with uv (#2322)
* feat: option to build directly with uv Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> tests: add uv to tests Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> fix: uv doesn't pick special Pythons at the top of the path Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> fix: set uv build constraints too Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> fix: uv doesn't support PyPy 3.8 Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> tests: add pyproject.toml for failing test Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> Revert "tests: add pyproject.toml for failing test" This reverts commit 619bde368a67dfdd8f02dc33a1525c27bc8cbe91. tests: skip uv output test Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> * tests: uv was in the wrong fixture Signed-off-by: Henry Schreiner <henryfs@princeton.edu> * fix: from @trim21 Signed-off-by: Henry Schreiner <henryfs@princeton.edu> * tests: improve skipping Signed-off-by: Henry Schreiner <henryfs@princeton.edu> * fix: some fixes from copilot review Signed-off-by: Henry Schreiner <henryfs@princeton.edu> --------- Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> Signed-off-by: Henry Schreiner <henryfs@princeton.edu>
This commit is contained in:
@@ -57,21 +57,23 @@ properties:
|
|||||||
type: string_array
|
type: string_array
|
||||||
build-frontend:
|
build-frontend:
|
||||||
default: default
|
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:
|
oneOf:
|
||||||
- enum: [pip, build, "build[uv]", default]
|
- enum: [pip, build, "build[uv]", uv, default]
|
||||||
- type: string
|
- type: string
|
||||||
pattern: '^pip; ?args:'
|
pattern: '^pip; ?args:'
|
||||||
- type: string
|
- type: string
|
||||||
pattern: '^build; ?args:'
|
pattern: '^build; ?args:'
|
||||||
- type: string
|
- type: string
|
||||||
pattern: '^build\\[uv\\]; ?args:'
|
pattern: '^build\\[uv\\]; ?args:'
|
||||||
|
- type: string
|
||||||
|
pattern: '^uv; ?args:'
|
||||||
- type: object
|
- type: object
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
required: [name]
|
required: [name]
|
||||||
properties:
|
properties:
|
||||||
name:
|
name:
|
||||||
enum: [pip, build, "build[uv]"]
|
enum: [pip, build, "build[uv]", uv]
|
||||||
args:
|
args:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from typing import Literal, Self, get_args
|
|||||||
from .logger import log
|
from .logger import log
|
||||||
from .util.helpers import parse_key_value_string
|
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)
|
@dataclasses.dataclass(frozen=True)
|
||||||
|
|||||||
@@ -284,7 +284,7 @@ def setup_python(
|
|||||||
build_frontend: BuildFrontendName,
|
build_frontend: BuildFrontendName,
|
||||||
xbuild_tools: Sequence[str] | None,
|
xbuild_tools: Sequence[str] | None,
|
||||||
) -> tuple[Path, dict[str, str]]:
|
) -> 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"
|
msg = "uv doesn't support iOS"
|
||||||
raise errors.FatalError(msg)
|
raise errors.FatalError(msg)
|
||||||
|
|
||||||
@@ -441,7 +441,7 @@ def build(options: Options, tmp_path: Path) -> None:
|
|||||||
build_options = options.build_options(config.identifier)
|
build_options = options.build_options(config.identifier)
|
||||||
build_frontend = build_options.build_frontend
|
build_frontend = build_options.build_frontend
|
||||||
# uv doesn't support iOS
|
# 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"
|
msg = "uv doesn't support iOS"
|
||||||
raise errors.FatalError(msg)
|
raise errors.FatalError(msg)
|
||||||
|
|
||||||
|
|||||||
@@ -207,7 +207,7 @@ def build_in_container(
|
|||||||
local_identifier_tmp_dir = local_tmp_dir / config.identifier
|
local_identifier_tmp_dir = local_tmp_dir / config.identifier
|
||||||
build_options = options.build_options(config.identifier)
|
build_options = options.build_options(config.identifier)
|
||||||
build_frontend = build_options.build_frontend
|
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"]
|
pip = ["uv", "pip"] if use_uv else ["pip"]
|
||||||
|
|
||||||
log.step("Setting up build environment...")
|
log.step("Setting up build environment...")
|
||||||
@@ -307,6 +307,19 @@ def build_in_container(
|
|||||||
],
|
],
|
||||||
env=env,
|
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 _:
|
case _:
|
||||||
assert_never(build_frontend)
|
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:
|
def troubleshoot(options: Options, error: Exception) -> None:
|
||||||
if isinstance(error, subprocess.CalledProcessError) and (
|
if isinstance(error, subprocess.CalledProcessError) and (
|
||||||
error.cmd[0:4] == ["python", "-m", "pip", "wheel"]
|
error.cmd[0:4] == ["python", "-m", "pip", "wheel"]
|
||||||
|
or error.cmd[0:2] == ["uv", "build"]
|
||||||
or error.cmd[0:3] == ["python", "-m", "build"]
|
or error.cmd[0:3] == ["python", "-m", "build"]
|
||||||
or _matches_prepared_command(
|
or _matches_prepared_command(
|
||||||
error.cmd, options.build_options(None).repair_command
|
error.cmd, options.build_options(None).repair_command
|
||||||
|
|||||||
@@ -218,7 +218,7 @@ def setup_python(
|
|||||||
build_frontend: BuildFrontendName,
|
build_frontend: BuildFrontendName,
|
||||||
) -> tuple[Path, dict[str, str]]:
|
) -> tuple[Path, dict[str, str]]:
|
||||||
uv_path = find_uv()
|
uv_path = find_uv()
|
||||||
use_uv = build_frontend == "build[uv]"
|
use_uv = build_frontend in {"build[uv]", "uv"}
|
||||||
|
|
||||||
tmp.mkdir()
|
tmp.mkdir()
|
||||||
implementation_id = python_configuration.identifier.split("-")[0]
|
implementation_id = python_configuration.identifier.split("-")[0]
|
||||||
@@ -381,6 +381,17 @@ def setup_python(
|
|||||||
*constraint_flags(dependency_constraint),
|
*constraint_flags(dependency_constraint),
|
||||||
env=env,
|
env=env,
|
||||||
)
|
)
|
||||||
|
case "uv":
|
||||||
|
assert uv_path is not None
|
||||||
|
call(
|
||||||
|
uv_path,
|
||||||
|
"pip",
|
||||||
|
"install",
|
||||||
|
"--upgrade",
|
||||||
|
"delocate",
|
||||||
|
*constraint_flags(dependency_constraint),
|
||||||
|
env=env,
|
||||||
|
)
|
||||||
case _:
|
case _:
|
||||||
assert_never(build_frontend)
|
assert_never(build_frontend)
|
||||||
|
|
||||||
@@ -413,7 +424,7 @@ def build(options: Options, tmp_path: Path) -> None:
|
|||||||
for config in python_configurations:
|
for config in python_configurations:
|
||||||
build_options = options.build_options(config.identifier)
|
build_options = options.build_options(config.identifier)
|
||||||
build_frontend = build_options.build_frontend
|
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()
|
uv_path = find_uv()
|
||||||
if use_uv and uv_path is None:
|
if use_uv and uv_path is None:
|
||||||
msg = "uv not found"
|
msg = "uv not found"
|
||||||
@@ -498,6 +509,18 @@ def build(options: Options, tmp_path: Path) -> None:
|
|||||||
*extra_flags,
|
*extra_flags,
|
||||||
env=build_env,
|
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 _:
|
case _:
|
||||||
assert_never(build_frontend)
|
assert_never(build_frontend)
|
||||||
|
|
||||||
|
|||||||
@@ -271,7 +271,7 @@ def setup_python(
|
|||||||
if build_frontend == "build[uv]" and not can_use_uv(python_configuration):
|
if build_frontend == "build[uv]" and not can_use_uv(python_configuration):
|
||||||
build_frontend = "build"
|
build_frontend = "build"
|
||||||
|
|
||||||
use_uv = build_frontend == "build[uv]"
|
use_uv = build_frontend in {"build[uv]", "uv"}
|
||||||
uv_path = find_uv()
|
uv_path = find_uv()
|
||||||
|
|
||||||
log.step("Setting up build environment...")
|
log.step("Setting up build environment...")
|
||||||
@@ -386,6 +386,8 @@ def build(options: Options, tmp_path: Path) -> None:
|
|||||||
if not python_configurations:
|
if not python_configurations:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
uv_path = find_uv()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
before_all_options_identifier = python_configurations[0].identifier
|
before_all_options_identifier = python_configurations[0].identifier
|
||||||
before_all_options = options.build_options(before_all_options_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:
|
for config in python_configurations:
|
||||||
build_options = options.build_options(config.identifier)
|
build_options = options.build_options(config.identifier)
|
||||||
build_frontend = build_options.build_frontend
|
build_frontend = build_options.build_frontend
|
||||||
|
use_uv = build_frontend.name in {"build[uv]", "uv"} and can_use_uv(config)
|
||||||
use_uv = build_frontend.name == "build[uv]" and can_use_uv(config)
|
|
||||||
log.build_start(config.identifier)
|
log.build_start(config.identifier)
|
||||||
|
|
||||||
identifier_tmp_dir = tmp_path / config.identifier
|
identifier_tmp_dir = tmp_path / config.identifier
|
||||||
@@ -501,6 +502,18 @@ def build(options: Options, tmp_path: Path) -> None:
|
|||||||
*extra_flags,
|
*extra_flags,
|
||||||
env=env,
|
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 _:
|
case _:
|
||||||
assert_never(build_frontend)
|
assert_never(build_frontend)
|
||||||
|
|
||||||
|
|||||||
+5
-3
@@ -163,7 +163,7 @@ def build_frontend_env_nouv(request: pytest.FixtureRequest) -> dict[str, str]:
|
|||||||
return {"CIBW_BUILD_FRONTEND": frontend}
|
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]:
|
def build_frontend_env(request: pytest.FixtureRequest) -> dict[str, str]:
|
||||||
frontend = request.param
|
frontend = request.param
|
||||||
marks = {m.name for m in request.node.iter_markers()}
|
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":
|
if platform in {"pyodide", "ios", "android"} and frontend == "pip":
|
||||||
pytest.skip(f"Can't use pip as build frontend for {platform}")
|
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")
|
pytest.skip("Can't use uv with pyodide yet")
|
||||||
uv_path = find_uv()
|
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")
|
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_path is not None and frontend == "build" and platform not in {"android", "ios"}:
|
||||||
pytest.skip("No need to check build when uv is present")
|
pytest.skip("No need to check build when uv is present")
|
||||||
|
|||||||
@@ -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"]
|
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):
|
def test_frontend_bad(frontend, tmp_path, capfd):
|
||||||
new_c_project().generate(tmp_path)
|
new_c_project().generate(tmp_path)
|
||||||
with pytest.raises(CalledProcessError):
|
with pytest.raises(CalledProcessError):
|
||||||
|
|||||||
@@ -328,6 +328,11 @@ def test_environment_pass_references():
|
|||||||
"build",
|
"build",
|
||||||
[],
|
[],
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
'build-frontend = "uv"',
|
||||||
|
"uv",
|
||||||
|
[],
|
||||||
|
),
|
||||||
(
|
(
|
||||||
'build-frontend = {name = "build"}',
|
'build-frontend = {name = "build"}',
|
||||||
"build",
|
"build",
|
||||||
|
|||||||
Reference in New Issue
Block a user