Compare commits

...
46 Commits
Author SHA1 Message Date
Henry Schreiner 4726cd35bb Bump version: v4.1.1 2026-07-24 16:31:06 -04:00
Henry Schreiner 1af5cd76db docs: switch from mkdocs to properdocs (#2946)
properdocs is a drop-in MkDocs fork by the original MkDocs maintainers.
The existing plugins (include-markdown, macros, intersphinx) and pymdown
extensions work unchanged; themes are unbundled, so the readthedocs
theme comes from properdocs-theme-readthedocs.

Assisted-by: ClaudeCode:claude-fable-5
2026-07-24 14:55:37 -04:00
Henry Schreiner b21d76ae03 docs: add missing cp314-pyodide_wasm32 to build-id table (#2947)
* docs: add missing cp314-pyodide_wasm32 to build-id table

The build-id table in docs/options.md is hand-maintained, so a new
identifier can be missed when build-platforms.toml changes. Add a unit
test that checks every identifier appears in the table to guard against
this.

Assisted-by: ClaudeCode:claude-opus-4.8

* test: skip docs table check when docs/options.md is absent

Assisted-by: ClaudeCode:claude-opus-4.8
2026-07-24 14:55:24 -04:00
Henry Schreiner 03cbe932e6 fix(macos): make matching python3-config available in build/test venvs (#2922) 2026-07-24 01:19:38 -04:00
Henry Schreiner b7cac6dfef fix: don't error when a global build-frontend is set on pyodide (#2945)
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
2026-07-23 11:40:11 -04:00
Yashas Gunderiaandychampion 1520daf8ae fix: support platform-specific test runtime environment variables (#2941)
Honor platform-specific test runtime settings

Let the shared option reader apply Android and iOS environment overrides, matching the documented configuration precedence.

Constraint: Preserve existing global, TOML, and override behavior for test-runtime.
Rejected: Add Android/iOS-specific lookup code | OptionsReader already implements platform environment precedence.
Confidence: high
Scope-risk: narrow
Directive: Keep test-runtime on the shared option cascade when adding future platforms.
Tested: Focused red/green regression; 824 unit tests; prek all hooks; pylint 10.00/10; diff check.
Not-tested: Android and iOS device integration runs.

Co-authored-by: ychampion <ychampion@users.noreply.github.com>
2026-07-21 21:12:40 -04:00
cibuildwheel-bot[bot]cibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com>Henry Schreinermayeut
17b74206ab [Bot] Update dependencies (#2939)
* 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>
2026-07-21 21:12:27 -04:00
dependabot[bot] f8d8cca5bb chore(deps): bump astral-sh/setup-uv from 8.2.0 to 8.3.2 in the actions group (#2943)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-18 17:18:12 +02:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 265b435c86 chore(deps): bump the actions group with 2 updates (#2942)
Bumps the actions group with 2 updates: [j178/prek-action](https://github.com/j178/prek-action) and [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action).


Updates `j178/prek-action` from 2.0.4 to 2.0.5
- [Release notes](https://github.com/j178/prek-action/releases)
- [Commits](https://github.com/j178/prek-action/compare/bdca6f102f98e2b4c7029491a53dfd366469e33d...e98a699c41eb69ab013a45817a0406469a748f8d)

Updates `docker/setup-qemu-action` from 4.1.0 to 4.2.0
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/06116385d9baf250c9f4dcb4858b16962ea869c3...96fe6ef7f33517b61c61be40b68a1882f3264fb8)

---
updated-dependencies:
- dependency-name: j178/prek-action
  dependency-version: 2.0.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
- dependency-name: docker/setup-qemu-action
  dependency-version: 4.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-11 20:55:54 +02:00
dependabot[bot] 3f8bd1c9ba chore(deps): bump the actions group with 3 updates (#2938)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-07 10:51:43 +05:30
pre-commit-ci[bot] 6647035c75 [pre-commit.ci] pre-commit autoupdate (#2940) 2026-07-07 10:45:25 +05:30
cibuildwheel-bot[bot]cibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com>mayeut
e6eb0f50ed [Bot] Update dependencies (#2935)
* Update dependencies

* Revert pip marker flattening for GraalPy on Windows

* 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: mayeut <mayeut@users.noreply.github.com>
2026-06-30 09:00:00 -04:00
Agriya Khetarpal cecc541579 Use absolute path for {project} placeholder in config-settings (#2934) 2026-06-30 18:09:52 +05:30
Joe Rickerbyandmayeut eab8065b00 Implement test enabling with the "CI: PyPy EoL" PR label (#2930)
* Implement test enabling with the "CI: PyPy EoL" PR label

* apply "CI: PyPy EoL" to "Update dependencies" PR

---------

Co-authored-by: mayeut <mayeut@users.noreply.github.com>
2026-06-29 09:13:31 +01:00
Matthieu Darbois 3666de056a feat: use digests instead of tags for pinned images (#2915) 2026-06-28 10:00:50 +02:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> e5054806ba chore(deps): bump actions/checkout from 6.0.3 to 7.0.0 in the actions group (#2932)
chore(deps): bump actions/checkout in the actions group

Bumps the actions group with 1 update: [actions/checkout](https://github.com/actions/checkout).


Updates `actions/checkout` from 6.0.3 to 7.0.0
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/df4cb1c069e1874edd31b4311f1884172cec0e10...9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-28 07:21:46 +02:00
Malcolm Smith a5624fcd2c Android fixes (#2933)
* Update Android test repository URL
* Update to current Python versions, and remove patch which isn't needed anymore
2026-06-28 07:05:08 +02:00
cibuildwheel-bot[bot]cibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com>Joe Rickerby
816a6ffbb1 [Bot] Update dependencies (#2917)
* 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>
2026-06-25 09:08:06 -04:00
Agriya Khetarpal 7933323184 Add pyodide-build as a separate build frontend and update its build verbosity handling (#2609) 2026-06-22 00:12:15 +05:30
Agriya Khetarpal 291be6a345 Validate pyodide-version option against Pyodide build identifier (#2925)
* Validate pyodide-version against identifier

* Add tests

* Allow Pyodide nightly xbuildenvs too (for later)
2026-06-21 18:17:55 +01:00
Agriya Khetarpal 78d8530df9 docs: replace outdated note about build frontend and remove mention of ClearLinux (#2926)
* Remove outdated `pip wheel` note

* Remove ClearLinux reference

* Change Alpine to BSD
2026-06-20 01:31:15 -04:00
Henry Schreiner dc30bfb527 fix: make CIBW_ARCHS parsing case-insensitive and platform-aware (#2920)
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
2026-06-18 11:41:17 -04:00
Henry Schreiner aeea2cba0a chore: drop orjson (#2923)
Not used by mypy 2+ (sqlite used instead of json)

Signed-off-by: Henry Schreiner <henryfs@princeton.edu>
2026-06-15 22:27:24 +05:30
dependabot[bot] ce96a012ef chore(deps): bump the actions group with 2 updates (#2914)
Signed-off-by: dependabot[bot] <support@github.com>
2026-06-13 22:08:41 +05:30
Agriya Khetarpal 034e6353e8 Fix quoting of test-requires and audit-requires options for PEP 508 specifiers (#2913) 2026-06-13 06:04:58 +05:30
Joe Rickerby 2947353127 Bump version: v4.1.0 2026-06-12 17:02:22 +01:00
Joe Rickerby 14a3c3a226 Remove Travis pre-commit check 2026-06-12 16:42:14 +01:00
Henry Schreiner 42aa1345c3 chore: minor cleanups and perf tweaks from code review (#2910)
* 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
2026-06-12 16:03:36 +01:00
Himanshu Agrawal 01265e5db0 Clarify shell used for command options (#2904) 2026-06-12 14:24:15 +01:00
Mike Evdokimov f4afd95cbc Add FAQ section on caching cibuildwheel's downloaded tools (#2842)
* 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)
2026-06-12 14:02:09 +01:00
Henry Schreiner 6c08562aa1 fix: faster CLI on Python 3.15 (#2797)
* chore: lazy imports

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

* chore: bump flake8-lazy, rerun

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

* chore: include android

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

* fix: after rebase

Assisted-by: ClaudeCode:claude-sonnet-4.6
Signed-off-by: Henry Schreiner <henryfs@princeton.edu>

* chore: autofix __lazy_modules__

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

* chore: avoid touching resources

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

* chore: bump flake8-lazy to v0.8.2, rerun

Assisted-by: ClaudeCode:claude-sonnet-4-6

---------

Signed-off-by: Henry Schreiner <henryfs@princeton.edu>
2026-06-12 14:01:49 +01:00
Henry Schreiner 4f42ee3f03 fix: raise clear errors when no wheel is produced (#2909) 2026-06-12 17:22:07 +05:30
Victorien f3aa1bed4f Fix dead Pyodide env info link, remove mention of alpha ABI (#2911) 2026-06-12 15:29:56 +05:30
Tim Felgentreff d60fc2bdd0 Support new graalpy asset names that include Python version. (#2863)
* Support new graalpy asset names that include Python version

* Slight readability improvement for graalpy asset regex
2026-06-11 12:05:48 +02:00
Himanshu Agrawal 55c8985c12 docs: clarify environment precedence (#2905) 2026-06-10 09:34:19 -04:00
Agriya Khetarpal 2f9a352352 Update to Pyodide 314.0.0 (#2906)
* Update to Pyodide 314.0.0

* Update selector

* Update tests

* Use v2 xbuildenvs endpoint

* Update pyodide-build in constraints

* Add comment about `test_build_filter_pyodide_prerelease`
2026-06-09 16:30:30 -04:00
Agriya Khetarpal c58c5c58ec Drop use of Unbuffered wrapper class (#2894) 2026-06-08 18:13:44 +05:30
Henry Schreiner 90b8fdffd3 docs: rerun prek after changelog fix
Signed-off-by: Henry Schreiner <henryfs@princeton.edu>
2026-06-07 16:27:27 -04:00
Henry Schreiner c3e47f896b docs: fix changelog formatting
Updates changelog to reflect changes in CPython 3.15 support and other enhancements.
2026-06-07 15:19:15 -04:00
Henry Schreiner f03ac7617d Bump version: v4.0.0 2026-06-07 14:53:57 -04:00
Henry Schreiner 557c5f65f8 feat: remove GraalPy 3.11 (gp311) support (#2895)
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
2026-06-07 14:24:31 -04:00
70975c2153 chore: use ConfigurationError when package_dir is outside cwd (#2898)
* 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>
2026-06-06 23:10:32 -04:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> e2f143c327 chore(deps): bump docker/setup-qemu-action from 4.0.0 to 4.1.0 in the actions group (#2902)
chore(deps): bump docker/setup-qemu-action in the actions group

Bumps the actions group with 1 update: [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action).


Updates `docker/setup-qemu-action` from 4.0.0 to 4.1.0
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/ce360397dd3f832beb865e1373c09c0e9f86d70a...06116385d9baf250c9f4dcb4858b16962ea869c3)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-version: 4.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-06 21:35:17 -04:00
Henry Schreiner 866ae74781 docs: fix CUDA manylinux container references in FAQ (#2900)
Noticed by Claude when doing something else.
2026-06-06 10:32:03 -04:00
Matthieu Darbois 84b518a1b1 chore: simplify pinned image lookup (#2897)
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.
2026-06-06 08:19:14 -04:00
Philip Hyunsu Cho 785d812a47 docs: add instructions for building CUDA wheels (#2896)
* Add documentation for building CUDA wheels

* Move to Tips section
2026-06-06 08:18:02 -04:00
98 changed files with 2326 additions and 1247 deletions
+3 -3
View File
@@ -19,16 +19,16 @@ jobs:
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
with:
python-version: "3.x"
allow-prereleases: true
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
- uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
- name: Install tooling
run: |
+2 -2
View File
@@ -13,7 +13,7 @@ jobs:
dist:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
@@ -37,7 +37,7 @@ jobs:
path: dist
- name: Generate artifact attestation for sdist and wheel
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
with:
subject-path: "dist/cibuildwheel-*"
+26 -23
View File
@@ -23,7 +23,7 @@ on:
- .travis.yml
- README.md
- azure-pipelines.yml
- mkdocs.yml
- properdocs.yml
- noxfile.py
workflow_dispatch:
# allow manual runs on branches without a PR
@@ -39,14 +39,14 @@ jobs:
name: Linters (mypy, ruff, etc.)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
with:
python-version: "3.x"
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
- uses: j178/prek-action@bdca6f102f98e2b4c7029491a53dfd366469e33d # v2.0.4
- uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
- uses: j178/prek-action@e98a699c41eb69ab013a45817a0406469a748f8d # v2.0.5
- name: PyLint checks
run: uvx nox -s pylint -- --output-format=github
@@ -55,14 +55,14 @@ jobs:
needs: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
name: Install Python
with:
python-version: "3.x"
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
- uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
- name: Install dependencies
run: uv sync --no-dev --group test
- name: Generate a sample project
@@ -117,16 +117,16 @@ jobs:
test_select: android
timeout-minutes: 180
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
name: Install Python ${{ matrix.python_version }}
with:
python-version: ${{ matrix.python_version }}
allow-prereleases: true
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
- uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
- name: Free up disk space
if: runner.os == 'Linux' && matrix.test_select != 'android'
@@ -139,7 +139,7 @@ jobs:
# for oci_container unit tests
- name: Set up QEMU
if: runner.os == 'Linux'
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0
- name: Set up Xcode
if: ${{ startsWith(matrix.os, 'macos-15') }}
@@ -168,6 +168,9 @@ jobs:
if [[ -n "$GITHUB_PR_LABEL_CI_GRAALPY" ]]; then
CIBW_ENABLE+=" graalpy"
fi
if [[ -n "$GITHUB_PR_LABEL_CI_PYPY_EOL" ]]; then
CIBW_ENABLE+=" pypy-eol"
fi
fi
echo "CIBW_ENABLE=${CIBW_ENABLE}" >> $GITHUB_ENV
@@ -247,13 +250,13 @@ jobs:
outputs:
archs: ${{ steps.archs.outputs.archs }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
with:
python-version: "3.x"
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
- uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
- name: Install dependencies
run: uv sync --no-dev --group test
- name: Get qemu emulated architectures
@@ -272,18 +275,18 @@ jobs:
matrix:
arch: ${{ fromJSON(needs.emulated-archs.outputs.archs) }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
with:
python-version: "3.x"
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
- uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
- name: Install dependencies
run: uv sync --no-dev --group test
- name: Set up QEMU
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0
- name: Run the emulation tests
env:
@@ -296,14 +299,14 @@ jobs:
runs-on: ubuntu-24.04
timeout-minutes: 180
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
name: Install Python 3.12
with:
python-version: '3.12'
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
- uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
- name: Install dependencies
run: uv sync --no-dev --group test
@@ -348,7 +351,7 @@ jobs:
- os: macos-15
test_select: android
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Download a sample project
+3 -2
View File
@@ -37,11 +37,11 @@ jobs:
permission-contents: write
permission-pull-requests: write
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
- uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
with:
python-version: "3.14"
@@ -69,4 +69,5 @@ jobs:
labels: |
CI: GraalPy
CI: PyPy
CI: PyPy EoL
dependencies
+1 -1
View File
@@ -21,7 +21,7 @@ jobs:
permissions:
contents: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
+2 -2
View File
@@ -16,9 +16,9 @@ jobs:
security-events: write
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Run zizmor 🌈
uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6
uses: zizmorcore/zizmor-action@192e21d79ab29983730a13d1382995c2307fbcaa # v0.5.7
+10 -4
View File
@@ -18,7 +18,7 @@ repos:
exclude: ^cibuildwheel/resources/android/android.patch$
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: 0671d8ab202c4ac093b78433ae5baf74f3fc7246 # frozen: v0.15.15
rev: c59bba8fb259db0fec2bbb77ad8ba51ea7341b56 # frozen: v0.15.20
hooks:
- id: ruff-check
args: ["--fix"]
@@ -37,7 +37,6 @@ repos:
- dependency-groups>=1.2
- humanize
- nox>=2025.2.9
- orjson
- packaging
- pygithub
- pytest
@@ -77,6 +76,14 @@ repos:
files: '^(README\.md|docs/changelog\.md|docs/options\.md|bin/readme.*)$'
additional_dependencies: [cogapp>=3.5]
- repo: https://github.com/henryiii/flake8-lazy
rev: 5fed9a818f2583f0403aed779bf2b6c9aa8d25f4 # frozen: v0.8.5
hooks:
- id: flake8-lazy
args: ["--apply=set"]
files: '^cibuildwheel'
exclude: "^cibuildwheel/resources/"
- repo: https://github.com/codespell-project/codespell
rev: 2ccb47ff45ad361a21071a7eedda4c37e6ae8c5a # frozen: v2.4.2
hooks:
@@ -86,14 +93,13 @@ repos:
- repo: https://github.com/python-jsonschema/check-jsonschema
rev: 943377262562a12b57292fc98fabd7dbf81451fe # frozen: 0.37.2
rev: 1a4bb160cab6417b3045e1b37b6b72449243e658 # frozen: 0.37.4
hooks:
- id: check-dependabot
- id: check-github-actions
- id: check-github-workflows
- id: check-gitlab-ci
- id: check-readthedocs
- id: check-travis
- id: check-jsonschema
name: Check projects
args: [--schemafile, docs/data/projects.schema.json]
+1 -1
View File
@@ -8,4 +8,4 @@ build:
- asdf plugin add uv
- asdf install uv latest
- asdf global uv latest
- NO_COLOR=1 uv run --python 3.14 --managed-python --no-dev --group docs mkdocs build --strict --site-dir $READTHEDOCS_OUTPUT/html
- NO_COLOR=1 uv run --python 3.14 --managed-python --no-dev --group docs properdocs build --strict --site-dir $READTHEDOCS_OUTPUT/html
+2 -2
View File
@@ -11,7 +11,7 @@
- `nox -s tests -- test -k before_build` — single integration test/file via pytest `-k`.
- `nox -s lint` — run all linters (pre-commit/prek).
- `nox -s pylint` — run pylint separately (not in pre-commit).
- `nox -s docs`mkdocs serve (interactive) or build (non-interactive).
- `nox -s docs`properdocs serve (interactive) or build (non-interactive).
- Set up local dev env at `.venv`: `uv sync` (dependency groups used).
## Project layout
@@ -19,7 +19,7 @@
- `test/`**integration tests** (expensive, run actual wheel builds).
- `unit_test/`**unit tests** (fast, no wheel builds).
- `bin/` — maintainer scripts (update pins, generate README tables, schema, etc.).
- `docs/`mkdocs source.
- `docs/`properdocs (MkDocs fork) source.
## Testing specifics
- Three test suites exist, run in this order by `bin/run_tests.py`:
+79 -55
View File
@@ -24,25 +24,23 @@ While cibuildwheel itself requires a recent Python version to run (we support th
| | macOS Intel | macOS Apple Silicon | Windows 64bit | Windows 32bit | Windows Arm64 | manylinux<br/>musllinux x86_64 | manylinux<br/>musllinux i686 | manylinux<br/>musllinux aarch64 | manylinux<br/>musllinux ppc64le | manylinux<br/>musllinux s390x | manylinux<br/>musllinux armv7l | Android | iOS | Pyodide |
| ------------------------ | ----------- | ------------------- | ------------- | ------------- | -------------- | ------------------------------ | ---------------------------- | ------------------------------- | ------------------------------- | ----------------------------- | ------------------------------ | ------- | --- | -------------- |
| CPython 3.9 | ✅ | ✅ | ✅ | ✅ | ✅<sup>2</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅<sup>5</sup> | N/A | N/A | N/A |
| CPython 3.10 | ✅ | ✅ | ✅ | ✅ | ✅<sup>2</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅<sup>5</sup> | N/A | N/A | N/A |
| CPython 3.11 | ✅ | ✅ | ✅ | ✅ | ✅<sup>2</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅<sup>5</sup> | N/A | N/A | N/A |
| CPython 3.12 | ✅ | ✅ | ✅ | ✅ | ✅<sup>2</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅<sup>5</sup> | N/A | N/A | ✅<sup>3</sup> |
| CPython 3.13 | ✅ | ✅ | ✅ | ✅ | ✅<sup>2</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅<sup>5</sup> | ✅ | ✅ | ✅ |
| CPython 3.14 | ✅ | ✅ | ✅ | ✅ | ✅<sup>2</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅<sup>5</sup> | ✅ | ✅ | ✅<sup>4</sup> |
| CPython 3.15<sup>6</sup> | ✅ | ✅ | ✅ | ✅ | ✅<sup>2</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅<sup>5</sup> | ✅ | ✅ | N/A |
| CPython 3.9 | ✅ | ✅ | ✅ | ✅ | ✅<sup>2</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅<sup>4</sup> | N/A | N/A | N/A |
| CPython 3.10 | ✅ | ✅ | ✅ | ✅ | ✅<sup>2</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅<sup>4</sup> | N/A | N/A | N/A |
| CPython 3.11 | ✅ | ✅ | ✅ | ✅ | ✅<sup>2</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅<sup>4</sup> | N/A | N/A | N/A |
| CPython 3.12 | ✅ | ✅ | ✅ | ✅ | ✅<sup>2</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅<sup>4</sup> | N/A | N/A | ✅<sup>3</sup> |
| CPython 3.13 | ✅ | ✅ | ✅ | ✅ | ✅<sup>2</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅<sup>4</sup> | ✅ | ✅ | ✅ |
| CPython 3.14 | ✅ | ✅ | ✅ | ✅ | ✅<sup>2</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅<sup>4</sup> | ✅ | ✅ | ✅ |
| CPython 3.15<sup>5</sup> | ✅ | ✅ | ✅ | ✅ | ✅<sup>2</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅<sup>4</sup> | ✅ | ✅ | N/A |
| PyPy 3.9 v7.3 | ✅ | ✅ | ✅ | N/A | N/A | ✅<sup>1</sup> | ✅<sup>1</sup> | ✅<sup>1</sup> | N/A | N/A | N/A | N/A | N/A | N/A |
| PyPy 3.10 v7.3 | ✅ | ✅ | ✅ | N/A | N/A | ✅<sup>1</sup> | ✅<sup>1</sup> | ✅<sup>1</sup> | N/A | N/A | N/A | N/A | N/A | N/A |
| PyPy 3.11 v7.3 | ✅ | ✅ | ✅ | N/A | N/A | ✅<sup>1</sup> | ✅<sup>1</sup> | ✅<sup>1</sup> | N/A | N/A | N/A | N/A | N/A | N/A |
| GraalPy 3.11 v24.2 | ✅ | ✅ | ✅ | N/A | N/A | ✅<sup>1</sup> | N/A | ✅<sup>1</sup> | N/A | N/A | N/A | N/A | N/A | N/A |
| GraalPy 3.12 v25.0 | ✅ | ✅ | ✅ | N/A | N/A | ✅<sup>1</sup> | N/A | ✅<sup>1</sup> | N/A | N/A | N/A | N/A | N/A | N/A |
<sup>**1** PyPy & GraalPy are only supported for manylinux wheels.</sup><br>
<sup>**2** Windows arm64 support is experimental.</sup><br>
<sup>**3** Not supported on PyPI, uses old `pyodide` tag instead of `pyemscripten`. Requires `pyodide-eol` [`enable`](https://cibuildwheel.pypa.io/en/stable/options/#enable).</sup><br>
<sup>**4** Experimental alpha ABI, do not upload to PyPI yet.</sup><br>
<sup>**5** manylinux armv7l support is experimental. As there are no RHEL based image for this architecture, it's using an Ubuntu based image instead.</sup><br>
<sup>**6** Python 3.15 requires opt-in using [`enable`](https://cibuildwheel.pypa.io/en/stable/options/#enable).</sup><br>
<sup>**4** manylinux armv7l support is experimental. As there are no RHEL based image for this architecture, it's using an Ubuntu based image instead.</sup><br>
<sup>**5** Python 3.15 requires opt-in using [`enable`](https://cibuildwheel.pypa.io/en/stable/options/#enable).</sup><br>
- Builds manylinux, musllinux, macOS, Windows, pyemscripten, iOS, and Android wheels
- Supports CPython, PyPy, and GraalPy
@@ -100,7 +98,7 @@ jobs:
- uses: actions/setup-python@v6
- name: Install cibuildwheel
run: python -m pip install cibuildwheel==4.0.0rc2
run: python -m pip install cibuildwheel==4.1.1
- name: Build wheels
run: python -m cibuildwheel --output-dir wheelhouse
@@ -191,8 +189,8 @@ Here are some repos that use cibuildwheel.
|-----------------------------------|----|----|:------|
| [scikit-learn][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] ![pyodide icon][] | The machine learning library. A complex but clean config using many of cibuildwheel's features to build a large project with Cython and C++ extensions. |
| [duckdb][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | DuckDB is an analytical in-process SQL database management system |
| [pytorch-fairseq][] | ![github icon][] | ![apple icon][] ![linux icon][] | Facebook AI Research Sequence-to-Sequence Toolkit written in Python. |
| [NumPy][] | ![github icon][] ![travisci icon][] | ![windows icon][] ![apple icon][] ![linux icon][] ![pyodide icon][] | The fundamental package for scientific computing with Python. |
| [pytorch-fairseq][] | ![github icon][] | ![apple icon][] ![linux icon][] | Facebook AI Research Sequence-to-Sequence Toolkit written in Python. |
| [NCNN][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | ncnn is a high-performance neural network inference framework optimized for the mobile platform |
| [Matplotlib][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] ![pyodide icon][] | The venerable Matplotlib, a Python library with C++ portions |
| [Tornado][] | ![github icon][] | ![linux icon][] ![apple icon][] ![windows icon][] | Tornado is a Python web framework and asynchronous networking library. Uses stable ABI for a small C extension. |
@@ -202,8 +200,8 @@ Here are some repos that use cibuildwheel.
[scikit-learn]: https://github.com/scikit-learn/scikit-learn
[duckdb]: https://github.com/duckdb/duckdb
[pytorch-fairseq]: https://github.com/facebookresearch/fairseq
[NumPy]: https://github.com/numpy/numpy
[pytorch-fairseq]: https://github.com/facebookresearch/fairseq
[NCNN]: https://github.com/Tencent/ncnn
[Matplotlib]: https://github.com/matplotlib/matplotlib
[Tornado]: https://github.com/tornadoweb/tornado
@@ -241,15 +239,62 @@ Changelog
<!-- [[[cog from readme_changelog import mini_changelog; print(mini_changelog()) ]]] -->
### v4.0.0rc2
### v4.1.1
_5 June 2026_
_24 July 2026_
- ✨ Adds `pyodide-build` as a separate [`build-frontend`](https://cibuildwheel.pypa.io/en/stable/options/#build-frontend), now the default frontend for Pyodide, with verbosity flags handling. Any other frontend is ignored with a warning on Pyodide. (#2609, #2945)
- 🔐 Uses digests instead of tags for pinned container images, strengthening supply-chain security. The human-readable tags remain as comments in `pinned_docker_images.cfg`. (#2915)
- 🐛 Fixes platform-specific [`test-runtime`](https://cibuildwheel.pypa.io/en/stable/options/#test-runtime) environment variables (e.g. `CIBW_TEST_RUNTIME_ANDROID`) not being honored (#2941)
- 🐛 Fixes quoting of [`test-requires`](https://cibuildwheel.pypa.io/en/stable/options/#test-requires) and [`audit-requires`](https://cibuildwheel.pypa.io/en/stable/options/#audit-requires) so PEP 508 specifiers containing spaces work (#2913)
- 🐛 Makes [`archs`](https://cibuildwheel.pypa.io/en/stable/options/#archs) parsing case-insensitive and platform-aware, so e.g. `arm64` works on Windows (#2920)
- 🐛 Uses an absolute path for the `{project}` placeholder in [`config-settings`](https://cibuildwheel.pypa.io/en/stable/options/#config-settings) (#2934)
- 🐛 Validates the [`pyodide-version`](https://cibuildwheel.pypa.io/en/stable/options/#pyodide-version) option against the build identifier with a clear error (#2925)
- 🐛 Fixes PyPy installs on macOS after PyPy switched its downloads from `.tar.bz2` to `.tar.gz` (#2939)
- 🐛 Makes a matching `python3-config` available in the build and test venvs on macOS (#2922)
- 🛠 Updates dependencies and container pins (#2917, #2935, #2939)
- 🛠 Updates Android tests to current Python versions and the new test repository URL (#2933)
- 🛠 Drops the `orjson` dependency, no longer used by mypy 2+ (#2923)
- 📚 Builds the docs with properdocs, a MkDocs fork (#2946)
- 📚 Adds the missing `cp314-pyodide_wasm32` entry to the build identifier table (#2947)
- 📚 Removes outdated notes about the `pip wheel` build frontend and ClearLinux (#2926)
- 💼 Adds a "CI: PyPy EoL" PR label to run PyPy EoL tests on PRs (#2930)
- 💼 Updates CI action pins and pre-commit hooks (#2914, #2932, #2938, #2940, #2942, #2943)
### v4.1.0
_12 June 2026_
- ✨ Updates Pyodide to the final 314.0.0 release, so Pyodide 3.14 wheels now build by default without the `pyodide-prerelease` [`enable`](https://cibuildwheel.pypa.io/en/stable/options/#enable) flag. (#2906)
- 🐛 Raises clear errors when a build produces no wheel, instead of failing later with a confusing message (#2909)
- 🛠 Speeds up CLI startup through lazy imports on Python 3.15 (#2797)
- 📚 Adds an FAQ section on caching cibuildwheel's downloaded tools with `CIBW_CACHE_PATH` (#2842)
- 📚 Documentation improvements: clarifies which shell is used for command options, clarifies environment variable precedence, and fixes a dead Pyodide env info link (#2904, #2905, #2911)
### v4.0.0
_7 June 2026_
See @henryiii's [release post](https://iscinumpy.dev/post/cibuildwheel-4-0-0/) for more info on new features!
- 🌟 Adds wheel auditing with `abi3audit` as a default after the repair step, with new [`audit-requires`](https://cibuildwheel.pypa.io/en/stable/options/#audit-requires) and [`audit-command`](https://cibuildwheel.pypa.io/en/stable/options/#audit-command) options (#2805)
- 🌟 Adds `pyemscripten` platform tag support (PEP 783), updates Pyodide to 314.0.0a2, and adds a `pyodide-eol` [`enable`](https://cibuildwheel.pypa.io/en/stable/options/#enable) flag for building end-of-life Pyodide versions (#2812, #2848)
- 🌟 Sets up `delvewheel` as the default [`repair-wheel-command`](https://cibuildwheel.pypa.io/en/stable/options/#repair-wheel-command) for Windows, so extension module DLLs are now bundled automatically. Skip by setting it to empty if not needed. (#2831)
- ✨ Adds CPython 3.15 support, under the [`enable` option](https://cibuildwheel.pypa.io/en/stable/options/#enable) `cpython-prerelease`. This version of cibuildwheel uses 3.15.0b2. (#2833, #2850)
_While CPython is in beta, the ABI can change, so your wheels might not be compatible with the final release. For this reason, we don't recommend distributing wheels until RC1, at which point 3.15 will be available in cibuildwheel without the flag._
- ✨ Adds CPython 3.15 support for iOS and Android (#2857, #2858)
- ✨ Adds Android improvements for building NumPy and related packages, including auditwheel support, pkg-config and Fortran configuration, and the [`xbuild-files`](https://cibuildwheel.pypa.io/en/stable/options/#xbuild-files) option (#2695)
- ✨ Adds `CIBUILDWHEEL_BUILD_IDENTIFIER` environment variable set to the current build identifier (e.g. `cp311-manylinux_x86_64`) during per-build steps (#2872)
- ✨ Adds `{project}` and `{package}` placeholders to [`config-settings`](https://cibuildwheel.pypa.io/en/stable/options/#config-settings) (#2827)
- ⚠️ Drops support for Python 3.8 (#2686)
- ⚠️ Removes the experimental CPython 3.13 free-threading builds and the `cpython-freethreading` [`enable`](https://cibuildwheel.pypa.io/en/stable/options/#enable) option. CPython 3.14+ free-threading support remains available without the enable flag. (#2684)
- ⚠️ Drops support for Cirrus CI, which is shutting down June 1, 2026 (#2817)
- ⚠️ Drops GraalPy 3.11 (gp311) support, as agreed in #2741, and removes GraalPy 24-only workarounds (#2895)
- 🔐 Adds SHA256 verification for direct downloads of Python interpreters, virtualenv, and python-build-standalone assets (#2873)
- 🔐 Adds tarfile extraction filter for safe archive extraction (#2856)
- 🐛 Fixes `UV_PYTHON` not being set for [`before-build`](https://cibuildwheel.pypa.io/en/stable/options/#before-build) on Linux when using `uv` as the [`build-frontend`](https://cibuildwheel.pypa.io/en/stable/options/#build-frontend) (#2830)
- 🐛 Fixes detection of musl libc when downloading python-build-standalone, which previously always selected the gnu asset on musl hosts like Alpine (#2889)
- 🐛 Fixes [`config-settings`](https://cibuildwheel.pypa.io/en/stable/options/#config-settings) expansion when `{project}` or `{package}` contains spaces or backslashes (#2886)
- 🐛 Prevents deadlock when `linux32` fails and forwards platform args to the sanity check (#2880, #2888)
@@ -257,45 +302,30 @@ _5 June 2026_
- 🐛 Removes potential partial cache-population in case of error (#2892)
- 🐛 Raises a clear error when `ANDROID_API_LEVEL` is not an integer (#2891)
- 🐛 Replaces assert with proper exception in python-build-standalone (#2859)
- 🛠 Updates dependencies and container pins (#2893, #2882, #2874, #2868, #2862, #2884)
- 🛠 Minor fixups across error messages, OCI container, and options (#2860)
- 📚 Updates documentation for delvewheel as the default Windows [`repair-wheel-command`](https://cibuildwheel.pypa.io/en/stable/options/#repair-wheel-command), including the build diagram, schema defaults, and legal note (#2877, #2853, #2891)
- 📚 Adds intersphinx support for external documentation linking (#2871)
- 📚 Removes outdated numpy info (#2855)
- 💼 Improves Azure test reliability with retries and caching (#2890)
- 💼 Fixes Windows GitLab CI test running (#2870)
- 💼 Updates CI action pins (#2867)
- 💼 Adds agent and copilot setup files (#2861)
- 💼 Uses `if TYPE_CHECKING:` blocks (#2866, #2864)
- 🧪 Adds unit tests for `OCIContainer._get_platform_args` (#2878)
### v4.0.0rc1
_14 May 2026_
- 🌟 Adds wheel auditing with `abi3audit` as a default after the repair step, with new [`audit-requires`](https://cibuildwheel.pypa.io/en/stable/options/#audit-requires) and [`audit-command`](https://cibuildwheel.pypa.io/en/stable/options/#audit-command) options (#2805)
- 🌟 Adds `pyemscripten` platform tag support (PEP 783), updates Pyodide to 314.0.0a1, and adds a `pyodide-eol` [`enable`](https://cibuildwheel.pypa.io/en/stable/options/#enable) flag for building end-of-life Pyodide versions (#2812, #2848)
- 🌟 Sets up `delvewheel` as the default [`repair-wheel-command`](https://cibuildwheel.pypa.io/en/stable/options/#repair-wheel-command) for Windows, so extension module DLLs are now bundled automatically. Skip by setting it to empty if not needed. (#2831)
- ✨ Adds CPython 3.15 support, under the [`enable` option](https://cibuildwheel.pypa.io/en/stable/options/#enable) `cpython-prerelease`. This version of cibuildwheel uses 3.15.0b1. (#2833, #2850)
_While CPython is in beta, the ABI can change, so your wheels might not be compatible with the final release. For this reason, we don't recommend distributing wheels until RC1, at which point 3.14 will be available in cibuildwheel without the flag._ (#2390)
- ✨ Adds `{project}` and `{package}` placeholders to [`config-settings`](https://cibuildwheel.pypa.io/en/stable/options/#config-settings) (#2827)
- ⚠️ Drops support for Python 3.8 (#2686)
- ⚠️ Removes the experimental CPython 3.13 free-threading builds and the `cpython-freethreading` [`enable`](https://cibuildwheel.pypa.io/en/stable/options/#enable) option. CPython 3.14+ free-threading support remains available without the enable flag. (#2684)
- ⚠️ Drops support for Cirrus CI, which is shutting down June 1, 2026 (#2817)
- 🐛 Fixes `UV_PYTHON` not being set for [`before-build`](https://cibuildwheel.pypa.io/en/stable/options/#before-build) on Linux when using `uv` as the [`build-frontend`](https://cibuildwheel.pypa.io/en/stable/options/#build-frontend) (#2830)
- 🐛 Uses ConfigurationError when `package_dir` is outside cwd instead of a generic Exception (#2898)
- 🛠 Updates dependencies and container pins (#2893, #2882, #2874, #2868, #2862, #2884, #2845, #2837, #2818, #2810, #2838, #2813)
- 🛠 Updates Android to Python 3.13.13 and 3.14.4 (#2821)
- 🛠 Applies Pyodide-specific patches to the Emscripten toolchain installation (#2800)
- 🛠 Updates dependencies and container pins (#2845, #2837, #2821, #2818, #2810, #2838, #2813)
- 🛠 Uses `python -V -V` for Windows build diagnostics (#2832)
- 🛠 Simplifies pinned container image lookup (#2897)
- 🛠 Minor fixups across error messages, OCI container, and options (#2860)
- 💼 Adds PEP 723 metadata for `bin/` scripts and drops the `bin` dependency group (#2819)
- 💼 Improves Azure test reliability with retries and caching (#2890)
- 💼 Fixes Windows GitLab CI test running (#2870)
- 💼 Updates CI action pins and dev dependencies (#2902, #2867, #2851, #2843, #2826, #2823, #2820, #2807)
- 💼 Adds agent and copilot setup files (#2861)
- 💼 Uses `if TYPE_CHECKING:` blocks (#2866, #2864)
- 🧪 Fixes Android tests using the `uv` frontend (#2809)
- 🧪 Fixes the update-dependencies workflow to use `uv` to run `nox` (#2808)
- 🧪 Adds unit tests for `OCIContainer._get_platform_args` (#2878)
- 📚 Updates documentation for delvewheel as the default Windows [`repair-wheel-command`](https://cibuildwheel.pypa.io/en/stable/options/#repair-wheel-command), including the build diagram, schema defaults, and legal note (#2877, #2853, #2891)
- 📚 Documents platform-specific [`before-build`](https://cibuildwheel.pypa.io/en/stable/options/#before-build) configuration (#2834)
- 📚 Updates the "How it works" diagram with details of Android, iOS, and Pyodide builds (#2816)
- 📚 Adds Pyodide icon and regenerates working examples data for Android, iOS, and Pyodide (#2815, #2811)
- 📚 Adds intersphinx support for external documentation linking (#2871)
- 📚 Adds instructions for building CUDA wheels and fixes manylinux container references in FAQ (#2896, #2900)
- 📚 Links back to source in docs (#2806)
- 💼 Adds PEP 723 metadata for `bin/` scripts and drops the `bin` dependency group (#2819)
- 💼 Updates CI action pins and dev dependencies (#2851, #2843, #2826, #2823, #2820, #2807)
- 🧪 Fixes Android tests using the `uv` frontend (#2809)
- 🧪 Fixes the update-dependencies workflow to use `uv` to run `nox` (#2808)
- 📚 Removes outdated numpy info (#2855)
### v3.4.1
@@ -320,13 +350,7 @@ _5 March 2026_
- ✨ Add support for quiet setting on `build` and `uv` from the cibuildwheel `build-verbosity` setting. (#2737)
- 📚 Docs updates, including guidance on using Meson on Windows (#2718)
### v3.3.1
_5 January 2026_
- 🛠 Update dependencies and container pins, including updating to CPython 3.14.2. (#2708)
<!-- [[[end]]] (sum: x/zQO1S1XP) -->
<!-- [[[end]]] (sum: kQUcUVgqfM) -->
---
+1 -1
View File
@@ -28,7 +28,7 @@ branding:
runs:
using: composite
steps:
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
id: python
with:
python-version: "3.11 - 3.14"
+1 -1
View File
@@ -12,7 +12,7 @@ pr:
- .readthedocs.yml
- .travis.yml
- README.md
- mkdocs.yml
- properdocs.yml
- noxfile.py
jobs:
+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
+90 -42
View File
@@ -9,6 +9,7 @@
import configparser
import dataclasses
from functools import cache
from pathlib import Path
import requests
@@ -33,7 +34,7 @@ class PyPAImage(Image):
super().__init__(manylinux_version, platforms, image_name, tag, True)
images = [
IMAGES = [
# manylinux2014 images
PyPAImage(
"manylinux2014",
@@ -88,49 +89,71 @@ images = [
),
]
config = configparser.ConfigParser()
for image in images:
# get the tag name whose digest matches 'latest'
if image.tag is not None:
# image has been pinned, do not update
tag_name = image.tag
elif image.image_name.startswith("quay.io/"):
_, _, repository_name = image.image_name.partition("/")
response = requests.get(
f"https://quay.io/api/v1/repository/{repository_name}?includeTags=true"
)
response.raise_for_status()
repo_info = response.json()
tags_dict = repo_info["tags"]
latest_tag = tags_dict.pop("latest")
@cache
def quay_lookup(image_name: str, tag_name: str) -> tuple[str, str]:
_, _, repository_name = image_name.partition("/")
if tag_name == "latest":
url = f"https://quay.io/api/v1/repository/{repository_name}?includeTags=true"
else:
url = f"https://quay.io/api/v1/repository/{repository_name}/tag?specificTag={tag_name}"
response = requests.get(url)
response.raise_for_status()
info = response.json()
if tag_name == "latest":
tags_dict = info["tags"]
tag_info = tags_dict.pop(tag_name)
# find the tag whose manifest matches 'latest'
tag_name = next(
name
tag_name, digest = next(
(name, info["manifest_digest"])
for (name, info) in tags_dict.items()
if info["manifest_digest"] == latest_tag["manifest_digest"]
if info["manifest_digest"] == tag_info["manifest_digest"]
)
elif image.image_name.startswith("ghcr.io/"):
repository = image.image_name[8:]
response = requests.get(
"https://ghcr.io/token", params={"scope": f"repository:{repository}:pull"}
)
response.raise_for_status()
token = response.json()["token"]
else:
tags_list = info["tags"]
tag_info = next(tag for tag in tags_list if tag["name"] == tag_name)
digest = tag_info["manifest_digest"]
return tag_name, digest
@cache
def ghcr_lookup(image_name: str, tag_name: str) -> tuple[str, str]:
repository = image_name[8:]
response = requests.get(
"https://ghcr.io/token", params={"scope": f"repository:{repository}:pull"}
)
response.raise_for_status()
token = response.json()["token"]
if tag_name == "latest":
response = requests.get(
f"https://ghcr.io/v2/{repository}/tags/list",
headers={"Authorization": f"Bearer {token}"},
)
response.raise_for_status()
ghcr_tags = [(Version(tag), tag) for tag in response.json()["tags"] if tag != "latest"]
info = response.json()
ghcr_tags = [(Version(tag), tag) for tag in info["tags"] if tag != "latest"]
ghcr_tags.sort(reverse=True)
tag_name = ghcr_tags[0][1]
else:
response = requests.get(f"https://hub.docker.com/v2/repositories/{image.image_name}/tags")
response.raise_for_status()
tags = response.json()["results"]
response = requests.head(
f"https://ghcr.io/v2/{repository}/manifests/{tag_name}",
headers={
"Authorization": f"Bearer {token}",
"Accept": "application/vnd.docker.distribution.manifest.v2+json, application/vnd.docker.distribution.manifest.list.v2+json, application/vnd.oci.image.manifest.v1+json, application/vnd.oci.image.index.v1+json, application/vnd.oci.artifact.manifest.v1+json",
},
)
response.raise_for_status()
digest = response.headers["Docker-Content-Digest"]
return tag_name, digest
@cache
def dockerhub_lookup(image_name: str, tag_name: str) -> tuple[str, str]:
response = requests.get(f"https://hub.docker.com/v2/repositories/{image_name}/tags")
response.raise_for_status()
tags = response.json()["results"]
if tag_name == "latest":
latest_tag = next(tag for tag in tags if tag["name"] == "latest")
# i don't know what it would mean to have multiple images per tag
assert len(latest_tag["images"]) == 1
@@ -139,15 +162,40 @@ for image in images:
pinned_tag = next(
tag for tag in tags if tag != latest_tag and tag["images"][0]["digest"] == digest
)
tag_name = pinned_tag["name"]
else:
pinned_tag = next(tag for tag in tags if tag["name"] == tag_name)
digest = pinned_tag["images"][0]["digest"]
tag_name = pinned_tag["name"]
return tag_name, digest
for platform in image.platforms:
if not config.has_section(platform):
config[platform] = {}
suffix = ""
if image.use_platform_suffix:
suffix = f"_{platform.removeprefix('pypy_')}"
config[platform][image.manylinux_version] = f"{image.image_name}{suffix}:{tag_name}"
with open(RESOURCES / "pinned_docker_images.cfg", "w") as f:
config.write(f)
def main() -> None:
config = configparser.ConfigParser()
for image in IMAGES:
# get the tag name whose digest matches 'latest'
# if image has been pinned, do not update
search_tag = image.tag or "latest"
if image.image_name.startswith("quay.io/"):
lookup = quay_lookup
elif image.image_name.startswith("ghcr.io/"):
lookup = ghcr_lookup
else:
lookup = dockerhub_lookup
tag_name, digest = lookup(image.image_name, search_tag)
for platform in image.platforms:
if not config.has_section(platform):
config[platform] = {}
image_name = image.image_name
if image.use_platform_suffix:
image_name = f"{image_name}_{platform.removeprefix('pypy_')}"
_, digest = lookup(image_name, tag_name)
assert digest.startswith("sha256:")
config[platform][image.manylinux_version] = f"{image_name}@{digest} # {tag_name}"
with open(RESOURCES / "pinned_docker_images.cfg", "w") as f:
config.write(f)
if __name__ == "__main__":
main()
+15 -8
View File
@@ -124,14 +124,22 @@ class GraalPyVersions:
response.raise_for_status()
releases = response.json()
gp_version_re = re.compile(r"-(\d+\.\d+\.\d+)$")
gp_asset_re = re.compile(
r"^(?P<prefix>graalpy(?P<cpython>\d+\.\d+)?-(?P<graalpy>\d+\.\d+\.\d+))-"
)
cp_version_re = re.compile(r"Python (\d+\.\d+(?:\.\d+)?)")
for release in releases:
m = gp_version_re.search(release["tag_name"])
if m:
release["graalpy_version"] = Version(m.group(1))
m = cp_version_re.search(release["body"])
if m:
for asset in release["assets"]:
m = gp_asset_re.match(asset["name"])
if m:
release["asset_prefix"] = m.group("prefix")
release["graalpy_version"] = Version(m.group("graalpy"))
if m.group("cpython"):
release["python_version"] = Version(m.group("cpython"))
break
if "python_version" not in release and (
m := cp_version_re.search(release["body"] or "")
):
release["python_version"] = Version(m.group(1))
self.releases = [r for r in releases if "graalpy_version" in r and "python_version" in r]
@@ -172,12 +180,11 @@ class GraalPyVersions:
ext = "zip" if "win" in identifier else "tar.gz"
for release in reversed(releases):
version = release["python_version"]
gpversion = release["graalpy_version"]
urls = [
rf["browser_download_url"]
for rf in release["assets"]
if rf["name"].endswith(f"{platform}-{arch}.{ext}")
and rf["name"].startswith(f"graalpy-{gpversion.major}")
and rf["name"].startswith(release["asset_prefix"])
]
if not urls:
continue
+1 -1
View File
@@ -1 +1 @@
__version__ = "4.0.0rc2"
__version__ = "4.1.1"
+32 -22
View File
@@ -1,9 +1,35 @@
from __future__ import annotations
__lazy_modules__ = {
"argparse",
"cibuildwheel._compat",
"cibuildwheel._compat.tarfile",
"cibuildwheel.architecture",
"cibuildwheel.ci",
"cibuildwheel.logger",
"cibuildwheel.options",
"cibuildwheel.platforms",
"cibuildwheel.selector",
"cibuildwheel.typing",
"cibuildwheel.util",
"cibuildwheel.util.file",
"cibuildwheel.util.helpers",
"cibuildwheel.util.resources",
"contextlib",
"functools",
"io",
"pathlib",
"shutil",
"tempfile",
"textwrap",
"traceback",
}
import argparse
import contextlib
import dataclasses
import functools
import io
import os
import shutil
import sys
@@ -30,7 +56,7 @@ from cibuildwheel.util.resources import read_all_configs
TYPE_CHECKING = False
if TYPE_CHECKING:
from collections.abc import Generator, Iterable, Sequence
from typing import Any, Literal, TextIO
from typing import Literal
@dataclasses.dataclass
@@ -44,23 +70,6 @@ class FileReport:
size: str
# Taken from https://stackoverflow.com/a/107717
class Unbuffered:
def __init__(self, stream: TextIO) -> None:
self.stream = stream
def write(self, data: str) -> None:
self.stream.write(data)
self.stream.flush()
def writelines(self, data: Iterable[str]) -> None:
self.stream.writelines(data)
self.stream.flush()
def __getattr__(self, attr: str) -> Any: # noqa: ANN401
return getattr(self.stream, attr)
def main() -> None:
global_options = GlobalOptions()
try:
@@ -360,10 +369,11 @@ def build_in_directory(args: CommandLineArguments) -> None:
# Add CIBUILDWHEEL environment variable
os.environ["CIBUILDWHEEL"] = "1"
# Python is buffering by default when running on the CI platforms, giving
# problems interleaving subprocess call output with unflushed calls to
# 'print'
sys.stdout = Unbuffered(sys.stdout)
# Python block-buffers stdout when it isn't a tty, which de-interleaves
# `print` output and direct fd-1 writes from subprocesses sharing this
# stdout. line_buffering allows each newline to flush all the way to fd-1.
if isinstance(sys.stdout, io.TextIOWrapper):
sys.stdout.reconfigure(line_buffering=True)
# create the cache dir before it gets printed & builds performed
CIBW_CACHE_PATH.mkdir(parents=True, exist_ok=True)
+22 -4
View File
@@ -1,5 +1,7 @@
from __future__ import annotations
__lazy_modules__ = {"platform", "re", "shutil", "subprocess"}
import platform as platform_module
import re
import shutil
@@ -105,13 +107,29 @@ class Architecture(StrEnum):
case "auto32":
result |= cls.bitness_archs(platform=platform, bitness="32")
case _:
try:
result.add(cls(arch_str))
except ValueError as e:
if arch := cls._parse_arch_name(arch_str, platform=platform):
result.add(arch)
else:
msg = f"Invalid architecture '{arch_str}'"
raise errors.ConfigurationError(msg) from e
raise errors.ConfigurationError(msg)
return result
@classmethod
def _parse_arch_name(cls, arch_str: str, platform: PlatformName) -> Self | None:
"""Resolve an architecture name case-insensitively.
The same value (e.g. "arm64"/"ARM64") can map to different members
depending on the platform, so prefer a member valid for ``platform``
before falling back to any case-insensitive match.
"""
for arch in cls.all_archs(platform):
if arch.value.lower() == arch_str.lower():
return arch
for arch in cls:
if arch.value.lower() == arch_str.lower():
return arch
return None
@classmethod
def native_arch(cls, platform: PlatformName) -> Self | None:
native_machine = platform_module.machine()
+11
View File
@@ -1,5 +1,16 @@
from __future__ import annotations
__lazy_modules__ = {
"cibuildwheel.logger",
"cibuildwheel.util",
"cibuildwheel.util.cmd",
"cibuildwheel.util.helpers",
"cibuildwheel.util.packaging",
"cibuildwheel.venv",
"pathlib",
"subprocess",
}
import subprocess
import sys
from pathlib import Path
+2
View File
@@ -1,5 +1,7 @@
from __future__ import annotations
__lazy_modules__ = {"bashlex", "subprocess"}
import dataclasses
import subprocess
+2
View File
@@ -1,3 +1,5 @@
__lazy_modules__ = {"cibuildwheel.util", "cibuildwheel.util.helpers"}
import os
import re
from enum import Enum
+2
View File
@@ -1,5 +1,7 @@
from __future__ import annotations
__lazy_modules__ = {"bashlex", "bashlex.errors"}
import dataclasses
from typing import Protocol
+18
View File
@@ -4,6 +4,8 @@ a different return code, by defining them all here, we can ensure that they're
semantically clear and unique.
"""
__lazy_modules__ = {"textwrap"}
import textwrap
@@ -109,3 +111,19 @@ class AuditCommandFailedError(FatalError):
def __init__(self, message: str) -> None:
super().__init__(message)
self.return_code = 9
class BuildProducedNoWheelError(FatalError):
def __init__(self) -> None:
message = textwrap.dedent(
"""
Build failed because the build frontend completed successfully but
did not produce a wheel.
This usually indicates a problem with your project configuration or
build backend. Check your project configuration, or run cibuildwheel
with CIBW_BUILD_VERBOSITY=1 to view build logs.
"""
)
super().__init__(message)
self.return_code = 10
+3 -1
View File
@@ -4,6 +4,8 @@ These are utilities for the `/bin` scripts, not for the `cibuildwheel` program.
from __future__ import annotations
__lazy_modules__ = {"io", "json", "urllib", "urllib.error", "urllib.request"}
import json
import time
import typing
@@ -103,7 +105,7 @@ class PyodideXBuildEnvInfo(typing.TypedDict):
def get_pyodide_xbuildenv_info() -> PyodideXBuildEnvInfo:
xbuildenv_info_url = (
"https://pyodide.github.io/pyodide/api/pyodide-cross-build-environments.json"
"https://pyodide.github.io/pyodide/api/v2/pyodide-cross-build-environments.json"
)
with urllib.request.urlopen(xbuildenv_info_url) as response:
return typing.cast("PyodideXBuildEnvInfo", json.loads(response.read().decode("utf-8")))
+8 -1
View File
@@ -1,5 +1,7 @@
from __future__ import annotations
__lazy_modules__ = {"cibuildwheel.util", "cibuildwheel.util.helpers", "shlex"}
import dataclasses
import shlex
import typing
@@ -14,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)
@@ -44,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"]
+14 -2
View File
@@ -1,5 +1,17 @@
from __future__ import annotations
__lazy_modules__ = {
"cibuildwheel.ci",
"contextlib",
"functools",
"hashlib",
"humanize",
"io",
"pathlib",
"re",
"textwrap",
}
import codecs
import contextlib
import dataclasses
@@ -363,11 +375,11 @@ class Logger:
out.write("\n")
return out.getvalue()
@property
@functools.cached_property
def colors(self) -> Colors:
return Colors(enabled=self.colors_enabled)
@property
@functools.cached_property
def symbols(self) -> Symbols:
return Symbols(unicode=self.unicode_enabled)
+18
View File
@@ -1,5 +1,23 @@
from __future__ import annotations
__lazy_modules__ = {
"cibuildwheel.ci",
"cibuildwheel.errors",
"cibuildwheel.logger",
"cibuildwheel.util",
"cibuildwheel.util.cmd",
"cibuildwheel.util.helpers",
"contextlib",
"io",
"json",
"platform",
"shlex",
"shutil",
"subprocess",
"textwrap",
"uuid",
}
import contextlib
import dataclasses
import io
+54 -20
View File
@@ -1,5 +1,29 @@
from __future__ import annotations
__lazy_modules__ = {
"cibuildwheel.architecture",
"cibuildwheel.environment",
"cibuildwheel.frontend",
"cibuildwheel.logger",
"cibuildwheel.oci_container",
"cibuildwheel.projectfiles",
"cibuildwheel.selector",
"cibuildwheel.typing",
"cibuildwheel.util",
"cibuildwheel.util.helpers",
"cibuildwheel.util.packaging",
"collections",
"configparser",
"contextlib",
"difflib",
"packaging",
"packaging.specifiers",
"pathlib",
"shlex",
"textwrap",
"tomllib",
}
import collections
import configparser
import contextlib
@@ -19,7 +43,7 @@ from packaging.specifiers import SpecifierSet
from cibuildwheel import errors
from cibuildwheel.architecture import Architecture
from cibuildwheel.environment import EnvironmentParseError, ParsedEnvironment, parse_environment
from cibuildwheel.frontend import BuildFrontendConfig
from cibuildwheel.frontend import BuildFrontendConfig, BuildFrontendName
from cibuildwheel.logger import log
from cibuildwheel.oci_container import OCIContainerEngineConfig
from cibuildwheel.projectfiles import get_requires_python_str, resolve_dependency_groups
@@ -797,7 +821,6 @@ class Options:
test_runtime_str = self.reader.get(
"test-runtime",
env_plat=False,
option_format=ShlexTableFormat(sep="; ", pair_sep=":", allow_merge=False),
)
if not test_runtime_str:
@@ -809,9 +832,11 @@ class Options:
msg = f"Failed to parse test runtime config. {e}"
raise errors.ConfigurationError(msg) from e
test_requires = self.reader.get(
"test-requires", option_format=ListFormat(sep=" ")
).split()
test_requires = shlex.split(
self.reader.get(
"test-requires", option_format=ListFormat(sep=" ", quote=shlex.quote)
)
)
test_extras = self.reader.get("test-extras", option_format=ListFormat(sep=","))
test_groups_str = self.reader.get("test-groups", option_format=ListFormat(sep=" "))
test_groups = [x for x in test_groups_str.split() if x]
@@ -825,8 +850,11 @@ class Options:
env_plat=False,
option_format=ShlexTableFormat(sep="; ", pair_sep=":", allow_merge=False),
)
default_frontend: BuildFrontendName = (
"pyodide-build" if self.platform == "pyodide" else "build"
)
if not build_frontend_str or build_frontend_str == "default":
build_frontend = BuildFrontendConfig("build")
build_frontend = BuildFrontendConfig(default_frontend)
else:
try:
build_frontend = BuildFrontendConfig.from_config_string(build_frontend_str)
@@ -834,6 +862,18 @@ 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":
# pip and uv could become an error, eventually. build -> pyodide-build is
# probably fine to keep as a warning
log.warning(
f"The pyodide platform ignores the {build_frontend.name!r} build "
"frontend; using 'pyodide-build' instead"
)
build_frontend = BuildFrontendConfig("pyodide-build", build_frontend.args)
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:
@@ -880,11 +920,7 @@ class Options:
f"manylinux-{build_platform}-image", ignore_empty=True
)
self._check_pinned_image(config_value, pinned_images)
if config_value in pinned_images:
image = pinned_images[config_value]
else:
image = config_value
manylinux_images[build_platform] = image
manylinux_images[build_platform] = pinned_images.get(config_value, config_value)
for build_platform in MUSLLINUX_ARCHS:
pinned_images = all_pinned_container_images[build_platform]
@@ -892,11 +928,7 @@ class Options:
f"musllinux-{build_platform}-image", ignore_empty=True
)
self._check_pinned_image(config_value, pinned_images)
if config_value in pinned_images:
image = pinned_images[config_value]
else:
image = config_value
musllinux_images[build_platform] = image
musllinux_images[build_platform] = pinned_images.get(config_value, config_value)
container_engine_str = self.reader.get(
"container-engine",
@@ -916,9 +948,11 @@ class Options:
)
audit_command = audit_command_str.split(" && ") if audit_command_str else []
audit_requires = self.reader.get(
"audit-requires", option_format=ListFormat(sep=" ")
).split()
audit_requires = shlex.split(
self.reader.get(
"audit-requires", option_format=ListFormat(sep=" ", quote=shlex.quote)
)
)
return BuildOptions(
globals=self.globals,
@@ -1104,6 +1138,6 @@ def _get_pinned_container_images() -> Mapping[str, Mapping[str, str]]:
'pypy_x86_64': {'manylinux2010': '...' }
... }
"""
all_pinned_images = configparser.ConfigParser()
all_pinned_images = configparser.ConfigParser(inline_comment_prefixes="#")
all_pinned_images.read(resources.PINNED_DOCKER_IMAGES)
return all_pinned_images
+39 -9
View File
@@ -1,5 +1,33 @@
from __future__ import annotations
__lazy_modules__ = {
"build",
"build.env",
"cibuildwheel.architecture",
"cibuildwheel.audit",
"cibuildwheel.frontend",
"cibuildwheel.logger",
"cibuildwheel.util.cmd",
"cibuildwheel.util.file",
"cibuildwheel.util.helpers",
"cibuildwheel.util.packaging",
"cibuildwheel.util.python_build_standalone",
"cibuildwheel.venv",
"filelock",
"packaging",
"packaging.utils",
"pathlib",
"platform",
"pprint",
"re",
"runpy",
"shlex",
"shutil",
"subprocess",
"textwrap",
"typing",
}
import os
import platform
import re
@@ -195,10 +223,6 @@ def setup_target_python(config: PythonConfiguration, build_path: Path) -> Path:
python_dir.mkdir()
shutil.unpack_archive(python_tgz, python_dir)
# Patch a testbed bug. This code and the patch file can both be removed once we've
# updated to Python versions that include the fix.
call("patch", "-p1", "-i", RESOURCES_ANDROID / "android.patch", cwd=python_dir)
# Work around https://github.com/python/cpython/issues/138800. This can be removed
# once we've updated to Python versions that include the fix.
pc_path = python_dir / f"prefix/lib/pkgconfig/python-{config.version}.pc"
@@ -286,7 +310,9 @@ def setup_env(
"wheel",
parse_config_settings(
prepare_config_settings(
build_options.config_settings, project=".", package=build_options.package_dir
build_options.config_settings,
project=Path.cwd(),
package=build_options.package_dir,
)
),
)
@@ -410,10 +436,14 @@ def setup_android_env(
# * a key=value string, without quotes
for i, token in enumerate(shlex.split(env_output)):
if i % 2 == 0:
assert token == "export", token
if token != "export":
msg = f"Unexpected output from android.py env: expected 'export', got {token!r}"
raise errors.FatalError(msg)
else:
key, sep, value = token.partition("=")
assert sep == "=", token
if sep != "=":
msg = f"Unexpected output from android.py env: expected 'key=value', got {token!r}"
raise errors.FatalError(msg)
android_env[key] = value
# localized_vars cleared the CFLAGS and CXXFLAGS in the sysconfigdata, but most
@@ -572,7 +602,7 @@ def build_wheel(state: BuildState) -> Path:
state.options.build_verbosity,
prepare_config_settings(
state.options.config_settings,
project=".",
project=Path.cwd(),
package=state.options.package_dir,
),
),
@@ -593,7 +623,7 @@ def build_wheel(state: BuildState) -> Path:
state.options.build_verbosity,
prepare_config_settings(
state.options.config_settings,
project=".",
project=Path.cwd(),
package=state.options.package_dir,
),
),
+42 -2
View File
@@ -1,5 +1,28 @@
from __future__ import annotations
__lazy_modules__ = {
"cibuildwheel.audit",
"cibuildwheel.frontend",
"cibuildwheel.logger",
"cibuildwheel.platforms.macos",
"cibuildwheel.util",
"cibuildwheel.util.cmd",
"cibuildwheel.util.file",
"cibuildwheel.util.helpers",
"cibuildwheel.util.packaging",
"cibuildwheel.venv",
"filelock",
"packaging",
"packaging.version",
"pathlib",
"platform",
"shlex",
"shutil",
"subprocess",
"textwrap",
"typing",
}
import dataclasses
import os
import platform
@@ -95,6 +118,14 @@ def all_python_configurations() -> list[PythonConfiguration]:
matching = [
config for config in macos_python_configs if config["identifier"] == macos_identifier
]
if not matching:
msg = (
f"Internal error: no macOS configuration found matching "
f"{macos_identifier!r}, needed to build the iOS configuration "
f"{config_dict['identifier']!r}. The bundled build-platforms.toml "
f"resources are inconsistent."
)
raise errors.FatalError(msg)
return matching[0]
# Load the platform configuration
@@ -432,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)
@@ -525,7 +559,7 @@ def build(options: Options, tmp_path: Path) -> None:
build_options.build_verbosity,
prepare_config_settings(
build_options.config_settings,
project=".",
project=Path.cwd(),
package=build_options.package_dir,
),
)
@@ -557,10 +591,16 @@ 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)
built_wheel = next(built_wheel_dir.glob("*.whl"))
try:
built_wheel = next(built_wheel_dir.glob("*.whl"))
except StopIteration:
raise errors.BuildProducedNoWheelError() from None
if built_wheel.name.endswith("none-any.whl"):
raise errors.NonPlatformWheelError()
+25 -2
View File
@@ -1,5 +1,22 @@
from __future__ import annotations
__lazy_modules__ = {
"cibuildwheel.audit",
"cibuildwheel.frontend",
"cibuildwheel.logger",
"cibuildwheel.util",
"cibuildwheel.util.file",
"cibuildwheel.util.helpers",
"cibuildwheel.util.packaging",
"collections",
"contextlib",
"pathlib",
"shutil",
"subprocess",
"textwrap",
"typing",
}
import contextlib
import dataclasses
import shutil
@@ -343,10 +360,16 @@ 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)
built_wheel = container.glob(built_wheel_dir, "*.whl")[0]
try:
built_wheel = container.glob(built_wheel_dir, "*.whl")[0]
except IndexError:
raise errors.BuildProducedNoWheelError() from None
repaired_wheel_dir = temp_dir / "repaired_wheel"
container.call(["rm", "-rf", repaired_wheel_dir])
@@ -493,7 +516,7 @@ def build(options: Options, tmp_path: Path) -> None:
abs_package_dir = options.globals.package_dir.resolve()
if cwd != abs_package_dir and cwd not in abs_package_dir.parents:
msg = "package_dir must be inside the working directory"
raise Exception(msg)
raise errors.ConfigurationError(msg)
container_project_path = PurePosixPath("/project")
container_package_dir = container_project_path / abs_package_dir.relative_to(cwd)
+42 -10
View File
@@ -1,5 +1,27 @@
from __future__ import annotations
__lazy_modules__ = {
"cibuildwheel.audit",
"cibuildwheel.ci",
"cibuildwheel.frontend",
"cibuildwheel.logger",
"cibuildwheel.util",
"cibuildwheel.util.cmd",
"cibuildwheel.util.file",
"cibuildwheel.util.helpers",
"cibuildwheel.util.packaging",
"cibuildwheel.venv",
"filelock",
"inspect",
"packaging",
"packaging.version",
"pathlib",
"platform",
"re",
"shutil",
"subprocess",
}
import dataclasses
import functools
import inspect
@@ -193,18 +215,19 @@ def install_cpython(_tmp: Path, version: str, url: str, free_threading: bool, sh
def install_pypy(tmp: Path, url: str, sha256: str) -> Path:
pypy_tar_bz2 = url.rsplit("/", 1)[-1]
extension = ".tar.bz2"
assert pypy_tar_bz2.endswith(extension)
installation_path = CIBW_CACHE_PATH / pypy_tar_bz2[: -len(extension)]
pypy_archive = url.rsplit("/", 1)[-1]
# PyPy ships .tar.bz2 for some platforms and .tar.gz for others.
extension = ".tar.bz2" if pypy_archive.endswith(".tar.bz2") else ".tar.gz"
assert pypy_archive.endswith(extension)
installation_path = CIBW_CACHE_PATH / pypy_archive[: -len(extension)]
with FileLock(str(installation_path) + ".lock"):
if not installation_path.exists():
downloaded_tar_bz2 = tmp / pypy_tar_bz2
download(url, downloaded_tar_bz2, sha256=sha256)
downloaded_archive = tmp / pypy_archive
download(url, downloaded_archive, sha256=sha256)
installation_path.parent.mkdir(parents=True, exist_ok=True)
with remove_on_error(installation_path):
call("tar", "-C", installation_path.parent, "-xf", downloaded_tar_bz2)
downloaded_tar_bz2.unlink()
call("tar", "-C", installation_path.parent, "-xf", downloaded_archive)
downloaded_archive.unlink()
return installation_path / "bin" / "pypy3"
@@ -427,6 +450,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)
@@ -512,7 +538,7 @@ def build(options: Options, tmp_path: Path) -> None:
build_options.build_verbosity,
prepare_config_settings(
build_options.config_settings,
project=".",
project=Path.cwd(),
package=build_options.package_dir,
),
)
@@ -563,10 +589,16 @@ 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)
built_wheel = next(built_wheel_dir.glob("*.whl"))
try:
built_wheel = next(built_wheel_dir.glob("*.whl"))
except StopIteration:
raise errors.BuildProducedNoWheelError() from None
repaired_wheel_dir.mkdir()
+64 -7
View File
@@ -1,5 +1,26 @@
from __future__ import annotations
__lazy_modules__ = {
"cibuildwheel.architecture",
"cibuildwheel.audit",
"cibuildwheel.frontend",
"cibuildwheel.logger",
"cibuildwheel.util",
"cibuildwheel.util.cmd",
"cibuildwheel.util.file",
"cibuildwheel.util.helpers",
"cibuildwheel.util.packaging",
"cibuildwheel.util.python_build_standalone",
"cibuildwheel.venv",
"filelock",
"json",
"pathlib",
"shutil",
"subprocess",
"tempfile",
"tomllib",
}
import dataclasses
import functools
import json
@@ -197,6 +218,36 @@ def validate_pyodide_build_version(
raise errors.FatalError(msg)
def validate_pyodide_target_python(
xbuildenv_info: PyodideXBuildEnvInfo, python_configuration: PythonConfiguration
) -> None:
"""
Validate that the resolved Pyodide xbuildenv targets the same Python version
as the identifier being built.
This is to catch the case where a global ``pyodide-version`` is applied to an
identifier whose Python version does not match, such as setting
``pyodide-version = "3Y.0.0"`` (a Python 3.Y environment) while
building ``cp3X-pyodide_wasm32``.
"""
xbuildenv_version = xbuildenv_info["version"]
identifier = python_configuration.identifier
expected_version = python_configuration.version
default_version = python_configuration.default_pyodide_version
xbuildenv_python_minor = ".".join(xbuildenv_info["python"].split(".")[:2])
if xbuildenv_python_minor != expected_version:
msg = unwrap_preserving_paragraphs(f"""
The `pyodide-version` option is set to {xbuildenv_version}, which
provides Python {xbuildenv_python_minor}, but the {identifier} build
needs Python {expected_version}.
Either remove `pyodide-version` so {identifier} uses its default
({default_version}), or stop building {identifier} so that
`pyodide-version` only applies to an identifier it matches.
""")
raise errors.ConfigurationError(msg)
def install_xbuildenv(env: dict[str, str], xbuildenv_cache_path: Path, pyodide_version: str) -> str:
"""Install a particular Pyodide xbuildenv version and set a path to the Pyodide root."""
pyodide_root = xbuildenv_cache_path / pyodide_version / "xbuildenv" / "pyodide-root"
@@ -316,6 +367,10 @@ def setup_python(
xbuildenv_info=xbuildenv_info,
pyodide_build_version=pyodide_build_version,
)
validate_pyodide_target_python(
xbuildenv_info=xbuildenv_info,
python_configuration=python_configuration,
)
xbuildenv_cache_path = CIBW_CACHE_PATH / f"pyodide-build-{pyodide_build_version}"
@@ -371,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
@@ -438,7 +489,7 @@ def build(options: Options, tmp_path: Path) -> None:
build_options.build_verbosity,
prepare_config_settings(
build_options.config_settings,
project=".",
project=Path.cwd(),
package=build_options.package_dir,
),
)
@@ -451,7 +502,10 @@ def build(options: Options, tmp_path: Path) -> None:
*extra_flags,
env=env,
)
built_wheel = next(built_wheel_dir.glob("*.whl"))
try:
built_wheel = next(built_wheel_dir.glob("*.whl"))
except StopIteration:
raise errors.BuildProducedNoWheelError() from None
if built_wheel.name.endswith("none-any.whl"):
raise errors.NonPlatformWheelError()
@@ -471,7 +525,10 @@ def build(options: Options, tmp_path: Path) -> None:
else:
shutil.move(str(built_wheel), repaired_wheel_dir)
repaired_wheel = next(repaired_wheel_dir.glob("*.whl"))
try:
repaired_wheel = next(repaired_wheel_dir.glob("*.whl"))
except StopIteration:
raise errors.RepairStepProducedNoWheelError() from None
if repaired_wheel.name in {wheel.name for wheel in built_wheels}:
raise errors.AlreadyBuiltWheelError(repaired_wheel.name)
+32 -56
View File
@@ -1,7 +1,26 @@
from __future__ import annotations
__lazy_modules__ = {
"cibuildwheel.architecture",
"cibuildwheel.audit",
"cibuildwheel.frontend",
"cibuildwheel.logger",
"cibuildwheel.util",
"cibuildwheel.util.cmd",
"cibuildwheel.util.file",
"cibuildwheel.util.helpers",
"cibuildwheel.util.packaging",
"cibuildwheel.venv",
"filelock",
"pathlib",
"platform",
"shutil",
"subprocess",
"textwrap",
"typing",
}
import dataclasses
import json
import os
import platform as platform_module
import shutil
@@ -329,7 +348,7 @@ def setup_python(
assert (venv_path / "Scripts" / "pip.exe").exists()
where_pip = call("where", "pip", env=env, capture_stdout=True).splitlines()[0].strip()
print(where_pip)
if where_pip.strip() != str(venv_path / "Scripts" / "pip.exe"):
if where_pip != str(venv_path / "Scripts" / "pip.exe"):
msg = "pip available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert pip above it."
raise errors.FatalError(msg)
call("pip", "--version", env=env)
@@ -382,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)
@@ -390,42 +412,6 @@ def setup_python(
setup_setuptools_cross_compile(tmp, python_configuration, python_libs_base, env)
setup_rust_cross_compile(tmp, python_configuration, python_libs_base, env)
if implementation_id.startswith("gp311"):
# GraalPy 24 fails to discover compilers, setup the relevant environment
# variables. Adapted from
# https://github.com/microsoft/vswhere/wiki/Start-Developer-Command-Prompt
# Remove when GraalPy 24.x is dropped.
vcpath = call(
Path(os.environ["PROGRAMFILES(X86)"])
/ "Microsoft Visual Studio"
/ "Installer"
/ "vswhere.exe",
"-products",
"*",
"-latest",
"-property",
"installationPath",
capture_stdout=True,
).strip()
log.notice(f"Discovering Visual Studio for GraalPy at {vcpath}")
vcvars_file = tmp / "vcvars.json"
call(
f"{vcpath}\\Common7\\Tools\\vsdevcmd.bat",
"-no_logo",
"-arch=amd64",
"-host_arch=amd64",
"&&",
"python",
"-c",
# this command needs to be one line for Windows reasons
"import sys, json, pathlib, os; pathlib.Path(sys.argv[1]).write_text(json.dumps(dict(os.environ)))",
vcvars_file,
env=env,
)
with open(vcvars_file, encoding="utf-8") as f:
vcvars = json.load(f)
env.update(vcvars)
return base_python, env
@@ -506,27 +492,11 @@ def build(options: Options, tmp_path: Path) -> None:
build_options.build_verbosity,
prepare_config_settings(
build_options.config_settings,
project=".",
project=Path.cwd(),
package=options.globals.package_dir,
),
)
if (
config.identifier.startswith("gp311")
and build_frontend.name == "build"
and "--no-isolation" not in extra_flags
and "-n" not in extra_flags
):
# GraalPy 24 fails to discover its standard library when a venv is created
# from a virtualenv seeded executable. See
# https://github.com/oracle/graalpython/issues/491 and remove this once
# GraalPy 24 is dropped.
log.notice(
"Disabling build isolation to workaround GraalPy bug. If the build fails, consider using pip or build[uv] as build frontend."
)
shell("graalpy -m pip install setuptools wheel", env=env)
extra_flags = [*extra_flags, "-n"]
match build_frontend.name:
case "pip":
# Path.resolve() is needed. Without it pip wheel may try to fetch package from pypi.org
@@ -572,10 +542,16 @@ 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)
built_wheel = next(built_wheel_dir.glob("*.whl"))
try:
built_wheel = next(built_wheel_dir.glob("*.whl"))
except StopIteration:
raise errors.BuildProducedNoWheelError() from None
# repair the wheel
repaired_wheel_dir.mkdir()
+2
View File
@@ -1,5 +1,7 @@
from __future__ import annotations
__lazy_modules__ = {"configparser", "contextlib", "dependency_groups"}
import ast
import configparser
import contextlib
@@ -1,11 +0,0 @@
--- a/android.py
+++ b/android.py
@@ -1021,7 +1021,7 @@ def main():
context = parse_args()
# Set the CROSS_BUILD_DIR if an argument was provided
- if context.cross_build_dir:
+ if getattr(context, "cross_build_dir", None):
global CROSS_BUILD_DIR
CROSS_BUILD_DIR = context.cross_build_dir.resolve()
+50 -55
View File
@@ -21,7 +21,6 @@ python_configurations = [
{ identifier = "pp39-manylinux_x86_64", version = "3.9", path_str = "/opt/python/pp39-pypy39_pp73" },
{ identifier = "pp310-manylinux_x86_64", version = "3.10", path_str = "/opt/python/pp310-pypy310_pp73" },
{ identifier = "pp311-manylinux_x86_64", version = "3.11", path_str = "/opt/python/pp311-pypy311_pp73" },
{ identifier = "gp311_242-manylinux_x86_64", version = "3.11", path_str = "/opt/python/graalpy311-graalpy242_311_native" },
{ identifier = "gp312_250-manylinux_x86_64", version = "3.12", path_str = "/opt/python/graalpy312-graalpy250_312_native" },
{ identifier = "cp39-manylinux_aarch64", version = "3.9", path_str = "/opt/python/cp39-cp39" },
{ identifier = "cp310-manylinux_aarch64", version = "3.10", path_str = "/opt/python/cp310-cp310" },
@@ -71,7 +70,6 @@ python_configurations = [
{ identifier = "pp39-manylinux_aarch64", version = "3.9", path_str = "/opt/python/pp39-pypy39_pp73" },
{ identifier = "pp310-manylinux_aarch64", version = "3.10", path_str = "/opt/python/pp310-pypy310_pp73" },
{ identifier = "pp311-manylinux_aarch64", version = "3.11", path_str = "/opt/python/pp311-pypy311_pp73" },
{ identifier = "gp311_242-manylinux_aarch64", version = "3.11", path_str = "/opt/python/graalpy311-graalpy242_311_native" },
{ identifier = "gp312_250-manylinux_aarch64", version = "3.12", path_str = "/opt/python/graalpy312-graalpy250_312_native" },
{ identifier = "pp39-manylinux_i686", version = "3.9", path_str = "/opt/python/pp39-pypy39_pp73" },
{ identifier = "pp310-manylinux_i686", version = "3.10", path_str = "/opt/python/pp310-pypy310_pp73" },
@@ -155,29 +153,27 @@ python_configurations = [
{ identifier = "cp312-macosx_x86_64", version = "3.12", url = "https://www.python.org/ftp/python/3.12.10/python-3.12.10-macos11.pkg", sha256 = "8373e58da4ea146b3eb1c1f9834f19a319440b6b679b06050b1f9ee3237aa8e4" },
{ identifier = "cp312-macosx_arm64", version = "3.12", url = "https://www.python.org/ftp/python/3.12.10/python-3.12.10-macos11.pkg", sha256 = "8373e58da4ea146b3eb1c1f9834f19a319440b6b679b06050b1f9ee3237aa8e4" },
{ identifier = "cp312-macosx_universal2", version = "3.12", url = "https://www.python.org/ftp/python/3.12.10/python-3.12.10-macos11.pkg", sha256 = "8373e58da4ea146b3eb1c1f9834f19a319440b6b679b06050b1f9ee3237aa8e4" },
{ identifier = "cp313-macosx_x86_64", version = "3.13", url = "https://www.python.org/ftp/python/3.13.13/python-3.13.13-macos11.pkg", sha256 = "a909cb655af5db67d5a90b3603437a1d58bec3446d624e4034e278ac62023cc9" },
{ identifier = "cp313-macosx_arm64", version = "3.13", url = "https://www.python.org/ftp/python/3.13.13/python-3.13.13-macos11.pkg", sha256 = "a909cb655af5db67d5a90b3603437a1d58bec3446d624e4034e278ac62023cc9" },
{ identifier = "cp313-macosx_universal2", version = "3.13", url = "https://www.python.org/ftp/python/3.13.13/python-3.13.13-macos11.pkg", sha256 = "a909cb655af5db67d5a90b3603437a1d58bec3446d624e4034e278ac62023cc9" },
{ identifier = "cp314-macosx_x86_64", version = "3.14", url = "https://www.python.org/ftp/python/3.14.5/python-3.14.5-macos11.pkg", sha256 = "b28a8dc33c456dd06c97024697d63ca916cfb494594c06fa3e4ef4d41fa82335" },
{ identifier = "cp314-macosx_arm64", version = "3.14", url = "https://www.python.org/ftp/python/3.14.5/python-3.14.5-macos11.pkg", sha256 = "b28a8dc33c456dd06c97024697d63ca916cfb494594c06fa3e4ef4d41fa82335" },
{ identifier = "cp314-macosx_universal2", version = "3.14", url = "https://www.python.org/ftp/python/3.14.5/python-3.14.5-macos11.pkg", sha256 = "b28a8dc33c456dd06c97024697d63ca916cfb494594c06fa3e4ef4d41fa82335" },
{ identifier = "cp314t-macosx_x86_64", version = "3.14", url = "https://www.python.org/ftp/python/3.14.5/python-3.14.5-macos11.pkg", sha256 = "b28a8dc33c456dd06c97024697d63ca916cfb494594c06fa3e4ef4d41fa82335" },
{ identifier = "cp314t-macosx_arm64", version = "3.14", url = "https://www.python.org/ftp/python/3.14.5/python-3.14.5-macos11.pkg", sha256 = "b28a8dc33c456dd06c97024697d63ca916cfb494594c06fa3e4ef4d41fa82335" },
{ identifier = "cp314t-macosx_universal2", version = "3.14", url = "https://www.python.org/ftp/python/3.14.5/python-3.14.5-macos11.pkg", sha256 = "b28a8dc33c456dd06c97024697d63ca916cfb494594c06fa3e4ef4d41fa82335" },
{ identifier = "cp315-macosx_x86_64", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b2-macos11.pkg", sha256 = "29b5c95f255f846f77e1c119146836445f929de1392a3ae9ef24f559e1345418" },
{ identifier = "cp315-macosx_arm64", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b2-macos11.pkg", sha256 = "29b5c95f255f846f77e1c119146836445f929de1392a3ae9ef24f559e1345418" },
{ identifier = "cp315-macosx_universal2", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b2-macos11.pkg", sha256 = "29b5c95f255f846f77e1c119146836445f929de1392a3ae9ef24f559e1345418" },
{ identifier = "cp315t-macosx_x86_64", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b2-macos11.pkg", sha256 = "29b5c95f255f846f77e1c119146836445f929de1392a3ae9ef24f559e1345418" },
{ identifier = "cp315t-macosx_arm64", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b2-macos11.pkg", sha256 = "29b5c95f255f846f77e1c119146836445f929de1392a3ae9ef24f559e1345418" },
{ identifier = "cp315t-macosx_universal2", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b2-macos11.pkg", sha256 = "29b5c95f255f846f77e1c119146836445f929de1392a3ae9ef24f559e1345418" },
{ identifier = "pp39-macosx_x86_64", version = "3.9", url = "https://downloads.python.org/pypy/pypy3.9-v7.3.16-macos_x86_64.tar.bz2", sha256 = "fda015431621e7e5aa16359d114f2c45a77ed936992c1efff86302e768a6b21c" },
{ identifier = "pp39-macosx_arm64", version = "3.9", url = "https://downloads.python.org/pypy/pypy3.9-v7.3.16-macos_arm64.tar.bz2", sha256 = "88f824e7a2d676440d09bc90fc959ae0fd3557d7e2f14bfbbe53d41d159a47fe" },
{ identifier = "pp310-macosx_x86_64", version = "3.10", url = "https://downloads.python.org/pypy/pypy3.10-v7.3.19-macos_x86_64.tar.bz2", sha256 = "2c5e5c2a33ac882551d7f28b98d19d486b8995aa50824a73b4edcc6aeec35c63" },
{ identifier = "pp310-macosx_arm64", version = "3.10", url = "https://downloads.python.org/pypy/pypy3.10-v7.3.19-macos_arm64.tar.bz2", sha256 = "3db8a03fc496164801646844d7f3b12baa0adb3ad9a0b7cb124521bc2e168a36" },
{ identifier = "pp311-macosx_x86_64", version = "3.11", url = "https://downloads.python.org/pypy/pypy3.11-v7.3.23-macos_x86_64.tar.bz2", sha256 = "c95363c4e87235d11a6cec8128239c291b1eb67a752778fbcfe029a71da82b5e" },
{ identifier = "pp311-macosx_arm64", version = "3.11", url = "https://downloads.python.org/pypy/pypy3.11-v7.3.23-macos_arm64.tar.bz2", sha256 = "4747b3aceba4c1c6104cddc0fe5ea302101d32955f0957347b9ecc4fbd7aed05" },
{ identifier = "gp311_242-macosx_x86_64", version = "3.11", url = "https://github.com/oracle/graalpython/releases/download/graal-24.2.2/graalpy-24.2.2-macos-amd64.tar.gz", sha256 = "2f4d5e7dbdf90e38778dfcb8ca3e1ec7eee257ef726b1937d5bc91b54cdddf9b" },
{ identifier = "gp311_242-macosx_arm64", version = "3.11", url = "https://github.com/oracle/graalpython/releases/download/graal-24.2.2/graalpy-24.2.2-macos-aarch64.tar.gz", sha256 = "f4a2ae01bae0fa53ec0d19f86d73c6dcc2a162d245552030183b84bfdd8f7635" },
{ identifier = "cp313-macosx_x86_64", version = "3.13", url = "https://www.python.org/ftp/python/3.13.14/python-3.13.14-macos11.pkg", sha256 = "8e58affb218c155a1dfdc27b291f817129669f8760e7a297adb2e4439ba5d2e8" },
{ identifier = "cp313-macosx_arm64", version = "3.13", url = "https://www.python.org/ftp/python/3.13.14/python-3.13.14-macos11.pkg", sha256 = "8e58affb218c155a1dfdc27b291f817129669f8760e7a297adb2e4439ba5d2e8" },
{ identifier = "cp313-macosx_universal2", version = "3.13", url = "https://www.python.org/ftp/python/3.13.14/python-3.13.14-macos11.pkg", sha256 = "8e58affb218c155a1dfdc27b291f817129669f8760e7a297adb2e4439ba5d2e8" },
{ identifier = "cp314-macosx_x86_64", version = "3.14", url = "https://www.python.org/ftp/python/3.14.6/python-3.14.6-macos11.pkg", sha256 = "d3c9fff52214847e4fab03e9eaf53dd2a8e51e3534aa0b61f201b749f86bef28" },
{ identifier = "cp314-macosx_arm64", version = "3.14", url = "https://www.python.org/ftp/python/3.14.6/python-3.14.6-macos11.pkg", sha256 = "d3c9fff52214847e4fab03e9eaf53dd2a8e51e3534aa0b61f201b749f86bef28" },
{ identifier = "cp314-macosx_universal2", version = "3.14", url = "https://www.python.org/ftp/python/3.14.6/python-3.14.6-macos11.pkg", sha256 = "d3c9fff52214847e4fab03e9eaf53dd2a8e51e3534aa0b61f201b749f86bef28" },
{ identifier = "cp314t-macosx_x86_64", version = "3.14", url = "https://www.python.org/ftp/python/3.14.6/python-3.14.6-macos11.pkg", sha256 = "d3c9fff52214847e4fab03e9eaf53dd2a8e51e3534aa0b61f201b749f86bef28" },
{ identifier = "cp314t-macosx_arm64", version = "3.14", url = "https://www.python.org/ftp/python/3.14.6/python-3.14.6-macos11.pkg", sha256 = "d3c9fff52214847e4fab03e9eaf53dd2a8e51e3534aa0b61f201b749f86bef28" },
{ identifier = "cp314t-macosx_universal2", version = "3.14", url = "https://www.python.org/ftp/python/3.14.6/python-3.14.6-macos11.pkg", sha256 = "d3c9fff52214847e4fab03e9eaf53dd2a8e51e3534aa0b61f201b749f86bef28" },
{ identifier = "cp315-macosx_x86_64", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b4-macos11.pkg", sha256 = "6f1ed07cc92ed6e694b38b29d929a4e55190532fddb3828afe68d07cd4bf4aff" },
{ identifier = "cp315-macosx_arm64", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b4-macos11.pkg", sha256 = "6f1ed07cc92ed6e694b38b29d929a4e55190532fddb3828afe68d07cd4bf4aff" },
{ identifier = "cp315-macosx_universal2", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b4-macos11.pkg", sha256 = "6f1ed07cc92ed6e694b38b29d929a4e55190532fddb3828afe68d07cd4bf4aff" },
{ identifier = "cp315t-macosx_x86_64", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b4-macos11.pkg", sha256 = "6f1ed07cc92ed6e694b38b29d929a4e55190532fddb3828afe68d07cd4bf4aff" },
{ identifier = "cp315t-macosx_arm64", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b4-macos11.pkg", sha256 = "6f1ed07cc92ed6e694b38b29d929a4e55190532fddb3828afe68d07cd4bf4aff" },
{ identifier = "cp315t-macosx_universal2", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b4-macos11.pkg", sha256 = "6f1ed07cc92ed6e694b38b29d929a4e55190532fddb3828afe68d07cd4bf4aff" },
{ identifier = "pp39-macosx_x86_64", version = "3.9", url = "https://downloads.python.org/pypy/pypy3.9-v7.3.16-macos_x86_64.tar.gz", sha256 = "7ea762a1b0af7545a74cd4260bb3fd2812813deffef2ef8d2fddd5e03fa63ef1" },
{ identifier = "pp39-macosx_arm64", version = "3.9", url = "https://downloads.python.org/pypy/pypy3.9-v7.3.16-macos_arm64.tar.gz", sha256 = "b09ce3a865c1c0f4e58c0ee4f70511f1b27dfc1f7148f4b6060cfdfd581c9e82" },
{ identifier = "pp310-macosx_x86_64", version = "3.10", url = "https://downloads.python.org/pypy/pypy3.10-v7.3.19-macos_x86_64.tar.gz", sha256 = "63f9d30cdd9e47cfe279be941975ac3d24e46c79756cd1d1bd2c551c27d8b32c" },
{ identifier = "pp310-macosx_arm64", version = "3.10", url = "https://downloads.python.org/pypy/pypy3.10-v7.3.19-macos_arm64.tar.gz", sha256 = "72a74cf645e43608f7d88831581fb0f8e1f963fb38f0793bacd37d5018a9aeb8" },
{ identifier = "pp311-macosx_x86_64", version = "3.11", url = "https://downloads.python.org/pypy/pypy3.11-v7.3.23-macos_x86_64.tar.gz", sha256 = "798279b0f233893ae22754290aedf35a1aea1ec9013a8ad8110f166d85944c91" },
{ identifier = "pp311-macosx_arm64", version = "3.11", url = "https://downloads.python.org/pypy/pypy3.11-v7.3.23-macos_arm64.tar.gz", sha256 = "f837b3c203d60d7e25a31e267671a07ba0c9b9ac8081ad225571fb06fccf1b7b" },
{ identifier = "gp312_250-macosx_x86_64", version = "3.12", url = "https://github.com/oracle/graalpython/releases/download/graal-25.0.1/graalpy-25.0.1-macos-amd64.tar.gz", sha256 = "10b0721d52397f0cc85f038900318da2203711cbcfae7899e3faed49d3dc6221" },
{ identifier = "gp312_250-macosx_arm64", version = "3.12", url = "https://github.com/oracle/graalpython/releases/download/graal-25.0.3/graalpy-25.0.3-macos-aarch64.tar.gz", sha256 = "387d9f5b376860842bea4d55aae5820974f3f3b68fc36c77ae863a061a888857" },
]
@@ -192,29 +188,28 @@ python_configurations = [
{ identifier = "cp311-win_amd64", version = "3.11.9" },
{ identifier = "cp312-win32", version = "3.12.10" },
{ identifier = "cp312-win_amd64", version = "3.12.10" },
{ identifier = "cp313-win32", version = "3.13.13" },
{ identifier = "cp313-win_amd64", version = "3.13.13" },
{ identifier = "cp314-win32", version = "3.14.5" },
{ identifier = "cp314t-win32", version = "3.14.5" },
{ identifier = "cp314-win_amd64", version = "3.14.5" },
{ identifier = "cp314t-win_amd64", version = "3.14.5" },
{ identifier = "cp315-win32", version = "3.15.0-b2" },
{ identifier = "cp315t-win32", version = "3.15.0-b2" },
{ identifier = "cp315-win_amd64", version = "3.15.0-b2" },
{ identifier = "cp315t-win_amd64", version = "3.15.0-b2" },
{ identifier = "cp313-win32", version = "3.13.14" },
{ identifier = "cp313-win_amd64", version = "3.13.14" },
{ identifier = "cp314-win32", version = "3.14.6" },
{ identifier = "cp314t-win32", version = "3.14.6" },
{ identifier = "cp314-win_amd64", version = "3.14.6" },
{ identifier = "cp314t-win_amd64", version = "3.14.6" },
{ identifier = "cp315-win32", version = "3.15.0-b4" },
{ identifier = "cp315t-win32", version = "3.15.0-b4" },
{ identifier = "cp315-win_amd64", version = "3.15.0-b4" },
{ identifier = "cp315t-win_amd64", version = "3.15.0-b4" },
{ identifier = "cp39-win_arm64", version = "3.9.10" },
{ identifier = "cp310-win_arm64", version = "3.10.11" },
{ identifier = "cp311-win_arm64", version = "3.11.9" },
{ identifier = "cp312-win_arm64", version = "3.12.10" },
{ identifier = "cp313-win_arm64", version = "3.13.13" },
{ identifier = "cp314-win_arm64", version = "3.14.5" },
{ identifier = "cp314t-win_arm64", version = "3.14.5" },
{ identifier = "cp315-win_arm64", version = "3.15.0-b2" },
{ identifier = "cp315t-win_arm64", version = "3.15.0-b2" },
{ identifier = "cp313-win_arm64", version = "3.13.14" },
{ identifier = "cp314-win_arm64", version = "3.14.6" },
{ identifier = "cp314t-win_arm64", version = "3.14.6" },
{ identifier = "cp315-win_arm64", version = "3.15.0-b4" },
{ identifier = "cp315t-win_arm64", version = "3.15.0-b4" },
{ identifier = "pp39-win_amd64", version = "3.9", url = "https://downloads.python.org/pypy/pypy3.9-v7.3.16-win64.zip", sha256 = "06ec12a5e964dc0ad33e6f380185a4d295178dce6d6df512f508e7aee00a1323" },
{ identifier = "pp310-win_amd64", version = "3.10", url = "https://downloads.python.org/pypy/pypy3.10-v7.3.19-win64.zip", sha256 = "c0d07bba6c8fb4e5804f4a8b3f8ef07cc3d89f6ad1db42a45ffb9be60bbb7cc2" },
{ identifier = "pp311-win_amd64", version = "3.11", url = "https://downloads.python.org/pypy/pypy3.11-v7.3.23-win64.zip", sha256 = "948b8ea58dea5b9917210fe4afd242c788fbfaba1c3f1a25e696a404f703389a" },
{ identifier = "gp311_242-win_amd64", version = "3.11", url = "https://github.com/oracle/graalpython/releases/download/graal-24.2.2/graalpy-24.2.2-windows-amd64.zip", sha256 = "9606134284d4d95b2f9d69c3087cd3e9e488f46355b419f5e66588a3281df6a3" },
{ identifier = "gp312_250-win_amd64", version = "3.12", url = "https://github.com/oracle/graalpython/releases/download/graal-25.0.3/graalpy-25.0.3-windows-amd64.zip", sha256 = "2ae5c42b93e08d9e106a017634a3442b272ccae6d08ace157244de0449e937d4" },
]
@@ -222,28 +217,28 @@ python_configurations = [
python_configurations = [
{ identifier = "cp312-pyodide_wasm32", version = "3.12", default_pyodide_version = "0.27.7", node_version = "v22", sha256 = "0c2e7db42efa5d1dac38b50f8b3d659a1e3885d0a233494831b8206281307d52" },
{ identifier = "cp313-pyodide_wasm32", version = "3.13", default_pyodide_version = "0.29.4", node_version = "v22", sha256 = "a29fc4a076408a18fc29eb4b280f80c6dddc95c19514c874c29e590d0931c02a" },
{ identifier = "cp314-pyodide_wasm32", version = "3.14", default_pyodide_version = "314.0.0a2", node_version = "v24", sha256 = "ac7bbcdf289ab3ae25621c436b1d013099ef6bec644ad0538dae5d65552c5c95" },
{ identifier = "cp314-pyodide_wasm32", version = "3.14", default_pyodide_version = "314.0.2", node_version = "v24", sha256 = "01ab1b229bf2031603bf20ad4ceb9c1059ba680b26df7bda2db4748906dfd78a" },
]
[android]
python_configurations = [
{ identifier = "cp313-android_arm64_v8a", version = "3.13", url = "https://repo.maven.apache.org/maven2/com/chaquo/python/python/3.13.13/python-3.13.13-aarch64-linux-android.tar.gz", sha256 = "a21250c246b36eb704be096be51e40ab24cafa3ae1d7ca4c396e6bf780cf87cf" },
{ identifier = "cp313-android_x86_64", version = "3.13", url = "https://repo.maven.apache.org/maven2/com/chaquo/python/python/3.13.13/python-3.13.13-x86_64-linux-android.tar.gz", sha256 = "b021b76df2c8d64e41c76d18a2d91f27652356bd932c06f9076bac1b664c0e37" },
{ identifier = "cp314-android_arm64_v8a", version = "3.14", url = "https://www.python.org/ftp/python/3.14.5/python-3.14.5-aarch64-linux-android.tar.gz", sha256 = "f008321abf837fcaec569df143283ece0e764b18d8c75763200160553f906af1" },
{ identifier = "cp314-android_x86_64", version = "3.14", url = "https://www.python.org/ftp/python/3.14.5/python-3.14.5-x86_64-linux-android.tar.gz", sha256 = "36184e31464b2b959d92c81ded8d025834342cc080623bced282a2a9a57bd47c" },
{ identifier = "cp313-android_arm64_v8a", version = "3.13", url = "https://repo.maven.apache.org/maven2/com/chaquo/python/python/3.13.14/python-3.13.14-aarch64-linux-android.tar.gz", sha256 = "2541c4fa77b6543504c97fa90a71c745225b5800fe25bc8c73a6516b594d0cbc" },
{ identifier = "cp313-android_x86_64", version = "3.13", url = "https://repo.maven.apache.org/maven2/com/chaquo/python/python/3.13.14/python-3.13.14-x86_64-linux-android.tar.gz", sha256 = "9d96f7e077677b6651b2e756034ed41ab4479f5f8bf3168a814f458bc2ac137e" },
{ identifier = "cp314-android_arm64_v8a", version = "3.14", url = "https://www.python.org/ftp/python/3.14.6/python-3.14.6-aarch64-linux-android.tar.gz", sha256 = "38bbe77d3167b5cd554e03b1021324926f09f3825202b065951dd7638e9c37e5" },
{ identifier = "cp314-android_x86_64", version = "3.14", url = "https://www.python.org/ftp/python/3.14.6/python-3.14.6-x86_64-linux-android.tar.gz", sha256 = "e04eb26607627e68d148f89de793372f54a345c91b13628567f24abcdd3bfa3e" },
{ identifier = "cp315-android_arm64_v8a", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b2-aarch64-linux-android.tar.gz", sha256 = "de5e36ffc3c33b63a1ed53fe83db4dee16b003a3180ac35e5c4c203d3750e076" },
{ identifier = "cp315-android_x86_64", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b2-x86_64-linux-android.tar.gz", sha256 = "e05ef9c142ea83b41b194b16014ffe2249d27783313c3b00b97f6c93d9a56b92" },
]
[ios]
python_configurations = [
{ identifier = "cp313-ios_arm64_iphoneos", version = "3.13", url = "https://github.com/beeware/Python-Apple-support/releases/download/3.13-b13/Python-3.13-iOS-support.b13.tar.gz", sha256 = "d1f95f95137a4b91dc0cbe9b99ddfc0a78918dde883c7fbe7147074a5e715274" },
{ identifier = "cp313-ios_x86_64_iphonesimulator", version = "3.13", url = "https://github.com/beeware/Python-Apple-support/releases/download/3.13-b13/Python-3.13-iOS-support.b13.tar.gz", sha256 = "d1f95f95137a4b91dc0cbe9b99ddfc0a78918dde883c7fbe7147074a5e715274" },
{ identifier = "cp313-ios_arm64_iphonesimulator", version = "3.13", url = "https://github.com/beeware/Python-Apple-support/releases/download/3.13-b13/Python-3.13-iOS-support.b13.tar.gz", sha256 = "d1f95f95137a4b91dc0cbe9b99ddfc0a78918dde883c7fbe7147074a5e715274" },
{ identifier = "cp314-ios_arm64_iphoneos", version = "3.14", url = "https://github.com/beeware/Python-Apple-support/releases/download/3.14-b9/Python-3.14-iOS-support.b9.tar.gz", sha256 = "8e4123b543bf17fdae2e2c6c033434487752438431014eb12e6d833aa35927a8" },
{ identifier = "cp314-ios_x86_64_iphonesimulator", version = "3.14", url = "https://github.com/beeware/Python-Apple-support/releases/download/3.14-b9/Python-3.14-iOS-support.b9.tar.gz", sha256 = "8e4123b543bf17fdae2e2c6c033434487752438431014eb12e6d833aa35927a8" },
{ identifier = "cp314-ios_arm64_iphonesimulator", version = "3.14", url = "https://github.com/beeware/Python-Apple-support/releases/download/3.14-b9/Python-3.14-iOS-support.b9.tar.gz", sha256 = "8e4123b543bf17fdae2e2c6c033434487752438431014eb12e6d833aa35927a8" },
{ identifier = "cp315-ios_arm64_iphoneos", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b2-iOS-XCframework.tar.gz", sha256 = "fd6b6fb8f82cf44622d28ea53b8197c89fb06505557d29d4d7a47f9386c1c896" },
{ identifier = "cp315-ios_x86_64_iphonesimulator", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b2-iOS-XCframework.tar.gz", sha256 = "fd6b6fb8f82cf44622d28ea53b8197c89fb06505557d29d4d7a47f9386c1c896" },
{ identifier = "cp315-ios_arm64_iphonesimulator", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b2-iOS-XCframework.tar.gz", sha256 = "fd6b6fb8f82cf44622d28ea53b8197c89fb06505557d29d4d7a47f9386c1c896" },
{ identifier = "cp313-ios_arm64_iphoneos", version = "3.13", url = "https://github.com/beeware/Python-Apple-support/releases/download/3.13-b14/Python-3.13-iOS-support.b14.tar.gz", sha256 = "8b5cb76ef8d8a2946052479358eeec9d54b4496cb60920e175ec1489b5cf7963" },
{ identifier = "cp313-ios_x86_64_iphonesimulator", version = "3.13", url = "https://github.com/beeware/Python-Apple-support/releases/download/3.13-b14/Python-3.13-iOS-support.b14.tar.gz", sha256 = "8b5cb76ef8d8a2946052479358eeec9d54b4496cb60920e175ec1489b5cf7963" },
{ identifier = "cp313-ios_arm64_iphonesimulator", version = "3.13", url = "https://github.com/beeware/Python-Apple-support/releases/download/3.13-b14/Python-3.13-iOS-support.b14.tar.gz", sha256 = "8b5cb76ef8d8a2946052479358eeec9d54b4496cb60920e175ec1489b5cf7963" },
{ identifier = "cp314-ios_arm64_iphoneos", version = "3.14", url = "https://github.com/beeware/Python-Apple-support/releases/download/3.14-b10/Python-3.14-iOS-support.b10.tar.gz", sha256 = "200ef60eb67be0483ceb638daa9048f84f41a9a952707a5ad4c3198037c7b583" },
{ identifier = "cp314-ios_x86_64_iphonesimulator", version = "3.14", url = "https://github.com/beeware/Python-Apple-support/releases/download/3.14-b10/Python-3.14-iOS-support.b10.tar.gz", sha256 = "200ef60eb67be0483ceb638daa9048f84f41a9a952707a5ad4c3198037c7b583" },
{ identifier = "cp314-ios_arm64_iphonesimulator", version = "3.14", url = "https://github.com/beeware/Python-Apple-support/releases/download/3.14-b10/Python-3.14-iOS-support.b10.tar.gz", sha256 = "200ef60eb67be0483ceb638daa9048f84f41a9a952707a5ad4c3198037c7b583" },
{ identifier = "cp315-ios_arm64_iphoneos", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b4-iOS-XCframework.tar.gz", sha256 = "dbf1095502e0b4a0dc2a4d96ae1b1d92ee8c0b7bea773862f7124ede0017e3dc" },
{ identifier = "cp315-ios_x86_64_iphonesimulator", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b4-iOS-XCframework.tar.gz", sha256 = "dbf1095502e0b4a0dc2a4d96ae1b1d92ee8c0b7bea773862f7124ede0017e3dc" },
{ identifier = "cp315-ios_arm64_iphonesimulator", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b4-iOS-XCframework.tar.gz", sha256 = "dbf1095502e0b4a0dc2a4d96ae1b1d92ee8c0b7bea773862f7124ede0017e3dc" },
]
@@ -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"
@@ -8,18 +8,18 @@ build==1.5.0
# via
# -r .nox/update_constraints/tmp/constraints-pyodide.in
# pyodide-build
certifi==2026.5.20
certifi==2026.6.17
# via requests
charset-normalizer==3.4.7
charset-normalizer==3.4.9
# via requests
click==8.1.8
# via
# -r .nox/update_constraints/tmp/constraints-pyodide.in
# pyodide-build
# pyodide-cli
distlib==0.4.1
distlib==0.4.3
# via virtualenv
filelock==3.29.1
filelock==3.31.1
# via
# python-discovery
# virtualenv
@@ -39,7 +39,7 @@ packaging==26.2
# wheel
pip==26.1.2
# via -r .nox/update_constraints/tmp/constraints-pyodide.in
platformdirs==4.10.0
platformdirs==4.10.1
# via
# pyodide-build
# python-discovery
@@ -52,7 +52,7 @@ pydantic-core==2.46.4
# via pydantic
pygments==2.20.0
# via rich
pyodide-build==0.34.4
pyodide-build==0.36.0
# via -r .nox/update_constraints/tmp/constraints-pyodide.in
pyodide-cli==0.5.0
# via
@@ -62,7 +62,7 @@ pyodide-lock==0.1.3
# via pyodide-build
pyproject-hooks==1.2.0
# via build
python-discovery==1.4.0
python-discovery==1.4.4
# via virtualenv
requests==2.34.2
# via pyodide-build
@@ -72,7 +72,7 @@ rich==15.0.0
# pyodide-cli
ruamel-yaml==0.19.1
# via pyodide-build
typing-extensions==4.15.0
typing-extensions==4.16.0
# via
# pydantic
# pydantic-core
@@ -81,7 +81,7 @@ typing-inspection==0.4.2
# via pydantic
urllib3==2.7.0
# via requests
virtualenv==21.4.2
virtualenv==21.6.1
# via
# build
# pyodide-build
@@ -8,18 +8,18 @@ build==1.5.0
# via
# -r .nox/update_constraints/tmp/constraints-pyodide.in
# pyodide-build
certifi==2026.5.20
certifi==2026.6.17
# via requests
charset-normalizer==3.4.7
charset-normalizer==3.4.9
# via requests
click==8.1.8
# via
# -r .nox/update_constraints/tmp/constraints-pyodide.in
# pyodide-build
# pyodide-cli
distlib==0.4.1
distlib==0.4.3
# via virtualenv
filelock==3.29.1
filelock==3.31.1
# via
# python-discovery
# virtualenv
@@ -39,7 +39,7 @@ packaging==26.2
# wheel
pip==26.1.2
# via -r .nox/update_constraints/tmp/constraints-pyodide.in
platformdirs==4.10.0
platformdirs==4.10.1
# via
# pyodide-build
# python-discovery
@@ -52,7 +52,7 @@ pydantic-core==2.46.4
# via pydantic
pygments==2.20.0
# via rich
pyodide-build==0.34.4
pyodide-build==0.36.0
# via -r .nox/update_constraints/tmp/constraints-pyodide.in
pyodide-cli==0.5.0
# via
@@ -62,7 +62,7 @@ pyodide-lock==0.1.3
# via pyodide-build
pyproject-hooks==1.2.0
# via build
python-discovery==1.4.0
python-discovery==1.4.4
# via virtualenv
requests==2.34.2
# via pyodide-build
@@ -72,7 +72,7 @@ rich==15.0.0
# pyodide-cli
ruamel-yaml==0.19.1
# via pyodide-build
typing-extensions==4.15.0
typing-extensions==4.16.0
# via
# pydantic
# pydantic-core
@@ -81,7 +81,7 @@ typing-inspection==0.4.2
# via pydantic
urllib3==2.7.0
# via requests
virtualenv==21.4.2
virtualenv==21.6.1
# via
# build
# pyodide-build
@@ -8,18 +8,18 @@ build==1.5.0
# via
# -r .nox/update_constraints/tmp/constraints-pyodide.in
# pyodide-build
certifi==2026.5.20
certifi==2026.6.17
# via requests
charset-normalizer==3.4.7
charset-normalizer==3.4.9
# via requests
click==8.1.8
# via
# -r .nox/update_constraints/tmp/constraints-pyodide.in
# pyodide-build
# pyodide-cli
distlib==0.4.1
distlib==0.4.3
# via virtualenv
filelock==3.29.1
filelock==3.31.1
# via
# python-discovery
# virtualenv
@@ -39,7 +39,7 @@ packaging==26.2
# wheel
pip==26.1.2
# via -r .nox/update_constraints/tmp/constraints-pyodide.in
platformdirs==4.10.0
platformdirs==4.10.1
# via
# pyodide-build
# python-discovery
@@ -52,7 +52,7 @@ pydantic-core==2.46.4
# via pydantic
pygments==2.20.0
# via rich
pyodide-build==0.34.4
pyodide-build==0.36.0
# via -r .nox/update_constraints/tmp/constraints-pyodide.in
pyodide-cli==0.5.0
# via
@@ -62,7 +62,7 @@ pyodide-lock==0.1.3
# via pyodide-build
pyproject-hooks==1.2.0
# via build
python-discovery==1.4.0
python-discovery==1.4.4
# via virtualenv
requests==2.34.2
# via pyodide-build
@@ -72,7 +72,7 @@ rich==15.0.0
# pyodide-cli
ruamel-yaml==0.19.1
# via pyodide-build
typing-extensions==4.15.0
typing-extensions==4.16.0
# via
# pydantic
# pydantic-core
@@ -81,7 +81,7 @@ typing-inspection==0.4.2
# via pydantic
urllib3==2.7.0
# via requests
virtualenv==21.4.2
virtualenv==21.6.1
# via
# build
# pyodide-build
@@ -16,19 +16,19 @@ build==1.5.0
# via -r cibuildwheel/resources/constraints.in
cattrs==26.1.0
# via requests-cache
certifi==2026.5.20
certifi==2026.6.17
# via requests
charset-normalizer==3.4.7
charset-normalizer==3.4.9
# via requests
delocate==0.13.0
# via -r cibuildwheel/resources/constraints.in
delvewheel==1.13.0
# via -r cibuildwheel/resources/constraints.in
distlib==0.4.1
distlib==0.4.3
# via virtualenv
exceptiongroup==1.3.1
# via cattrs
filelock==3.29.1
filelock==3.31.1
# via
# python-discovery
# virtualenv
@@ -60,9 +60,9 @@ pefile==2024.8.26
# delvewheel
pip==26.1.2
# via -r cibuildwheel/resources/constraints.in
pkgconf==2.5.1.post2
pkgconf==3.0.1.post0
# via -r cibuildwheel/resources/constraints.in
platformdirs==4.10.0
platformdirs==4.10.1
# via
# python-discovery
# requests-cache
@@ -75,19 +75,19 @@ pygments==2.20.0
# via rich
pyproject-hooks==1.2.0
# via build
python-discovery==1.4.0
python-discovery==1.4.4
# via virtualenv
requests==2.34.2
# via
# abi3audit
# requests-cache
requests-cache==1.3.2
requests-cache==1.3.3
# via abi3audit
rich==15.0.0
# via abi3audit
tomli==2.4.1
# via build
typing-extensions==4.15.0
typing-extensions==4.16.0
# via
# cattrs
# delocate
@@ -99,7 +99,7 @@ urllib3==2.7.0
# via
# requests
# requests-cache
virtualenv==21.4.2
virtualenv==21.6.1
# via -r cibuildwheel/resources/constraints.in
zipp==4.1.0
# via importlib-metadata
@@ -16,17 +16,17 @@ build==1.5.0
# via -r cibuildwheel/resources/constraints.in
cattrs==26.1.0
# via requests-cache
certifi==2026.5.20
certifi==2026.6.17
# via requests
charset-normalizer==3.4.7
charset-normalizer==3.4.9
# via requests
delocate==0.13.0
# via -r cibuildwheel/resources/constraints.in
delvewheel==1.13.0
# via -r cibuildwheel/resources/constraints.in
distlib==0.4.1
distlib==0.4.3
# via virtualenv
filelock==3.29.1
filelock==3.31.1
# via
# python-discovery
# virtualenv
@@ -54,12 +54,11 @@ pefile==2024.8.26
# via
# abi3audit
# delvewheel
pip==26.1.2; implementation_name != "graalpy" or platform_system != "Windows"
pip==26.0.1; implementation_name == "graalpy" and platform_system == "Windows"
pip==26.1.2
# via -r cibuildwheel/resources/constraints.in
pkgconf==2.5.1.post2
pkgconf==3.0.1.post0
# via -r cibuildwheel/resources/constraints.in
platformdirs==4.10.0
platformdirs==4.10.1
# via
# python-discovery
# requests-cache
@@ -72,17 +71,17 @@ pygments==2.20.0
# via rich
pyproject-hooks==1.2.0
# via build
python-discovery==1.4.0
python-discovery==1.4.4
# via virtualenv
requests==2.34.2
# via
# abi3audit
# requests-cache
requests-cache==1.3.2
requests-cache==1.3.3
# via abi3audit
rich==15.0.0
# via abi3audit
typing-extensions==4.15.0
typing-extensions==4.16.0
# via
# cattrs
# delocate
@@ -92,5 +91,5 @@ urllib3==2.7.0
# via
# requests
# requests-cache
virtualenv==21.4.2
virtualenv==21.6.1
# via -r cibuildwheel/resources/constraints.in
@@ -16,17 +16,18 @@ build==1.5.0
# via -r cibuildwheel/resources/constraints.in
cattrs==26.1.0
# via requests-cache
certifi==2026.5.20
certifi==2026.6.17
# via requests
charset-normalizer==3.4.7
charset-normalizer==3.4.9
# via requests
delocate==0.13.0
# via -r cibuildwheel/resources/constraints.in
delvewheel==1.13.0
# via -r cibuildwheel/resources/constraints.in
distlib==0.4.1
distlib==0.4.3
# via virtualenv
filelock==3.29.1
filelock==3.31.1; implementation_name != "graalpy"
filelock==3.29.7; implementation_name == "graalpy"
# via
# python-discovery
# virtualenv
@@ -57,9 +58,9 @@ pefile==2024.8.26
pip==26.1.2; implementation_name != "graalpy" or platform_system != "Windows"
pip==26.0.1; implementation_name == "graalpy" and platform_system == "Windows"
# via -r cibuildwheel/resources/constraints.in
pkgconf==2.5.1.post2
pkgconf==3.0.1.post0
# via -r cibuildwheel/resources/constraints.in
platformdirs==4.10.0
platformdirs==4.10.1
# via
# python-discovery
# requests-cache
@@ -72,17 +73,17 @@ pygments==2.20.0
# via rich
pyproject-hooks==1.2.0
# via build
python-discovery==1.4.0
python-discovery==1.4.4
# via virtualenv
requests==2.34.2
# via
# abi3audit
# requests-cache
requests-cache==1.3.2
requests-cache==1.3.3
# via abi3audit
rich==15.0.0
# via abi3audit
typing-extensions==4.15.0
typing-extensions==4.16.0
# via
# cattrs
# delocate
@@ -92,5 +93,5 @@ urllib3==2.7.0
# via
# requests
# requests-cache
virtualenv==21.4.2
virtualenv==21.6.1
# via -r cibuildwheel/resources/constraints.in
@@ -16,17 +16,17 @@ build==1.5.0
# via -r cibuildwheel/resources/constraints.in
cattrs==26.1.0
# via requests-cache
certifi==2026.5.20
certifi==2026.6.17
# via requests
charset-normalizer==3.4.7
charset-normalizer==3.4.9
# via requests
delocate==0.13.0
# via -r cibuildwheel/resources/constraints.in
delvewheel==1.13.0
# via -r cibuildwheel/resources/constraints.in
distlib==0.4.1
distlib==0.4.3
# via virtualenv
filelock==3.29.1
filelock==3.31.1
# via
# python-discovery
# virtualenv
@@ -56,9 +56,9 @@ pefile==2024.8.26
# delvewheel
pip==26.1.2
# via -r cibuildwheel/resources/constraints.in
pkgconf==2.5.1.post2
pkgconf==3.0.1.post0
# via -r cibuildwheel/resources/constraints.in
platformdirs==4.10.0
platformdirs==4.10.1
# via
# python-discovery
# requests-cache
@@ -71,17 +71,17 @@ pygments==2.20.0
# via rich
pyproject-hooks==1.2.0
# via build
python-discovery==1.4.0
python-discovery==1.4.4
# via virtualenv
requests==2.34.2
# via
# abi3audit
# requests-cache
requests-cache==1.3.2
requests-cache==1.3.3
# via abi3audit
rich==15.0.0
# via abi3audit
typing-extensions==4.15.0
typing-extensions==4.16.0
# via
# cattrs
# delocate
@@ -91,5 +91,5 @@ urllib3==2.7.0
# via
# requests
# requests-cache
virtualenv==21.4.2
virtualenv==21.6.1
# via -r cibuildwheel/resources/constraints.in
@@ -16,17 +16,17 @@ build==1.5.0
# via -r cibuildwheel/resources/constraints.in
cattrs==26.1.0
# via requests-cache
certifi==2026.5.20
certifi==2026.6.17
# via requests
charset-normalizer==3.4.7
charset-normalizer==3.4.9
# via requests
delocate==0.13.0
# via -r cibuildwheel/resources/constraints.in
delvewheel==1.13.0
# via -r cibuildwheel/resources/constraints.in
distlib==0.4.1
distlib==0.4.3
# via virtualenv
filelock==3.29.1
filelock==3.31.1
# via
# python-discovery
# virtualenv
@@ -56,9 +56,9 @@ pefile==2024.8.26
# delvewheel
pip==26.1.2
# via -r cibuildwheel/resources/constraints.in
pkgconf==2.5.1.post2
pkgconf==3.0.1.post0
# via -r cibuildwheel/resources/constraints.in
platformdirs==4.10.0
platformdirs==4.10.1
# via
# python-discovery
# requests-cache
@@ -71,17 +71,17 @@ pygments==2.20.0
# via rich
pyproject-hooks==1.2.0
# via build
python-discovery==1.4.0
python-discovery==1.4.4
# via virtualenv
requests==2.34.2
# via
# abi3audit
# requests-cache
requests-cache==1.3.2
requests-cache==1.3.3
# via abi3audit
rich==15.0.0
# via abi3audit
typing-extensions==4.15.0
typing-extensions==4.16.0
# via
# cattrs
# delocate
@@ -91,5 +91,5 @@ urllib3==2.7.0
# via
# requests
# requests-cache
virtualenv==21.4.2
virtualenv==21.6.1
# via -r cibuildwheel/resources/constraints.in
@@ -16,17 +16,17 @@ build==1.5.0
# via -r cibuildwheel/resources/constraints.in
cattrs==26.1.0
# via requests-cache
certifi==2026.5.20
certifi==2026.6.17
# via requests
charset-normalizer==3.4.7
charset-normalizer==3.4.9
# via requests
delocate==0.13.0
# via -r cibuildwheel/resources/constraints.in
delvewheel==1.13.0
# via -r cibuildwheel/resources/constraints.in
distlib==0.4.1
distlib==0.4.3
# via virtualenv
filelock==3.29.1
filelock==3.31.1
# via
# python-discovery
# virtualenv
@@ -56,9 +56,9 @@ pefile==2024.8.26
# delvewheel
pip==26.1.2
# via -r cibuildwheel/resources/constraints.in
pkgconf==2.5.1.post2
pkgconf==3.0.1.post0
# via -r cibuildwheel/resources/constraints.in
platformdirs==4.10.0
platformdirs==4.10.1
# via
# python-discovery
# requests-cache
@@ -71,17 +71,17 @@ pygments==2.20.0
# via rich
pyproject-hooks==1.2.0
# via build
python-discovery==1.4.0
python-discovery==1.4.4
# via virtualenv
requests==2.34.2
# via
# abi3audit
# requests-cache
requests-cache==1.3.2
requests-cache==1.3.3
# via abi3audit
rich==15.0.0
# via abi3audit
typing-extensions==4.15.0
typing-extensions==4.16.0
# via
# cattrs
# delocate
@@ -91,5 +91,5 @@ urllib3==2.7.0
# via
# requests
# requests-cache
virtualenv==21.4.2
virtualenv==21.6.1
# via -r cibuildwheel/resources/constraints.in
@@ -16,15 +16,15 @@ build==1.4.4
# via -r cibuildwheel/resources/constraints.in
cattrs==25.3.0
# via requests-cache
certifi==2026.5.20
certifi==2026.6.17
# via requests
charset-normalizer==3.4.7
charset-normalizer==3.4.9
# via requests
delocate==0.13.0
# via -r cibuildwheel/resources/constraints.in
delvewheel==1.13.0
# via -r cibuildwheel/resources/constraints.in
distlib==0.4.1
distlib==0.4.3
# via virtualenv
exceptiongroup==1.3.1
# via cattrs
@@ -77,7 +77,7 @@ pygments==2.20.0
# via rich
pyproject-hooks==1.2.0
# via build
python-discovery==1.4.0
python-discovery==1.4.4
# via virtualenv
requests==2.32.5
# via
@@ -89,7 +89,7 @@ rich==14.2.0
# via abi3audit
tomli==2.4.1
# via build
typing-extensions==4.15.0
typing-extensions==4.16.0
# via
# cattrs
# delocate
@@ -101,7 +101,7 @@ urllib3==2.6.3
# via
# requests
# requests-cache
virtualenv==21.4.2
virtualenv==21.6.1
# via -r cibuildwheel/resources/constraints.in
zipp==3.23.1
# via importlib-metadata
+10 -10
View File
@@ -16,17 +16,17 @@ build==1.5.0
# via -r cibuildwheel/resources/constraints.in
cattrs==26.1.0
# via requests-cache
certifi==2026.5.20
certifi==2026.6.17
# via requests
charset-normalizer==3.4.7
charset-normalizer==3.4.9
# via requests
delocate==0.13.0
# via -r cibuildwheel/resources/constraints.in
delvewheel==1.13.0
# via -r cibuildwheel/resources/constraints.in
distlib==0.4.1
distlib==0.4.3
# via virtualenv
filelock==3.29.1
filelock==3.31.1
# via
# python-discovery
# virtualenv
@@ -56,9 +56,9 @@ pefile==2024.8.26
# delvewheel
pip==26.1.2
# via -r cibuildwheel/resources/constraints.in
pkgconf==2.5.1.post2
pkgconf==3.0.1.post0
# via -r cibuildwheel/resources/constraints.in
platformdirs==4.10.0
platformdirs==4.10.1
# via
# python-discovery
# requests-cache
@@ -71,17 +71,17 @@ pygments==2.20.0
# via rich
pyproject-hooks==1.2.0
# via build
python-discovery==1.4.0
python-discovery==1.4.4
# via virtualenv
requests==2.34.2
# via
# abi3audit
# requests-cache
requests-cache==1.3.2
requests-cache==1.3.3
# via abi3audit
rich==15.0.0
# via abi3audit
typing-extensions==4.15.0
typing-extensions==4.16.0
# via
# cattrs
# delocate
@@ -91,5 +91,5 @@ urllib3==2.7.0
# via
# requests
# requests-cache
virtualenv==21.4.2
virtualenv==21.6.1
# via -r cibuildwheel/resources/constraints.in
+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"
+2 -2
View File
@@ -1,4 +1,4 @@
url = "https://nodejs.org/dist/"
v24 = "v24.16.0"
v22 = "v22.22.3"
v24 = "v24.18.0"
v22 = "v22.23.1"
v20 = "v20.20.2"
+34 -34
View File
@@ -1,54 +1,54 @@
[x86_64]
manylinux2014 = quay.io/pypa/manylinux2014_x86_64:2026.06.04-1
manylinux_2_28 = quay.io/pypa/manylinux_2_28_x86_64:2026.06.04-1
manylinux_2_34 = quay.io/pypa/manylinux_2_34_x86_64:2026.06.04-1
musllinux_1_2 = quay.io/pypa/musllinux_1_2_x86_64:2026.06.04-1
manylinux2014 = quay.io/pypa/manylinux2014_x86_64@sha256:0d25b049964b2549b83384036abdff06789a8c0b1e9ff003ec80f0d531f79e50 # 2026.07.19-1
manylinux_2_28 = quay.io/pypa/manylinux_2_28_x86_64@sha256:a61875a2f84cab7df8de222ff12cabc08ff86eb4ad402ac90ba7bdaed9600cca # 2026.07.19-1
manylinux_2_34 = quay.io/pypa/manylinux_2_34_x86_64@sha256:b6e1f264da7bf4e8db3e6a625ea1ecaf1e52290253df8588f71e4a9e50a85a17 # 2026.07.19-1
musllinux_1_2 = quay.io/pypa/musllinux_1_2_x86_64@sha256:fd0694e23637ae8d13433c58f647aba1aae5de4fd50350201307be453a8b2ff4 # 2026.07.19-1
[i686]
manylinux2014 = quay.io/pypa/manylinux2014_i686:2026.06.04-1
manylinux_2_28 = quay.io/pypa/manylinux_2_28_i686:2026.06.04-1
manylinux_2_34 = quay.io/pypa/manylinux_2_34_i686:2026.06.04-1
musllinux_1_2 = quay.io/pypa/musllinux_1_2_i686:2026.06.04-1
manylinux2014 = quay.io/pypa/manylinux2014_i686@sha256:9833ed0665e4bd2aceeda6f7ea81b69c75a941464b659edf62c98aa63526d1ba # 2026.07.19-1
manylinux_2_28 = quay.io/pypa/manylinux_2_28_i686@sha256:3a4de216ae551c1cbb8f93d4bbf9faebe7fb16a67afa3380b9a3ed2cc7b80fcf # 2026.07.19-1
manylinux_2_34 = quay.io/pypa/manylinux_2_34_i686@sha256:1fc55618156152a6570991a6ee81038fd507b48c41cbdede495931146b074869 # 2026.07.19-1
musllinux_1_2 = quay.io/pypa/musllinux_1_2_i686@sha256:a85be8fc5aa7fa291b8bee5647469d3d48dd26600cc7295553ff47ca60906ff2 # 2026.07.19-1
[aarch64]
manylinux2014 = quay.io/pypa/manylinux2014_aarch64:2026.06.04-1
manylinux_2_28 = quay.io/pypa/manylinux_2_28_aarch64:2026.06.04-1
manylinux_2_34 = quay.io/pypa/manylinux_2_34_aarch64:2026.06.04-1
musllinux_1_2 = quay.io/pypa/musllinux_1_2_aarch64:2026.06.04-1
manylinux2014 = quay.io/pypa/manylinux2014_aarch64@sha256:ae34a6ceeefe0b535c525b2e96f297311c8f1e01cfd8b9be5f647eece5b5c60b # 2026.07.19-1
manylinux_2_28 = quay.io/pypa/manylinux_2_28_aarch64@sha256:162c81dfd3efc710732a571717d3c916a6945ebf279e879ddee3243af96fe46f # 2026.07.19-1
manylinux_2_34 = quay.io/pypa/manylinux_2_34_aarch64@sha256:ae032906c631ad572a91bcc9545023d327822912957ae85a57c85181836662b6 # 2026.07.19-1
musllinux_1_2 = quay.io/pypa/musllinux_1_2_aarch64@sha256:e6ac3eca5f7d2cd5b8267b94b65d9a693e7ba06f5ba4c6643b1b0e6f9ae2cde3 # 2026.07.19-1
[ppc64le]
manylinux2014 = quay.io/pypa/manylinux2014_ppc64le:2026.06.04-1
manylinux_2_28 = quay.io/pypa/manylinux_2_28_ppc64le:2026.06.04-1
manylinux_2_34 = quay.io/pypa/manylinux_2_34_ppc64le:2026.06.04-1
musllinux_1_2 = quay.io/pypa/musllinux_1_2_ppc64le:2026.06.04-1
manylinux2014 = quay.io/pypa/manylinux2014_ppc64le@sha256:7e311ceece0fd3faa54a8539fb0b7906ee2ac068ecc39095e48ed1097312eaa0 # 2026.07.19-1
manylinux_2_28 = quay.io/pypa/manylinux_2_28_ppc64le@sha256:32ec597dd87e27980750115a6c020cae54fd17386e18d86d695e35e6b3a3e0da # 2026.07.19-1
manylinux_2_34 = quay.io/pypa/manylinux_2_34_ppc64le@sha256:7f649dea6f31fbc4ca6d665e78e87a192b55e739c44ed9965129956be77c4e3d # 2026.07.19-1
musllinux_1_2 = quay.io/pypa/musllinux_1_2_ppc64le@sha256:d59ad21615cf5b83cc9046cf7c44d07e8ef1247333e8a72147d7c781008605c1 # 2026.07.19-1
[s390x]
manylinux2014 = quay.io/pypa/manylinux2014_s390x:2026.06.04-1
manylinux_2_28 = quay.io/pypa/manylinux_2_28_s390x:2026.06.04-1
manylinux_2_34 = quay.io/pypa/manylinux_2_34_s390x:2026.06.04-1
musllinux_1_2 = quay.io/pypa/musllinux_1_2_s390x:2026.06.04-1
manylinux2014 = quay.io/pypa/manylinux2014_s390x@sha256:e392818123219665c259c9a5e389cecd978918dc279fe8e3542864f1067b9cdb # 2026.07.19-1
manylinux_2_28 = quay.io/pypa/manylinux_2_28_s390x@sha256:9c4f347bc43b1b799a296f6801a8845af132fa6f919cc6237b889c8d2e16ae01 # 2026.07.19-1
manylinux_2_34 = quay.io/pypa/manylinux_2_34_s390x@sha256:37fe79ec71fed0eaa4d6edfb22244699877e3cd92a2ca4679bf9e4fd5f45c61d # 2026.07.19-1
musllinux_1_2 = quay.io/pypa/musllinux_1_2_s390x@sha256:e29d28c68a0bd4594fd41cb2435ca64388038b05c24e77b34f186ff90a2968c4 # 2026.07.19-1
[pypy_x86_64]
manylinux2014 = quay.io/pypa/manylinux2014_x86_64:2026.06.04-1
manylinux_2_28 = quay.io/pypa/manylinux_2_28_x86_64:2026.06.04-1
manylinux_2_34 = quay.io/pypa/manylinux_2_34_x86_64:2026.06.04-1
manylinux2014 = quay.io/pypa/manylinux2014_x86_64@sha256:0d25b049964b2549b83384036abdff06789a8c0b1e9ff003ec80f0d531f79e50 # 2026.07.19-1
manylinux_2_28 = quay.io/pypa/manylinux_2_28_x86_64@sha256:a61875a2f84cab7df8de222ff12cabc08ff86eb4ad402ac90ba7bdaed9600cca # 2026.07.19-1
manylinux_2_34 = quay.io/pypa/manylinux_2_34_x86_64@sha256:b6e1f264da7bf4e8db3e6a625ea1ecaf1e52290253df8588f71e4a9e50a85a17 # 2026.07.19-1
[pypy_i686]
manylinux2014 = quay.io/pypa/manylinux2014_i686:2026.06.04-1
manylinux_2_28 = quay.io/pypa/manylinux_2_28_i686:2026.06.04-1
manylinux_2_34 = quay.io/pypa/manylinux_2_34_i686:2026.06.04-1
manylinux2014 = quay.io/pypa/manylinux2014_i686@sha256:9833ed0665e4bd2aceeda6f7ea81b69c75a941464b659edf62c98aa63526d1ba # 2026.07.19-1
manylinux_2_28 = quay.io/pypa/manylinux_2_28_i686@sha256:3a4de216ae551c1cbb8f93d4bbf9faebe7fb16a67afa3380b9a3ed2cc7b80fcf # 2026.07.19-1
manylinux_2_34 = quay.io/pypa/manylinux_2_34_i686@sha256:1fc55618156152a6570991a6ee81038fd507b48c41cbdede495931146b074869 # 2026.07.19-1
[pypy_aarch64]
manylinux2014 = quay.io/pypa/manylinux2014_aarch64:2026.06.04-1
manylinux_2_28 = quay.io/pypa/manylinux_2_28_aarch64:2026.06.04-1
manylinux_2_34 = quay.io/pypa/manylinux_2_34_aarch64:2026.06.04-1
manylinux2014 = quay.io/pypa/manylinux2014_aarch64@sha256:ae34a6ceeefe0b535c525b2e96f297311c8f1e01cfd8b9be5f647eece5b5c60b # 2026.07.19-1
manylinux_2_28 = quay.io/pypa/manylinux_2_28_aarch64@sha256:162c81dfd3efc710732a571717d3c916a6945ebf279e879ddee3243af96fe46f # 2026.07.19-1
manylinux_2_34 = quay.io/pypa/manylinux_2_34_aarch64@sha256:ae032906c631ad572a91bcc9545023d327822912957ae85a57c85181836662b6 # 2026.07.19-1
[armv7l]
manylinux_2_31 = quay.io/pypa/manylinux_2_31_armv7l:2026.06.04-1
manylinux_2_35 = quay.io/pypa/manylinux_2_35_armv7l:2026.06.04-1
musllinux_1_2 = quay.io/pypa/musllinux_1_2_armv7l:2026.06.04-1
manylinux_2_31 = quay.io/pypa/manylinux_2_31_armv7l@sha256:a58ece566251a37b3b4cd17d7ef85d88df05f80670c7dfe80c1b1ffe4cd00bf8 # 2026.07.19-1
manylinux_2_35 = quay.io/pypa/manylinux_2_35_armv7l@sha256:c8dba9616907a810e2668afb88153bf47a84422933733dbf4ea7b561af5e8d1e # 2026.07.19-1
musllinux_1_2 = quay.io/pypa/musllinux_1_2_armv7l@sha256:d87cbc211df71486a6a90fb2bece474166f3e2dcf81bbfc4be07607fd64185b0 # 2026.07.19-1
[riscv64]
manylinux_2_39 = quay.io/pypa/manylinux_2_39_riscv64:2026.06.04-1
musllinux_1_2 = quay.io/pypa/musllinux_1_2_riscv64:2026.06.04-1
manylinux_2_39 = quay.io/pypa/manylinux_2_39_riscv64@sha256:04b3acc69a6cebb10ac1252587d202acce6445d91c783d3f92229bb2b2483069 # 2026.07.19-1
musllinux_1_2 = quay.io/pypa/musllinux_1_2_riscv64@sha256:2d36623ffc59785e124ecf2992456847baaf2f2e0a87e64d23f2f6a69e5a0357 # 2026.07.19-1
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1 +1 @@
default = { version = "21.4.2", url = "https://github.com/pypa/get-virtualenv/releases/download/21.4.2/virtualenv.pyz", sha256 = "b54c48b9c81468403626762747c5ee5939616f18f46c3c4bbebdbe0aa3a8ba1d" }
default = { version = "21.6.1", url = "https://github.com/pypa/get-virtualenv/releases/download/21.6.1/virtualenv.pyz", sha256 = "32490c6eea415c3a8f44ee112bbef07bc7b70a0aba706672240c6c66d4f7d9cc" }
+2
View File
@@ -1,5 +1,7 @@
from __future__ import annotations
__lazy_modules__ = {"cibuildwheel.util", "json"}
import json
from cibuildwheel.util import resources
+9 -7
View File
@@ -1,5 +1,7 @@
from __future__ import annotations
__lazy_modules__ = {"bracex", "fnmatch", "itertools", "packaging", "packaging.version"}
import dataclasses
import itertools
from enum import StrEnum
@@ -102,19 +104,19 @@ class BuildSelector:
return False
if EnableGroup.PyodideEoL not in self.enable and fnmatch(build_id, "cp312-pyodide_*"):
return False
# NOTE: Disable this when we don't have any Pyodide prereleases (e.g., 314.0.0a1+)
# NOTE: Re-enable this when we have a new Pyodide prerelease (e.g., 315.0.0a1+)
# When doing this, also:
# 1. update Pyodide tests in unit_test/build_selector_test.py and unit_test/options_test.py accordingly.
# 2. update Python versions for Pyodide identifiers in cibuildwheel/selector.py.
# 3. update constraints as necessary via bin/generate_pyodide_constraints.py and add/delete
# Pyodide constraints files in cibuildwheel/resources/constraints/ as necessary.
# When disabling, update the pattern to match the experimental Python version in case
# it is bumped to Python 3.15 (likely cp315-pyodide_* but could remain as 3.14 as well).
# When re-enabling, update the pattern to match the experimental Python version when
# it is bumped to Python 3.15 (likely cp315-pyodide_*).
# This depends on the CPython version being used in the Pyodide runtime at the time.
if EnableGroup.PyodidePrerelease not in self.enable and fnmatch(
build_id, "cp314-pyodide_*"
):
return False
# if EnableGroup.PyodidePrerelease not in self.enable and fnmatch(
# build_id, "cp315-pyodide_*"
# ):
# return False
should_build = selector_matches(self.build_config, build_id)
should_skip = selector_matches(self.skip_config, build_id)
+2
View File
@@ -1,5 +1,7 @@
from __future__ import annotations
__lazy_modules__ = {"cibuildwheel.errors", "shlex", "shutil", "subprocess"}
import os
import shlex
import shutil
+18 -1
View File
@@ -1,5 +1,19 @@
from __future__ import annotations
__lazy_modules__ = {
"certifi",
"cibuildwheel.errors",
"hashlib",
"shutil",
"ssl",
"tarfile",
"typing",
"urllib",
"urllib.request",
"zipfile",
}
import hashlib
import os
import shutil
@@ -85,7 +99,8 @@ def download(url: str, dest: Path, *, sha256: str | None = None) -> None:
time.sleep(3)
if sha256:
computed = hashlib.sha256(dest.read_bytes()).hexdigest()
with dest.open("rb") as f:
computed = hashlib.file_digest(f, "sha256").hexdigest()
if computed != sha256:
dest.unlink(missing_ok=True)
msg = f"SHA256 mismatch for {url}: expected {sha256!r}, got {computed!r}"
@@ -116,6 +131,8 @@ def extract_tar(tar_src: Path, dest: Path) -> None:
See: https://docs.python.org/3/library/tarfile.html#tarfile.tar_filter for filter details
"""
with tarfile.open(tar_src) as tar_:
# getattr shim needed while Python 3.11.0-3.11.3 are supported;
# once the minimum is 3.11.4+/3.12, replace with: tar_.extractall(dest, filter="tar")
tar_.extraction_filter = getattr(tarfile, "tar_filter", (lambda member, _: member))
tar_.extractall(dest)
+2
View File
@@ -1,5 +1,7 @@
from __future__ import annotations
__lazy_modules__ = {"collections", "itertools", "re", "shlex", "textwrap"}
import dataclasses
import itertools
import re
+8
View File
@@ -1,5 +1,13 @@
from __future__ import annotations
__lazy_modules__ = {
"cibuildwheel.util.cmd",
"cibuildwheel.util.helpers",
"packaging",
"packaging.utils",
"shlex",
}
import shlex
from dataclasses import dataclass, field
from pathlib import Path, PurePath
+14 -4
View File
@@ -1,5 +1,16 @@
from __future__ import annotations
__lazy_modules__ = {
"cibuildwheel.util.file",
"cibuildwheel.util.resources",
"filelock",
"fnmatch",
"hashlib",
"json",
"platform",
"subprocess",
}
import fnmatch
import functools
import hashlib
@@ -143,7 +154,8 @@ def _download_or_get_from_cache(
asset_cache_path = cache_dir / asset_filename
if asset_cache_path.is_file():
if sha256:
computed = hashlib.sha256(asset_cache_path.read_bytes()).hexdigest()
with asset_cache_path.open("rb") as f:
computed = hashlib.file_digest(f, "sha256").hexdigest()
if computed != sha256:
print(
f"Cached python_build_standalone SHA256 mismatch for {asset_cache_path}; redownloading."
@@ -218,9 +230,7 @@ def create_python_build_standalone_environment(
)
python_base_dir = temp_dir / "pbs"
if python_base_dir.exists():
msg = f"python-build-standalone directory already exists: {python_base_dir}"
raise PythonBuildStandaloneError(msg)
assert not python_base_dir.exists()
extract_tar(archive_path, python_base_dir)
return _find_python_executable(python_base_dir)
+6 -1
View File
@@ -1,6 +1,9 @@
from __future__ import annotations
__lazy_modules__ = {"tomllib"}
import functools
import os
import tomllib
from pathlib import Path
@@ -16,7 +19,9 @@ FREE_THREAD_ENABLE_314: Final[Path] = PATH / "free-threaded-enable-314.xml"
FREE_THREAD_ENABLE_315: Final[Path] = PATH / "free-threaded-enable-315.xml"
NODEJS: Final[Path] = PATH / "nodejs.toml"
DEFAULTS: Final[Path] = PATH / "defaults.toml"
PINNED_DOCKER_IMAGES: Final[Path] = PATH / "pinned_docker_images.cfg"
PINNED_DOCKER_IMAGES: Final[Path] = Path(
os.environ.get("CIBW_INTERNAL_PINNED_DOCKER_IMAGES", str(PATH / "pinned_docker_images.cfg"))
)
BUILD_PLATFORMS: Final[Path] = PATH / "build-platforms.toml"
CONSTRAINTS: Final[Path] = PATH / "constraints.txt"
VIRTUALENV: Final[Path] = PATH / "virtualenv.toml"
+28 -1
View File
@@ -1,5 +1,20 @@
from __future__ import annotations
__lazy_modules__ = {
"cibuildwheel.util",
"cibuildwheel.util.cmd",
"cibuildwheel.util.file",
"contextlib",
"filelock",
"packaging",
"packaging.markers",
"packaging.requirements",
"packaging.version",
"pathlib",
"shutil",
"tomllib",
}
import contextlib
import functools
import os
@@ -137,6 +152,10 @@ def virtualenv(
venv. Otherwise, pip is installed.
"""
# the unresolved path, so e.g. the python.org framework bin directory is
# used rather than the internal location of the real binary
base_python_bin_dir = None if _IS_WIN else python.parent
# virtualenv may fail if this is a symlink.
python = python.resolve()
@@ -174,7 +193,7 @@ def virtualenv(
python,
venv_path,
)
venv_env = activate_virtualenv(venv_path, env=env)
venv_env = activate_virtualenv(venv_path, env=env, base_python_bin_dir=base_python_bin_dir)
if not use_uv and pip_version == "embed":
call(
"python",
@@ -193,11 +212,19 @@ def virtualenv(
def activate_virtualenv(
venv_path: Path,
env: dict[str, str] | None = None,
base_python_bin_dir: Path | None = None,
) -> dict[str, str]:
"""
Return a copy of the environment with the virtualenv at `venv_path` activated.
If given, `base_python_bin_dir` is placed on PATH right after the venv, so
that scripts installed alongside the base interpreter that aren't copied
into the venv (such as python3-config, see #2021) resolve to the matching
interpreter.
"""
paths = [str(venv_path), str(venv_path / "Scripts")] if _IS_WIN else [str(venv_path / "bin")]
if base_python_bin_dir is not None:
paths.append(str(base_python_bin_dir))
venv_env = os.environ.copy() if env is None else env.copy()
venv_env["PATH"] = os.pathsep.join([*paths, venv_env["PATH"]])
venv_env["VIRTUAL_ENV"] = str(venv_path)
+66 -34
View File
@@ -5,15 +5,62 @@ ref: changelog
# Changelog
### v4.0.0rc2
### v4.1.1
_5 June 2026_
_24 July 2026_
- ✨ Adds `pyodide-build` as a separate [`build-frontend`](https://cibuildwheel.pypa.io/en/stable/options/#build-frontend), now the default frontend for Pyodide, with verbosity flags handling. Any other frontend is ignored with a warning on Pyodide. (#2609, #2945)
- 🔐 Uses digests instead of tags for pinned container images, strengthening supply-chain security. The human-readable tags remain as comments in `pinned_docker_images.cfg`. (#2915)
- 🐛 Fixes platform-specific [`test-runtime`](https://cibuildwheel.pypa.io/en/stable/options/#test-runtime) environment variables (e.g. `CIBW_TEST_RUNTIME_ANDROID`) not being honored (#2941)
- 🐛 Fixes quoting of [`test-requires`](https://cibuildwheel.pypa.io/en/stable/options/#test-requires) and [`audit-requires`](https://cibuildwheel.pypa.io/en/stable/options/#audit-requires) so PEP 508 specifiers containing spaces work (#2913)
- 🐛 Makes [`archs`](https://cibuildwheel.pypa.io/en/stable/options/#archs) parsing case-insensitive and platform-aware, so e.g. `arm64` works on Windows (#2920)
- 🐛 Uses an absolute path for the `{project}` placeholder in [`config-settings`](https://cibuildwheel.pypa.io/en/stable/options/#config-settings) (#2934)
- 🐛 Validates the [`pyodide-version`](https://cibuildwheel.pypa.io/en/stable/options/#pyodide-version) option against the build identifier with a clear error (#2925)
- 🐛 Fixes PyPy installs on macOS after PyPy switched its downloads from `.tar.bz2` to `.tar.gz` (#2939)
- 🐛 Makes a matching `python3-config` available in the build and test venvs on macOS (#2922)
- 🛠 Updates dependencies and container pins (#2917, #2935, #2939)
- 🛠 Updates Android tests to current Python versions and the new test repository URL (#2933)
- 🛠 Drops the `orjson` dependency, no longer used by mypy 2+ (#2923)
- 📚 Builds the docs with properdocs, a MkDocs fork (#2946)
- 📚 Adds the missing `cp314-pyodide_wasm32` entry to the build identifier table (#2947)
- 📚 Removes outdated notes about the `pip wheel` build frontend and ClearLinux (#2926)
- 💼 Adds a "CI: PyPy EoL" PR label to run PyPy EoL tests on PRs (#2930)
- 💼 Updates CI action pins and pre-commit hooks (#2914, #2932, #2938, #2940, #2942, #2943)
### v4.1.0
_12 June 2026_
- ✨ Updates Pyodide to the final 314.0.0 release, so Pyodide 3.14 wheels now build by default without the `pyodide-prerelease` [`enable`](https://cibuildwheel.pypa.io/en/stable/options/#enable) flag. (#2906)
- 🐛 Raises clear errors when a build produces no wheel, instead of failing later with a confusing message (#2909)
- 🛠 Speeds up CLI startup through lazy imports on Python 3.15 (#2797)
- 📚 Adds an FAQ section on caching cibuildwheel's downloaded tools with `CIBW_CACHE_PATH` (#2842)
- 📚 Documentation improvements: clarifies which shell is used for command options, clarifies environment variable precedence, and fixes a dead Pyodide env info link (#2904, #2905, #2911)
### v4.0.0
_7 June 2026_
See @henryiii's [release post](https://iscinumpy.dev/post/cibuildwheel-4-0-0/) for more info on new features!
- 🌟 Adds wheel auditing with `abi3audit` as a default after the repair step, with new [`audit-requires`](https://cibuildwheel.pypa.io/en/stable/options/#audit-requires) and [`audit-command`](https://cibuildwheel.pypa.io/en/stable/options/#audit-command) options (#2805)
- 🌟 Adds `pyemscripten` platform tag support (PEP 783), updates Pyodide to 314.0.0a2, and adds a `pyodide-eol` [`enable`](https://cibuildwheel.pypa.io/en/stable/options/#enable) flag for building end-of-life Pyodide versions (#2812, #2848)
- 🌟 Sets up `delvewheel` as the default [`repair-wheel-command`](https://cibuildwheel.pypa.io/en/stable/options/#repair-wheel-command) for Windows, so extension module DLLs are now bundled automatically. Skip by setting it to empty if not needed. (#2831)
- ✨ Adds CPython 3.15 support, under the [`enable` option](https://cibuildwheel.pypa.io/en/stable/options/#enable) `cpython-prerelease`. This version of cibuildwheel uses 3.15.0b2. (#2833, #2850)
_While CPython is in beta, the ABI can change, so your wheels might not be compatible with the final release. For this reason, we don't recommend distributing wheels until RC1, at which point 3.15 will be available in cibuildwheel without the flag._
- ✨ Adds CPython 3.15 support for iOS and Android (#2857, #2858)
- ✨ Adds Android improvements for building NumPy and related packages, including auditwheel support, pkg-config and Fortran configuration, and the [`xbuild-files`](https://cibuildwheel.pypa.io/en/stable/options/#xbuild-files) option (#2695)
- ✨ Adds `CIBUILDWHEEL_BUILD_IDENTIFIER` environment variable set to the current build identifier (e.g. `cp311-manylinux_x86_64`) during per-build steps (#2872)
- ✨ Adds `{project}` and `{package}` placeholders to [`config-settings`](https://cibuildwheel.pypa.io/en/stable/options/#config-settings) (#2827)
- ⚠️ Drops support for Python 3.8 (#2686)
- ⚠️ Removes the experimental CPython 3.13 free-threading builds and the `cpython-freethreading` [`enable`](https://cibuildwheel.pypa.io/en/stable/options/#enable) option. CPython 3.14+ free-threading support remains available without the enable flag. (#2684)
- ⚠️ Drops support for Cirrus CI, which is shutting down June 1, 2026 (#2817)
- ⚠️ Drops GraalPy 3.11 (gp311) support, as agreed in #2741, and removes GraalPy 24-only workarounds (#2895)
- 🔐 Adds SHA256 verification for direct downloads of Python interpreters, virtualenv, and python-build-standalone assets (#2873)
- 🔐 Adds tarfile extraction filter for safe archive extraction (#2856)
- 🐛 Fixes `UV_PYTHON` not being set for [`before-build`](https://cibuildwheel.pypa.io/en/stable/options/#before-build) on Linux when using `uv` as the [`build-frontend`](https://cibuildwheel.pypa.io/en/stable/options/#build-frontend) (#2830)
- 🐛 Fixes detection of musl libc when downloading python-build-standalone, which previously always selected the gnu asset on musl hosts like Alpine (#2889)
- 🐛 Fixes [`config-settings`](https://cibuildwheel.pypa.io/en/stable/options/#config-settings) expansion when `{project}` or `{package}` contains spaces or backslashes (#2886)
- 🐛 Prevents deadlock when `linux32` fails and forwards platform args to the sanity check (#2880, #2888)
@@ -21,45 +68,30 @@ _5 June 2026_
- 🐛 Removes potential partial cache-population in case of error (#2892)
- 🐛 Raises a clear error when `ANDROID_API_LEVEL` is not an integer (#2891)
- 🐛 Replaces assert with proper exception in python-build-standalone (#2859)
- 🛠 Updates dependencies and container pins (#2893, #2882, #2874, #2868, #2862, #2884)
- 🛠 Minor fixups across error messages, OCI container, and options (#2860)
- 📚 Updates documentation for delvewheel as the default Windows [`repair-wheel-command`](https://cibuildwheel.pypa.io/en/stable/options/#repair-wheel-command), including the build diagram, schema defaults, and legal note (#2877, #2853, #2891)
- 📚 Adds intersphinx support for external documentation linking (#2871)
- 📚 Removes outdated numpy info (#2855)
- 💼 Improves Azure test reliability with retries and caching (#2890)
- 💼 Fixes Windows GitLab CI test running (#2870)
- 💼 Updates CI action pins (#2867)
- 💼 Adds agent and copilot setup files (#2861)
- 💼 Uses `if TYPE_CHECKING:` blocks (#2866, #2864)
- 🧪 Adds unit tests for `OCIContainer._get_platform_args` (#2878)
### v4.0.0rc1
_14 May 2026_
- 🌟 Adds wheel auditing with `abi3audit` as a default after the repair step, with new [`audit-requires`](https://cibuildwheel.pypa.io/en/stable/options/#audit-requires) and [`audit-command`](https://cibuildwheel.pypa.io/en/stable/options/#audit-command) options (#2805)
- 🌟 Adds `pyemscripten` platform tag support (PEP 783), updates Pyodide to 314.0.0a1, and adds a `pyodide-eol` [`enable`](https://cibuildwheel.pypa.io/en/stable/options/#enable) flag for building end-of-life Pyodide versions (#2812, #2848)
- 🌟 Sets up `delvewheel` as the default [`repair-wheel-command`](https://cibuildwheel.pypa.io/en/stable/options/#repair-wheel-command) for Windows, so extension module DLLs are now bundled automatically. Skip by setting it to empty if not needed. (#2831)
- ✨ Adds CPython 3.15 support, under the [`enable` option](https://cibuildwheel.pypa.io/en/stable/options/#enable) `cpython-prerelease`. This version of cibuildwheel uses 3.15.0b1. (#2833, #2850)
_While CPython is in beta, the ABI can change, so your wheels might not be compatible with the final release. For this reason, we don't recommend distributing wheels until RC1, at which point 3.14 will be available in cibuildwheel without the flag._ (#2390)
- ✨ Adds `{project}` and `{package}` placeholders to [`config-settings`](https://cibuildwheel.pypa.io/en/stable/options/#config-settings) (#2827)
- ⚠️ Drops support for Python 3.8 (#2686)
- ⚠️ Removes the experimental CPython 3.13 free-threading builds and the `cpython-freethreading` [`enable`](https://cibuildwheel.pypa.io/en/stable/options/#enable) option. CPython 3.14+ free-threading support remains available without the enable flag. (#2684)
- ⚠️ Drops support for Cirrus CI, which is shutting down June 1, 2026 (#2817)
- 🐛 Fixes `UV_PYTHON` not being set for [`before-build`](https://cibuildwheel.pypa.io/en/stable/options/#before-build) on Linux when using `uv` as the [`build-frontend`](https://cibuildwheel.pypa.io/en/stable/options/#build-frontend) (#2830)
- 🐛 Uses ConfigurationError when `package_dir` is outside cwd instead of a generic Exception (#2898)
- 🛠 Updates dependencies and container pins (#2893, #2882, #2874, #2868, #2862, #2884, #2845, #2837, #2818, #2810, #2838, #2813)
- 🛠 Updates Android to Python 3.13.13 and 3.14.4 (#2821)
- 🛠 Applies Pyodide-specific patches to the Emscripten toolchain installation (#2800)
- 🛠 Updates dependencies and container pins (#2845, #2837, #2821, #2818, #2810, #2838, #2813)
- 🛠 Uses `python -V -V` for Windows build diagnostics (#2832)
- 🛠 Simplifies pinned container image lookup (#2897)
- 🛠 Minor fixups across error messages, OCI container, and options (#2860)
- 💼 Adds PEP 723 metadata for `bin/` scripts and drops the `bin` dependency group (#2819)
- 💼 Improves Azure test reliability with retries and caching (#2890)
- 💼 Fixes Windows GitLab CI test running (#2870)
- 💼 Updates CI action pins and dev dependencies (#2902, #2867, #2851, #2843, #2826, #2823, #2820, #2807)
- 💼 Adds agent and copilot setup files (#2861)
- 💼 Uses `if TYPE_CHECKING:` blocks (#2866, #2864)
- 🧪 Fixes Android tests using the `uv` frontend (#2809)
- 🧪 Fixes the update-dependencies workflow to use `uv` to run `nox` (#2808)
- 🧪 Adds unit tests for `OCIContainer._get_platform_args` (#2878)
- 📚 Updates documentation for delvewheel as the default Windows [`repair-wheel-command`](https://cibuildwheel.pypa.io/en/stable/options/#repair-wheel-command), including the build diagram, schema defaults, and legal note (#2877, #2853, #2891)
- 📚 Documents platform-specific [`before-build`](https://cibuildwheel.pypa.io/en/stable/options/#before-build) configuration (#2834)
- 📚 Updates the "How it works" diagram with details of Android, iOS, and Pyodide builds (#2816)
- 📚 Adds Pyodide icon and regenerates working examples data for Android, iOS, and Pyodide (#2815, #2811)
- 📚 Adds intersphinx support for external documentation linking (#2871)
- 📚 Adds instructions for building CUDA wheels and fixes manylinux container references in FAQ (#2896, #2900)
- 📚 Links back to source in docs (#2806)
- 💼 Adds PEP 723 metadata for `bin/` scripts and drops the `bin` dependency group (#2819)
- 💼 Updates CI action pins and dev dependencies (#2851, #2843, #2826, #2823, #2820, #2807)
- 🧪 Fixes Android tests using the `uv` frontend (#2809)
- 🧪 Fixes the update-dependencies workflow to use `uv` to run `nox` (#2808)
- 📚 Removes outdated numpy info (#2855)
### v3.4.1
+7 -5
View File
@@ -64,10 +64,12 @@ You can configure cibuildwheel with a config file, such as `pyproject.toml`.
Options have the same names as the environment variable overrides, but are
placed in `[tool.cibuildwheel]` and are lower case, with dashes, following
common [TOML](https://toml.io) practice. Anything placed in subsections
named after a platform will only affect those platforms. Lists can be used
instead of strings for items that are naturally a list. Multiline strings also
work just like in the environment variables. Environment variables will take
precedence if defined.
named after a platform will only affect those platforms. Platform-specific
values replace the corresponding global value for that platform; table options
are not merged key by key. Lists can be used instead of strings for items that
are naturally a list. Multiline strings also work just like in the environment
variables. Environment variable overrides, such as `CIBW_TEST_COMMAND` and
`CIBW_TEST_COMMAND_LINUX`, will take precedence if defined.
The example above using environment variables could have been written like this:
@@ -178,7 +180,7 @@ on Python 3.11, and will have `environment = {FOO="BAZ", "PYTHON"="MONTY", "HAM"
In the TOML configuration, you can choose how tables and lists are inherited.
By default, all values are overridden completely (`"none"`) but sometimes you'd
rather `"append"` or `"prepend"` to an existing list or table. You can do this
with the `inherit` table in overrides. For example, if you want to add an environment
with the `inherit` table in overrides. For example, if you want to add an environment
variable for CPython 3.11, without `inherit` you'd have to repeat all the
original environment variables in the override. With `inherit`, it's just:
+102 -3
View File
@@ -102,6 +102,51 @@ Meson generally works well with cibuildwheel, but there are a few things to be a
- If you need to build 32-bit Windows wheels, you need to activate a 32-bit compiler toolchain before starting cibuildwheel. Many users use [ilammy/msvc-dev-cmd](https://github.com/ilammy/msvc-dev-cmd) for this purpose.
### Caching cibuildwheel's downloaded tools {: #caching}
To speed up builds, cibuildwheel caches the tools it downloads — CPython/PyPy installers, [`virtualenv`][virtualenv], [python-build-standalone][pbs] archives used on Android/iOS, etc. The active cache folder is printed in the preamble of every run:
```
Cache folder: /Users/Matt/Library/Caches/cibuildwheel
```
By default it lives under the OS user-cache directory:
| Platform | Default cache folder |
| ------------ | --------------------------------------------- |
| Linux | `~/.cache/cibuildwheel` |
| macOS / iOS | `~/Library/Caches/cibuildwheel` |
| Windows | `%LOCALAPPDATA%\pypa\cibuildwheel\Cache` |
Set the `CIBW_CACHE_PATH` environment variable to point cibuildwheel at a different folder. On CI you'll typically want a workflow-defined path so that the runner's cache action can persist it between runs.
#### Persisting the cache on GitHub Actions
```yaml
- uses: actions/cache@v5
with:
path: ${{ runner.temp }}/cibw-cache
key: cibw-${{ runner.os }}-${{ hashFiles('pyproject.toml') }}
restore-keys: |
cibw-${{ runner.os }}-
- uses: pypa/cibuildwheel@v3.4.1
env:
CIBW_CACHE_PATH: ${{ runner.temp }}/cibw-cache
```
The `restore-keys` fallback lets a slightly stale cache still be reused if `pyproject.toml` changes. Adjust the cache key to whatever set of inputs determines what cibuildwheel will download (e.g. include a hash of `pyproject.toml`'s `[tool.cibuildwheel]` section, or pin on a Python build-tools version).
For platform-specific notes (e.g. caching the official python.org installers on macOS, or NuGet CPython downloads on Windows), see the [platforms documentation](platforms.md).
If the cache becomes stale or corrupt, run `cibuildwheel --clean-cache` (or simply delete the folder) before re-running.
!!! warning "Cache poisoning security risk"
Use of caching in a release pipeline means the cache folder is now a possible security risk - an attacker could [poison the cache](https://hivesecurity.gitlab.io/blog/github-actions-cache-poisoning-supply-chain/) with executables they have compromised. If you use this for release builds, consider who has access to modify the cache. Specifically be careful if your repo has any workflows using `pull_request_target`, even if they appear unrelated.
[virtualenv]: https://virtualenv.pypa.io/
[pbs]: https://gregoryszorc.com/docs/python-build-standalone/main/
### Automatic updates using Dependabot {: #automatic-updates}
Selecting a moving target (like the latest release) is generally a bad idea in CI. If something breaks, you can't tell whether it was your code or an upstream update that caused the breakage, and in a worst-case scenario, it could occur during a release.
@@ -113,7 +158,7 @@ There are two suggested methods for keeping cibuildwheel up to date that instead
If you use GitHub Actions for builds, you can use cibuildwheel as an action:
```yaml
uses: pypa/cibuildwheel@v4.0.0rc2
uses: pypa/cibuildwheel@v4.1.1
```
This is a composite step that just runs cibuildwheel using pipx. You can set command-line options as `with:` parameters, and use `env:` as normal.
@@ -138,7 +183,7 @@ The second option, and the only one that supports other CI systems, is using a `
```bash
# requirements-cibw.txt
cibuildwheel==4.0.0rc2
cibuildwheel==4.1.1
```
Then your install step would have `python -m pip install -r requirements-cibw.txt` in it. Your `.github/dependabot.yml` file could look like this:
@@ -221,6 +266,60 @@ Your build might need some compiler flags to be set through environment variable
Consider incorporating these into your package, for example, in `setup.py` using [`extra_compile_args` or
`extra_link_args`](https://setuptools.pypa.io/en/latest/userguide/ext_modules.html#setuptools.Extension).
### Building wheels with CUDA on Linux
On Linux, you can build binary wheels with CUDA to take advantage of NVIDIA GPUs for hardware acceleration.
Specify the custom Docker containers with CUDA Toolkit as follows:
```yaml
CIBW_MANYLINUX_X86_64_IMAGE: >-
quay.io/manylinux_cuda/manylinux_2_28_x86_64_cuda13_1:latest
CIBW_MANYLINUX_AARCH64_IMAGE: >-
quay.io/manylinux_cuda/manylinux_2_28_aarch64_cuda13_1:latest
```
Currently, we support the following CUDA manylinux containers:
* `quay.io/manylinux_cuda/manylinux_2_28_x86_64_cuda12_9:latest`
* `quay.io/manylinux_cuda/manylinux_2_28_aarch64_cuda12_9:latest`
* `quay.io/manylinux_cuda/manylinux_2_28_x86_64_cuda13_1:latest`
* `quay.io/manylinux_cuda/manylinux_2_28_aarch64_cuda13_1:latest`
* `quay.io/manylinux_cuda/manylinux_2_34_x86_64_cuda12_9:latest`
* `quay.io/manylinux_cuda/manylinux_2_34_aarch64_cuda12_9:latest`
* `quay.io/manylinux_cuda/manylinux_2_34_x86_64_cuda13_1:latest`
* `quay.io/manylinux_cuda/manylinux_2_34_aarch64_cuda13_1:latest`
A typical GitHub Actions workflow will look like this:
```yaml
jobs:
build-wheels:
name: Build wheels
runs-on: ${{ matrix.target.runner }}
strategy:
matrix:
manylinux-base: [manylinux_2_28, manylinux_2_34]
cuda-version: [12_9, 13_1]
target:
- arch: x86_64
runner: ubuntu-24.04
- arch: aarch64
runner: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- name: Build wheels
uses: pypa/cibuildwheel@v3
env:
CIBW_MANYLINUX_X86_64_IMAGE: >-
quay.io/manylinux_cuda/${{ matrix.manylinux-base }}_x86_64_cuda${{ matrix.cuda-version }}:latest
CIBW_MANYLINUX_AARCH64_IMAGE: >-
quay.io/manylinux_cuda/${{ matrix.manylinux-base }}_aarch64_cuda${{ matrix.cuda-version }}:latest
CIBW_BUILD: cp312-manylinux_${{ matrix.target.arch }}
```
## Troubleshooting
If your wheel didn't compile, you might have a mistake in your config.
@@ -303,7 +402,7 @@ Solutions to this vary, but the simplest is to use pipx:
# most runners have pipx preinstalled, but in case you don't
python3 -m pip install pipx
pipx run cibuildwheel==4.0.0rc2 --output-dir wheelhouse
pipx run cibuildwheel==4.1.1 --output-dir wheelhouse
pipx run twine upload wheelhouse/*.whl
```
+41 -13
View File
@@ -64,12 +64,11 @@ When setting the options, you can use shell-style globbing syntax, as per [fnmat
| Python 3.11 | cp311-macosx_x86_64<br/>cp311-macosx_universal2<br/>cp311-macosx_arm64 | cp311-win_amd64<br/>cp311-win32<br/>cp311-win_arm64 | cp311-manylinux_x86_64<br/>cp311-manylinux_i686<br/>cp311-musllinux_x86_64<br/>cp311-musllinux_i686 | cp311-manylinux_aarch64<br/>cp311-manylinux_ppc64le<br/>cp311-manylinux_s390x<br/>cp311-manylinux_armv7l<br/>cp311-manylinux_riscv64<br/>cp311-musllinux_aarch64<br/>cp311-musllinux_ppc64le<br/>cp311-musllinux_s390x<br/>cp311-musllinux_armv7l<br/>cp311-musllinux_riscv64 | | | |
| Python 3.12 | cp312-macosx_x86_64<br/>cp312-macosx_universal2<br/>cp312-macosx_arm64 | cp312-win_amd64<br/>cp312-win32<br/>cp312-win_arm64 | cp312-manylinux_x86_64<br/>cp312-manylinux_i686<br/>cp312-musllinux_x86_64<br/>cp312-musllinux_i686 | cp312-manylinux_aarch64<br/>cp312-manylinux_ppc64le<br/>cp312-manylinux_s390x<br/>cp312-manylinux_armv7l<br/>cp312-manylinux_riscv64<br/>cp312-musllinux_aarch64<br/>cp312-musllinux_ppc64le<br/>cp312-musllinux_s390x<br/>cp312-musllinux_armv7l<br/>cp312-musllinux_riscv64 | | | cp312-pyodide_wasm32 |
| Python 3.13 | cp313-macosx_x86_64<br/>cp313-macosx_universal2<br/>cp313-macosx_arm64 | cp313-win_amd64<br/>cp313-win32<br/>cp313-win_arm64 | cp313-manylinux_x86_64<br/>cp313-manylinux_i686<br/>cp313-musllinux_x86_64<br/>cp313-musllinux_i686 | cp313-manylinux_aarch64<br/>cp313-manylinux_ppc64le<br/>cp313-manylinux_s390x<br/>cp313-manylinux_armv7l<br/>cp313-manylinux_riscv64<br/>cp313-musllinux_aarch64<br/>cp313-musllinux_ppc64le<br/>cp313-musllinux_s390x<br/>cp313-musllinux_armv7l<br/>cp313-musllinux_riscv64 | cp313-android_arm64_v8a<br/>cp313-android_x86_64 | cp313-ios_arm64_iphoneos<br/>cp313-ios_arm64_iphonesimulator<br/>cp313-ios_x86_64_iphonesimulator | cp313-pyodide_wasm32 |
| Python 3.14 | cp314-macosx_x86_64<br/>cp314-macosx_universal2<br/>cp314-macosx_arm64<br><br>cp314t-macosx_x86_64<br/>cp314t-macosx_universal2<br/>cp314t-macosx_arm64 | cp314-win_amd64<br/>cp314-win32<br/>cp314-win_arm64<br><br>cp314t-win_amd64<br/>cp314t-win32<br/>cp314t-win_arm64 | cp314-manylinux_x86_64<br/>cp314-manylinux_i686<br/>cp314-musllinux_x86_64<br/>cp314-musllinux_i686<br><br>cp314t-manylinux_x86_64<br/>cp314t-manylinux_i686<br/>cp314t-musllinux_x86_64<br/>cp314t-musllinux_i686 | cp314-manylinux_aarch64<br/>cp314-manylinux_ppc64le<br/>cp314-manylinux_s390x<br/>cp314-manylinux_armv7l<br/>cp314-manylinux_riscv64<br/>cp314-musllinux_aarch64<br/>cp314-musllinux_ppc64le<br/>cp314-musllinux_s390x<br/>cp314-musllinux_armv7l<br/>cp314-musllinux_riscv64<br><br>cp314t-manylinux_aarch64<br/>cp314t-manylinux_ppc64le<br/>cp314t-manylinux_s390x<br/>cp314t-manylinux_armv7l<br/>cp314t-manylinux_riscv64<br/>cp314t-musllinux_aarch64<br/>cp314t-musllinux_ppc64le<br/>cp314t-musllinux_s390x<br/>cp314t-musllinux_armv7l<br/>cp314t-musllinux_riscv64 | cp314-android_arm64_v8a<br/>cp314-android_x86_64 | cp314-ios_arm64_iphoneos<br/>cp314-ios_arm64_iphonesimulator<br/>cp314-ios_x86_64_iphonesimulator | |
| Python 3.14 | cp314-macosx_x86_64<br/>cp314-macosx_universal2<br/>cp314-macosx_arm64<br><br>cp314t-macosx_x86_64<br/>cp314t-macosx_universal2<br/>cp314t-macosx_arm64 | cp314-win_amd64<br/>cp314-win32<br/>cp314-win_arm64<br><br>cp314t-win_amd64<br/>cp314t-win32<br/>cp314t-win_arm64 | cp314-manylinux_x86_64<br/>cp314-manylinux_i686<br/>cp314-musllinux_x86_64<br/>cp314-musllinux_i686<br><br>cp314t-manylinux_x86_64<br/>cp314t-manylinux_i686<br/>cp314t-musllinux_x86_64<br/>cp314t-musllinux_i686 | cp314-manylinux_aarch64<br/>cp314-manylinux_ppc64le<br/>cp314-manylinux_s390x<br/>cp314-manylinux_armv7l<br/>cp314-manylinux_riscv64<br/>cp314-musllinux_aarch64<br/>cp314-musllinux_ppc64le<br/>cp314-musllinux_s390x<br/>cp314-musllinux_armv7l<br/>cp314-musllinux_riscv64<br><br>cp314t-manylinux_aarch64<br/>cp314t-manylinux_ppc64le<br/>cp314t-manylinux_s390x<br/>cp314t-manylinux_armv7l<br/>cp314t-manylinux_riscv64<br/>cp314t-musllinux_aarch64<br/>cp314t-musllinux_ppc64le<br/>cp314t-musllinux_s390x<br/>cp314t-musllinux_armv7l<br/>cp314t-musllinux_riscv64 | cp314-android_arm64_v8a<br/>cp314-android_x86_64 | cp314-ios_arm64_iphoneos<br/>cp314-ios_arm64_iphonesimulator<br/>cp314-ios_x86_64_iphonesimulator | cp314-pyodide_wasm32 |
| Python 3.15 | cp315-macosx_x86_64<br/>cp315-macosx_universal2<br/>cp315-macosx_arm64<br><br>cp315t-macosx_x86_64<br/>cp315t-macosx_universal2<br/>cp315t-macosx_arm64 | cp315-win_amd64<br/>cp315-win32<br/>cp315-win_arm64<br><br>cp315t-win_amd64<br/>cp315t-win32<br/>cp315t-win_arm64 | cp315-manylinux_x86_64<br/>cp315-manylinux_i686<br/>cp315-musllinux_x86_64<br/>cp315-musllinux_i686<br><br>cp315t-manylinux_x86_64<br/>cp315t-manylinux_i686<br/>cp315t-musllinux_x86_64<br/>cp315t-musllinux_i686 | cp315-manylinux_aarch64<br/>cp315-manylinux_ppc64le<br/>cp315-manylinux_s390x<br/>cp315-manylinux_armv7l<br/>cp315-manylinux_riscv64<br/>cp315-musllinux_aarch64<br/>cp315-musllinux_ppc64le<br/>cp315-musllinux_s390x<br/>cp315-musllinux_armv7l<br/>cp315-musllinux_riscv64<br><br>cp315t-manylinux_aarch64<br/>cp315t-manylinux_ppc64le<br/>cp315t-manylinux_s390x<br/>cp315t-manylinux_armv7l<br/>cp315t-manylinux_riscv64<br/>cp315t-musllinux_aarch64<br/>cp315t-musllinux_ppc64le<br/>cp315t-musllinux_s390x<br/>cp315t-musllinux_armv7l<br/>cp315t-musllinux_riscv64 | cp315-android_arm64_v8a<br/>cp315-android_x86_64 | cp315-ios_arm64_iphoneos<br/>cp315-ios_arm64_iphonesimulator<br/>cp315-ios_x86_64_iphonesimulator | |
| PyPy3.9 v7.3 | pp39-macosx_x86_64<br/>pp39-macosx_arm64 | pp39-win_amd64 | pp39-manylinux_x86_64<br/>pp39-manylinux_i686 | pp39-manylinux_aarch64 | | | |
| PyPy3.10 v7.3 | pp310-macosx_x86_64<br/>pp310-macosx_arm64 | pp310-win_amd64 | pp310-manylinux_x86_64<br/>pp310-manylinux_i686 | pp310-manylinux_aarch64 | | | |
| PyPy3.11 v7.3 | pp311-macosx_x86_64<br/>pp311-macosx_arm64 | pp311-win_amd64 | pp311-manylinux_x86_64<br/>pp311-manylinux_i686 | pp311-manylinux_aarch64 | | | |
| GraalPy 3.11 v24.2 | gp311_242-macosx_x86_64<br/>gp311_242-macosx_arm64 | gp311_242-win_amd64 | gp311_242-manylinux_x86_64 | gp311_242-manylinux_aarch64 | | |
| GraalPy 3.12 v25.0 | gp312_250-macosx_x86_64<br/>gp312_250-macosx_arm64 | gp312_250-win_amd64 | gp312_250-manylinux_x86_64 | gp312_250-manylinux_aarch64 | | |
The list of supported and currently selected build identifiers can also be retrieved by passing the `--print-build-identifiers` flag to cibuildwheel.
@@ -482,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, `pyodide-build` is the default and only supported frontend; setting
another frontend produces a warning and uses `pyodide-build` instead. 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.
@@ -594,6 +597,12 @@ You must use this variable to pass variables to Linux builds, since they execute
You can use `$PATH` syntax to insert other variables, or the `$(pwd)` syntax to insert the output of other shell commands.
Variables are evaluated in the order they appear. Any variable referenced before it is set will evaluate to an empty string.
The environment seen by the build and test steps starts with the build
environment. On Linux, this is the container environment, plus any variables
passed through with [`environment-pass`](#environment-pass). On other platforms,
the host environment is already available. Assignments in `environment` are
evaluated after that base environment and replace any duplicate variable names.
To specify more than one environment variable, separate the assignments by spaces.
Platform-specific environment variables are also available:<br/>
@@ -729,6 +738,17 @@ The placeholder `{package}` can be used here; it will be replaced by the path to
On Windows and macOS, the version of Python available inside `before-all` is whatever is available on the host machine. On Linux, a modern Python version is available on PATH.
!!! note "Shell selection"
Shell command options such as `before-all`, `before-build`,
`repair-wheel-command`, and `before-test` are executed by a platform shell.
Linux builds run inside the build container and invoke the container's `sh -c`.
On macOS and Windows, cibuildwheel calls Python's
`subprocess.run(..., shell=True)`, which normally means `/bin/sh` on macOS
and `cmd.exe` on Windows. If you need a specific interpreter, invoke it
explicitly, for example `python scripts/prepare.py` or
`bash scripts/prepare.sh`.
This option has special behavior in the overrides section in `pyproject.toml`.
On linux, overriding it triggers a new container launch. It cannot be overridden
on macOS and Windows.
@@ -791,9 +811,9 @@ instead.
### `before-build` {: #before-build env-var toml}
> Execute a shell command preparing each wheel's build
A shell command to run before building the wheel. This option allows you to run a command in **each** Python environment before the `pip wheel` command. This is useful if you need to set up some dependency so it's available during the build.
A shell command to run before building the wheel. This option allows you to run a command in **each** Python environment before the wheel is built. This is useful if you need to set up some dependency so it's available during the build.
If dependencies are required to build your wheel (for example if you include a header from a Python module), instead of using this command, we recommend adding requirements to a `pyproject.toml` file's `build-system.requires` array instead. This is reproducible, and users who do not get your wheels (such as Alpine or ClearLinux users) will still benefit.
If dependencies are required to build your wheel (for example if you include a header from a Python module), instead of using this command, we recommend adding requirements to a `pyproject.toml` file's `build-system.requires` array instead. This is reproducible, and users who do not get your wheels (such as BSD operating system variants) will still benefit.
The active Python binary can be accessed using `python`, and pip with `pip`; cibuildwheel makes sure the right version of Python and pip will be executed. The placeholder `{package}` can be used here; it will be replaced by the path to the package being built by cibuildwheel.
@@ -1654,6 +1674,10 @@ Platform-specific environment variables are also available:<br/>
# Install specific versions of test dependencies
[tool.cibuildwheel]
test-requires = ["pytest==8.2.2", "packaging==24.1"]
# Dependency specifiers with environment markers are supported
[tool.cibuildwheel]
test-requires = ["pytest", "pyzstd; python_version >= '3.14'"]
```
In configuration files, you can use an array, and the items will be joined with a space.
@@ -1666,6 +1690,10 @@ Platform-specific environment variables are also available:<br/>
# Install specific versions of test dependencies
CIBW_TEST_REQUIRES: pytest==8.2.2 packaging==24.1
# Use shell-style quoting around specifiers containing spaces, such as
# those with environment markers
CIBW_TEST_REQUIRES: "pytest 'pyzstd; python_version >= \"3.14\"'"
```
@@ -1886,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
+6 -1
View File
@@ -169,7 +169,7 @@ You must target pyodide with `--platform pyodide` (or use `--only` on the identi
### Choosing a Pyodide version {: #pyodide-choosing-a-version}
It is also possible to target a specific Pyodide version by setting the [`pyodide-version`](options.md#pyodide-version) option to the desired version. Users are responsible for setting an appropriate Pyodide version according to the `pyodide-build` version. A list is available in Pyodide's [cross-build environments metadata file](https://github.com/pyodide/pyodide/blob/main/pyodide-cross-build-environments.json), which can be viewed more easily by installing `pyodide-build` from PyPI and using `pyodide xbuildenv search --all` to see a compatibility table.
It is also possible to target a specific Pyodide version by setting the [`pyodide-version`](options.md#pyodide-version) option to the desired version. Users are responsible for setting an appropriate Pyodide version according to the `pyodide-build` version. A list is available in Pyodide's [cross-build environments metadata file](https://pyodide.github.io/pyodide/api/v2/pyodide-cross-build-environments.json), which can be viewed more easily by installing `pyodide-build` from PyPI and using `pyodide xbuildenv search --all` to see a compatibility table.
If there are pre-releases available for a newer Pyodide version, the `pyodide-prerelease` [`enable`](options.md#enable) can be used to include pre-release versions. To build for older Pyodide versions that are no longer the current stable, use the `pyodide-eol` [`enable`](options.md#enable).
@@ -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. A global [`build-frontend`](options.md#build-frontend) setting naming another frontend is ignored with a warning on this platform.
[`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}
+16 -16
View File
@@ -11,8 +11,8 @@ ref: working-examples
|-----------------------------------|----|----|:------|
| [scikit-learn][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] ![pyodide icon][] | The machine learning library. A complex but clean config using many of cibuildwheel's features to build a large project with Cython and C++ extensions. |
| [duckdb][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | DuckDB is an analytical in-process SQL database management system |
| [pytorch-fairseq][] | ![github icon][] | ![apple icon][] ![linux icon][] | Facebook AI Research Sequence-to-Sequence Toolkit written in Python. |
| [NumPy][] | ![github icon][] ![travisci icon][] | ![windows icon][] ![apple icon][] ![linux icon][] ![pyodide icon][] | The fundamental package for scientific computing with Python. |
| [pytorch-fairseq][] | ![github icon][] | ![apple icon][] ![linux icon][] | Facebook AI Research Sequence-to-Sequence Toolkit written in Python. |
| [NCNN][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | ncnn is a high-performance neural network inference framework optimized for the mobile platform |
| [Matplotlib][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] ![pyodide icon][] | The venerable Matplotlib, a Python library with C++ portions |
| [Tornado][] | ![github icon][] | ![linux icon][] ![apple icon][] ![windows icon][] | Tornado is a Python web framework and asynchronous networking library. Uses stable ABI for a small C extension. |
@@ -48,9 +48,9 @@ ref: working-examples
| [Implicit][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Includes GPU support for linux wheels |
| [vispy][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Main repository for Vispy |
| [coverage.py][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | The coverage tool for Python |
| [Line Profiler][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Line-by-line profiling for Python |
| [PyCryptodome][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | A self-contained cryptographic library for Python |
| [PyAV][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Pythonic bindings for FFmpeg's libraries. |
| [PyCryptodome][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | A self-contained cryptographic library for Python |
| [Line Profiler][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Line-by-line profiling for Python |
| [PyYAML][] | ![github icon][] | ![apple icon][] | Canonical source repository for PyYAML |
| [pikepdf][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | A Python library for reading and writing PDF, powered by QPDF |
| [numexpr][] | ![github icon][] ![travisci icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Fast numerical array expression evaluator for Python, NumPy, Pandas, PyTables and more |
@@ -77,12 +77,12 @@ ref: working-examples
| [pybind11 cmake_example][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] ![pyodide icon][] | Example pybind11 module built with a CMake-based build system |
| [KDEpy][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Kernel Density Estimation in Python |
| [dd-trace-py][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Uses custom alternate arch emulation on GitHub |
| [tgcalls][] | ![github icon][] | ![apple icon][] ![windows icon][] | Python `pybind11` binding to Telegram's WebRTC library with third party dependencies like `OpenSSL`, `MozJPEG`, `FFmpeg`, etc. |
| [sourmash][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] ![pyodide icon][] | Quickly search, compare, and analyze genomic and metagenomic data sets. |
| [tgcalls][] | ![github icon][] | ![apple icon][] ![windows icon][] | Python `pybind11` binding to Telegram's WebRTC library with third party dependencies like `OpenSSL`, `MozJPEG`, `FFmpeg`, etc. |
| [python-rapidjson][] | ![travisci icon][] ![gitlab icon][] | ![windows icon][] ![linux icon][] | Python wrapper around rapidjson |
| [pybind11 python_example][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] ![pyodide icon][] | Example pybind11 module built with a Python-based build system |
| [python-snappy][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Python bindings for the snappy google library |
| [Confluent client for Kafka][] | ![travisci icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | setup in `tools/wheels/build-wheels.bat` |
| [python-snappy][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Python bindings for the snappy google library |
| [abess][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | A fast best-subset selection library. It uses cibuildwheel to build a large project with C++ extensions. |
| [jq.py][] | ![travisci icon][] | ![apple icon][] ![linux icon][] | Python bindings for jq |
| [cyvcf2][] | ![github icon][] | ![apple icon][] ![linux icon][] | cython + htslib == fast VCF and BCF processing |
@@ -92,8 +92,8 @@ ref: working-examples
| [pillow-heif][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Bindings to libheif library with third party dependencies. Fully automated CI for tests and publishing including Apple Silicon builds. |
| [keyvi][] | ![github icon][] | ![linux icon][] ![apple icon][] | FST based key value index highly optimized for size and lookup performance, utilizes ccache action for improved runtime |
| [PyGLM][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Fast OpenGL Mathematics (GLM) for Python |
| [iDynTree][] | ![github icon][] | ![linux icon][] | Uses manylinux_2_24 |
| [power-grid-model][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Python/C++ library for distribution power system analysis |
| [iDynTree][] | ![github icon][] | ![linux icon][] | Uses manylinux_2_24 |
| [TgCrypto][] | ![travisci icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Includes a Windows Travis build. |
| [streaming-form-data][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Streaming parser for multipart/form-data written in Cython |
| [Imagecodecs (fork)][] | ![azurepipelines icon][] | ![apple icon][] ![linux icon][] | Over 20 external dependencies in compiled libraries, custom docker image, `libomp`, `openblas` and `install_name_tool` for macOS. |
@@ -102,16 +102,16 @@ ref: working-examples
| [bx-python][] | ![travisci icon][] | ![apple icon][] ![linux icon][] | A library that includes Cython extensions. |
| [Python-WebRTC][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | a Python extension that provides bindings to WebRTC M92 |
| [pybind11 scikit_build_example][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] ![pyodide icon][] | An example combining scikit-build and pybind11 |
| [fathon][] | ![travisci icon][] | ![apple icon][] ![linux icon][] | python package for DFA (Detrended Fluctuation Analysis) and related algorithms |
| [Arbor][] | ![github icon][] | ![apple icon][] ![linux icon][] | Arbor is a multi-compartment neuron simulation library; compatible with next-generation accelerators; best-practices applied to research software; focused on community-driven development. Includes a [small script](https://github.com/arbor-sim/arbor/blob/master/scripts/patchwheel.py) patching `rpath` in bundled libraries. |
| [fathon][] | ![travisci icon][] | ![apple icon][] ![linux icon][] | python package for DFA (Detrended Fluctuation Analysis) and related algorithms |
| [clang-format][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Scikit-build wrapper around LLVM's CMake, all platforms, generic wheels. |
| [polaroid][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Full range of wheels for setuptools rust, with auto release and PyPI deploy. |
| [ninja][] | ![github icon][] ![travisci icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Multitagged binary builds for all supported platforms, using cibw 2 config configuration. |
| [cf-units][] | ![github icon][] | ![apple icon][] ![linux icon][] | Units of measure as required by the Climate and Forecast (CF) Metadata Conventions |
| [etebase-py][] | ![travisci icon][] | ![linux icon][] | Python bindings to a Rust library using `setuptools-rust`, and `sccache` for improved speed. |
| [SiPM][] | ![github icon][] | ![apple icon][] ![linux icon][] | High performance library for SiPM detectors simulation using C++17, OpenMP and AVX2 intrinsics. |
| [aalink][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Async Python interface for Ableton Link. |
| [numpythia][] | ![github icon][] | ![apple icon][] ![linux icon][] | The interface between PYTHIA and NumPy |
| [aalink][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Async Python interface for Ableton Link. |
| [pyjet][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | The interface between FastJet and NumPy |
| [ril][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | A python binding to Rust Imaging library using maturin and Pyo3, utilizes Github Action cache to improve speed. Builds abi3 wheels. |
| [GSD][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Cython and NumPy project with 64-bit wheels. |
@@ -122,8 +122,8 @@ ref: working-examples
[scikit-learn]: https://github.com/scikit-learn/scikit-learn
[duckdb]: https://github.com/duckdb/duckdb
[pytorch-fairseq]: https://github.com/facebookresearch/fairseq
[NumPy]: https://github.com/numpy/numpy
[pytorch-fairseq]: https://github.com/facebookresearch/fairseq
[NCNN]: https://github.com/Tencent/ncnn
[Matplotlib]: https://github.com/matplotlib/matplotlib
[Tornado]: https://github.com/tornadoweb/tornado
@@ -159,9 +159,9 @@ ref: working-examples
[Implicit]: https://github.com/benfred/implicit
[vispy]: https://github.com/vispy/vispy
[coverage.py]: https://github.com/nedbat/coveragepy
[Line Profiler]: https://github.com/pyutils/line_profiler
[PyCryptodome]: https://github.com/Legrandin/pycryptodome
[PyAV]: https://github.com/PyAV-Org/PyAV
[PyCryptodome]: https://github.com/Legrandin/pycryptodome
[Line Profiler]: https://github.com/pyutils/line_profiler
[PyYAML]: https://github.com/yaml/pyyaml
[pikepdf]: https://github.com/pikepdf/pikepdf
[numexpr]: https://github.com/pydata/numexpr
@@ -188,12 +188,12 @@ ref: working-examples
[pybind11 cmake_example]: https://github.com/pybind/cmake_example
[KDEpy]: https://github.com/tommyod/KDEpy
[dd-trace-py]: https://github.com/DataDog/dd-trace-py
[tgcalls]: https://github.com/MarshalX/tgcalls
[sourmash]: https://github.com/sourmash-bio/sourmash
[tgcalls]: https://github.com/MarshalX/tgcalls
[python-rapidjson]: https://github.com/python-rapidjson/python-rapidjson
[pybind11 python_example]: https://github.com/pybind/python_example
[python-snappy]: https://github.com/intake/python-snappy
[Confluent client for Kafka]: https://github.com/confluentinc/confluent-kafka-python
[python-snappy]: https://github.com/intake/python-snappy
[abess]: https://github.com/abess-team/abess
[jq.py]: https://github.com/mwilliamson/jq.py
[cyvcf2]: https://github.com/brentp/cyvcf2
@@ -203,8 +203,8 @@ ref: working-examples
[pillow-heif]: https://github.com/bigcat88/pillow_heif
[keyvi]: https://github.com/KeyviDev/keyvi
[PyGLM]: https://github.com/Zuzu-Typ/PyGLM
[iDynTree]: https://github.com/robotology/idyntree
[power-grid-model]: https://github.com/PowerGridModel/power-grid-model
[iDynTree]: https://github.com/robotology/idyntree
[TgCrypto]: https://github.com/pyrogram/tgcrypto
[streaming-form-data]: https://github.com/siddhantgoel/streaming-form-data
[Imagecodecs (fork)]: https://github.com/czaki/imagecodecs_build
@@ -213,16 +213,16 @@ ref: working-examples
[bx-python]: https://github.com/bxlab/bx-python
[Python-WebRTC]: https://github.com/MarshalX/python-webrtc
[pybind11 scikit_build_example]: https://github.com/pybind/scikit_build_example
[fathon]: https://github.com/stfbnc/fathon
[Arbor]: https://github.com/arbor-sim/arbor
[fathon]: https://github.com/stfbnc/fathon
[clang-format]: https://github.com/ssciwr/clang-format-wheel
[polaroid]: https://github.com/daggy1234/polaroid
[ninja]: https://github.com/scikit-build/ninja-python-distributions
[cf-units]: https://github.com/SciTools/cf-units
[etebase-py]: https://github.com/etesync/etebase-py
[SiPM]: https://github.com/EdoPro98/SimSiPM
[aalink]: https://github.com/artfwo/aalink
[numpythia]: https://github.com/scikit-hep/numpythia
[aalink]: https://github.com/artfwo/aalink
[pyjet]: https://github.com/scikit-hep/pyjet
[ril]: https://github.com/Cryptex-github/ril-py
[GSD]: https://github.com/glotzerlab/gsd
+3 -3
View File
@@ -6,7 +6,7 @@ jobs:
- bash: |
set -o errexit
python3 -m pip install --upgrade pip
pip3 install cibuildwheel==4.0.0rc2
pip3 install cibuildwheel==4.1.1
displayName: Install dependencies
- bash: cibuildwheel --output-dir wheelhouse .
displayName: Build wheels
@@ -20,7 +20,7 @@ jobs:
- bash: |
set -o errexit
python3 -m pip install --upgrade pip
python3 -m pip install cibuildwheel==4.0.0rc2
python3 -m pip install cibuildwheel==4.1.1
displayName: Install dependencies
- bash: cibuildwheel --output-dir wheelhouse .
displayName: Build wheels
@@ -34,7 +34,7 @@ jobs:
- bash: |
set -o errexit
python -m pip install --upgrade pip
pip install cibuildwheel==4.0.0rc2
pip install cibuildwheel==4.1.1
displayName: Install dependencies
- bash: cibuildwheel --output-dir wheelhouse .
displayName: Build wheels
+3 -3
View File
@@ -11,7 +11,7 @@ jobs:
- run:
name: Build the Linux wheels.
command: |
python3 -m pip install --user cibuildwheel==4.0.0rc2
python3 -m pip install --user cibuildwheel==4.1.1
cibuildwheel --output-dir wheelhouse
- store_artifacts:
path: wheelhouse/
@@ -28,7 +28,7 @@ jobs:
- run:
name: Build the Linux aarch64 wheels.
command: |
python3 -m pip install --user cibuildwheel==4.0.0rc2
python3 -m pip install --user cibuildwheel==4.1.1
python3 -m cibuildwheel --output-dir wheelhouse
- store_artifacts:
path: wheelhouse/
@@ -44,7 +44,7 @@ jobs:
name: Build the OS X wheels.
command: |
sudo softwareupdate --install-rosetta --agree-to-license # for x86_64/universal2 tests
pip3 install cibuildwheel==4.0.0rc2
pip3 install cibuildwheel==4.1.1
cibuildwheel --output-dir wheelhouse
- store_artifacts:
path: wheelhouse/
+1 -1
View File
@@ -53,7 +53,7 @@ jobs:
persist-credentials: false
- name: Build wheels
uses: pypa/cibuildwheel@v4.0.0rc2
uses: pypa/cibuildwheel@v4.1.1
env:
CIBW_PLATFORM: ${{ matrix.platform || 'auto' }}
CIBW_ARCHS: ${{ matrix.archs || 'auto' }}
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
persist-credentials: false
- name: Build wheels
uses: pypa/cibuildwheel@v4.0.0rc2
uses: pypa/cibuildwheel@v4.1.1
# env:
# CIBW_SOME_OPTION: value
# ...
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
persist-credentials: false
- name: Build wheels
run: pipx run cibuildwheel==4.0.0rc2
run: pipx run cibuildwheel==4.1.1
- uses: actions/upload-artifact@v4
with:
+1 -1
View File
@@ -23,7 +23,7 @@ jobs:
platforms: all
- name: Build wheels
uses: pypa/cibuildwheel@v4.0.0rc2
uses: pypa/cibuildwheel@v4.1.1
env:
# configure cibuildwheel on Linux to build native archs ('auto'),
# and to split the remaining architectures between the x86_64 and
+3 -3
View File
@@ -12,7 +12,7 @@ linux:
DOCKER_TLS_CERTDIR: ""
script:
- curl -sSL https://get.docker.com/ | sh
- python -m pip install cibuildwheel==4.0.0rc2
- python -m pip install cibuildwheel==4.1.1
- cibuildwheel --output-dir wheelhouse
artifacts:
paths:
@@ -23,7 +23,7 @@ windows:
before_script:
- choco install python -y --allow-downgrade --version 3.12.4
- choco install git.install -y
- py -m pip install cibuildwheel==4.0.0rc2
- py -m pip install cibuildwheel==4.1.1
script:
- py -m cibuildwheel --output-dir wheelhouse --platform windows
artifacts:
@@ -35,7 +35,7 @@ windows:
macos:
image: macos-14-xcode-15
before_script:
- python3 -m pip install cibuildwheel==4.0.0rc2
- python3 -m pip install cibuildwheel==4.1.1
script:
- python3 -m cibuildwheel --output-dir wheelhouse
artifacts:
+1 -1
View File
@@ -14,7 +14,7 @@ linux:
- curl -sSL https://get.docker.com/ | sh
# Warning: This is extremely slow, be careful with how many wheels you build
- docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
- python -m pip install cibuildwheel==4.0.0rc2
- python -m pip install cibuildwheel==4.1.1
# Assuming your CI runner's default architecture is x86_64...
- cibuildwheel --output-dir wheelhouse --platform linux --archs aarch64
artifacts:
+56 -2
View File
@@ -16,9 +16,11 @@ See sessions with `nox -l`
"""
import os
import re
import shutil
import sys
from pathlib import Path
from typing import Any
import nox
@@ -63,6 +65,52 @@ def tests(session: nox.Session) -> None:
session.run("pytest", "test", "-x", "--durations", "0", "--timeout=2400", "test")
# Packages held back for GraalPy until upstream fixes land. uv pip compile
# can't express a per-implementation split, so the compiled pin is patched:
# GraalPy (matching `graalpy_marker`) gets `held`, everything else
# (`other_marker`) tracks the freshly compiled version.
GRAALPY_HELD_BACK = (
# Newer pip breaks GraalPy on Windows.
{
"package": "pip",
"held": "26.0.1",
"graalpy_marker": 'implementation_name == "graalpy" and platform_system == "Windows"',
"other_marker": 'implementation_name != "graalpy" or platform_system != "Windows"',
},
# filelock >=3.30 imports errno.ENOTSUP, which GraalPy lacks (fix due ~2026-08).
{
"package": "filelock",
"held": "3.29.7",
"graalpy_marker": 'implementation_name == "graalpy"',
"other_marker": 'implementation_name != "graalpy"',
},
)
def _pin_graalpy_workarounds(output_file: Path) -> None:
text = output_file.read_text()
for pin in GRAALPY_HELD_BACK:
package = re.escape(pin["package"])
text = re.sub(
rf"^{package}==(?P<version>[^\s;]+)$",
f"{pin['package']}==\\g<version>; {pin['other_marker']}\n"
f"{pin['package']}=={pin['held']}; {pin['graalpy_marker']}",
text,
flags=re.MULTILINE,
)
output_file.write_text(text)
def _graalpy_python_versions(build_platforms: dict[str, Any]) -> set[str]:
"""Python minor versions (e.g. "3.12") that ship a GraalPy configuration."""
return {
".".join(config["version"].split(".")[:2])
for platform in build_platforms.values()
for config in platform["python_configurations"]
if config["identifier"].startswith("gp")
}
@nox.session(default=False, tags=["update"])
def update_constraints(session: nox.Session) -> None:
"""
@@ -79,6 +127,9 @@ def update_constraints(session: nox.Session) -> None:
env = os.environ.copy()
env["UV_CUSTOM_COMPILE_COMMAND"] = f"nox -s {session.name}"
build_platforms = nox.project.load_toml(resources / "build-platforms.toml")
graalpy_versions = _graalpy_python_versions(build_platforms)
for minor_version in range(9, 16):
python_version = f"3.{minor_version}"
output_file = resources / f"constraints-python{python_version.replace('.', '')}.txt"
@@ -92,13 +143,14 @@ def update_constraints(session: nox.Session) -> None:
f"--output-file={output_file}",
env=env,
)
if python_version in graalpy_versions:
_pin_graalpy_workarounds(output_file)
shutil.copyfile(
resources / "constraints-python315.txt",
resources / "constraints.txt",
)
build_platforms = nox.project.load_toml(resources / "build-platforms.toml")
pyodides = build_platforms["pyodide"]["python_configurations"]
for pyodide in pyodides:
python_version = ".".join(pyodide["version"].split(".")[:2])
@@ -176,7 +228,9 @@ def docs(session: nox.Session) -> None:
"""
pyproject = nox.project.load_toml()
session.install("-e.", *nox.project.dependency_groups(pyproject, "docs"))
session.run("mkdocs", "serve" if session.interactive else "build", "--strict", *session.posargs)
session.run(
"properdocs", "serve" if session.interactive else "build", "--strict", *session.posargs
)
@nox.session(default=False)
View File
+3 -2
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "cibuildwheel"
version = "4.0.0rc2"
version = "4.1.1"
description = "Build Python wheels on CI with minimal configuration."
readme = "README.md"
license = "BSD-2-Clause"
@@ -68,7 +68,8 @@ docs = [
"mkdocs-include-markdown-plugin==7.1.8",
"mkdocs-intersphinx>=0.1.1; python_version >= '3.12'",
"mkdocs-macros-plugin>=1.4.1",
"mkdocs==1.6.1",
"properdocs==1.6.7",
"properdocs-theme-readthedocs",
"pymdown-extensions",
"rich",
]
+64 -27
View File
@@ -1,5 +1,6 @@
from __future__ import annotations
import dataclasses
import json
import os
import subprocess
@@ -12,14 +13,16 @@ from cibuildwheel.ci import detect_ci_provider
from cibuildwheel.options import CommandLineArguments, Options
from cibuildwheel.selector import EnableGroup
from cibuildwheel.typing import PLATFORMS
from cibuildwheel.util.resources import PINNED_DOCKER_IMAGES
from cibuildwheel.venv import find_uv
from . import utils
from .utils import DEFAULT_CIBW_ENABLE, EMULATED_ARCHS, get_platform
from .utils import DEFAULT_CIBW_ENABLE, EMULATED_ARCHS, get_enable_groups, get_platform
TYPE_CHECKING = False
if TYPE_CHECKING:
from collections.abc import Generator
from pathlib import Path
def pytest_addoption(parser: pytest.Parser) -> None:
@@ -74,7 +77,19 @@ def pytest_configure(config: pytest.Config) -> None:
os.environ["CIBW_PLATFORM"] = flag_platform
def docker_warmup(request: pytest.FixtureRequest) -> None:
@dataclasses.dataclass(frozen=True)
class DockerWarmUpImage:
source_name: str
cached_name: str
def get_docker_warmup_images(request: pytest.FixtureRequest) -> list[DockerWarmUpImage] | None:
enable_groups = get_enable_groups()
# Check missing pre-installed interpreters are needed
enable_groups &= {EnableGroup.GraalPy, EnableGroup.PyPyEoL}
if not enable_groups:
return None
machine = request.config.getoption("--run-emulation", default=None)
if machine is None:
archs = {arch.value for arch in Architecture.auto_archs("linux")}
@@ -86,7 +101,7 @@ def docker_warmup(request: pytest.FixtureRequest) -> None:
# Only include architectures where there are missing pre-installed interpreters
archs &= {"x86_64", "i686", "aarch64"}
if not archs:
return
return None
options = Options(
platform="linux",
@@ -96,17 +111,28 @@ def docker_warmup(request: pytest.FixtureRequest) -> None:
)
build_options = options.build_options(None)
assert build_options.manylinux_images is not None
assert build_options.musllinux_images is not None
images = [build_options.manylinux_images[arch] for arch in archs] + [
build_options.musllinux_images[arch] for arch in archs
]
images: list[DockerWarmUpImage] = []
for arch in archs:
source_name = build_options.manylinux_images[arch]
cached_name = source_name
if "@sha256:" in source_name:
cached_name = source_name.rsplit("@sha256:", 1)[0] + ":cibw_cache_fixture"
images.append(DockerWarmUpImage(source_name, cached_name))
if images:
return images
return None
def docker_warmup(images: list[DockerWarmUpImage], docker_warmup_config: Path) -> None:
command = (
"manylinux-interpreters ensure-all &&"
"cpython3.13 -m pip download -d /tmp setuptools wheel pytest"
)
config_content = PINNED_DOCKER_IMAGES.read_text()
for image in images:
config_content = config_content.replace(image.source_name, image.cached_name)
container_id = subprocess.run(
["docker", "create", image, "bash", "-c", command],
["docker", "create", image.source_name, "bash", "-c", command],
text=True,
check=True,
stdout=subprocess.PIPE,
@@ -118,37 +144,48 @@ def docker_warmup(request: pytest.FixtureRequest) -> None:
).stdout.strip()
assert exit_code == "0"
subprocess.run(
["docker", "commit", container_id, image], check=True, stdout=subprocess.DEVNULL
["docker", "commit", container_id, image.cached_name],
check=True,
stdout=subprocess.DEVNULL,
)
finally:
subprocess.run(["docker", "rm", container_id], check=True, stdout=subprocess.DEVNULL)
docker_warmup_config.write_text(config_content)
@pytest.fixture(scope="session", autouse=True)
def docker_warmup_fixture(
request: pytest.FixtureRequest, tmp_path_factory: pytest.TempPathFactory, worker_id: str
) -> None:
) -> Generator[None, None, None]:
# if we're in CI testing linux, let's warm-up docker images
if detect_ci_provider() is None or get_platform() != "linux":
return None
if request.config.getoption("--run-emulation", default=None) is not None:
images = None
elif request.config.getoption("--run-emulation", default=None) is not None:
# emulation tests only run one test in CI, caching the image only slows down the test
return None
images = None
else:
images = get_docker_warmup_images(request)
if images is None:
yield None
return
if worker_id == "master":
# not executing with multiple workers
# it might be unsafe to write to tmp_path_factory.getbasetemp().parent
return docker_warmup(request)
docker_warmup_config = tmp_path_factory.getbasetemp() / "docker_warmup.cfg"
docker_warmup(images, docker_warmup_config)
else:
# get the temp directory shared by all workers
root_tmp_dir = tmp_path_factory.getbasetemp().parent
# get the temp directory shared by all workers
root_tmp_dir = tmp_path_factory.getbasetemp().parent
docker_warmup_config = root_tmp_dir / "docker_warmup.cfg"
with FileLock(str(docker_warmup_config) + ".lock"):
if not docker_warmup_config.is_file():
docker_warmup(images, docker_warmup_config)
fn = root_tmp_dir / "warmup.done"
with FileLock(str(fn) + ".lock"):
if not fn.is_file():
docker_warmup(request)
fn.write_text("done")
return None
with pytest.MonkeyPatch.context() as monkeypatch:
monkeypatch.setenv("CIBW_INTERNAL_PINNED_DOCKER_IMAGES", str(docker_warmup_config))
yield None
@pytest.fixture(params=["pip", "build"])
@@ -157,8 +194,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 +213,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")
+2 -2
View File
@@ -49,10 +49,10 @@ def test_abi3(tmp_path: Path) -> None:
project_dir,
add_env={
# free_threaded, GraalPy, and PyPy do not have a Py_LIMITED_API equivalent, just build one of those
# pyodide uses cp313 (the stable version) which supports limited API / abi3
# pyodide uses cp314 (the latest stable version) which supports limited API / abi3
# also limit the number of builds for test performance reasons
"CIBW_BUILD": (
"cp313-*"
"cp314-*"
if utils.get_platform() == "pyodide"
else "cp39-* cp310-* pp310-* gp312_250-* cp312-* cp314t-*"
),
+6 -2
View File
@@ -405,7 +405,11 @@ def test_environment_markers(tmp_path: Path) -> None:
**cp313_env,
"CIBW_TEST_COMMAND": f"python -m pytest {test_filename}",
"CIBW_TEST_SOURCES": test_filename,
"CIBW_TEST_REQUIRES": "pytest certifi;sys_platform=='android' platformdirs;sys_platform!='android'",
"CIBW_TEST_REQUIRES": (
"pytest"
" 'certifi; sys_platform == \"android\"'"
" 'platformdirs; sys_platform != \"android\"'"
),
},
)
@@ -686,7 +690,7 @@ def test_xbuild_files(tmp_path: Path, capfd: pytest.CaptureFixture[str]) -> None
add_env={
**cp313_env,
# TODO: remove this once there are official Android NumPy wheels on PyPI.
"PIP_EXTRA_INDEX_URL": "https://chaquo.com/pypi-test",
"PIP_EXTRA_INDEX_URL": "https://chaquo.com/pypi-upstream",
"CIBW_ARCHS": "all", # Include both native and non-native archs.
"CIBW_TEST_COMMAND": f"python -c '{script}'",
},
+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
+7 -1
View File
@@ -12,7 +12,10 @@ basic_project = test_projects.new_c_project()
def test_podman(
tmp_path: Path, capfd: pytest.CaptureFixture[str], request: pytest.FixtureRequest
tmp_path: Path,
capfd: pytest.CaptureFixture[str],
request: pytest.FixtureRequest,
monkeypatch: pytest.MonkeyPatch,
) -> None:
if utils.get_platform() != "linux":
pytest.skip("the test is only relevant to the linux build")
@@ -20,6 +23,9 @@ def test_podman(
if not request.config.getoption("--run-podman"):
pytest.skip("needs --run-podman option to run")
# we can't use images cached in the docker_warmup_fixture autouse fixture
monkeypatch.delenv("CIBW_INTERNAL_PINNED_DOCKER_IMAGES", raising=False)
project_dir = tmp_path / "project"
basic_project.generate(project_dir)
+2 -21
View File
@@ -87,13 +87,13 @@ def get_versions_from_constraint_file(constraint_file: Path) -> dict[str, str]:
return result
@pytest.mark.parametrize("python_version", ["3.9", "3.13"])
@pytest.mark.parametrize("python_version", ["3.9", "3.14"])
def test_pinned_versions(
tmp_path: Path, python_version: str, build_frontend_env_nouv: dict[str, str]
) -> None:
if utils.get_platform() == "linux":
pytest.skip("linux doesn't pin individual tool versions, it pins manylinux images instead")
if python_version != "3.13" and utils.get_platform() == "pyodide":
if python_version != "3.14" and utils.get_platform() == "pyodide":
pytest.skip(f"pyodide does not support Python {python_version}")
project_dir = tmp_path / "project"
@@ -179,19 +179,6 @@ def test_dependency_constraints(
msg = f"Unknown method: {method}"
raise ValueError(msg)
skip = ""
if (
utils.get_platform() == "windows"
and method == "file"
and build_frontend_env_nouv["CIBW_BUILD_FRONTEND"] == "build"
):
# GraalPy 24 fails to discover its standard library when a venv is created
# from a virtualenv seeded executable. See
# https://github.com/oracle/graalpython/issues/491 and remove this once
# GraalPy 24 is dropped
skip = "gp311*"
# cross-platform Python script for dependency constraint checks
before_build_script = project_dir / "check_versions.py"
before_build_script.write_text(CHECK_VERSIONS_SCRIPT)
@@ -200,7 +187,6 @@ def test_dependency_constraints(
actual_wheels = utils.cibuildwheel_run(
project_dir,
add_env={
"CIBW_SKIP": skip,
"CIBW_DEPENDENCY_VERSIONS": dependency_version_option,
"CIBW_BEFORE_BUILD": f"python {before_build_script.name}",
"EXPECTED_VERSIONS": json.dumps(tool_versions),
@@ -212,9 +198,4 @@ def test_dependency_constraints(
# also check that we got the right wheels
expected_wheels = utils.expected_wheels("spam", "0.1.0", single_python=True)
if skip == "gp*":
# See reference to https://github.com/oracle/graalpython/issues/491
# above
expected_wheels = [w for w in expected_wheels if "graalpy311" not in w]
assert set(actual_wheels) == set(expected_wheels)
-14
View File
@@ -40,26 +40,12 @@ def test_pep518(tmp_path: Path, build_frontend_env: dict[str, str]) -> None:
project_dir = tmp_path / "project"
basic_project.generate(project_dir)
# GraalPy fails to discover its standard library when a venv is created
# from a virtualenv seeded executable. See
# https://github.com/oracle/graalpython/issues/491 and remove this once
# fixed upstream.
if build_frontend_env["CIBW_BUILD_FRONTEND"] == "build" and utils.get_platform() == "windows":
build_frontend_env["CIBW_SKIP"] = "gp*"
# build the wheels
actual_wheels = utils.cibuildwheel_run(project_dir, add_env=build_frontend_env)
# check that the expected wheels are produced
expected_wheels = utils.expected_wheels("spam", "0.1.0")
# GraalPy fails to discover its standard library when a venv is created
# from a virtualenv seeded executable. See
# https://github.com/oracle/graalpython/issues/491 and remove this once
# fixed upstream.
if build_frontend_env["CIBW_BUILD_FRONTEND"] == "build" and utils.get_platform() == "windows":
expected_wheels = [w for w in expected_wheels if "graalpy" not in w]
assert set(actual_wheels) == set(expected_wheels)
# These checks ensure an extra file is not created when using custom
+1 -1
View File
@@ -175,6 +175,6 @@ def test_pyodide_repair_wheel(tmp_path: Path) -> None:
# check that the expected wheels are produced
expected_wheels = [
"spam-0.1.0-cp313-cp313-pyemscripten_2025_0_wasm32.whl",
"spam-0.1.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl",
]
assert set(actual_wheels) == set(expected_wheels)
+7 -11
View File
@@ -31,7 +31,7 @@ EMULATED_ARCHS: Final[list[str]] = sorted(
PYPY_ARCHS = ["x86_64", "i686", "AMD64", "aarch64", "arm64"]
GRAALPY_ARCHS = ["x86_64", "AMD64", "aarch64", "arm64"]
SINGLE_PYTHON_VERSION: Final[tuple[int, int]] = (3, 13)
SINGLE_PYTHON_VERSION: Final[tuple[int, int]] = (3, 14)
# temporary workaround: set by build_frontend_env fixture to skip graalpy
# when uv is the build frontend (compatibility issue between graalpy and uv)
@@ -278,9 +278,9 @@ def _expected_wheels(
# To be kept in sync with Python versions for Pyodide identifiers in cibuildwheel/selector.py.
if platform == "pyodide" and python_abi_tags is None:
python_abi_tags = ["cp313-cp313"]
if EnableGroup.PyodidePrerelease in enable_groups:
python_abi_tags.append("cp314-cp314")
python_abi_tags = ["cp313-cp313", "cp314-cp314"]
if EnableGroup.PyodideEoL in enable_groups:
python_abi_tags.insert(0, "cp312-cp312")
elif (platform == "android" and python_abi_tags is None) or (
platform == "ios" and python_abi_tags is None
):
@@ -319,7 +319,6 @@ def _expected_wheels(
if EnableGroup.GraalPy in enable_groups and include_graalpy_in_expected_wheels:
python_abi_tags += [
"graalpy311-graalpy242_311_native",
"graalpy312-graalpy250_312_native",
]
@@ -376,10 +375,7 @@ def _expected_wheels(
else:
min_macosx = _floor_macosx(macosx_deployment_target, "10.15")
elif python_abi_tag.startswith("graalpy"):
if python_abi_tag.startswith("graalpy311"):
min_macosx = macosx_deployment_target
else:
min_macosx = _floor_macosx(macosx_deployment_target, "10.13")
min_macosx = _floor_macosx(macosx_deployment_target, "10.13")
else:
min_macosx = macosx_deployment_target
@@ -418,8 +414,8 @@ def _expected_wheels(
if not platform_tags:
# for example if the python tag is `none` or `abi3`, the wheel
# is built by the stable cp313 Python version
platform_tags = ["pyemscripten_2025_0_wasm32"]
# is built by the latest stable cp314 Python version
platform_tags = ["pyemscripten_2026_0_wasm32"]
else:
msg = f"Unsupported platform {platform!r}"
+31
View File
@@ -7,6 +7,7 @@ import sys
import pytest
import cibuildwheel.architecture
import cibuildwheel.errors
from cibuildwheel.architecture import Architecture, arch_synonym
TYPE_CHECKING = False
@@ -142,3 +143,33 @@ def test_arch_synonym(
arch: str, from_platform: PlatformName, to_platform: PlatformName, expected: str | None
) -> None:
assert arch_synonym(arch, from_platform, to_platform) == expected
@pytest.mark.parametrize(
("config", "platform", "expected"),
[
# Case-insensitive, platform-resolved names (issue #2373).
("arm64", "windows", {Architecture.ARM64}),
("ARM64", "windows", {Architecture.ARM64}),
("amd64", "windows", {Architecture.AMD64}),
("AMD64", "windows", {Architecture.AMD64}),
("X86", "windows", {Architecture.x86}),
("x86", "windows", {Architecture.x86}),
# macOS resolves the lowercase arm64 even when given uppercase.
("ARM64", "macos", {Architecture.arm64}),
("arm64", "macos", {Architecture.arm64}),
("X86_64", "macos", {Architecture.x86_64}),
# Multiple, mixed-case archs in one config string.
("arm64 amd64", "windows", {Architecture.ARM64, Architecture.AMD64}),
],
)
def test_arch_parse_config_case_insensitive(
config: str, platform: PlatformName, expected: set[Architecture]
) -> None:
assert Architecture.parse_config(config, platform) == expected
@pytest.mark.parametrize("platform", ["windows", "macos", "linux"])
def test_arch_parse_config_invalid(platform: PlatformName) -> None:
with pytest.raises(cibuildwheel.errors.ConfigurationError):
Architecture.parse_config("nonexistent", platform)
+18
View File
@@ -1,10 +1,14 @@
import tomllib
from pathlib import Path
import pytest
from packaging.version import Version
from cibuildwheel.extra import Printable, dump_python_configurations
from cibuildwheel.util import resources
OPTIONS_MD = Path(__file__).parents[1] / "docs" / "options.md"
def test_compare_configs() -> None:
txt = resources.BUILD_PLATFORMS.read_text()
@@ -18,6 +22,20 @@ def test_compare_configs() -> None:
assert new_txt == txt
@pytest.mark.skipif(not OPTIONS_MD.is_file(), reason="docs/options.md not present")
def test_all_identifiers_in_docs_table() -> None:
# The build-id table in docs/options.md is hand-maintained; this guards
# against forgetting an identifier when build-platforms.toml changes.
docs = OPTIONS_MD.read_text()
identifiers = [
config["identifier"]
for configs in resources.read_all_configs().values()
for config in configs
]
missing = [i for i in identifiers if i not in docs]
assert not missing, f"Missing from docs/options.md build-id table: {missing}"
def test_dump_with_Version() -> None:
# MyPy doesn't understand deeply nested dicts correctly
example: dict[str, dict[str, list[dict[str, Printable]]]] = {
+12 -11
View File
@@ -91,14 +91,15 @@ def test_build_filter_pypy_all() -> None:
assert build_selector("pp39-manylinux_x86_64")
def test_build_filter_pyodide_prerelease() -> None:
build_selector = BuildSelector(
build_config="*",
skip_config="",
enable=frozenset([EnableGroup.PyodidePrerelease]),
)
assert build_selector("cp313-pyodide_wasm32")
assert build_selector("cp314-pyodide_wasm32")
# Re-enable when we have Pyodide 3.15 prerelease builds to test against
# def test_build_filter_pyodide_prerelease() -> None:
# build_selector = BuildSelector(
# build_config="*",
# skip_config="",
# enable=frozenset([EnableGroup.PyodidePrerelease]),
# )
# assert build_selector("cp314-pyodide_wasm32")
# assert build_selector("cp315-pyodide_wasm32")
def test_build_filter_pyodide() -> None:
@@ -108,8 +109,8 @@ def test_build_filter_pyodide() -> None:
enable=frozenset(),
)
assert build_selector("cp313-pyodide_wasm32")
assert build_selector("cp314-pyodide_wasm32")
assert not build_selector("cp312-pyodide_wasm32")
assert not build_selector("cp314-pyodide_wasm32")
def test_build_filter_pyodide_eol() -> None:
@@ -120,7 +121,7 @@ def test_build_filter_pyodide_eol() -> None:
)
assert build_selector("cp312-pyodide_wasm32")
assert build_selector("cp313-pyodide_wasm32")
assert not build_selector("cp314-pyodide_wasm32")
assert build_selector("cp314-pyodide_wasm32")
def test_skip() -> None:
@@ -199,7 +200,7 @@ def test_build_limited_python() -> None:
assert build_selector("cp37-win32")
assert build_selector("cp38-win32")
assert build_selector("pp37-win_amd64")
assert build_selector("gp311_242-win_amd64")
assert build_selector("gp312_250-win_amd64")
def test_build_limited_python_partial() -> None:
+24 -2
View File
@@ -3,7 +3,10 @@ from __future__ import annotations
import textwrap
from pprint import pprint
import pytest
import cibuildwheel.platforms.linux
from cibuildwheel.errors import ConfigurationError
from cibuildwheel.oci_container import OCIContainerEngineConfig
from cibuildwheel.options import CommandLineArguments, Options
@@ -11,8 +14,6 @@ TYPE_CHECKING = False
if TYPE_CHECKING:
from pathlib import Path
import pytest
def test_linux_container_split(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
"""
@@ -99,3 +100,24 @@ def test_linux_container_split(tmp_path: Path, monkeypatch: pytest.MonkeyPatch)
]
assert before_alls(build_steps[3]) == [""] * 4
assert container_engines(build_steps[3]) == [default_container_engine] * 4
def test_package_dir_outside_working_directory_raises_configuration_error(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
work_dir = tmp_path / "work"
work_dir.mkdir()
package_dir = tmp_path / "package"
package_dir.mkdir()
package_dir.joinpath("pyproject.toml").touch()
monkeypatch.chdir(work_dir)
command_line_arguments = CommandLineArguments.defaults()
command_line_arguments.package_dir = package_dir
options = Options(platform="linux", command_line_arguments=command_line_arguments, env={})
with pytest.raises(
ConfigurationError, match="package_dir must be inside the working directory"
):
cibuildwheel.platforms.linux.build(options, tmp_path / "build")
+11 -11
View File
@@ -173,19 +173,19 @@ def test_empty_selector(monkeypatch: pytest.MonkeyPatch) -> None:
@pytest.mark.parametrize(
("architecture", "image", "full_image"),
[
("x86_64", None, "quay.io/pypa/manylinux_2_28_x86_64:*"),
("x86_64", "manylinux2014", "quay.io/pypa/manylinux2014_x86_64:*"),
("x86_64", "manylinux_2_28", "quay.io/pypa/manylinux_2_28_x86_64:*"),
("x86_64", "manylinux_2_34", "quay.io/pypa/manylinux_2_34_x86_64:*"),
("x86_64", None, "quay.io/pypa/manylinux_2_28_x86_64@*"),
("x86_64", "manylinux2014", "quay.io/pypa/manylinux2014_x86_64@*"),
("x86_64", "manylinux_2_28", "quay.io/pypa/manylinux_2_28_x86_64@*"),
("x86_64", "manylinux_2_34", "quay.io/pypa/manylinux_2_34_x86_64@*"),
("x86_64", "custom_image", "custom_image"),
("i686", None, "quay.io/pypa/manylinux_2_28_i686:*"),
("i686", "manylinux2014", "quay.io/pypa/manylinux2014_i686:*"),
("i686", "manylinux_2_28", "quay.io/pypa/manylinux_2_28_i686:*"),
("i686", None, "quay.io/pypa/manylinux_2_28_i686@*"),
("i686", "manylinux2014", "quay.io/pypa/manylinux2014_i686@*"),
("i686", "manylinux_2_28", "quay.io/pypa/manylinux_2_28_i686@*"),
("i686", "custom_image", "custom_image"),
("pypy_x86_64", None, "quay.io/pypa/manylinux_2_28_x86_64:*"),
("pypy_x86_64", "manylinux2014", "quay.io/pypa/manylinux2014_x86_64:*"),
("pypy_x86_64", "manylinux_2_28", "quay.io/pypa/manylinux_2_28_x86_64:*"),
("pypy_x86_64", "manylinux_2_34", "quay.io/pypa/manylinux_2_34_x86_64:*"),
("pypy_x86_64", None, "quay.io/pypa/manylinux_2_28_x86_64@*"),
("pypy_x86_64", "manylinux2014", "quay.io/pypa/manylinux2014_x86_64@*"),
("pypy_x86_64", "manylinux_2_28", "quay.io/pypa/manylinux_2_28_x86_64@*"),
("pypy_x86_64", "manylinux_2_34", "quay.io/pypa/manylinux_2_34_x86_64@*"),
("pypy_x86_64", "custom_image", "custom_image"),
],
)
+1 -3
View File
@@ -14,7 +14,7 @@ from cibuildwheel.oci_container import OCIPlatform
from cibuildwheel.util import file
DEFAULT_IDS = {"cp39", "cp310", "cp311", "cp312", "cp313", "cp314", "cp314t"}
ALL_IDS = DEFAULT_IDS | {"pp39", "pp310", "pp311", "gp311_242", "gp312_250"}
ALL_IDS = DEFAULT_IDS | {"pp39", "pp310", "pp311", "gp312_250"}
@pytest.fixture
@@ -158,7 +158,6 @@ before-all = "true"
"pp39",
"pp310",
"pp311",
"gp311_242",
"gp312_250",
}
}
@@ -180,7 +179,6 @@ before-all = "true"
"pp39",
"pp310",
"pp311",
"gp311_242",
"gp312_250",
]
}
+213 -2
View File
@@ -25,12 +25,21 @@ from cibuildwheel.options import (
_get_pinned_container_images,
)
from cibuildwheel.platforms import ALL_PLATFORM_MODULES, get_build_identifiers
from cibuildwheel.platforms.pyodide import (
PyodideXBuildEnvInfo,
validate_pyodide_target_python,
)
from cibuildwheel.platforms.pyodide import (
PythonConfiguration as PyodidePythonConfiguration,
)
from cibuildwheel.util import resources
from cibuildwheel.util.packaging import DependencyConstraints
TYPE_CHECKING = False
if TYPE_CHECKING:
from collections.abc import Sequence
from collections.abc import Callable, Sequence
from cibuildwheel.typing import PlatformName
PYPROJECT_1 = """
[tool.cibuildwheel]
@@ -106,6 +115,91 @@ def test_options_1(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
assert local.pyodide_version == "0.29.4"
@pytest.fixture
def make_pyodide_xbuildenv_info() -> Callable[[str, str], PyodideXBuildEnvInfo]:
def _make(version: str, python: str) -> PyodideXBuildEnvInfo:
return {
"version": version,
"python": python,
"emscripten": "5.0.3",
"pyodide_build": {"min": None, "max": None},
"compatible": True,
}
return _make
@pytest.fixture
def make_pyodide_python_configuration() -> Callable[[str, str], PyodidePythonConfiguration]:
def _make(identifier: str, version: str) -> PyodidePythonConfiguration:
return PyodidePythonConfiguration(
version=version,
identifier=identifier,
default_pyodide_version="0.0.0",
node_version="v22",
)
return _make
def test_validate_pyodide_target_python_matching(
make_pyodide_xbuildenv_info: Callable[[str, str], PyodideXBuildEnvInfo],
make_pyodide_python_configuration: Callable[[str, str], PyodidePythonConfiguration],
) -> None:
xbuildenv_info = make_pyodide_xbuildenv_info("314.0.0", "3.14.2")
config = make_pyodide_python_configuration("cp314-pyodide_wasm32", "3.14")
# should not raise
validate_pyodide_target_python(xbuildenv_info, config)
def test_validate_pyodide_target_python_mismatch(
make_pyodide_xbuildenv_info: Callable[[str, str], PyodideXBuildEnvInfo],
make_pyodide_python_configuration: Callable[[str, str], PyodidePythonConfiguration],
) -> None:
# a Python 3.14 xbuildenv applied to a cp313-pyodide_wasm32 build, when a global
# pyodide-version is set across mismatched targets
xbuildenv_info = make_pyodide_xbuildenv_info("314.0.0", "3.14.2")
config = make_pyodide_python_configuration("cp313-pyodide_wasm32", "3.13")
with pytest.raises(errors.ConfigurationError) as exc_info:
validate_pyodide_target_python(xbuildenv_info, config)
message = str(exc_info.value)
assert "cp313-pyodide_wasm32" in message
assert "314.0.0" in message
assert "3.13" in message
assert "3.14" in message
def test_test_and_audit_requires_with_dependency_specifiers(tmp_path: Path) -> None:
"""Regression test for https://github.com/pypa/cibuildwheel/issues/2912"""
pyproject_toml = tmp_path / "pyproject.toml"
pyproject_toml.write_text(
"""
[tool.cibuildwheel]
test-requires = [
"pytest",
"pyzstd; python_version >= '3.14'",
"zarr>=3",
]
audit-requires = ["abi3audit; python_version >= '3.9'"]
"""
)
args = CommandLineArguments.defaults()
args.package_dir = tmp_path
options = Options(platform="linux", command_line_arguments=args, env={})
build_options = options.build_options(identifier=None)
assert build_options.test_requires == [
"pytest",
"pyzstd; python_version >= '3.14'",
"zarr>=3",
]
assert build_options.audit_requires == ["abi3audit; python_version >= '3.9'"]
def test_passthrough(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
with tmp_path.joinpath("pyproject.toml").open("w") as f:
f.write(PYPROJECT_1)
@@ -388,6 +482,93 @@ 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(
"build_frontend_str",
["default", "pip", "build", "build[uv]", "uv"],
)
def test_pyodide_global_build_frontend_coerced(tmp_path: Path, build_frontend_str: str) -> None:
"""A global non-pyodide frontend setting is coerced to pyodide-build, not an error."""
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="pyodide", command_line_arguments=args, env={})
build_frontend = options.build_options(identifier=None).build_frontend
assert build_frontend.name == "pyodide-build"
@pytest.mark.parametrize(
("platform", "build_frontend_str"),
[
("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
@@ -571,10 +752,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,
@@ -666,6 +852,31 @@ def test_test_runtime_handling(
assert local.test_runtime.args == expected_args
@pytest.mark.parametrize(
("platform", "platform_envvar"),
[
("android", "CIBW_TEST_RUNTIME_ANDROID"),
("ios", "CIBW_TEST_RUNTIME_IOS"),
],
)
def test_test_runtime_platform_environment(
tmp_path: Path, platform: PlatformName, platform_envvar: str
) -> None:
args = CommandLineArguments.defaults()
args.package_dir = tmp_path
options = Options(
platform=platform,
command_line_arguments=args,
env={
"CIBW_TEST_RUNTIME": "args: --global",
platform_envvar: "args: --platform-specific",
},
)
assert options.build_options(None).test_runtime.args == ["--platform-specific"]
@pytest.mark.parametrize(
("definition", "expected"),
[
+60
View File
@@ -0,0 +1,60 @@
import os
import sys
from pathlib import Path
import pytest
import cibuildwheel.venv
from cibuildwheel.venv import activate_virtualenv, find_uv, virtualenv
def test_activate_virtualenv(tmp_path: Path) -> None:
venv_path = tmp_path / "venv"
env = activate_virtualenv(venv_path, env={"PATH": "/usr/bin"})
paths = env["PATH"].split(os.pathsep)
if sys.platform == "win32":
assert paths[:2] == [str(venv_path), str(venv_path / "Scripts")]
else:
assert paths[0] == str(venv_path / "bin")
assert paths[-1] == "/usr/bin"
assert env["VIRTUAL_ENV"] == str(venv_path)
def test_activate_virtualenv_base_python_bin_dir(tmp_path: Path) -> None:
venv_path = tmp_path / "venv"
base_bin = tmp_path / "base" / "bin"
env = activate_virtualenv(venv_path, env={"PATH": "/usr/bin"}, base_python_bin_dir=base_bin)
paths = env["PATH"].split(os.pathsep)
# the base interpreter's bin dir comes right after the venv, so scripts
# like python3-config resolve to the matching interpreter (see #2021)
if sys.platform == "win32":
assert paths[:3] == [str(venv_path), str(venv_path / "Scripts"), str(base_bin)]
else:
assert paths[:2] == [str(venv_path / "bin"), str(base_bin)]
assert paths[-1] == "/usr/bin"
@pytest.mark.skipif(sys.platform == "win32", reason="POSIX-only behavior")
@pytest.mark.skipif(find_uv() is None, reason="requires uv")
def test_virtualenv_puts_base_python_bin_dir_on_path(tmp_path: Path) -> None:
version = "{}.{}".format(*sys.version_info[:2])
venv_path = tmp_path / "venv"
env = virtualenv(
version, Path(sys.executable), venv_path, None, use_uv=True, env={"PATH": "/usr/bin"}
)
paths = env["PATH"].split(os.pathsep)
assert paths == [str(venv_path / "bin"), str(Path(sys.executable).parent), "/usr/bin"]
@pytest.mark.skipif(find_uv() is None, reason="requires uv")
def test_virtualenv_no_base_python_bin_dir_on_windows(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setattr(cibuildwheel.venv, "_IS_WIN", True)
version = "{}.{}".format(*sys.version_info[:2])
venv_path = tmp_path / "venv"
env = virtualenv(
version, Path(sys.executable), venv_path, None, use_uv=True, env={"PATH": "/usr/bin"}
)
paths = env["PATH"].split(os.pathsep)
assert str(Path(sys.executable).parent) not in paths