Add pyodide-build as a separate build frontend and update its build verbosity handling (#2609)

This commit is contained in:
Agriya Khetarpal
2026-06-22 00:12:15 +05:30
committed by GitHub
parent 291be6a345
commit 7933323184
16 changed files with 200 additions and 25 deletions
+19
View File
@@ -393,6 +393,25 @@ for os_name, command in [
del oses["linux"]["properties"]["dependency-versions"]
oses["pyodide"]["properties"]["build-frontend"] = {
**schema["properties"]["build-frontend"],
"default": "pyodide-build",
"description": 'On the pyodide platform, the build frontend must be "pyodide-build"',
"oneOf": [
{"enum": ["pyodide-build"]},
{"type": "string", "pattern": "^pyodide-build; ?args:"},
{
"type": "object",
"additionalProperties": False,
"required": ["name"],
"properties": {
"name": {"enum": ["pyodide-build"]},
"args": {"type": "array", "items": {"type": "string"}},
},
},
],
}
schema["properties"]["overrides"] = overrides
schema["properties"] |= oses
+6 -1
View File
@@ -16,7 +16,7 @@ if TYPE_CHECKING:
from cibuildwheel.typing import PathOrStr
BuildFrontendName = Literal["pip", "build", "build[uv]", "uv"]
BuildFrontendName = Literal["pip", "build", "build[uv]", "uv", "pyodide-build"]
@dataclasses.dataclass(frozen=True)
@@ -46,6 +46,11 @@ class BuildFrontendConfig:
def _get_verbosity_flags(level: int, frontend: BuildFrontendName) -> list[str]:
if frontend == "pyodide-build":
if level > 0:
return ["-" + min(level, 2) * "v"]
return []
if level < 0:
return ["-" + -level * "q"]
+7
View File
@@ -860,6 +860,13 @@ class Options:
msg = f"Failed to parse build frontend. {e}"
raise errors.ConfigurationError(msg) from e
if self.platform == "pyodide" and build_frontend.name != "pyodide-build":
msg = "The pyodide platform requires the 'pyodide-build' build frontend"
raise errors.ConfigurationError(msg)
if self.platform != "pyodide" and build_frontend.name == "pyodide-build":
msg = "The 'pyodide-build' build frontend is only supported on the pyodide platform"
raise errors.ConfigurationError(msg)
try:
environment = parse_environment(environment_config)
except (EnvironmentParseError, ValueError) as e:
+6
View File
@@ -463,6 +463,9 @@ def setup_python(
*constraint_flags(dependency_constraint),
env=env,
)
case "pyodide-build":
msg = "The 'pyodide-build' build frontend is not supported on this platform"
raise errors.FatalError(msg)
case _:
assert_never(build_frontend)
@@ -588,6 +591,9 @@ def build(options: Options, tmp_path: Path) -> None:
*extra_flags,
env=env,
)
case "pyodide-build":
msg = "The 'pyodide-build' build frontend is not supported on this platform"
raise errors.FatalError(msg)
case _:
assert_never(build_frontend)
+3
View File
@@ -360,6 +360,9 @@ def build_in_container(
],
env=env,
)
case "pyodide-build":
msg = "The 'pyodide-build' build frontend is not supported on this platform"
raise errors.FatalError(msg)
case _:
assert_never(build_frontend)
+6
View File
@@ -449,6 +449,9 @@ def setup_python(
*constraint_flags(dependency_constraint),
env=env,
)
case "pyodide-build":
msg = "The 'pyodide-build' build frontend is not supported on this platform"
raise errors.FatalError(msg)
case _:
assert_never(build_frontend)
@@ -585,6 +588,9 @@ def build(options: Options, tmp_path: Path) -> None:
*extra_flags,
env=build_env,
)
case "pyodide-build":
msg = "The 'pyodide-build' build frontend is not supported on this platform"
raise errors.FatalError(msg)
case _:
assert_never(build_frontend)
-4
View File
@@ -426,10 +426,6 @@ def build(options: Options, tmp_path: Path) -> None:
build_options = options.build_options(config.identifier)
build_frontend = build_options.build_frontend
if build_frontend.name == "pip":
msg = "The pyodide platform doesn't support pip frontend"
raise errors.FatalError(msg)
log.build_start(config.identifier)
identifier_tmp_dir = tmp_path / config.identifier
+6
View File
@@ -401,6 +401,9 @@ def setup_python(
*constraint_flags(dependency_constraint),
env=env,
)
case "pyodide-build":
msg = "The 'pyodide-build' build frontend is not supported on this platform"
raise errors.FatalError(msg)
case _:
assert_never(build_frontend)
@@ -539,6 +542,9 @@ def build(options: Options, tmp_path: Path) -> None:
*extra_flags,
env=env,
)
case "pyodide-build":
msg = "The 'pyodide-build' build frontend is not supported on this platform"
raise errors.FatalError(msg)
case _:
assert_never(build_frontend)
@@ -1203,7 +1203,40 @@
"$ref": "#/properties/before-test"
},
"build-frontend": {
"$ref": "#/properties/build-frontend"
"default": "pyodide-build",
"description": "On the pyodide platform, the build frontend must be \"pyodide-build\"",
"oneOf": [
{
"enum": [
"pyodide-build"
]
},
{
"type": "string",
"pattern": "^pyodide-build; ?args:"
},
{
"type": "object",
"additionalProperties": false,
"required": [
"name"
],
"properties": {
"name": {
"enum": [
"pyodide-build"
]
},
"args": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
],
"title": "CIBW_BUILD_FRONTEND"
},
"build-verbosity": {
"$ref": "#/properties/build-verbosity"
+1
View File
@@ -79,3 +79,4 @@ repair-wheel-command = "auditwheel repair --ldpaths {ldpaths} -w {dest_dir} {whe
[tool.cibuildwheel.pyodide]
audit-command = ""
build-frontend = "pyodide-build"
+13 -9
View File
@@ -481,7 +481,11 @@ files (from workspaces) are not supported.
are building GraalPy wheels, use `build` or `pip` as the build frontend
instead of `build[uv]` or `uv`.
On Android and Pyodide, the "pip" frontend is not supported.
On Android, the "pip" frontend is not supported.
On Pyodide, `build-frontend` must be `pyodide-build`, which is the default for that
platform. See [Pyodide build frontend support](platforms.md#pyodide-build-frontend) for
details.
You can specify extra arguments to pass to the build frontend using the
optional `args` option.
@@ -1910,14 +1914,14 @@ will not produce more logging about the build itself. Other levels only affect
the build frontend output, which is usually things like resolving and
downloading dependencies. The settings are:
| | build | pip | uv | desc |
|-------------|-------|--------|-------|----------------------------------------|
| -2 | `-qq` | `-qq` | `-qq` | even more quiet, where supported |
| -1 | `-q` | `-q` | `-q` | quiet mode, where supported |
| 0 (default) | | | | default for build tool |
| 1 | | `-v` | | print backend output |
| 2 | `-v` | `-vv` | `-v` | print log messages e.g. resolving info |
| 3 | `-vv` | `-vvv` | `-vv` | print even more debug info |
| | build | pip | uv | pyodide-build | desc |
|-------------|-------|--------|-------|---------------|----------------------------------------|
| -2 | `-qq` | `-qq` | `-qq` | | even more quiet, where supported |
| -1 | `-q` | `-q` | `-q` | | quiet mode, where supported |
| 0 (default) | | | | | default for build tool |
| 1 | | `-v` | | `-v` | print backend output |
| 2 | `-v` | `-vv` | `-v` | `-vv` | print log messages e.g. resolving info |
| 3 | `-vv` | `-vvv` | `-vv` | | print even more debug info |
Settings that are not supported for a specific frontend will log a warning.
The default build frontend is `build`, which does show build backend output by
+5
View File
@@ -188,6 +188,11 @@ repair-wheel-command = "pyodide auditwheel repair --libdir /path/to/libraries --
The `--libdir` option specifies the directory containing cross-compiled shared libraries for WASM. You should not use the system library directories (e.g. `/usr/lib`), as those libraries are not built for WebAssembly.
### Build frontend support {: #pyodide-build-frontend}
The pyodide platform builds wheels by shelling out to `pyodide build`, via the `pyodide-build` [`build-frontend`](options.md#build-frontend), which itself is a meta build frontend and passes through commands to pypa/build with specialised handling. This is the only supported frontend for this platform, and is used by default.
[`build-verbosity`](options.md#build-verbosity) is passed through to `pyodide build` as `-v`/`-vv`. It is capped at `-vv`. `pyodide build` has no `-vvv` flag.
## Android {: android}
+5 -5
View File
@@ -157,8 +157,8 @@ def build_frontend_env_nouv(request: pytest.FixtureRequest) -> dict[str, str]:
marks = {m.name for m in request.node.iter_markers()}
platform = "pyodide" if "pyodide" in marks else get_platform()
if platform == "pyodide" and frontend == "pip":
pytest.skip("Can't use pip as build frontend for pyodide platform")
if platform == "pyodide":
pytest.skip("pyodide only supports the pyodide-build frontend")
return {"CIBW_BUILD_FRONTEND": frontend}
@@ -176,10 +176,10 @@ def build_frontend_env(request: pytest.FixtureRequest) -> Generator[dict[str, st
else:
platform = get_platform()
if platform in {"pyodide", "ios", "android"} and frontend == "pip":
if platform == "pyodide":
pytest.skip("pyodide only supports the pyodide-build frontend")
if platform in {"ios", "android"} and frontend == "pip":
pytest.skip(f"Can't use pip 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 in {"build[uv]", "uv"}:
pytest.skip("Can't find uv, so skipping uv tests")
+1 -1
View File
@@ -50,7 +50,7 @@ def test(tmp_path: Path) -> None:
"""python -c "import pathlib, sys; pathlib.Path('{project}/pythonversion_bb.txt').write_text(sys.version)" && """
f'''python -c "import pathlib, sys; pathlib.Path('{{project}}/pythonprefix_bb.txt').write_text({SYS_PREFIX})"'''
)
frontend = "build"
frontend = "pyodide-build" if utils.get_platform() == "pyodide" else "build"
if utils.get_platform() != "pyodide":
before_build = f"python -m pip install setuptools && {before_build}"
frontend = f"{frontend};args: --no-isolation"
+13 -3
View File
@@ -15,7 +15,17 @@ if TYPE_CHECKING:
"frontend_name",
[
pytest.param("pip", marks=utils.skip_if_pyodide("No pip for pyodide")),
"build",
pytest.param(
"build",
marks=utils.skip_if_pyodide("pyodide only supports the pyodide-build frontend"),
),
pytest.param(
"pyodide-build",
marks=pytest.mark.skipif(
utils.get_platform() != "pyodide",
reason="pyodide-build frontend is only valid on pyodide",
),
),
],
)
def test_build_frontend_args(
@@ -27,7 +37,7 @@ def test_build_frontend_args(
# the build will fail because the frontend is called with '-h' - it prints the help message
add_env = {"CIBW_BUILD_FRONTEND": f"{frontend_name}; args: -h"}
if utils.get_platform() == "pyodide":
if frontend_name == "pyodide-build":
add_env["TERM"] = "dumb" # disable color / style
add_env["NO_COLOR"] = "1"
with pytest.raises(subprocess.CalledProcessError):
@@ -40,7 +50,7 @@ def test_build_frontend_args(
if frontend_name == "pip":
assert "Usage:" in captured.out
assert "Wheel Options:" in captured.out
elif utils.get_platform() == "pyodide":
elif frontend_name == "pyodide-build":
assert "Usage: pyodide build" in captured.out
else:
assert "usage:" in captured.out
+75 -1
View File
@@ -39,6 +39,8 @@ TYPE_CHECKING = False
if TYPE_CHECKING:
from collections.abc import Callable, Sequence
from cibuildwheel.typing import PlatformName
PYPROJECT_1 = """
[tool.cibuildwheel]
build = ["cp38-*", "cp313-*"]
@@ -480,6 +482,73 @@ def test_build_frontend_option(
assert parsed_build_frontend.args == ()
def test_pyodide_build_frontend_default(tmp_path: Path) -> None:
args = CommandLineArguments.defaults()
args.package_dir = tmp_path
tmp_path.joinpath("pyproject.toml").write_text("[tool.cibuildwheel]\n")
options = Options(platform="pyodide", command_line_arguments=args, env={})
build_frontend = options.build_options(identifier=None).build_frontend
assert build_frontend.name == "pyodide-build"
assert build_frontend.args == []
def test_pyodide_build_frontend_args(tmp_path: Path) -> None:
args = CommandLineArguments.defaults()
args.package_dir = tmp_path
tmp_path.joinpath("pyproject.toml").write_text(
textwrap.dedent(
"""\
[tool.cibuildwheel.pyodide]
build-frontend = {name = "pyodide-build", args = ["--exports=whole_archive"]}
"""
)
)
options = Options(platform="pyodide", command_line_arguments=args, env={})
build_frontend = options.build_options(identifier=None).build_frontend
assert build_frontend.name == "pyodide-build"
assert build_frontend.args == ["--exports=whole_archive"]
@pytest.mark.parametrize(
("platform", "build_frontend_str"),
[
("pyodide", "pip"),
("pyodide", "build"),
("pyodide", "build[uv]"),
("pyodide", "uv"),
("linux", "pyodide-build"),
("macos", "pyodide-build"),
("windows", "pyodide-build"),
("android", "pyodide-build"),
("ios", "pyodide-build"),
],
)
def test_build_frontend_platform_mismatch(
tmp_path: Path, platform: PlatformName, build_frontend_str: str
) -> None:
args = CommandLineArguments.defaults()
args.package_dir = tmp_path
tmp_path.joinpath("pyproject.toml").write_text(
textwrap.dedent(
f"""\
[tool.cibuildwheel]
build-frontend = "{build_frontend_str}"
"""
)
)
options = Options(platform=platform, command_line_arguments=args, env={})
with pytest.raises(errors.ConfigurationError):
options.build_options(identifier=None)
def test_override_inherit_environment(tmp_path: Path) -> None:
args = CommandLineArguments.defaults()
args.package_dir = tmp_path
@@ -663,10 +732,15 @@ def test_deprecated_image(
("build", 3, ["-Ca", "-Cb", "-1", "-vv"]),
("build[uv]", 3, ["-Ca", "-Cb", "-1", "-vv"]),
("uv", 3, ["-Ca", "-Cb", "-1", "-vv"]),
("pyodide-build", -1, ["-Ca", "-Cb", "-1"]),
("pyodide-build", 0, ["-Ca", "-Cb", "-1"]),
("pyodide-build", 1, ["-Ca", "-Cb", "-1", "-v"]),
("pyodide-build", 2, ["-Ca", "-Cb", "-1", "-vv"]),
("pyodide-build", 3, ["-Ca", "-Cb", "-1", "-vv"]),
],
)
def test_get_build_frontend_extra_flags(
frontend: Literal["pip", "build", "build[uv]"],
frontend: Literal["pip", "build", "build[uv]", "uv", "pyodide-build"],
verbosity: int,
result: list[str],
monkeypatch: pytest.MonkeyPatch,