Since #2609, the pyodide platform required the 'pyodide-build' frontend,
but a global build-frontend setting (TOML or CIBW_BUILD_FRONTEND)
overrides the platform default, so previously-working configs like
build-frontend = "build" failed with a ConfigurationError. Even
"default" failed, since it was mapped to "build" before the check.
Resolve "default" to the platform default, and warn and use
pyodide-build when another frontend is set on pyodide, matching
pre-#2609 behavior where the frontend name only affected verbosity
flags.
Assisted-by: ClaudeCode:claude-opus-4.8
* Update dependencies
* fix: older pip for graalpy on Windows
The dependency update regenerated the constraints and dropped the pip
hold-back for GraalPy on Windows, where newer pip breaks. Restore the
marker-based pin.
Assisted-by: ClaudeCode:claude-opus-4.8
* ci: keep graalpy pip workaround across dependency bumps
update_constraints now re-applies the GraalPy Windows pip hold-back after
uv pip compile, so the bot no longer wipes it each run. Target only the
Python versions that ship a GraalPy config (3.12), derived from
build-platforms.toml; drop the stale pin from the 3.11 constraints.
Assisted-by: ClaudeCode:claude-opus-4.8
* fix(macos): accept .tar.gz PyPy archives
PyPy switched its macOS downloads from .tar.bz2 to .tar.gz, which tripped
the hard-coded extension assert in install_pypy. Accept both.
Assisted-by: ClaudeCode:claude-opus-4.8
* ci: hold filelock back for graalpy
filelock >=3.30 imports errno.ENOTSUP, which GraalPy lacks, so virtualenv
fails on GraalPy. Hold it at 3.29.4 for GraalPy until the upstream fix
ships (~2026-08), reusing the update_constraints workaround mechanism.
Assisted-by: ClaudeCode:claude-opus-4.8
* ci: bump filelock for graalpy
* revert android cp315 update
see https://github.com/pypa/cibuildwheel/pull/2933#issuecomment-4821042715
---------
Co-authored-by: cibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com>
Co-authored-by: Henry Schreiner <henryfs@princeton.edu>
Co-authored-by: mayeut <mayeut@users.noreply.github.com>
* Update dependencies
* restore graalpy windows workaround
* aand the other one
---------
Co-authored-by: cibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com>
Co-authored-by: Joe Rickerby <joerick@mac.com>
Architecture name lookup in `parse_config` was a case-sensitive StrEnum
lookup. Since the enum carries both lowercase `arm64` (macOS/Android) and
uppercase `ARM64` (Windows), `parse_config("arm64", "windows")` silently
returned the macOS member and later failed with a confusing "Invalid archs
option" error, while lowercase `amd64` failed to parse at all.
Resolve names case-insensitively, preferring a member valid for the target
platform before falling back to any case-insensitive match (which keeps the
clear ConfigurationError for genuinely invalid names).
Closes#2373
Assisted-by: ClaudeCode:claude-opus-4.8
* chore: minor cleanups and perf tweaks from code review
- util/file.py, util/python_build_standalone.py: use hashlib.file_digest
for streaming SHA-256 verification instead of loading whole archives
into memory with read_bytes()
- logger.py: convert colors/symbols properties to functools.cached_property
so the Colors/Symbols objects are constructed only once per Logger instance
- platforms/windows.py: remove redundant .strip() on where_pip (already
stripped at assignment)
- util/python_build_standalone.py: remove unreachable python_base_dir.exists()
guard (callers always pass a fresh temp subdirectory)
- util/file.py: add comment explaining the getattr shim for
tar_.extraction_filter and when it can be removed
Assisted-by: ClaudeCode:claude-fable-5
* revert: restore assertion to check python_base_dir existence
* Add FAQ section on caching cibuildwheel's downloaded tools
Adds a Tips entry covering:
- What cibuildwheel caches (CPython/PyPy installers, virtualenv,
python-build-standalone archives) and the default per-OS cache folder.
- How to override the cache location with ``CIBW_CACHE_PATH``.
- A worked GitHub Actions example pairing ``actions/cache`` with
``CIBW_CACHE_PATH`` so the cache survives between runs.
- A pointer to ``--clean-cache`` for invalidating stale entries.
Closes#1585.
Per @joerick's request in the issue ("a caching section in the FAQ
would be great, if you can contribute it") the scope is intentionally
narrow — just FAQ-level guidance. Platform-specific caching (e.g.
Windows NuGet, addressed in #2839) lives in the platforms doc.
* docs: bump actions/cache v4 to v5
* docs: move cache to runner.temp, add cache poisoning warning (review feedback)
Drops the GraalPy 3.11 / GraalPy 24.2 builds (gp311_242) as agreed in
pypa/cibuildwheel#2741: a ~6-month overlap after the GraalPy 25 (gp312)
release. GraalPy 3.12 (gp312_250) stays.
This also removes the GraalPy 24-only workarounds that were explicitly
marked "Remove when GraalPy 24.x is dropped":
- the Visual Studio compiler-discovery (vswhere) and build-isolation
(graalpython#491) workarounds in the Windows backend
- the issue-491 Windows skip/filter hacks in test_pep518 and
test_dependency_versions (added in the original GraalPy PR #1538 and
unneeded since GraalPy 25 per #2597)
The uv-doesn't-support-graalpy skip (#2754), the `graalpy` enable group,
and the generated constraints pins apply to all GraalPy and are kept.
Assisted-by: ClaudeCode:claude-opus-4.8
* chore: use ConfigurationError when package_dir is outside cwd
Replace a generic Exception with errors. ConfigurationError when package_dir is not inside the working directory. This makes the error type consistent with the project's error handling and allows the CLI to treat this as a fatal error with the expected exit behavior.
* test: merge linux package_dir test into linux_build_steps_test
Fold the standalone linux_build_test.py into the existing
linux_build_steps_test.py, which already covers the linux platform
build orchestration. Reuses the file's existing import boilerplate and
module-qualified build() call style.
Assisted-by: ClaudeCode:claude-opus-4.8
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Henry Schreiner <henryfs@princeton.edu>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Replace the explicit if/else used to resolve pinned container images with pinned_images.get(config_value, config_value) in the manylinux and musllinux loops inside _compute_build_options. This keeps behavior identical while reducing duplication and simplifying the code in cibuildwheel/options.py.
* ci: improve Azure test reliability
Azure has intermittent failures, often the macOS framework-Python
`posix_spawn: Undefined error: 0` spawn race under load, plus transient
download flakiness. Address both:
- Retry integration tests via pytest-rerunfailures (already required):
add `--reruns=2 --reruns-delay=5` to the serial and non-serial runs so a
single transient flake self-heals instead of failing the whole job.
- Cap Azure at `--num-processes 3` to reduce concurrent process spawning
(the macOS runner was using 4 xdist workers), lowering the spawn-race
probability and peak resource pressure.
- Cache downloads across runs: relocate CIBW_CACHE_PATH to a stable path
and add a Cache@2 task (interpreter downloads + per-worker test pip
caches live under it), cutting the network-download flake surface.
- Bump the Azure host interpreter to 3.13.
Assisted-by: ClaudeCode:claude-opus-4.8
* ci: stabilize iOS tests, bound rerun cost
An iOS run on Azure (build 9043) hung for the full 40-min pytest timeout
on the second build config of test_ios_platforms, then reran twice
(reruns=2), blowing past the 180-min job cap with no useful signal. The
hang was a stuck simulator/xcodebuild on the second config, after the
first config had run.
- Shut down running simulators before each parametrized config via a
`clean_ios_simulators` fixture, so a simulator left booted/wedged by the
previous config isn't reused.
- Drop iOS reruns from 2 to 1. One retry still covers the documented
"fails the first time" simulator-boot flake, while halving the
worst-case retry cost (3x40=120min -> 2x40=80min) on a hang.
Assisted-by: ClaudeCode:claude-opus-4.8
* ci: don't limit num processes
Signed-off-by: Henry Schreiner <henryfs@princeton.edu>
* ci: rerun OCI unit tests that pull from Docker Hub
test_local_image et al. occasionally fail on a transient Docker Hub
anonymous-pull blip ("unauthorized: authentication required") when
pulling debian:trixie-slim. The integration runs already self-heal via
pytest-rerunfailures, but the unit run had no reruns, so a single flake
failed the whole job. Mark the three network-dependent OCI tests
(test_local_image, test_enter_error, test_multiarch_image) flaky so they
retry instead of blanket-rerunning the suite.
Assisted-by: ClaudeCode:claude-opus-4.8
* Drop iOS clean step
Co-authored-by: Henry Schreiner <HenrySchreinerIII@gmail.com>
* ci: scope integration reruns to test_0_basic::test
Drop the blanket --reruns from the serial and non-serial integration
runs and instead mark test_0_basic.py::test as flaky, so only the known
flaky basic test is retried.
Assisted-by: ClaudeCode:claude-opus-4.8
---------
Signed-off-by: Henry Schreiner <henryfs@princeton.edu>
`OCIContainer.__enter__` was hardened in #2879 to always clean up on
failure, but `__exit__` was left unguarded. If the container/bash had
already died, `bash_stdin.write("exit 0")` raises `BrokenPipeError`; if
bash refuses to exit, `process.wait(timeout=30)` raises `TimeoutExpired`.
Either propagated out of `__exit__` before `_remove_container()` ran,
leaking the container (and, on timeout, the `start` process too).
Wrap the teardown so a broken pipe or timeout instead forces the process
down (kill + wait) and always falls through to container removal, while
still respecting CIBW_DEBUG_KEEP_CONTAINER. Pipe closes are now also
guarded so a flush-on-close against a dead pipe can't mask cleanup.
Add non-docker unit tests for the clean-exit, already-dead-bash, and
shutdown-timeout paths.
Assisted-by: ClaudeCode:claude-opus-4.8
* fix: minor 4.0 issues in android validation, docs, and docstring
- android: raise a clear FatalError when ANDROID_API_LEVEL is not an
integer, instead of an uncaught ValueError deep in localized_vars
- docs: note that delvewheel is the default Windows repair-wheel-command
since 4.0 and how to skip it for platform-tagged wheels with no
extension module; update the now-outdated FAQ wording
- venv: fix _parse_pip_constraint_for_virtualenv docstring to say
marker-bearing constraints are evaluated against the host's default
environment (not skipped) when marker_env is None
Assisted-by: ClaudeCode:claude-opus-4.8
* fix: use int() with original error for ANDROID_API_LEVEL validation
Handle ANDROID_API_LEVEL validation via try/except around int() so the
original exception message is surfaced, and so non-ASCII Unicode digits
(which isdigit() accepts but int() may reject) are handled correctly.
Assisted-by: ClaudeCode:claude-opus-4.8
* refactor: address reivew by moving error catch
Assisted-by: ClaudeCode:claude-opus-4.8
Signed-off-by: Henry Schreiner <henryfs@princeton.edu>
---------
Signed-off-by: Henry Schreiner <henryfs@princeton.edu>
* fix: detect musl libc when downloading python-build-standalone
`platform.libc_ver()` only ever reports glibc and returns `("", "")` on
musl systems, so `"musl" in platform.libc_ver()` was never true and musl
hosts (e.g. Alpine) always selected the `gnu` python-build-standalone
asset, which won't run.
Detect musl by shelling out to `ldd --version`, which prints "musl libc"
on musl-based systems. This probes the actual system loader rather than
the host Python's linkage, so it stays correct even when cibuildwheel
runs under a relocatable/statically-linked interpreter. Falls back to
glibc when `ldd` is unavailable.
Assisted-by: ClaudeCode:claude-opus-4.8
* test: add type annotations to satisfy mypy and ruff
Assisted-by: ClaudeCode:claude-opus-4.8