Files
cibuildwheel/bin/generate_schema.py
T
Henry Schreiner 148ea702ac chore: faster and stricter mypy (#2968)
* chore: faster mypy

This makes mypy around 26% faster from a cold cache.

Signed-off-by: Henry Schreiner <henryfs@princeton.edu>

* chore: enable more mypy error codes

Turn on possibly-undefined, exhaustive-match, and other strict flags, and
fix what they report. Two real bugs: pyodide never set `repaired_wheel`
when it reused a compatible wheel, so the test step failed with a
NameError; the OCI shell read loop spun forever if the shell exited.

Assisted-by: ClaudeCode:claude-opus-5

* test: carry default OCI runtime into podman vfs test config

Podman ignores its usual config files when CONTAINERS_CONF is set, so it
falls back to the first runtime found on PATH. On current GitHub Ubuntu
runner images that is crun 1.14.1, which cannot parse the OCI spec v1.2.x
config that podman 5.8.4 generates ("crun: unknown version specified").
Query the default runtime path and set it in the generated config.

Assisted-by: ClaudeCode:claude-fable-5

* fix: annotate vfs config dict so mypy passes on Linux

mypy on macOS marks the rest of the function unreachable after the
darwin pytest.skip, so the strict index error only appears on Linux.

Assisted-by: ClaudeCode:claude-fable-5

* chore: minor cleanup of variable name

Signed-off-by: Henry Schreiner <henryfs@princeton.edu>

* test: cover container shell exiting during call

Assisted-by: ClaudeCode:claude-opus-5

---------

Signed-off-by: Henry Schreiner <henryfs@princeton.edu>
2026-08-17 19:02:37 -04:00

438 lines
13 KiB
Python
Executable File

#!/usr/bin/env -S uv run --script
# /// script
# dependencies = ["pyyaml"]
# ///
import argparse
import copy
import json
from typing import Any
import yaml
parser = argparse.ArgumentParser(allow_abbrev=False)
parser.add_argument("--schemastore", action="store_true", help="Generate schema_store version")
args = parser.parse_args()
# The defaults in the schema are used by external tools for validation and IDE support. They
# should match the values in defaults.toml, which are used by cibuildwheel itself.
starter = """
$schema: http://json-schema.org/draft-07/schema#
$id: https://github.com/pypa/cibuildwheel/blob/main/cibuildwheel/resources/cibuildwheel.schema.json
$defs:
inherit:
enum:
- none
- prepend
- append
default: none
description: How to inherit the parent's value.
enable:
enum:
- cpython-prerelease
- graalpy
- pyodide-prerelease
- pypy
- pypy-eol
description: A Python version or flavor to enable.
additionalProperties: false
description: cibuildwheel's settings.
type: object
properties:
audit-command:
description: Execute a shell command to audit each wheel after it is repaired. Use {wheel} for each wheel path, or {abi3_wheel} to only audit abi3 wheels.
type: string_array
audit-requires:
description: Install Python dependencies for the audit step.
type: string_array
archs:
description: Change the architectures built on your machine by default.
type: string_array
before-all:
description: Execute a shell command on the build system before any wheels are built.
type: string_array
before-build:
description: Execute a shell command preparing each wheel's build.
type: string_array
before-test:
description: Execute a shell command before testing each wheel.
type: string_array
build:
default: ['*']
description: Choose the Python versions to build.
type: string_array
build-frontend: {} # filled in by build_frontend_schema below
build-verbosity:
type: integer
minimum: -3
maximum: 3
default: 0
description: Increase/decrease the output of pip wheel.
config-settings:
description: Specify config-settings for the build backend.
type: string_table_array
container-engine:
oneOf:
- enum: [docker, podman]
- type: string
pattern: '^docker; ?(create_args|disable_host_mount):'
- type: string
pattern: '^podman; ?(create_args|disable_host_mount):'
- type: object
additionalProperties: false
required: [name]
properties:
name:
enum: [docker, podman]
create-args:
type: array
items:
type: string
disable-host-mount:
type: boolean
dependency-versions:
default: pinned
description: Specify how cibuildwheel controls the versions of the tools it uses
oneOf:
- enum: [pinned, latest]
- type: string
description: Path to a file containing dependency versions, or inline package specifications, starting with "packages:"
not:
enum: [pinned, latest]
- type: object
additionalProperties: false
properties:
file:
type: string
- type: object
additionalProperties: false
properties:
packages:
type: array
items:
type: string
enable:
description: Enable or disable certain builds.
oneOf:
- $ref: "#/$defs/enable"
- type: array
items:
$ref: "#/$defs/enable"
environment:
description: Set environment variables needed during the build.
type: string_table
environment-pass:
description: Set environment variables on the host to pass-through to the container
during the build.
type: string_array
manylinux-aarch64-image:
type: string
description: Specify alternative manylinux / musllinux container images
manylinux-armv7l-image:
type: string
description: Specify alternative manylinux / musllinux container images
manylinux-i686-image:
type: string
description: Specify alternative manylinux / musllinux container images
manylinux-ppc64le-image:
type: string
description: Specify alternative manylinux / musllinux container images
manylinux-pypy_aarch64-image:
type: string
description: Specify alternative manylinux / musllinux container images
manylinux-pypy_i686-image:
type: string
description: Specify alternative manylinux / musllinux container images
manylinux-pypy_x86_64-image:
type: string
description: Specify alternative manylinux / musllinux container images
manylinux-riscv64-image:
type: string
description: Specify alternative manylinux / musllinux container images
manylinux-s390x-image:
type: string
description: Specify alternative manylinux / musllinux container images
manylinux-x86_64-image:
type: string
description: Specify alternative manylinux / musllinux container images
musllinux-aarch64-image:
type: string
description: Specify alternative manylinux / musllinux container images
musllinux-armv7l-image:
type: string
description: Specify alternative manylinux / musllinux container images
musllinux-i686-image:
type: string
description: Specify alternative manylinux / musllinux container images
musllinux-ppc64le-image:
type: string
description: Specify alternative manylinux / musllinux container images
musllinux-riscv64-image:
type: string
description: Specify alternative manylinux / musllinux container images
musllinux-s390x-image:
type: string
description: Specify alternative manylinux / musllinux container images
musllinux-x86_64-image:
type: string
description: Specify alternative manylinux / musllinux container images
xbuild-tools:
description: Binaries on the path that should be included in an isolated cross-build environment
type: string_array
xbuild-files:
description: Platform-specific files in the build environment
type: string_table_array
pyodide-version:
type: string
description: Specify the version of Pyodide to use
repair-wheel-command:
description: Execute a shell command to repair each built wheel.
type: string_array
skip:
description: Choose the Python versions to skip.
type: string_array
test-command:
description: Execute a shell command to test each built wheel.
type: string_array
test-extras:
description: Install your wheel for testing using `extras_require`
type: string_array
test-sources:
description: Test files that are required by the test environment
type: string_array
test-groups:
description: Install extra groups when testing
type: string_array
test-requires:
description: Install Python dependencies before running the tests
type: string_array
test-skip:
description: Skip running tests on some builds.
type: string_array
test-environment:
description: Set environment variables for the test environment
type: string_table
test-runtime:
description: Additional configuration for the test runner
oneOf:
- type: string
pattern: '^$'
- type: object
additionalProperties: false
- type: string
pattern: 'args:'
- type: object
additionalProperties: false
required: [args]
properties:
args:
type: array
items:
type: string
"""
schema = yaml.safe_load(starter)
string_array = yaml.safe_load(
"""
- type: string
- type: array
items:
type: string
"""
)
string_table_array = yaml.safe_load(
"""
- type: string
- type: object
additionalProperties: false
patternProperties:
.+:
oneOf:
- type: string
- type: array
items:
type: string
"""
)
string_table = yaml.safe_load(
"""
- type: string
- type: object
additionalProperties: false
patternProperties:
.+:
type: string
"""
)
FRONTENDS = ["pip", "build", "build[uv]", "uv"]
def build_frontend_schema(
names: list[str], description: str, default: str = "default"
) -> dict[str, Any]:
"""
A frontend is a name, a "name; args: ..." string, or a table with a name and args.
"""
# Only the brackets in "build[uv]" need escaping
patterns = [name.replace("[", r"\[").replace("]", r"\]") for name in names]
return {
"default": default,
"description": description,
"oneOf": [
{"enum": [*names, "default"]},
*({"type": "string", "pattern": f"^{pattern}; ?args:"} for pattern in patterns),
{
"type": "object",
"additionalProperties": False,
"required": ["name"],
"properties": {
"name": {"enum": names},
"args": {"type": "array", "items": {"type": "string"}},
},
},
],
"title": "CIBW_BUILD_FRONTEND",
}
schema["properties"]["build-frontend"] = build_frontend_schema(
[*FRONTENDS, "pyodide-build"],
'Set the tool to use to build, either "build" (default), "build[uv]", "uv", or "pip"'
' ("pyodide-build" for pyodide)',
)
for value in schema["properties"].values():
match value:
case {"type": "string_array"}:
del value["type"]
value["oneOf"] = string_array
case {"type": "string_table"}:
del value["type"]
value["oneOf"] = string_table
case {"type": "string_table_array"}:
del value["type"]
value["oneOf"] = string_table_array
case _:
pass
overrides = yaml.safe_load(
"""
type: array
description: An overrides array
items:
type: object
required: ["select"]
minProperties: 2
additionalProperties: false
properties:
select: {}
inherit:
type: object
additionalProperties: false
properties:
audit-command: {"$ref": "#/$defs/inherit"}
audit-requires: {"$ref": "#/$defs/inherit"}
before-all: {"$ref": "#/$defs/inherit"}
before-build: {"$ref": "#/$defs/inherit"}
xbuild-tools: {"$ref": "#/$defs/inherit"}
xbuild-files: {"$ref": "#/$defs/inherit"}
before-test: {"$ref": "#/$defs/inherit"}
config-settings: {"$ref": "#/$defs/inherit"}
container-engine: {"$ref": "#/$defs/inherit"}
environment: {"$ref": "#/$defs/inherit"}
environment-pass: {"$ref": "#/$defs/inherit"}
repair-wheel-command: {"$ref": "#/$defs/inherit"}
test-command: {"$ref": "#/$defs/inherit"}
test-extras: {"$ref": "#/$defs/inherit"}
test-sources: {"$ref": "#/$defs/inherit"}
test-requires: {"$ref": "#/$defs/inherit"}
test-environment: {"$ref": "#/$defs/inherit"}
test-runtime: {"$ref": "#/$defs/inherit"}
"""
)
for key, value in schema["properties"].items():
value["title"] = f"CIBW_{key.replace('-', '_').upper()}"
non_global_options = {k: {"$ref": f"#/properties/{k}"} for k in schema["properties"]}
del non_global_options["build"]
del non_global_options["skip"]
del non_global_options["test-skip"]
del non_global_options["enable"]
overrides["items"]["properties"]["select"]["oneOf"] = string_array
overrides["items"]["properties"] |= non_global_options.copy()
del overrides["items"]["properties"]["archs"]
not_linux = non_global_options.copy()
del not_linux["environment-pass"]
del not_linux["container-engine"]
for key in list(not_linux):
if "linux-" in key:
del not_linux[key]
def as_object(d: dict[str, Any]) -> dict[str, Any]:
return {
"type": "object",
"additionalProperties": False,
"properties": copy.deepcopy(d),
}
oses = {
"linux": as_object(non_global_options),
"windows": as_object(not_linux),
"macos": as_object(not_linux),
"pyodide": as_object(not_linux),
"android": as_object(not_linux),
"ios": as_object(not_linux),
}
for os_name, command in [
("linux", "auditwheel repair -w {dest_dir} {wheel}"),
("windows", "delvewheel repair -w {dest_dir} -v {wheel}"),
("macos", "delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel}"),
("android", "auditwheel repair --ldpaths {ldpaths} -w {dest_dir} {wheel}"),
]:
oses[os_name]["properties"]["repair-wheel-command"] = {
**schema["properties"]["repair-wheel-command"],
"default": command,
}
del oses["linux"]["properties"]["dependency-versions"]
schema["$defs"]["build-frontend-no-pyodide"] = build_frontend_schema(
FRONTENDS,
'Set the tool to use to build, either "build" (default), "build[uv]", "uv", or "pip"',
)
for os_val in oses.values():
os_val["properties"]["build-frontend"] = {"$ref": "#/$defs/build-frontend-no-pyodide"}
oses["pyodide"]["properties"]["build-frontend"] = build_frontend_schema(
["pyodide-build"],
'On the pyodide platform, the build frontend must be "pyodide-build"',
default="pyodide-build",
)
schema["properties"]["overrides"] = overrides
schema["properties"] |= oses
if args.schemastore:
schema["$id"] = "https://json.schemastore.org/partial-cibuildwheel.json"
schema["description"] = (
"cibuildwheel's settings. Generated with ./bin/generate_schema.py --schemastore from cibuildwheel."
)
print(json.dumps(schema, indent=2))