Compare commits

...
Author SHA1 Message Date
copilot-swe-agent[bot] 892871796a Merge remote-tracking branch 'origin/copilot/fix-code-comments' into copilot/fix-code-comments
Co-authored-by: henryiii <4616906+henryiii@users.noreply.github.com>
2026-05-29 03:42:47 +00:00
copilot-swe-agent[bot]andhenryiii 39b605f6e0 Fix PR review comments for cache verification and docs wording
Co-authored-by: henryiii <4616906+henryiii@users.noreply.github.com>
2026-05-29 03:42:23 +00:00
copilot-swe-agent[bot] 3f6f9b3b1f Initial plan 2026-05-29 03:35:59 +00:00
Henry Schreiner f925e8ccef fix: also include pyodide
Assisted-by: CopilotCLI:gpt-5.3-codex
Signed-off-by: Henry Schreiner <henryfs@princeton.edu>
2026-05-28 22:36:20 -04:00
Henry SchreinerandCopilot b597cb5aff fix: populate sha256 in resource files and fix Windows PythonConfiguration
- Add sha256 field to Windows PythonConfiguration (PyPy/GraalPy have
  direct download URLs on Windows too)
- Pass sha256 to install_pypy() and install_graalpy() in windows.py
- Fix update_pythons.py: handle empty sha256 from CPython API (older
  versions) by streaming download to compute it; fix condition to
  check 'not sha256' rather than 'not in dict'
- Fix update_virtualenv.py: compute sha256 even when version unchanged
  but sha256 is empty (first-time population)
- Fix update_python_build_standalone.py: resolve file path relative to
  the script itself (not the installed package) so writes go to source
  checkout, not the uv cache
- Populate actual sha256 values by running all three update scripts

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Assisted-by: copilot-cli:claude-sonnet-4.6
2026-05-28 17:25:09 -04:00
Henry SchreinerandCopilot 97537fe937 feat: add SHA256 verification for direct downloads
Store SHA256 hashes when running update scripts and verify them
when downloading files at build time. This improves security by
detecting unexpected changes to downloaded artifacts.

Platforms covered: macOS (CPython, PyPy, GraalPy), iOS, Android,
virtualenv, and python-build-standalone. Windows (nuget) and
Linux (Docker) are excluded.

SHA256 sources per platform:
- macOS/iOS/Android CPython (python.org): sha256_sum from API
- GraalPy: .sha256 sidecar assets from GitHub releases
- python-build-standalone: SHA256SUMS file in release
- PyPy, BeeWare iOS, Maven (Chaquopy): stream-download and compute

Changes:
- cibuildwheel/util/file.py: add sha256 param to download()
- cibuildwheel/platforms/{macos,ios,android}.py: add sha256 to
  PythonConfiguration and pass to download()
- cibuildwheel/venv.py: read sha256 from toml and pass to download()
- cibuildwheel/util/python_build_standalone.py: add sha256 to
  PythonBuildStandaloneAsset and pass to download()
- cibuildwheel/resources/build-platforms.toml: add sha256 fields
- cibuildwheel/resources/virtualenv.toml: add sha256 field
- cibuildwheel/resources/python-build-standalone-releases.json: add sha256
- bin/update_pythons.py: compute/store sha256 per source strategy
- bin/update_virtualenv.py: compute sha256 by streaming download
- bin/update_python_build_standalone.py: parse SHA256SUMS file

Closes #908

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Assisted-by: copilot-cli:claude-sonnet-4.6
2026-05-28 11:03:52 -04:00
Matthieu Darbois e3e7cc9e07 chore: use if TYPE_CHECKING: blocks (#2866) 2026-05-27 18:20:08 -04:00
cibuildwheel-bot[bot]henryiiicibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com>copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2380f52783 [Bot] Update dependencies (#2874)
* Update dependencies

* Restore pip GraalPy Windows pinning workaround

Co-authored-by: henryiii <4616906+henryiii@users.noreply.github.com>

---------

Co-authored-by: cibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: henryiii <4616906+henryiii@users.noreply.github.com>
2026-05-27 18:18:04 -04:00
cibuildwheel-bot[bot]cibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com>mayeut
8286363b4d [Bot] Update dependencies (#2868)
* Update dependencies

* Revert pip marker flattening for GraalPy 3.11 on Windows

* Revert pip marker flattening for GraalPy 3.12 on Windows

---------

Co-authored-by: cibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com>
Co-authored-by: mayeut <mayeut@users.noreply.github.com>
2026-05-26 06:27:17 +02:00
dependabot[bot]dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>mayeut
a78478edc4 chore(deps): bump the actions group with 4 updates (#2867)
* chore(deps): bump the actions group with 4 updates

Bumps the actions group with 4 updates: [hynek/build-and-inspect-python-package](https://github.com/hynek/build-and-inspect-python-package), [j178/prek-action](https://github.com/j178/prek-action), [actions/create-github-app-token](https://github.com/actions/create-github-app-token) and [zizmorcore/zizmor-action](https://github.com/zizmorcore/zizmor-action).


Updates `hynek/build-and-inspect-python-package` from 2.17.0 to 2.18.0
- [Release notes](https://github.com/hynek/build-and-inspect-python-package/releases)
- [Changelog](https://github.com/hynek/build-and-inspect-python-package/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hynek/build-and-inspect-python-package/compare/fe0a0fb1925ca263d076ca4f2c13e93a6e92a33e...d44ca7d91762de7a7d5436ddae667c6da6d1c3df)

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

Updates `actions/create-github-app-token` from 3.1.1 to 3.2.0
- [Release notes](https://github.com/actions/create-github-app-token/releases)
- [Changelog](https://github.com/actions/create-github-app-token/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/create-github-app-token/compare/1b10c78c7865c340bc4f6099eb2f838309f1e8c3...bcd2ba49218906704ab6c1aa796996da409d3eb1)

Updates `zizmorcore/zizmor-action` from 0.5.3 to 0.5.6
- [Release notes](https://github.com/zizmorcore/zizmor-action/releases)
- [Commits](https://github.com/zizmorcore/zizmor-action/compare/b1d7e1fb5de872772f31590499237e7cce841e8e...5f14fd08f7cf1cb1609c1e344975f152c7ee938d)

---
updated-dependencies:
- dependency-name: hynek/build-and-inspect-python-package
  dependency-version: 2.18.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
- dependency-name: j178/prek-action
  dependency-version: 2.0.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
- dependency-name: actions/create-github-app-token
  dependency-version: 3.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
- dependency-name: zizmorcore/zizmor-action
  dependency-version: 0.5.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix create-github-app-token parameters

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mayeut <mayeut@users.noreply.github.com>
2026-05-23 17:52:46 +02:00
Henry SchreinerandRussell Keith-Magee 45cfffd3ee feat: add CPython 3.15 support for iOS (#2857)
* feat: add CPython 3.15 support for iOS

Assisted-by: OpenCode:Kimi-K2.6
Signed-off-by: Henry Schreiner <henryfs@princeton.edu>

* fix: copy in iOS support files

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

* fix: skip directories when copying iOS support files

When copying multiarch-specific support files (e.g. _cross_arm64_iphoneos.py),
the code was trying to copy all items in the directory including __pycache__
directories. The shutil.copy() function only works with files, not directories,
which caused an IsADirectoryError.

This fix adds a check to only copy files, skipping any directories like
__pycache__ that may have been created by Python imports.

Assisted-by: Copilot:claude-haiku-4.5

* fix: - in dir fine for now

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

* fix: apply review suggestions

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

* fix: only do this on 3.15+

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

* Ensure target Python directory exists before build

Add assertion to check if target Python directory exists.

* fix(ios): use stdlib dir directly instead of copying to platform-config

Remove the _inject_support_files workaround for python.org 3.15+
distributions. Instead of copying sysconfig files from the stdlib into
a synthetic platform-config/ directory, pass the stdlib directory
directly to make_cross_venv.py.

make_cross_venv.py is updated to derive the multiarch tag from the
_sysconfigdata_ filename rather than assuming it comes from the
directory name.

Co-authored-by: Russell Keith-Magee <russell@keith-magee.com>
Assisted-by: OpenCode:Kimi-K2.6
Signed-off-by: Henry Schreiner <henryfs@princeton.edu>

* Minor format cleanups on pre-commit skipped files.

* Clarify path names in make_cross_venv script.

---------

Signed-off-by: Henry Schreiner <henryfs@princeton.edu>
Co-authored-by: Russell Keith-Magee <russell@keith-magee.com>
2026-05-23 10:47:49 -04:00
Henry Schreiner a7b3f178c3 chore: avoid importing TYPE_CHECKING (#2864)
Assisted-by: OpenCode:glm-5.1

Signed-off-by: Henry Schreiner <henryfs@princeton.edu>
2026-05-18 20:10:51 -07:00
cibuildwheel-bot[bot]henryiiicibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com>copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
525f538878 [Bot] Update dependencies (#2862)
* Update dependencies

* Revert pip marker flattening for GraalPy on Windows

Agent-Logs-Url: https://github.com/pypa/cibuildwheel/sessions/a34e363b-219c-487f-8e2e-a1fe4ad5d768

Co-authored-by: henryiii <4616906+henryiii@users.noreply.github.com>

---------

Co-authored-by: cibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: henryiii <4616906+henryiii@users.noreply.github.com>
2026-05-18 16:47:31 -07:00
Henry Schreiner 3a3f4dcfeb chore: add agent and copilot setup files (#2861)
* chore: Add agent and copilot setup files

Add AGENTS.md with project-specific developer notes for AI agents.
Add GitHub Actions workflow to validate Copilot setup steps.
Add changelog-entry skill for automated changelog generation.

Assisted-by: OpenCode:kimi

* Update .gitignore to include CLAUDE.md

Add CLAUDE.md to .gitignore for symlink instructions
2026-05-18 08:26:26 -07:00
Henry Schreiner 51b25dc0aa fix: replace assert with proper exception in python_build_standalone (#2859)
Assert statements are skipped under python -O, making them unsuitable
for runtime validation. Replace assert with an explicit check that
raises PythonBuildStandaloneError if the target directory already exists.

Assisted-by: OpenCode:glm-5
2026-05-17 11:26:29 -07:00
Henry SchreinerandAgriya Khetarpal d08d9acda2 fix: tarfile extraction filter (#2856)
* fix: tarfile extraction filter

From pypa/build, with pathlib improvements and adapted for 3.11+. See #2854.

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

* Update cibuildwheel/_compat/tarfile.py

Co-authored-by: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com>

* Add source reference comment to tarfile.py

Add header comment indicating source of the file.

---------

Signed-off-by: Henry Schreiner <henryfs@princeton.edu>
Co-authored-by: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com>
2026-05-17 02:03:05 -07:00
Henry Schreiner 36b8e7e524 chore: minor fixups across errors, oci_container, and options (#2860)
* chore: minor fixups across errors, oci_container, and options

- Strengthen error message wording: 'is expected to' → 'must' and
  'is expected to place one' → 'must place exactly one' in
  FailedWheelRepairError classes.
- Remove shell=True from subprocess.run in oci_container debug_info,
  passing command as a list instead of a string.
- Update pinned image error message from 'cibuildwheel 3.x' to
  'cibuildwheel 4.x' and add comment about next warning candidate.

Assisted-by: OpenCode:glm-5

* Update cibuildwheel/options.py
2026-05-16 22:38:43 -07:00
Henry Schreiner c274b4e02c feat: add CPython 3.15 support for Android (#2858)
* feat: add CPython 3.15 support for iOS and Android

Add cp315 identifiers to build-platforms.toml for both iOS (arm64_iphoneos,
x86_64_iphonesimulator, arm64_iphonesimulator) and Android (arm64_v8a, x86_64).
Update bin/update_pythons.py to route iOS 3.15+ to python.org instead of
BeeWare GitHub releases. Update README.md platform table to show Android and
iOS as supported for 3.15. Add cp315-cp315 to expected_wheels test defaults
for android and ios platforms.

Assisted-by: OpenCode:Kimi-K2.6
Signed-off-by: Henry Schreiner <henryfs@princeton.edu>

* fix: adjust expectations

All changes made and linting passes. Here's a summary of what was done:
1. test/utils.py:390-394 — Added "cp315-cp315": 24 to the Android API level mapping, fixing the KeyError: 'cp315-cp315' in test_android.py::test_expected_wheels.
2. cibuildwheel/resources/build-platforms.toml:247 — Removed the cp315-ios_x86_64_iphonesimulator entry since CPython 3.15 dropped Intel iOS simulator support.
3. test/utils.py:397-405 — Updated the iOS expected wheels logic to return an empty platform_tags list for x86_64 when the Python version is 3.15+, so no x86_64_iphonesimulator wheels are expected for cp315+ on Intel Macs.
The XCframework slice name (ios-arm64_x86_64-simulator) stays the same in ios.py — I verified the 3.15 tarball still uses that name. The target_python.exists() iOS ARM64 failure on the feature branch is a separate issue (the 3.15 python.org distribution has a different structure than the beeware distributions used for 3.13/3.14) — left alone per your request.

Signed-off-by: Henry Schreiner <henryfs@princeton.edu>
Assisted-by: OpenCode:glm-5.1

* revert: x86 is still supposed to exist

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

* docs: fix readme for Pyodide

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

* docs: mention eol

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

* fix: drop assert

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

* feat: add CPython 3.15 support for Android only

Cherry-picked all changes from henryiii/feat/cp315-ios-android, then
reverted iOS-specific parts (iOS 3.15 build configs, ios.py assert
removal, update_pythons.py iOS URL logic, iOS test expectations, and
README iOS column for 3.15). Android cp315 entries, test expectations,
and README Android column remain.

Assisted-by: OpenCode:glm-5

---------

Signed-off-by: Henry Schreiner <henryfs@princeton.edu>
2026-05-15 23:47:20 -07:00
Henry Schreiner 02b64875a0 docs: remove outdated numpy info (#2855)
Signed-off-by: Henry Schreiner <henryfs@princeton.edu>
2026-05-15 16:08:44 +05:30
Henry Schreiner b6293e1712 docs: include delvewheel in legal note (#2853) 2026-05-14 23:51:39 +05:30
121 changed files with 2443 additions and 763 deletions
+122
View File
@@ -0,0 +1,122 @@
---
name: changelog-entry
description: Generate a new changelog entry for cibuildwheel based on all changes since the last tag. Use when updating the changelog or preparing a release.
---
# Generate Changelog Entry
Produce a new version section at the top of `docs/changelog.md` summarizing all changes since the last git tag.
## Steps
1. **Find the last tag** — run `git tag --sort=-version:refname | head -1` to get the most recent version tag.
2. **Gather changes** — run `git log <last-tag>..HEAD --oneline` and `git log <last-tag>..HEAD --format="%H %s"` to see all commits since that tag.
3. **Read each merge/commit** — for substantive commits, read the full message and any linked PRs to understand the change. Use `git log <last-tag>..HEAD --format="---%n%B"` for full messages. If a commit message is ambiguous, use `gh pr view <number>` to get richer context from GitHub.
4. **Classify and draft entries** — assign each change an emoji and write a one-line description following the style rules below. Combine related small PRs of the same type (bot dependency updates, dependabot CI action bumps, pre-commit autoupdates) into a single entry listing all PR numbers. Skip meta-changelog PRs (e.g., "Add missing CHANGELOG entries") — they don't describe user-facing changes. If a bot/dependency PR contains a substantive fix mixed in (e.g., a pip revert inside a dependency update), split it: mention the fix separately under its own category.
5. **Determine the new version number** — inspect commits for breaking changes or new features to decide patch/minor/major bump. Ask the user if unclear.
6. **Determine the date** — use today's date.
7. **Insert the new section** — add it at the top of `docs/changelog.md`, right after the `# Changelog` heading and a blank line, before any existing version sections.
## Style Rules
These are non-negotiable formatting conventions derived from the existing changelog.
### Version heading
```markdown
### v3.4.2
```
`###` heading, `v` prefix, full semver.
### Date line
```markdown
_14 May 2026_
```
Italic (underscore-wrapped), day without leading zero, full month name, 4-digit year. One blank line after the date.
### Entry format
```markdown
- <emoji> <Description> (#<PR number>)
```
- Each entry is a single bullet starting with `- `.
- Emoji immediately after the dash-space.
- Space between emoji and description text.
- PR number(s) in parens at end: `(#1234)` or `(#1234, #5678)`.
- No trailing period for single-sentence entries.
- Period at end of multi-sentence entries only.
### Emoji categories
Use exactly one emoji per entry, chosen by category:
| Emoji | Category | Used for |
|-------|----------|----------|
| 🌟 | Major feature | New platforms, significant new capabilities |
| ✨ | Feature | New features, additions, user-visible enhancements |
| 🐛 | Bug fix | Bug fixes |
| 🛠 | Maintenance | Dep updates, internal improvements, behavior tweaks |
| ⚠️ | Warning | Deprecations, breaking changes, dropped support |
| 📚 | Docs | Documentation changes |
| 💼 | Internal | Non-user-facing infra/tooling changes |
| 🧪 | Tests | Test changes |
| 🔐 | Security | Security-related changes (used in past changelogs) |
### Category disambiguation
When a change could fit multiple categories, use these tiebreakers:
- **CI/workflow fixes** → 💼 (not 🧪) — they fix infra, not test logic.
- **Test suite changes** → 🧪 — only for changes to the test code itself.
- **Diagnostic output changes** (e.g., printing more info during builds) → 🛠 (not 🐛) — they're improvements, not bug fixes.
- **A bug fix that also changes test code** → 🐛 — the user-facing fix takes priority; test changes are implicit.
### Writing style
- **Present tense**: "Adds", "Fixes", "Updates", not "Added", "Fixed".
- **Sentence case**: capitalize only the first word after the emoji.
- **Link option names to docs**: `[`option-name`](https://cibuildwheel.pypa.io/en/stable/options/#option-name)`. Option anchors match the option name — verify in `docs/options.md` by searching for `{: #option-name }`.
- Be specific about what changed and why, not just that something changed.
### Ordering
Within a version section, order entries by importance:
1. 🌟 entries first
2. ⚠️ entries next
3. ✨ entries
4. 🐛 entries
5. 🛠 entries
6. 📚 entries
7. 💼 entries
8. 🧪 entries last
### Multi-line entries
For complex features needing explanation, use an indented italic paragraph:
```markdown
- ✨ Short summary here. (#1234)
_Longer explanation with details and caveats._ (#1234)
```
Adding a new Python beta version always has a specific longer explanation, check for a previous addition (like 3.14) for the note to use.
### Blank lines
- One blank line between version sections.
- No blank lines between bullets within a version.
## Validation
After inserting the new section:
1. Check that the new section follows all style rules above.
2. Verify PR numbers match actual PRs in the commit log.
3. Ensure no duplicate entries — multiple commits to the same PR should produce one entry.
4. Run a final review of the file to confirm formatting is consistent with surrounding entries.
+40
View File
@@ -0,0 +1,40 @@
name: "Copilot Setup Steps"
permissions: {}
on:
workflow_dispatch:
push:
paths:
- .github/workflows/copilot-setup-steps.yml
pull_request:
paths:
- .github/workflows/copilot-setup-steps.yml
jobs:
copilot-setup-steps:
name: Copilot Setup Steps
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.x"
allow-prereleases: true
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
- name: Install tooling
run: |
uv tool install nox
uv tool install prek
- name: Pre-install checks
run: |
prek prepare-hooks
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
with:
persist-credentials: false
- uses: hynek/build-and-inspect-python-package@fe0a0fb1925ca263d076ca4f2c13e93a6e92a33e # v2.17.0
- uses: hynek/build-and-inspect-python-package@d44ca7d91762de7a7d5436ddae667c6da6d1c3df # v2.18.0
publish:
needs: [dist]
+1 -1
View File
@@ -46,7 +46,7 @@ jobs:
with:
python-version: "3.x"
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
- uses: j178/prek-action@6ad80277337ad479fe43bd70701c3f7f8aa74db3 # v2.0.3
- uses: j178/prek-action@bdca6f102f98e2b4c7029491a53dfd366469e33d # v2.0.4
- name: PyLint checks
run: uvx nox -s pylint -- --output-format=github
+4 -2
View File
@@ -28,12 +28,14 @@ jobs:
# we use this step to grab a Github App auth token, so that PRs generated by this workflow
# run the GHA tests.
- uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
- uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
id: generate-token
if: github.ref == 'refs/heads/main' && github.repository == 'pypa/cibuildwheel'
with:
app-id: ${{ secrets.CIBUILDWHEEL_BOT_APP_ID }}
client-id: ${{ secrets.CIBUILDWHEEL_BOT_APP_ID }}
private-key: ${{ secrets.CIBUILDWHEEL_BOT_APP_PRIVATE_KEY }}
permission-contents: write
permission-pull-requests: write
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
+1 -1
View File
@@ -21,4 +21,4 @@ jobs:
persist-credentials: false
- name: Run zizmor 🌈
uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3
uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6
+3
View File
@@ -119,3 +119,6 @@ site/
# OS files
.DS_Store
# This file should be a symlink or contain "See @AGENTS.md"
CLAUDE.md
+2 -1
View File
@@ -1,5 +1,6 @@
ci:
autoupdate_schedule: monthly
exclude: "^cibuildwheel/resources/ios-support/(?!make_cross_venv\\.py)"
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
@@ -17,7 +18,7 @@ repos:
exclude: ^cibuildwheel/resources/android/android.patch$
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: 6fec9b7edb08fd9989088709d864a7826dc74e80 # frozen: v0.15.12
rev: 0c7b6c989466a93942def1f84baf36ddfcd60c83 # frozen: v0.15.14
hooks:
- id: ruff-check
args: ["--fix"]
+57
View File
@@ -0,0 +1,57 @@
# cibuildwheel — Agent Notes
## Always run
- `prek -a` should be run after changes to reformat, lint, and type check
## Developer commands
- `uv run pytest unit_test` — Quick run for unit tests
- `nox -s tests` — run doctests + unit tests + integration tests (slow, >30 min, requires system Python installs on macOS).
- `nox -s tests -- unit_test` — fast unit tests only.
- `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).
- Set up local dev env at `.venv`: `uv sync` (dependency groups used).
## Project layout
- `cibuildwheel/` — main package. Entry point: `cibuildwheel.__main__:main`.
- `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.
## Testing specifics
- Three test suites exist, run in this order by `bin/run_tests.py`:
1. `pytest cibuildwheel` — doctests.
2. `pytest unit_test [...]` — unit tests.
3. `pytest test [...]` — integration tests (split into `serial` and `not serial` runs).
- Serial integration tests **must not** run in parallel; non-serial use pytest-xdist by default.
- Custom pytest options:
- `--run-docker` (unit_test + test): run OCI container tests. Linux only.
- `--run-podman`: run podman tests (Linux).
- `--run-emulation` (test): run QEMU emulation tests (e.g., `--run-emulation aarch64`).
- `--platform linux` (test): force integration tests to target Linux container builds even on macOS/Windows.
- `--enable` (test): sets `CIBW_ENABLE` env var (e.g., `pypy`, `graalpy`).
- Integration tests auto-set a default `CIBW_ENABLE` if the env var is absent.
- The `build_frontend_env` fixture parameterizes over `pip`, `build`, `build[uv]`, `uv` and skips unsupported combos per platform.
- Some integration tests require system Python.org installs on macOS; missing them prints a download URL in the error.
- iOS/Android/pyodide tests have dedicated pytest marks (`ios`, `android`, `pyodide`) and need platform-specific runners/simulators.
## Lint / typecheck
- Ruff (lint + format) and mypy run via pre-commit. Pylint runs separately via `nox -s pylint`.
- Mypy is strict (`strict = true`) and targets Python 3.11 for the package, 3.14 for a second check in pre-commit.
- Ruff config in `pyproject.toml` (`line-length = 100`).
- Python 3.11 is the minimum supported version for the package itself.
## Generated / maintained files
- `README.md` contains two **cog-generated** tables (options table, changelog preview). Pre-commit runs `cog -c -P -r -I ./bin README.md`. Edit the source scripts (`bin/readme_*.py`) or the upstream files (`docs/options.md`, `docs/changelog.md`) — do not hand-edit the generated blocks. Note the identifier is _not_ cog generated, and can be edited.
- `cibuildwheel/resources/cibuildwheel.schema.json` is generated by `bin/generate_schema.py` (run via `nox -s generate_schema`).
- `cibuildwheel/resources/constraints-*.txt` are generated via `nox -s update_constraints`.
- `cibuildwheel/resources/pinned_docker_images.cfg` and other resource files are updated via `nox -s update_pins`.
## CI / release quirks
- CI uses `uv sync --no-dev --group test` for test installs, then `uv run --no-sync` to execute.
- The release workflow uses `hynek/build-and-inspect-python-package` for dist building.
- `test.yml` skips unrelated paths to avoid burning CI time on docs-only changes.
- A sample project artifact is built once and downloaded by downstream test jobs to avoid redundant work.
+15 -13
View File
@@ -24,13 +24,13 @@ 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>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> | ✅ | ✅ | ✅<sup>3</sup> |
| CPython 3.14 | ✅ | ✅ | ✅ | ✅ | ✅<sup>2</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅<sup>4</sup> | ✅ | ✅ | N/A |
| CPython 3.15<sup>5</sup> | ✅ | ✅ | ✅ | ✅ | ✅<sup>2</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅<sup>4</sup> | N/A | N/A | N/A |
| 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 |
| 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 |
@@ -39,13 +39,15 @@ While cibuildwheel itself requires a recent Python version to run (we support th
<sup>**1** PyPy & GraalPy are only supported for manylinux wheels.</sup><br>
<sup>**2** Windows arm64 support is experimental.</sup><br>
<sup>**3** Experimental, not yet supported on PyPI, but can be used directly in web deployment. Use `--platform pyodide` to build.</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>
<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>
- Builds manylinux, musllinux, macOS, and Windows wheels for CPython, PyPy, and GraalPy
- Builds manylinux, musllinux, macOS, Windows, pyemscripten, iOS, and Android wheels
- Supports CPython, PyPy, and GraalPy
- Works on GitHub Actions, Azure Pipelines, CircleCI, and GitLab CI
- Bundles shared library dependencies on Linux and macOS through [auditwheel](https://github.com/pypa/auditwheel) and [delocate](https://github.com/matthew-brett/delocate)
- Bundles shared library dependencies on Linux through [auditwheel](https://github.com/pypa/auditwheel), macOS through [delocate](https://github.com/matthew-brett/delocate), and Windows through [delvewheel](https://github.com/adang1345/delvewheel)
- Runs your library's tests against the wheel-installed version of your library
See the [cibuildwheel 1 documentation](https://cibuildwheel.pypa.io/en/1.x/) if you need to build unsupported versions of Python, such as Python 2.
@@ -227,7 +229,7 @@ Here are some repos that use cibuildwheel.
Legal note
----------
Since `cibuildwheel` repairs the wheel with `delocate` or `auditwheel`, it might automatically bundle dynamically linked libraries from the build machine.
Since `cibuildwheel` repairs the wheel with `delocate`, `auditwheel`, or `delvewheel`, it might automatically bundle dynamically linked libraries from the build machine.
It helps ensure that the library can run without any dependencies outside of the pip toolchain.
+6 -1
View File
@@ -24,8 +24,9 @@ This will cache the results to all_known_setup.yaml; you can reprint
the results without the `--online` setting.
"""
from __future__ import annotations
import ast
from collections.abc import Iterable, Iterator
from pathlib import Path
import click
@@ -35,6 +36,10 @@ from rich import print
from cibuildwheel.projectfiles import Analyzer
TYPE_CHECKING = False
if TYPE_CHECKING:
from collections.abc import Iterable, Iterator
DIR = Path(__file__).parent.resolve()
+6 -1
View File
@@ -17,13 +17,14 @@ Suggested usage:
git diff
"""
from __future__ import annotations
import builtins
import functools
import textwrap
import urllib.error
import urllib.request
import xml.dom.minidom
from collections.abc import Iterable, Mapping, Sequence
from datetime import UTC, datetime
from io import StringIO
from pathlib import Path
@@ -33,6 +34,10 @@ import click
import yaml
from github import Auth, Github, GithubException
TYPE_CHECKING = False
if TYPE_CHECKING:
from collections.abc import Iterable, Mapping, Sequence
ICONS = (
"github",
"azurepipelines",
+32 -2
View File
@@ -3,6 +3,7 @@
# /// script
# dependencies = [
# "cibuildwheel",
# "requests",
# ]
#
# [tool.uv.sources]
@@ -10,13 +11,23 @@
# ///
import json
from pathlib import Path
from typing import Final
import requests
from cibuildwheel.extra import github_api_request
from cibuildwheel.util.python_build_standalone import (
PythonBuildStandaloneAsset,
PythonBuildStandaloneReleaseData,
)
from cibuildwheel.util.resources import PYTHON_BUILD_STANDALONE_RELEASES
# Resolve path relative to this script so writes go to the source checkout,
# not the uv-installed copy of the package.
DIR: Final[Path] = Path(__file__).parent.parent.resolve()
PYTHON_BUILD_STANDALONE_RELEASES: Final[Path] = (
DIR / "cibuildwheel/resources/python-build-standalone-releases.json"
)
def main() -> None:
@@ -34,8 +45,27 @@ def main() -> None:
f"repos/astral-sh/python-build-standalone/releases/tags/{latest_tag}"
)["assets"]
# Build a sha256 map from the SHA256SUMS file in the release
sha256_sums_urls = [
ga["browser_download_url"] for ga in github_assets if ga["name"] == "SHA256SUMS"
]
name_to_sha256: dict[str, str] = {}
if sha256_sums_urls:
response = requests.get(sha256_sums_urls[0])
response.raise_for_status()
for line in response.text.splitlines():
parts = line.split()
if len(parts) == 2:
sha256_hex, filename = parts
# The filename may have a leading "./" or spaces - strip it
name_to_sha256[filename.lstrip("./")] = sha256_hex
assets = [
PythonBuildStandaloneAsset(name=ga["name"], url=ga["browser_download_url"])
PythonBuildStandaloneAsset(
name=ga["name"],
url=ga["browser_download_url"],
sha256=name_to_sha256.get(ga["name"], ""),
)
for ga in github_assets
if ga["name"].endswith("install_only.tar.gz")
]
+62 -7
View File
@@ -12,16 +12,16 @@
# [tool.uv.sources]
# cibuildwheel = { path = ".." }
# ///
from __future__ import annotations
import difflib
import hashlib
import logging
import operator
import re
import tomllib
from collections.abc import Mapping, MutableMapping
from pathlib import Path
from typing import Any, Final, Literal, TypedDict
from typing import Any, Final, Literal, NotRequired, TypedDict
from xml.etree import ElementTree as ET
import click
@@ -35,6 +35,10 @@ from rich.syntax import Syntax
from cibuildwheel.extra import dump_python_configurations, get_pyodide_xbuildenv_info
from cibuildwheel.platforms.android import android_triplet
TYPE_CHECKING = False
if TYPE_CHECKING:
from collections.abc import Mapping, MutableMapping
log = logging.getLogger("cibw")
# Looking up the dir instead of using utils.resources_dir
@@ -53,11 +57,13 @@ class Config(TypedDict):
class ConfigUrl(Config):
url: str
sha256: NotRequired[str]
class ConfigPyodide(Config):
default_pyodide_version: str
node_version: str
sha256: str
# The following set of "Versions" classes allow the initial call to the APIs to
@@ -176,10 +182,23 @@ class GraalPyVersions:
]
if urls:
(url,) = urls
# Fetch sha256 from the ".sha256" sidecar asset in the same release
sha256 = ""
sha256_asset_name = url.rsplit("/", 1)[-1] + ".sha256"
sha256_urls = [
rf["browser_download_url"]
for rf in release["assets"]
if rf["name"] == sha256_asset_name
]
if sha256_urls:
sha256_response = requests.get(sha256_urls[0])
sha256_response.raise_for_status()
sha256 = sha256_response.text.strip().split()[0]
return ConfigUrl(
identifier=identifier,
version=f"{version.major}.{version.minor}",
url=url,
sha256=sha256,
)
return None
@@ -295,12 +314,14 @@ class CPythonVersions:
uri = self.versions_dict[new_version]
files = [rf for rf in self.files_info if rf["release"] == uri]
urls = [rf["url"] for rf in files if file_ident in rf["url"]]
if urls:
matching = [rf for rf in files if file_ident in rf["url"]]
if matching:
rf = matching[0]
return ConfigUrl(
identifier=identifier,
version=f"{new_version.major}.{new_version.minor}",
url=urls[0],
url=rf["url"],
sha256=rf.get("sha256_sum", ""),
)
return None
@@ -417,6 +438,7 @@ class PyodideVersions:
version=str(version),
default_pyodide_version=release["version"],
node_version=node_version,
sha256=release["sha256"],
)
@@ -445,6 +467,16 @@ class AllVersions:
self.pyodide = PyodideVersions()
def _stream_sha256(self, url: str) -> str:
"""Download a file (streaming) and return its SHA256 hex digest."""
log.debug("Computing sha256 for %s by streaming download...", url)
response = requests.get(url, stream=True)
response.raise_for_status()
hasher = hashlib.sha256()
for chunk in response.iter_content(65536):
hasher.update(chunk)
return hasher.hexdigest()
def update_config(self, config: MutableMapping[str, str]) -> None:
identifier = config["identifier"]
version = Version(config["version"])
@@ -488,13 +520,36 @@ class AllVersions:
versions = self.maven if identifier.startswith("cp313") else self.cpython
config_update = versions.update_version_android(identifier, spec)
elif "ios" in identifier:
config_update = self.ios_cpython.update_version_ios(identifier, version)
# Python 3.13 and 3.14 are released by BeeWare on GitHub.
# Python 3.15 and newer have official releases on python.org.
if identifier.startswith(("cp313", "cp314")):
config_update = self.ios_cpython.update_version_ios(identifier, version)
else:
config_update = self.cpython.update_version(
identifier, spec, "iOS-XCframework.tar.gz"
)
elif "pyodide" in identifier:
config_update = self.pyodide.update_version_pyodide(
identifier, version, spec, config["node_version"]
)
assert config_update is not None, f"{identifier} not found!"
# Fill in sha256 for URL-based configs if not already provided by the
# update_version_* method (e.g. PyPy, BeeWare iOS, Maven have no sidecar).
# Also fills in sha256 when the CPython API doesn't return a sha256_sum
# (e.g. for older releases).
# Widen the type to allow arbitrary key access on the underlying dict.
config_update_dict: dict[str, str] = config_update # type: ignore[assignment]
if "url" in config_update_dict and not config_update_dict.get("sha256"):
url = config_update_dict["url"]
existing_sha256 = config.get("sha256", "")
if url == config.get("url") and existing_sha256:
# URL unchanged — preserve the existing sha256
config_update_dict["sha256"] = existing_sha256
else:
config_update_dict["sha256"] = self._stream_sha256(url)
if config_update != config:
log.info(" Updated %s to %s", config, config_update)
config.clear()
+17 -1
View File
@@ -4,6 +4,7 @@
# dependencies = [
# "click",
# "packaging",
# "requests",
# "rich",
# "cibuildwheel",
# ]
@@ -15,12 +16,14 @@
import dataclasses
import difflib
import hashlib
import logging
import tomllib
from pathlib import Path
from typing import Final
import click
import requests
import rich
from packaging.version import Version
from rich.logging import RichHandler
@@ -92,16 +95,29 @@ def update_virtualenv(force: bool, level: str) -> None:
if latest_release.version > Version(local_version):
version = latest_release.name
url = latest_release.download_url
sha256 = "" # recomputed below
else:
version = local_version
url = default["url"]
sha256 = default.get("sha256", "")
# Compute sha256 if not already stored (new version or first-time population)
if not sha256:
log.info("Computing sha256 for %s...", url)
response = requests.get(url, stream=True)
response.raise_for_status()
hasher = hashlib.sha256()
for chunk in response.iter_content(65536):
hasher.update(chunk)
sha256 = hasher.hexdigest()
configurations["default"] = {
"version": version,
"url": url,
"sha256": sha256,
}
result_toml = "".join(
f'{key} = {{ version = "{value["version"]}", url = "{value["url"]}" }}\n'
f'{key} = {{ version = "{value["version"]}", url = "{value["url"]}", sha256 = "{value.get("sha256", "")}" }}\n'
for key, value in configurations.items()
)
+10 -5
View File
@@ -1,3 +1,5 @@
from __future__ import annotations
import argparse
import contextlib
import dataclasses
@@ -5,17 +7,15 @@ import functools
import os
import shutil
import sys
import tarfile
import textwrap
import traceback
import typing
from collections.abc import Generator, Iterable, Sequence
from pathlib import Path
from tempfile import mkdtemp
from typing import Any, Literal, TextIO
import cibuildwheel
from cibuildwheel import errors
from cibuildwheel._compat.tarfile import TarFile, safe_extractall
from cibuildwheel.architecture import Architecture, allowed_architectures_check
from cibuildwheel.ci import CIProvider, detect_ci_provider, fix_ansi_codes_for_github_actions
from cibuildwheel.logger import log
@@ -27,6 +27,11 @@ from cibuildwheel.util.file import CIBW_CACHE_PATH, ensure_cache_sentinel
from cibuildwheel.util.helpers import strtobool
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
@dataclasses.dataclass
class GlobalOptions:
@@ -262,8 +267,8 @@ def main_inner(global_options: GlobalOptions) -> None:
# Tarfile builds require extraction and changing the directory
temp_dir = Path(mkdtemp(prefix="cibw-sdist-")).resolve(strict=True)
try:
with tarfile.open(args.package_dir) as tar:
tar.extractall(path=temp_dir)
with TarFile.open(args.package_dir) as tar:
safe_extractall(tar, temp_dir)
# The extract directory is now the project dir
try:
View File
+71
View File
@@ -0,0 +1,71 @@
# Based on https://github.com/pypa/build/blob/f4ebd495cc0c2c74155bd4fe48b76399fb7927ac/src/build/_compat/tarfile.py
from __future__ import annotations
import sys
import tarfile
TYPE_CHECKING = False
if TYPE_CHECKING:
from pathlib import Path
TarFile = tarfile.TarFile
# Per https://peps.python.org/pep-0706/, the "data" filter will become
# the default in Python 3.14. The first series of releases with the filter
# had a broken filter that could not process symlinks correctly.
elif (3, 11, 5) <= sys.version_info < (3, 14):
class TarFile(tarfile.TarFile): # pragma: no cover
extraction_filter = staticmethod(tarfile.data_filter)
else:
TarFile = tarfile.TarFile # pragma: no cover
# Same availability matrix as the TarFile subclass above. On runtimes that
# ship the stdlib ``data`` filter we delegate to it; the fallback branch is
# only reached on 3.10.0-3.10.12 / 3.11.0-3.11.4 and validates each member
# manually before extraction.
if sys.version_info >= (3, 11, 5):
def safe_extractall(tar: tarfile.TarFile, path: Path) -> None: # pragma: no cover
"""Extract every member of ``tar`` into ``path`` via the PEP 706 ``data`` filter."""
tar.extractall(path, filter="data")
else:
def safe_extractall(tar: tarfile.TarFile, path: Path) -> None: # pragma: no cover
"""Validate every member of ``tar``, then extract into ``path``.
Reached on 3.10.0-3.10.12 / 3.11.0-3.11.4 where the stdlib ``data`` filter is missing. Device or special files,
paths that escape ``path``, and symlinks/hardlinks whose targets resolve outside ``path`` are rejected before
any write hits the disk.
"""
base = path.resolve()
for member in tar.getmembers():
_validate_safe_member(member, base)
tar.extractall(path)
def _validate_safe_member(member: tarfile.TarInfo, base: Path) -> None:
if member.ischr() or member.isblk() or member.isfifo():
msg = f"refusing to extract special device file {member.name!r}"
raise tarfile.TarError(msg)
target = (base / member.name).resolve(strict=False)
if not target.is_relative_to(base):
msg = f"refusing to extract {member.name!r}: path escapes destination"
raise tarfile.TarError(msg)
if member.issym() or member.islnk():
link_base = target.parent if member.issym() else base
link_target = (link_base / member.linkname).resolve(strict=False)
if not link_target.is_relative_to(base):
msg = f"refusing to extract {member.name!r}: link target escapes destination"
raise tarfile.TarError(msg)
__all__ = [
"TarFile",
"safe_extractall",
]
+9 -3
View File
@@ -1,15 +1,21 @@
from __future__ import annotations
import platform as platform_module
import re
import shutil
import subprocess
import sys
import typing
from collections.abc import Set
from enum import StrEnum, auto
from typing import Final, Literal, Self
from cibuildwheel import errors
from cibuildwheel.typing import PlatformName
TYPE_CHECKING = False
if TYPE_CHECKING:
from collections.abc import Set
from typing import Final, Literal, Self
from cibuildwheel.typing import PlatformName
PRETTY_NAMES: Final[dict[PlatformName, str]] = {
"linux": "Linux",
+6 -1
View File
@@ -1,15 +1,20 @@
from __future__ import annotations
import subprocess
import sys
from pathlib import Path
from cibuildwheel import errors
from cibuildwheel.logger import log
from cibuildwheel.options import BuildOptions
from cibuildwheel.util.cmd import call, shell
from cibuildwheel.util.helpers import prepare_command
from cibuildwheel.util.packaging import is_abi3_wheel
from cibuildwheel.venv import activate_virtualenv, find_uv, virtualenv
TYPE_CHECKING = False
if TYPE_CHECKING:
from cibuildwheel.options import BuildOptions
def run_audit(
*,
+13 -8
View File
@@ -1,16 +1,21 @@
from __future__ import annotations
import dataclasses
import subprocess
from collections.abc import (
Callable,
Iterable,
Mapping,
Sequence,
)
import bashlex
# a function that takes a command and the environment, and returns the result
EnvironmentExecutor = Callable[[list[str], dict[str, str]], str]
TYPE_CHECKING = False
if TYPE_CHECKING:
from collections.abc import (
Callable,
Iterable,
Mapping,
Sequence,
)
# a function that takes a command and the environment, and returns the result
EnvironmentExecutor = Callable[[list[str], dict[str, str]], str]
def local_environment_executor(command: Sequence[str], env: Mapping[str, str]) -> str:
+8 -2
View File
@@ -1,12 +1,18 @@
from __future__ import annotations
import dataclasses
from collections.abc import Mapping, Sequence
from typing import Any, Protocol
from typing import Protocol
import bashlex
import bashlex.errors
from cibuildwheel import bashlex_eval
TYPE_CHECKING = False
if TYPE_CHECKING:
from collections.abc import Mapping, Sequence
from typing import Any
class EnvironmentParseError(Exception):
pass
+2 -2
View File
@@ -76,7 +76,7 @@ class RepairStepProducedNoWheelError(FatalError):
Build failed because the repair step completed successfully but
did not produce a wheel.
Your `repair-wheel-command` is expected to place the repaired
Your `repair-wheel-command` must place the repaired
wheel in the {dest_dir} directory. See the documentation for
example configurations:
@@ -94,7 +94,7 @@ class RepairStepProducedMultipleWheelsError(FatalError):
Build failed because the repair step completed successfully but
produced multiple wheels: {wheels}
Your `repair-wheel-command` is expected to place one repaired
Your `repair-wheel-command` must place exactly one repaired
wheel in the {{dest_dir}} directory. See the documentation for
example configurations:
+10 -2
View File
@@ -2,17 +2,24 @@
These are utilities for the `/bin` scripts, not for the `cibuildwheel` program.
"""
from __future__ import annotations
import json
import time
import typing
import urllib.error
import urllib.request
from collections.abc import Mapping, Sequence
from io import StringIO
from typing import Any, NotRequired, Protocol
from typing import NotRequired, Protocol
from cibuildwheel import __version__ as cibw_version
TYPE_CHECKING = False
if TYPE_CHECKING:
from collections.abc import Mapping, Sequence
from typing import Any
__all__ = ("Printable", "dump_python_configurations")
@@ -83,6 +90,7 @@ def github_api_request(path: str, *, max_retries: int = 3) -> dict[str, Any]:
class PyodideXBuildEnvRelease(typing.TypedDict):
version: str
sha256: str
python_version: str
emscripten_version: str
min_pyodide_build_version: NotRequired[str]
+10 -3
View File
@@ -1,12 +1,19 @@
from __future__ import annotations
import dataclasses
import shlex
import typing
from collections.abc import Sequence
from typing import Literal, Self, get_args
from typing import Literal, get_args
from cibuildwheel.typing import PathOrStr
from cibuildwheel.util.helpers import parse_key_value_string, prepare_command
TYPE_CHECKING = False
if TYPE_CHECKING:
from collections.abc import Sequence
from typing import Self
from cibuildwheel.typing import PathOrStr
BuildFrontendName = Literal["pip", "build", "build[uv]", "uv"]
+10 -5
View File
@@ -1,3 +1,5 @@
from __future__ import annotations
import codecs
import contextlib
import dataclasses
@@ -9,18 +11,21 @@ import re
import sys
import textwrap
import time
from collections.abc import Generator
from pathlib import Path
from typing import IO, TYPE_CHECKING, AnyStr, Final, Literal
import humanize
from cibuildwheel.ci import CIProvider, detect_ci_provider, filter_ansi_codes
TYPE_CHECKING = False
if TYPE_CHECKING:
from collections.abc import Generator
from typing import IO, AnyStr, Final, Literal
from cibuildwheel.options import Options
FoldPattern = tuple[str, str]
FoldPattern = tuple[str, str]
DEFAULT_FOLD_PATTERN: Final[FoldPattern] = ("{name}", "")
FOLD_PATTERNS: Final[dict[str, FoldPattern]] = {
"azure": ("##[group]{name}", "##[endgroup]"),
@@ -234,7 +239,7 @@ class Logger:
print(f"cibuildwheel: {c.bright_red}error{c.end}: {error}\n", file=sys.stderr)
@contextlib.contextmanager
def print_summary(self, *, options: "Options") -> Generator[None, None, None]:
def print_summary(self, *, options: Options) -> Generator[None, None, None]:
start = time.time()
yield
duration = time.time() - start
@@ -292,7 +297,7 @@ class Logger:
# lowercase, shorten
return identifier.lower()[:20]
def _github_step_summary(self, duration: float, options: "Options") -> str:
def _github_step_summary(self, duration: float, options: Options) -> str:
"""
Returns the GitHub step summary, in markdown format.
"""
+16 -9
View File
@@ -1,3 +1,5 @@
from __future__ import annotations
import dataclasses
import io
import json
@@ -10,19 +12,25 @@ import sys
import textwrap
import typing
import uuid
from collections.abc import Mapping, Sequence
from enum import Enum
from pathlib import Path, PurePath, PurePosixPath
from types import TracebackType
from typing import IO, Literal, Self, assert_never
from pathlib import PurePosixPath
from typing import Literal, assert_never
from cibuildwheel.ci import CIProvider, detect_ci_provider
from cibuildwheel.errors import OCIEngineTooOldError
from cibuildwheel.logger import log
from cibuildwheel.typing import PathOrStr
from cibuildwheel.util.cmd import call
from cibuildwheel.util.helpers import FlexibleVersion, parse_key_value_string, strtobool
TYPE_CHECKING = False
if TYPE_CHECKING:
from collections.abc import Mapping, Sequence
from pathlib import Path, PurePath
from types import TracebackType
from typing import IO, Self
from cibuildwheel.typing import PathOrStr
ContainerEngineName = Literal["docker", "podman"]
@@ -525,13 +533,12 @@ class OCIContainer:
return self.call(command, env=environment, capture_output=True)
def debug_info(self) -> str:
command = [self.engine.name, "info"]
if self.engine.name == "podman":
command = f"{self.engine.name} info --debug"
else:
command = f"{self.engine.name} info"
command.append("--debug")
completed = subprocess.run(
command,
shell=True,
check=True,
cwd=self.cwd,
stdin=subprocess.PIPE,
+11 -3
View File
@@ -1,3 +1,5 @@
from __future__ import annotations
import collections
import configparser
import contextlib
@@ -8,9 +10,9 @@ import functools
import shlex
import textwrap
import tomllib
from collections.abc import Callable, Generator, Iterable, Mapping, Sequence, Set
from collections.abc import Mapping, Sequence
from pathlib import Path
from typing import Any, Final, Literal, Self, assert_never
from typing import assert_never
from packaging.specifiers import SpecifierSet
@@ -27,6 +29,11 @@ from cibuildwheel.util import resources
from cibuildwheel.util.helpers import format_safe, parse_key_value_string, strtobool, unwrap
from cibuildwheel.util.packaging import DependencyConstraints
TYPE_CHECKING = False
if TYPE_CHECKING:
from collections.abc import Callable, Generator, Iterable, Set
from typing import Any, Final, Literal, Self
MANYLINUX_ARCHS: Final[tuple[str, ...]] = (
"x86_64",
"i686",
@@ -708,11 +715,12 @@ class Options:
def _check_pinned_image(self, value: str, pinned_images: Mapping[str, str]) -> None:
error_set = {"manylinux1", "manylinux2010", "manylinux_2_24", "musllinux_1_1"}
# Currently no warnings, next: https://github.com/pypa/manylinux/issues/1925
warning_set: set[str] = set()
if value in error_set:
msg = (
f"cibuildwheel 3.x does not support the image {value!r}. Either upgrade to a "
f"cibuildwheel 4.x does not support the image {value!r}. Either upgrade to a "
"supported image or continue using the image by pinning it directly with"
" its full OCI registry '<name>{:<tag>|@<digest>}'."
)
+14 -7
View File
@@ -1,14 +1,21 @@
from __future__ import annotations
import sys
from collections.abc import Sequence
from pathlib import Path
from typing import Final, Protocol
from typing import Protocol
from cibuildwheel import errors
from cibuildwheel.architecture import Architecture
from cibuildwheel.options import Options
from cibuildwheel.platforms import android, ios, linux, macos, pyodide, windows
from cibuildwheel.selector import BuildSelector
from cibuildwheel.typing import GenericPythonConfiguration, PlatformName
TYPE_CHECKING = False
if TYPE_CHECKING:
from collections.abc import Sequence
from pathlib import Path
from typing import Final
from cibuildwheel.architecture import Architecture
from cibuildwheel.options import Options
from cibuildwheel.selector import BuildSelector
from cibuildwheel.typing import GenericPythonConfiguration, PlatformName
class PlatformModule(Protocol):
+11 -5
View File
@@ -1,3 +1,5 @@
from __future__ import annotations
import csv
import hashlib
import os
@@ -7,7 +9,6 @@ import shlex
import shutil
import subprocess
import sysconfig
from collections.abc import Iterable, Iterator, MutableMapping
from dataclasses import dataclass
from os.path import relpath
from pathlib import Path
@@ -31,8 +32,6 @@ from cibuildwheel.frontend import (
prepare_config_settings,
)
from cibuildwheel.logger import log
from cibuildwheel.options import BuildOptions, Options
from cibuildwheel.selector import BuildSelector
from cibuildwheel.util import resources
from cibuildwheel.util.cmd import call, shell
from cibuildwheel.util.file import CIBW_CACHE_PATH, copy_test_sources, download, move_file
@@ -41,8 +40,14 @@ from cibuildwheel.util.packaging import find_compatible_wheel
from cibuildwheel.util.python_build_standalone import create_python_build_standalone_environment
from cibuildwheel.venv import constraint_flags, find_uv, virtualenv
RESOURCES_ANDROID = resources.PATH / "android"
TYPE_CHECKING = False
if TYPE_CHECKING:
from collections.abc import Iterable, Iterator, MutableMapping
from cibuildwheel.options import BuildOptions, Options
from cibuildwheel.selector import BuildSelector
RESOURCES_ANDROID = resources.PATH / "android"
ANDROID_TRIPLET = {
"arm64_v8a": "aarch64-linux-android",
"x86_64": "x86_64-linux-android",
@@ -67,6 +72,7 @@ class PythonConfiguration:
version: str
identifier: str
url: str
sha256: str = ""
@property
def arch(self) -> str:
@@ -179,7 +185,7 @@ def setup_target_python(config: PythonConfiguration, build_path: Path) -> Path:
python_tgz = CIBW_CACHE_PATH / config.url.rpartition("/")[-1]
with FileLock(f"{python_tgz}.lock"):
if not python_tgz.exists():
download(config.url, python_tgz)
download(config.url, python_tgz, sha256=config.sha256 or None)
python_dir = build_path / "python"
python_dir.mkdir()
+43 -16
View File
@@ -1,3 +1,5 @@
from __future__ import annotations
import dataclasses
import os
import platform
@@ -6,25 +8,21 @@ import shutil
import subprocess
import sys
import textwrap
from collections.abc import Sequence, Set
from pathlib import Path
from typing import assert_never
from filelock import FileLock
from packaging.version import Version
from cibuildwheel import errors
from cibuildwheel.architecture import Architecture
from cibuildwheel.audit import run_audit
from cibuildwheel.environment import ParsedEnvironment
from cibuildwheel.frontend import (
BuildFrontendName,
get_build_frontend_extra_flags,
prepare_config_settings,
)
from cibuildwheel.logger import log
from cibuildwheel.options import Options
from cibuildwheel.platforms.macos import install_cpython as install_build_cpython
from cibuildwheel.selector import BuildSelector
from cibuildwheel.util import resources
from cibuildwheel.util.cmd import call, shell, split_command
from cibuildwheel.util.file import CIBW_CACHE_PATH, copy_test_sources, download, move_file
@@ -32,6 +30,15 @@ from cibuildwheel.util.helpers import prepare_command, unwrap_preserving_paragra
from cibuildwheel.util.packaging import find_compatible_wheel
from cibuildwheel.venv import constraint_flags, virtualenv
TYPE_CHECKING = False
if TYPE_CHECKING:
from collections.abc import Sequence, Set
from cibuildwheel.architecture import Architecture
from cibuildwheel.environment import ParsedEnvironment
from cibuildwheel.options import Options
from cibuildwheel.selector import BuildSelector
@dataclasses.dataclass(frozen=True, kw_only=True)
class PythonConfiguration:
@@ -39,6 +46,7 @@ class PythonConfiguration:
identifier: str
url: str
build_url: str
sha256: str = ""
@property
def sdk(self) -> str:
@@ -126,7 +134,7 @@ def install_target_cpython(tmp: Path, config: PythonConfiguration, free_threadin
with FileLock(str(installation_path) + ".lock"):
if not installation_path.exists():
downloaded_tar_gz = tmp / ios_python_tar_gz
download(config.url, downloaded_tar_gz)
download(config.url, downloaded_tar_gz, sha256=config.sha256 or None)
installation_path.mkdir(parents=True, exist_ok=True)
call("tar", "-C", installation_path, "-xf", downloaded_tar_gz)
downloaded_tar_gz.unlink()
@@ -183,18 +191,37 @@ def cross_virtualenv(
)
# Convert the macOS virtual environment into an iOS virtual environment
# using the cross-platform conversion script in the iOS distribution.
# using make_cross_venv.py.
# target_python is the path to the Python binary;
# determine the root of the XCframework slice that is being used.
slice_path = target_python.parent.parent
call(
"python",
str(slice_path / f"platform-config/{multiarch}/make_cross_venv.py"),
str(venv_path),
env=env,
cwd=venv_path,
)
if Version(py_version) >= Version("3.15"):
# python.org 3.15+ distributions: sysconfig data lives in the
# stdlib directory (lib-<arch>/python<version>); cross-venv scripts
# come from cibuildwheel resources.
arch = multiarch.split("-", maxsplit=1)[0]
stdlib_path = slice_path / f"lib-{arch}" / f"python{py_version}"
call(
"python",
str(resources.IOS_SUPPORT_FILES / "make_cross_venv.py"),
str(venv_path),
str(stdlib_path),
str(resources.IOS_SUPPORT_FILES),
env=env,
cwd=venv_path,
)
else:
# BeeWare distributions: platform-config already contains both
# sysconfig data and the make_cross_venv.py script.
platform_config_path = slice_path / f"platform-config/{multiarch}"
call(
"python",
str(platform_config_path / "make_cross_venv.py"),
str(venv_path),
env=env,
cwd=venv_path,
)
# When running on macOS, it's easy for the build environment to leak into
# the target environment, especially when building for ARM64 (because the
@@ -312,8 +339,8 @@ def setup_python(
/ f"python{python_configuration.version}"
)
assert target_python.exists(), (
f"{target_python.name} not found, has {list(target_install_path.iterdir())}"
assert target_python.parent.exists(), (
f"{target_python.parent} not found, has {list(target_install_path.iterdir())}"
)
log.step("Creating cross build environment...")
+8 -4
View File
@@ -1,3 +1,5 @@
from __future__ import annotations
import contextlib
import dataclasses
import shutil
@@ -5,9 +7,8 @@ import subprocess
import sys
import textwrap
from collections import OrderedDict
from collections.abc import Iterable, Iterator, Sequence, Set
from pathlib import Path, PurePath, PurePosixPath
from typing import TYPE_CHECKING, assert_never
from typing import assert_never
from cibuildwheel import errors
from cibuildwheel.architecture import Architecture
@@ -15,14 +16,17 @@ from cibuildwheel.audit import needs_audit, run_audit
from cibuildwheel.frontend import get_build_frontend_extra_flags, prepare_config_settings
from cibuildwheel.logger import log
from cibuildwheel.oci_container import OCIContainer, OCIContainerEngineConfig, OCIPlatform
from cibuildwheel.options import BuildOptions, Options
from cibuildwheel.selector import BuildSelector
from cibuildwheel.util import resources
from cibuildwheel.util.file import copy_test_sources
from cibuildwheel.util.helpers import prepare_command, unwrap
from cibuildwheel.util.packaging import find_compatible_wheel
TYPE_CHECKING = False
if TYPE_CHECKING:
from collections.abc import Iterable, Iterator, Sequence, Set
from cibuildwheel.options import BuildOptions, Options
from cibuildwheel.selector import BuildSelector
from cibuildwheel.typing import PathOrStr
ARCHITECTURE_OCI_PLATFORM_MAP = {
+29 -15
View File
@@ -1,3 +1,5 @@
from __future__ import annotations
import dataclasses
import functools
import inspect
@@ -8,26 +10,21 @@ import shutil
import subprocess
import sys
import typing
from collections.abc import Set
from pathlib import Path
from typing import Literal, assert_never
from typing import assert_never
from filelock import FileLock
from packaging.version import Version
from cibuildwheel import errors
from cibuildwheel.architecture import Architecture
from cibuildwheel.audit import run_audit
from cibuildwheel.ci import detect_ci_provider
from cibuildwheel.environment import ParsedEnvironment
from cibuildwheel.frontend import (
BuildFrontendName,
get_build_frontend_extra_flags,
prepare_config_settings,
)
from cibuildwheel.logger import log
from cibuildwheel.options import Options
from cibuildwheel.selector import BuildSelector
from cibuildwheel.util import resources
from cibuildwheel.util.cmd import call, shell
from cibuildwheel.util.file import CIBW_CACHE_PATH, copy_test_sources, download, move_file
@@ -35,6 +32,16 @@ from cibuildwheel.util.helpers import prepare_command, unwrap
from cibuildwheel.util.packaging import find_compatible_wheel, get_pip_version
from cibuildwheel.venv import constraint_flags, find_uv, target_marker_env, virtualenv
TYPE_CHECKING = False
if TYPE_CHECKING:
from collections.abc import Set
from typing import Literal
from cibuildwheel.architecture import Architecture
from cibuildwheel.environment import ParsedEnvironment
from cibuildwheel.options import Options
from cibuildwheel.selector import BuildSelector
@functools.cache
def get_macos_version() -> tuple[int, int]:
@@ -81,6 +88,7 @@ class PythonConfiguration:
version: str
identifier: str
url: str
sha256: str = ""
def all_python_configurations() -> list[PythonConfiguration]:
@@ -128,7 +136,9 @@ def get_python_configurations(
return python_configurations
def install_cpython(_tmp: Path, version: str, url: str, free_threading: bool) -> Path:
def install_cpython(
_tmp: Path, version: str, url: str, free_threading: bool, sha256: str = ""
) -> Path:
ft = "T" if free_threading else ""
installation_path = Path(f"/Library/Frameworks/Python{ft}.framework/Versions/{version}")
with FileLock(CIBW_CACHE_PATH / f"cpython{version}.lock"):
@@ -154,7 +164,7 @@ def install_cpython(_tmp: Path, version: str, url: str, free_threading: bool) ->
python_filename = url.rsplit("/", maxsplit=1)[-1]
pkg_path = CIBW_CACHE_PATH / "cpython-installer" / python_filename
if not pkg_path.exists():
download(url, pkg_path)
download(url, pkg_path, sha256=sha256 or None)
args = []
if version.startswith("3.14"):
args += ["-applyChoiceChangesXML", str(resources.FREE_THREAD_ENABLE_314.resolve())]
@@ -178,7 +188,7 @@ def install_cpython(_tmp: Path, version: str, url: str, free_threading: bool) ->
return installation_path / "bin" / (f"python{version}t" if free_threading else "python3")
def install_pypy(tmp: Path, url: str) -> Path:
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)
@@ -186,14 +196,14 @@ def install_pypy(tmp: Path, url: str) -> Path:
with FileLock(str(installation_path) + ".lock"):
if not installation_path.exists():
downloaded_tar_bz2 = tmp / pypy_tar_bz2
download(url, downloaded_tar_bz2)
download(url, downloaded_tar_bz2, sha256=sha256 or None)
installation_path.parent.mkdir(parents=True, exist_ok=True)
call("tar", "-C", installation_path.parent, "-xf", downloaded_tar_bz2)
downloaded_tar_bz2.unlink()
return installation_path / "bin" / "pypy3"
def install_graalpy(tmp: Path, url: str) -> Path:
def install_graalpy(tmp: Path, url: str, sha256: str = "") -> Path:
graalpy_archive = url.rsplit("/", 1)[-1]
extension = ".tar.gz"
assert graalpy_archive.endswith(extension)
@@ -201,7 +211,7 @@ def install_graalpy(tmp: Path, url: str) -> Path:
with FileLock(str(installation_path) + ".lock"):
if not installation_path.exists():
downloaded_archive = tmp / graalpy_archive
download(url, downloaded_archive)
download(url, downloaded_archive, sha256=sha256 or None)
installation_path.mkdir(parents=True)
# GraalPy top-folder name is inconsistent with archive name
call("tar", "-C", installation_path, "--strip-components=1", "-xzf", downloaded_archive)
@@ -225,13 +235,17 @@ def setup_python(
if implementation_id.startswith("cp"):
free_threading = "t-macos" in python_configuration.identifier
base_python = install_cpython(
tmp, python_configuration.version, python_configuration.url, free_threading
tmp,
python_configuration.version,
python_configuration.url,
free_threading,
python_configuration.sha256,
)
elif implementation_id.startswith("pp"):
base_python = install_pypy(tmp, python_configuration.url)
base_python = install_pypy(tmp, python_configuration.url, python_configuration.sha256)
elif implementation_id.startswith("gp"):
base_python = install_graalpy(tmp, python_configuration.url)
base_python = install_graalpy(tmp, python_configuration.url, python_configuration.sha256)
else:
msg = "Unknown Python implementation"
raise ValueError(msg)
+11 -4
View File
@@ -1,3 +1,5 @@
from __future__ import annotations
import dataclasses
import functools
import json
@@ -7,7 +9,6 @@ import subprocess
import sys
import tomllib
import typing
from collections.abc import Set
from pathlib import Path
from tempfile import TemporaryDirectory
from typing import Final, TypedDict
@@ -17,11 +18,8 @@ from filelock import FileLock
from cibuildwheel import errors
from cibuildwheel.architecture import Architecture
from cibuildwheel.audit import run_audit
from cibuildwheel.environment import ParsedEnvironment
from cibuildwheel.frontend import get_build_frontend_extra_flags, prepare_config_settings
from cibuildwheel.logger import log
from cibuildwheel.options import Options
from cibuildwheel.selector import BuildSelector
from cibuildwheel.util import resources
from cibuildwheel.util.cmd import call, shell
from cibuildwheel.util.file import (
@@ -40,6 +38,14 @@ from cibuildwheel.util.python_build_standalone import (
)
from cibuildwheel.venv import constraint_flags, virtualenv
TYPE_CHECKING = False
if TYPE_CHECKING:
from collections.abc import Set
from cibuildwheel.environment import ParsedEnvironment
from cibuildwheel.options import Options
from cibuildwheel.selector import BuildSelector
IS_WIN: Final[bool] = sys.platform.startswith("win")
@@ -49,6 +55,7 @@ class PythonConfiguration:
identifier: str
default_pyodide_version: str
node_version: str
sha256: str = ""
class PyodideXBuildEnvInfoVersionRange(TypedDict):
+21 -10
View File
@@ -1,3 +1,5 @@
from __future__ import annotations
import dataclasses
import json
import os
@@ -5,7 +7,6 @@ import platform as platform_module
import shutil
import subprocess
import textwrap
from collections.abc import MutableMapping, Sequence, Set
from functools import cache
from pathlib import Path
from typing import assert_never
@@ -15,15 +16,12 @@ from filelock import FileLock
from cibuildwheel import errors
from cibuildwheel.architecture import Architecture
from cibuildwheel.audit import run_audit
from cibuildwheel.environment import ParsedEnvironment
from cibuildwheel.frontend import (
BuildFrontendName,
get_build_frontend_extra_flags,
prepare_config_settings,
)
from cibuildwheel.logger import log
from cibuildwheel.options import Options
from cibuildwheel.selector import BuildSelector
from cibuildwheel.util import resources
from cibuildwheel.util.cmd import call, shell
from cibuildwheel.util.file import (
@@ -37,6 +35,14 @@ from cibuildwheel.util.helpers import prepare_command, unwrap
from cibuildwheel.util.packaging import find_compatible_wheel, get_pip_version
from cibuildwheel.venv import constraint_flags, find_uv, target_marker_env, virtualenv
TYPE_CHECKING = False
if TYPE_CHECKING:
from collections.abc import MutableMapping, Sequence, Set
from cibuildwheel.environment import ParsedEnvironment
from cibuildwheel.options import Options
from cibuildwheel.selector import BuildSelector
def get_nuget_args(
version: str, arch: str, free_threaded: bool, output_directory: Path
@@ -67,6 +73,7 @@ class PythonConfiguration:
version: str
identifier: str
url: str | None = None
sha256: str = ""
@property
def arch(self) -> str:
@@ -125,7 +132,7 @@ def install_cpython(configuration: PythonConfiguration, arch: str | None = None)
return installation_path / "python.exe"
def install_pypy(tmp: Path, arch: str, url: str) -> Path:
def install_pypy(tmp: Path, arch: str, url: str, sha256: str = "") -> Path:
assert arch == "64"
assert "win64" in url
# Inside the PyPy zip file is a directory with the same name
@@ -136,13 +143,13 @@ def install_pypy(tmp: Path, arch: str, url: str) -> Path:
with FileLock(str(installation_path) + ".lock"):
if not installation_path.exists():
pypy_zip = tmp / zip_filename
download(url, pypy_zip)
download(url, pypy_zip, sha256=sha256 or None)
# Extract to the parent directory because the zip file still contains a directory
extract_zip(pypy_zip, installation_path.parent)
return installation_path / "python.exe"
def install_graalpy(tmp: Path, url: str) -> Path:
def install_graalpy(tmp: Path, url: str, sha256: str = "") -> Path:
zip_filename = url.rsplit("/", 1)[-1]
extension = ".zip"
assert zip_filename.endswith(extension)
@@ -150,7 +157,7 @@ def install_graalpy(tmp: Path, url: str) -> Path:
with FileLock(str(installation_path) + ".lock"):
if not installation_path.exists():
graalpy_zip = tmp / zip_filename
download(url, graalpy_zip)
download(url, graalpy_zip, sha256=sha256 or None)
# Extract to the parent directory because the zip file still contains a directory
extract_zip(graalpy_zip, installation_path.parent)
return installation_path / "bin" / "graalpy.exe"
@@ -266,9 +273,13 @@ def setup_python(
base_python = install_cpython(python_configuration, arch=native_arch)
elif implementation_id.startswith("pp"):
assert python_configuration.url is not None
base_python = install_pypy(tmp, python_configuration.arch, python_configuration.url)
base_python = install_pypy(
tmp, python_configuration.arch, python_configuration.url, python_configuration.sha256
)
elif implementation_id.startswith("gp"):
base_python = install_graalpy(tmp, python_configuration.url or "")
base_python = install_graalpy(
tmp, python_configuration.url or "", python_configuration.sha256
)
else:
msg = "Unknown Python implementation"
raise ValueError(msg)
+7 -2
View File
@@ -1,11 +1,16 @@
from __future__ import annotations
import ast
import configparser
import contextlib
from pathlib import Path
from typing import Any
import dependency_groups
TYPE_CHECKING = False
if TYPE_CHECKING:
from pathlib import Path
from typing import Any
def get_parent(node: ast.AST | None, depth: int = 1) -> ast.AST | None:
for _ in range(depth):
+60 -55
View File
@@ -143,43 +143,43 @@ python_configurations = [
[macos]
python_configurations = [
{ identifier = "cp39-macosx_x86_64", version = "3.9", url = "https://www.python.org/ftp/python/3.9.13/python-3.9.13-macos11.pkg" },
{ identifier = "cp39-macosx_arm64", version = "3.9", url = "https://www.python.org/ftp/python/3.9.13/python-3.9.13-macos11.pkg" },
{ identifier = "cp39-macosx_universal2", version = "3.9", url = "https://www.python.org/ftp/python/3.9.13/python-3.9.13-macos11.pkg" },
{ identifier = "cp310-macosx_x86_64", version = "3.10", url = "https://www.python.org/ftp/python/3.10.11/python-3.10.11-macos11.pkg" },
{ identifier = "cp310-macosx_arm64", version = "3.10", url = "https://www.python.org/ftp/python/3.10.11/python-3.10.11-macos11.pkg" },
{ identifier = "cp310-macosx_universal2", version = "3.10", url = "https://www.python.org/ftp/python/3.10.11/python-3.10.11-macos11.pkg" },
{ identifier = "cp311-macosx_x86_64", version = "3.11", url = "https://www.python.org/ftp/python/3.11.9/python-3.11.9-macos11.pkg" },
{ identifier = "cp311-macosx_arm64", version = "3.11", url = "https://www.python.org/ftp/python/3.11.9/python-3.11.9-macos11.pkg" },
{ identifier = "cp311-macosx_universal2", version = "3.11", url = "https://www.python.org/ftp/python/3.11.9/python-3.11.9-macos11.pkg" },
{ identifier = "cp312-macosx_x86_64", version = "3.12", url = "https://www.python.org/ftp/python/3.12.10/python-3.12.10-macos11.pkg" },
{ identifier = "cp312-macosx_arm64", version = "3.12", url = "https://www.python.org/ftp/python/3.12.10/python-3.12.10-macos11.pkg" },
{ identifier = "cp312-macosx_universal2", version = "3.12", url = "https://www.python.org/ftp/python/3.12.10/python-3.12.10-macos11.pkg" },
{ identifier = "cp313-macosx_x86_64", version = "3.13", url = "https://www.python.org/ftp/python/3.13.13/python-3.13.13-macos11.pkg" },
{ identifier = "cp313-macosx_arm64", version = "3.13", url = "https://www.python.org/ftp/python/3.13.13/python-3.13.13-macos11.pkg" },
{ identifier = "cp313-macosx_universal2", version = "3.13", url = "https://www.python.org/ftp/python/3.13.13/python-3.13.13-macos11.pkg" },
{ identifier = "cp314-macosx_x86_64", version = "3.14", url = "https://www.python.org/ftp/python/3.14.5/python-3.14.5-macos11.pkg" },
{ identifier = "cp314-macosx_arm64", version = "3.14", url = "https://www.python.org/ftp/python/3.14.5/python-3.14.5-macos11.pkg" },
{ identifier = "cp314-macosx_universal2", version = "3.14", url = "https://www.python.org/ftp/python/3.14.5/python-3.14.5-macos11.pkg" },
{ identifier = "cp314t-macosx_x86_64", version = "3.14", url = "https://www.python.org/ftp/python/3.14.5/python-3.14.5-macos11.pkg" },
{ identifier = "cp314t-macosx_arm64", version = "3.14", url = "https://www.python.org/ftp/python/3.14.5/python-3.14.5-macos11.pkg" },
{ identifier = "cp314t-macosx_universal2", version = "3.14", url = "https://www.python.org/ftp/python/3.14.5/python-3.14.5-macos11.pkg" },
{ identifier = "cp315-macosx_x86_64", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b1-macos11.pkg" },
{ identifier = "cp315-macosx_arm64", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b1-macos11.pkg" },
{ identifier = "cp315-macosx_universal2", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b1-macos11.pkg" },
{ identifier = "cp315t-macosx_x86_64", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b1-macos11.pkg" },
{ identifier = "cp315t-macosx_arm64", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b1-macos11.pkg" },
{ identifier = "cp315t-macosx_universal2", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b1-macos11.pkg" },
{ identifier = "pp39-macosx_x86_64", version = "3.9", url = "https://downloads.python.org/pypy/pypy3.9-v7.3.16-macos_x86_64.tar.bz2" },
{ identifier = "pp39-macosx_arm64", version = "3.9", url = "https://downloads.python.org/pypy/pypy3.9-v7.3.16-macos_arm64.tar.bz2" },
{ identifier = "pp310-macosx_x86_64", version = "3.10", url = "https://downloads.python.org/pypy/pypy3.10-v7.3.19-macos_x86_64.tar.bz2" },
{ identifier = "pp310-macosx_arm64", version = "3.10", url = "https://downloads.python.org/pypy/pypy3.10-v7.3.19-macos_arm64.tar.bz2" },
{ identifier = "pp311-macosx_x86_64", version = "3.11", url = "https://downloads.python.org/pypy/pypy3.11-v7.3.22-macos_x86_64.tar.bz2" },
{ identifier = "pp311-macosx_arm64", version = "3.11", url = "https://downloads.python.org/pypy/pypy3.11-v7.3.22-macos_arm64.tar.bz2" },
{ 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" },
{ 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" },
{ 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" },
{ identifier = "gp312_250-macosx_arm64", version = "3.12", url = "https://github.com/oracle/graalpython/releases/download/graal-25.0.2/graalpy-25.0.2-macos-aarch64.tar.gz" },
{ identifier = "cp39-macosx_x86_64", version = "3.9", url = "https://www.python.org/ftp/python/3.9.13/python-3.9.13-macos11.pkg", sha256 = "351fe18f4fb03be7afac5e4012fc0a51345f43202af43ef620cf1eee5ee36578" },
{ identifier = "cp39-macosx_arm64", version = "3.9", url = "https://www.python.org/ftp/python/3.9.13/python-3.9.13-macos11.pkg", sha256 = "351fe18f4fb03be7afac5e4012fc0a51345f43202af43ef620cf1eee5ee36578" },
{ identifier = "cp39-macosx_universal2", version = "3.9", url = "https://www.python.org/ftp/python/3.9.13/python-3.9.13-macos11.pkg", sha256 = "351fe18f4fb03be7afac5e4012fc0a51345f43202af43ef620cf1eee5ee36578" },
{ identifier = "cp310-macosx_x86_64", version = "3.10", url = "https://www.python.org/ftp/python/3.10.11/python-3.10.11-macos11.pkg", sha256 = "767ed35ad688d28ea4494081ae96408a0318d0d5bb9ca0139d74d6247b231cfc" },
{ identifier = "cp310-macosx_arm64", version = "3.10", url = "https://www.python.org/ftp/python/3.10.11/python-3.10.11-macos11.pkg", sha256 = "767ed35ad688d28ea4494081ae96408a0318d0d5bb9ca0139d74d6247b231cfc" },
{ identifier = "cp310-macosx_universal2", version = "3.10", url = "https://www.python.org/ftp/python/3.10.11/python-3.10.11-macos11.pkg", sha256 = "767ed35ad688d28ea4494081ae96408a0318d0d5bb9ca0139d74d6247b231cfc" },
{ identifier = "cp311-macosx_x86_64", version = "3.11", url = "https://www.python.org/ftp/python/3.11.9/python-3.11.9-macos11.pkg", sha256 = "b6cfdee2571ca56ee895043ca1e7110fb78a878cee3eb0c21accb2de34d24b55" },
{ identifier = "cp311-macosx_arm64", version = "3.11", url = "https://www.python.org/ftp/python/3.11.9/python-3.11.9-macos11.pkg", sha256 = "b6cfdee2571ca56ee895043ca1e7110fb78a878cee3eb0c21accb2de34d24b55" },
{ identifier = "cp311-macosx_universal2", version = "3.11", url = "https://www.python.org/ftp/python/3.11.9/python-3.11.9-macos11.pkg", sha256 = "b6cfdee2571ca56ee895043ca1e7110fb78a878cee3eb0c21accb2de34d24b55" },
{ 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.0b1-macos11.pkg", sha256 = "d9e6de70d8bb02c9300b9d8e8b56aadb8dd426073d97e6225ac5319b3eb6d84e" },
{ identifier = "cp315-macosx_arm64", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b1-macos11.pkg", sha256 = "d9e6de70d8bb02c9300b9d8e8b56aadb8dd426073d97e6225ac5319b3eb6d84e" },
{ identifier = "cp315-macosx_universal2", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b1-macos11.pkg", sha256 = "d9e6de70d8bb02c9300b9d8e8b56aadb8dd426073d97e6225ac5319b3eb6d84e" },
{ identifier = "cp315t-macosx_x86_64", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b1-macos11.pkg", sha256 = "d9e6de70d8bb02c9300b9d8e8b56aadb8dd426073d97e6225ac5319b3eb6d84e" },
{ identifier = "cp315t-macosx_arm64", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b1-macos11.pkg", sha256 = "d9e6de70d8bb02c9300b9d8e8b56aadb8dd426073d97e6225ac5319b3eb6d84e" },
{ identifier = "cp315t-macosx_universal2", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b1-macos11.pkg", sha256 = "d9e6de70d8bb02c9300b9d8e8b56aadb8dd426073d97e6225ac5319b3eb6d84e" },
{ 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 = "" },
{ 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 = "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 = "" },
{ 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" },
]
[windows]
@@ -211,34 +211,39 @@ python_configurations = [
{ identifier = "cp314t-win_arm64", version = "3.14.5" },
{ identifier = "cp315-win_arm64", version = "3.15.0-b1" },
{ identifier = "cp315t-win_arm64", version = "3.15.0-b1" },
{ identifier = "pp39-win_amd64", version = "3.9", url = "https://downloads.python.org/pypy/pypy3.9-v7.3.16-win64.zip" },
{ identifier = "pp310-win_amd64", version = "3.10", url = "https://downloads.python.org/pypy/pypy3.10-v7.3.19-win64.zip" },
{ identifier = "pp311-win_amd64", version = "3.11", url = "https://downloads.python.org/pypy/pypy3.11-v7.3.22-win64.zip" },
{ 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" },
{ identifier = "gp312_250-win_amd64", version = "3.12", url = "https://github.com/oracle/graalpython/releases/download/graal-25.0.2/graalpy-25.0.2-windows-amd64.zip" },
{ 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" },
]
[pyodide]
python_configurations = [
{ identifier = "cp312-pyodide_wasm32", version = "3.12", default_pyodide_version = "0.27.7", node_version = "v22" },
{ identifier = "cp313-pyodide_wasm32", version = "3.13", default_pyodide_version = "0.29.4", node_version = "v22" },
{ identifier = "cp314-pyodide_wasm32", version = "3.14", default_pyodide_version = "314.0.0a2", node_version = "v24" },
{ 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" },
]
[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" },
{ 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" },
{ 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" },
{ 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" },
{ 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 = "cp315-android_arm64_v8a", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b1-aarch64-linux-android.tar.gz", sha256 = "9a25e5499d639d4f59bc766ad36c9eddd892232a9f9224c2fa4ef55dc1d49ff3" },
{ identifier = "cp315-android_x86_64", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b1-x86_64-linux-android.tar.gz", sha256 = "86f492e92340028c8c7efedde6760b40f5e645062d0ef17af0d0bf4d18b127f2" },
]
[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" },
{ 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" },
{ 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" },
{ 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" },
{ 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" },
{ 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" },
{ 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.0b1-iOS-XCframework.tar.gz", sha256 = "abd43fc362bb6e40a5233a499aa3fa96bf49306ca10b4cbcb27418778e429eb8" },
{ identifier = "cp315-ios_x86_64_iphonesimulator", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b1-iOS-XCframework.tar.gz", sha256 = "abd43fc362bb6e40a5233a499aa3fa96bf49306ca10b4cbcb27418778e429eb8" },
{ identifier = "cp315-ios_arm64_iphonesimulator", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b1-iOS-XCframework.tar.gz", sha256 = "abd43fc362bb6e40a5233a499aa3fa96bf49306ca10b4cbcb27418778e429eb8" },
]
@@ -8,7 +8,7 @@ build==1.5.0
# via
# -r .nox/update_constraints/tmp/constraints-pyodide.in
# pyodide-build
certifi==2026.4.22
certifi==2026.5.20
# via requests
charset-normalizer==3.4.7
# via requests
@@ -23,7 +23,7 @@ filelock==3.29.0
# via
# python-discovery
# virtualenv
idna==3.15
idna==3.16
# via requests
leb128==1.0.9
# via auditwheel-emscripten
@@ -52,7 +52,7 @@ pydantic-core==2.46.4
# via pydantic
pygments==2.20.0
# via rich
pyodide-build==0.34.3
pyodide-build==0.34.4
# via -r .nox/update_constraints/tmp/constraints-pyodide.in
pyodide-cli==0.5.0
# via
@@ -64,7 +64,7 @@ pyproject-hooks==1.2.0
# via build
python-discovery==1.3.1
# via virtualenv
requests==2.34.1
requests==2.34.2
# via pyodide-build
rich==15.0.0
# via
@@ -8,7 +8,7 @@ build==1.5.0
# via
# -r .nox/update_constraints/tmp/constraints-pyodide.in
# pyodide-build
certifi==2026.4.22
certifi==2026.5.20
# via requests
charset-normalizer==3.4.7
# via requests
@@ -23,7 +23,7 @@ filelock==3.29.0
# via
# python-discovery
# virtualenv
idna==3.15
idna==3.16
# via requests
leb128==1.0.9
# via auditwheel-emscripten
@@ -52,7 +52,7 @@ pydantic-core==2.46.4
# via pydantic
pygments==2.20.0
# via rich
pyodide-build==0.34.3
pyodide-build==0.34.4
# via -r .nox/update_constraints/tmp/constraints-pyodide.in
pyodide-cli==0.5.0
# via
@@ -64,7 +64,7 @@ pyproject-hooks==1.2.0
# via build
python-discovery==1.3.1
# via virtualenv
requests==2.34.1
requests==2.34.2
# via pyodide-build
rich==15.0.0
# via
@@ -8,7 +8,7 @@ build==1.5.0
# via
# -r .nox/update_constraints/tmp/constraints-pyodide.in
# pyodide-build
certifi==2026.4.22
certifi==2026.5.20
# via requests
charset-normalizer==3.4.7
# via requests
@@ -23,7 +23,7 @@ filelock==3.29.0
# via
# python-discovery
# virtualenv
idna==3.15
idna==3.16
# via requests
leb128==1.0.9
# via auditwheel-emscripten
@@ -52,7 +52,7 @@ pydantic-core==2.46.4
# via pydantic
pygments==2.20.0
# via rich
pyodide-build==0.34.3
pyodide-build==0.34.4
# via -r .nox/update_constraints/tmp/constraints-pyodide.in
pyodide-cli==0.5.0
# via
@@ -64,7 +64,7 @@ pyproject-hooks==1.2.0
# via build
python-discovery==1.3.1
# via virtualenv
requests==2.34.1
requests==2.34.2
# via pyodide-build
rich==15.0.0
# via
@@ -14,7 +14,7 @@ build==1.5.0
# via -r cibuildwheel/resources/constraints.in
cattrs==26.1.0
# via requests-cache
certifi==2026.4.22
certifi==2026.5.20
# via requests
charset-normalizer==3.4.7
# via requests
@@ -30,7 +30,7 @@ filelock==3.29.0
# via
# python-discovery
# virtualenv
idna==3.15
idna==3.16
# via
# requests
# url-normalize
@@ -68,7 +68,7 @@ pyproject-hooks==1.2.0
# via build
python-discovery==1.3.1
# via virtualenv
requests==2.34.1
requests==2.34.2
# via
# abi3audit
# requests-cache
@@ -92,5 +92,5 @@ urllib3==2.7.0
# requests-cache
virtualenv==21.3.3
# via -r cibuildwheel/resources/constraints.in
zipp==3.23.1
zipp==4.1.0
# via importlib-metadata
@@ -14,7 +14,7 @@ build==1.5.0
# via -r cibuildwheel/resources/constraints.in
cattrs==26.1.0
# via requests-cache
certifi==2026.4.22
certifi==2026.5.20
# via requests
charset-normalizer==3.4.7
# via requests
@@ -28,7 +28,7 @@ filelock==3.29.0
# via
# python-discovery
# virtualenv
idna==3.15
idna==3.16
# via
# requests
# url-normalize
@@ -65,7 +65,7 @@ pyproject-hooks==1.2.0
# via build
python-discovery==1.3.1
# via virtualenv
requests==2.34.1
requests==2.34.2
# via
# abi3audit
# requests-cache
@@ -14,7 +14,7 @@ build==1.5.0
# via -r cibuildwheel/resources/constraints.in
cattrs==26.1.0
# via requests-cache
certifi==2026.4.22
certifi==2026.5.20
# via requests
charset-normalizer==3.4.7
# via requests
@@ -28,7 +28,7 @@ filelock==3.29.0
# via
# python-discovery
# virtualenv
idna==3.15
idna==3.16
# via
# requests
# url-normalize
@@ -65,7 +65,7 @@ pyproject-hooks==1.2.0
# via build
python-discovery==1.3.1
# via virtualenv
requests==2.34.1
requests==2.34.2
# via
# abi3audit
# requests-cache
@@ -14,7 +14,7 @@ build==1.5.0
# via -r cibuildwheel/resources/constraints.in
cattrs==26.1.0
# via requests-cache
certifi==2026.4.22
certifi==2026.5.20
# via requests
charset-normalizer==3.4.7
# via requests
@@ -28,7 +28,7 @@ filelock==3.29.0
# via
# python-discovery
# virtualenv
idna==3.15
idna==3.16
# via
# requests
# url-normalize
@@ -64,7 +64,7 @@ pyproject-hooks==1.2.0
# via build
python-discovery==1.3.1
# via virtualenv
requests==2.34.1
requests==2.34.2
# via
# abi3audit
# requests-cache
@@ -14,7 +14,7 @@ build==1.5.0
# via -r cibuildwheel/resources/constraints.in
cattrs==26.1.0
# via requests-cache
certifi==2026.4.22
certifi==2026.5.20
# via requests
charset-normalizer==3.4.7
# via requests
@@ -28,7 +28,7 @@ filelock==3.29.0
# via
# python-discovery
# virtualenv
idna==3.15
idna==3.16
# via
# requests
# url-normalize
@@ -64,7 +64,7 @@ pyproject-hooks==1.2.0
# via build
python-discovery==1.3.1
# via virtualenv
requests==2.34.1
requests==2.34.2
# via
# abi3audit
# requests-cache
@@ -14,7 +14,7 @@ build==1.5.0
# via -r cibuildwheel/resources/constraints.in
cattrs==26.1.0
# via requests-cache
certifi==2026.4.22
certifi==2026.5.20
# via requests
charset-normalizer==3.4.7
# via requests
@@ -28,7 +28,7 @@ filelock==3.29.0
# via
# python-discovery
# virtualenv
idna==3.15
idna==3.16
# via
# requests
# url-normalize
@@ -64,7 +64,7 @@ pyproject-hooks==1.2.0
# via build
python-discovery==1.3.1
# via virtualenv
requests==2.34.1
requests==2.34.2
# via
# abi3audit
# requests-cache
@@ -14,7 +14,7 @@ build==1.4.4
# via -r cibuildwheel/resources/constraints.in
cattrs==25.3.0
# via requests-cache
certifi==2026.4.22
certifi==2026.5.20
# via requests
charset-normalizer==3.4.7
# via requests
@@ -30,7 +30,7 @@ filelock==3.19.1
# via
# python-discovery
# virtualenv
idna==3.15
idna==3.16
# via
# requests
# url-normalize
+3 -3
View File
@@ -14,7 +14,7 @@ build==1.5.0
# via -r cibuildwheel/resources/constraints.in
cattrs==26.1.0
# via requests-cache
certifi==2026.4.22
certifi==2026.5.20
# via requests
charset-normalizer==3.4.7
# via requests
@@ -28,7 +28,7 @@ filelock==3.29.0
# via
# python-discovery
# virtualenv
idna==3.15
idna==3.16
# via
# requests
# url-normalize
@@ -64,7 +64,7 @@ pyproject-hooks==1.2.0
# via build
python-discovery==1.3.1
# via virtualenv
requests==2.34.1
requests==2.34.2
# via
# abi3audit
# requests-cache
@@ -0,0 +1,103 @@
import shutil
import sys
import sysconfig
from pathlib import Path
SITE_PACKAGE_PATH = Path(__file__).parent
###########################################################################
# importlib module patches
###########################################################################
def patch_env_create(env):
"""
Patch the process of creating virtual environments to ensure that the cross
environment modification files are also copied as part of environment
creation.
"""
old_pip_env_create = env._PipBackend.create
def pip_env_create(self, path, *args, **kwargs):
result = old_pip_env_create(self, path, *args, **kwargs)
# Copy any _cross_*.pth or _cross_*.py file, plus the cross-platform
# sysconfigdata module and sysconfig_vars JSON to the new environment.
data_name = sysconfig._get_sysconfigdata_name()
json_name = data_name.replace("_sysconfigdata", "_sysconfig_vars")
for filename in [
"_cross_venv.pth",
"_cross_venv.py",
f"_cross_{sys.implementation._multiarch.replace('-', '_')}.py",
f"{data_name}.py",
f"{json_name}.json",
]:
src = SITE_PACKAGE_PATH / filename
target = Path(path) / src.relative_to(SITE_PACKAGE_PATH.parent.parent.parent)
if not target.exists():
shutil.copy(src, target)
return result
env._PipBackend.create = pip_env_create
# Import hook that patches the creation of virtual environments by `build`
#
# The approach used here is the same as the one used by virtualenv to patch
# distutils (but without support for the older load_module API).
# https://docs.python.org/3/library/importlib.html#setting-up-an-importer
_BUILD_PATCH = ("build.env",)
class _Finder:
"""A meta path finder that allows patching the imported build modules."""
fullname = None
# lock[0] is threading.Lock(), but initialized lazily to avoid importing
# threading very early at startup, because there are gevent-based
# applications that need to be first to import threading by themselves.
# See https://github.com/pypa/virtualenv/issues/1895 for details.
lock = [] # noqa: RUF012
def find_spec(self, fullname, path, target=None):
if fullname in _BUILD_PATCH and self.fullname is None:
# initialize lock[0] lazily
if len(self.lock) == 0:
import threading
lock = threading.Lock()
# there is possibility that two threads T1 and T2 are
# simultaneously running into find_spec, observing .lock as
# empty, and further going into hereby initialization. However
# due to the GIL, list.append() operation is atomic and this
# way only one of the threads will "win" to put the lock
# - that every thread will use - into .lock[0].
# https://docs.python.org/3/faq/library.html#what-kinds-of-global-value-mutation-are-thread-safe
self.lock.append(lock)
from functools import partial
from importlib.util import find_spec
with self.lock[0]:
self.fullname = fullname
try:
spec = find_spec(fullname, path)
if spec is not None:
# https://www.python.org/dev/peps/pep-0451/#how-loading-will-work
old = spec.loader.exec_module
func = self.exec_module
if old is not func:
spec.loader.exec_module = partial(func, old)
return spec
finally:
self.fullname = None
return None
@staticmethod
def exec_module(old, module):
old(module)
if module.__name__ in _BUILD_PATCH:
patch_env_create(module)
sys.meta_path.insert(0, _Finder())
@@ -0,0 +1,79 @@
# A site package that turns a macOS virtual environment
# into an arm64 iphoneos cross-platform virtual environment
import platform
import subprocess
import sys
import sysconfig
###########################################################################
# sys module patches
###########################################################################
sys.cross_compiling = True
sys.platform = "ios"
sys.implementation._multiarch = "arm64-iphoneos"
sys.base_prefix = sysconfig._get_sysconfigdata()["prefix"]
sys.base_exec_prefix = sysconfig._get_sysconfigdata()["prefix"]
###########################################################################
# subprocess module patches
###########################################################################
subprocess._can_fork_exec = True
###########################################################################
# platform module patches
###########################################################################
def cross_system():
return "iOS"
def cross_uname():
return platform.uname_result(
system="iOS",
node="build",
release="13.0",
version="",
machine="arm64",
)
def cross_ios_ver(system="", release="", model="", is_simulator=False):
if system == "":
system = "iOS"
if release == "":
release = "13.0"
if model == "":
model = "iphoneos"
return platform.IOSVersionInfo(system, release, model, False)
platform.system = cross_system
platform.uname = cross_uname
platform.ios_ver = cross_ios_ver
###########################################################################
# sysconfig module patches
###########################################################################
def cross_get_platform():
return "ios-13.0-arm64-iphoneos"
def cross_get_sysconfigdata_name():
return "_sysconfigdata__ios_arm64-iphoneos"
sysconfig.get_platform = cross_get_platform
sysconfig._get_sysconfigdata_name = cross_get_sysconfigdata_name
# Ensure module-level values cached at time of import are updated.
sysconfig._BASE_PREFIX = sys.base_prefix
sysconfig._BASE_EXEC_PREFIX = sys.base_exec_prefix
# Force sysconfig data to be loaded (and cached).
sysconfig._init_config_vars()
@@ -0,0 +1,22 @@
# A site customization that can be used to trick pip into installing packages
# cross-platform. If the folder containing this file is on your PYTHONPATH when
# you invoke python, the interpreter will behave as if it were running on
# arm64 iphoneos.
import sys
import os
# Apply the cross-platform patch
import _cross_arm64_iphoneos
import _cross_venv
# Call the next sitecustomize script if there is one
# (https://nedbatchelder.com/blog/201001/running_code_at_python_startup.html).
del sys.modules["sitecustomize"]
this_dir = os.path.dirname(__file__)
path_index = sys.path.index(this_dir)
del sys.path[path_index]
try:
import sitecustomize # noqa: F401
finally:
sys.path.insert(path_index, this_dir)
@@ -0,0 +1,79 @@
# A site package that turns a macOS virtual environment
# into an arm64 iphonesimulator cross-platform virtual environment
import platform
import subprocess
import sys
import sysconfig
###########################################################################
# sys module patches
###########################################################################
sys.cross_compiling = True
sys.platform = "ios"
sys.implementation._multiarch = "arm64-iphonesimulator"
sys.base_prefix = sysconfig._get_sysconfigdata()["prefix"]
sys.base_exec_prefix = sysconfig._get_sysconfigdata()["prefix"]
###########################################################################
# subprocess module patches
###########################################################################
subprocess._can_fork_exec = True
###########################################################################
# platform module patches
###########################################################################
def cross_system():
return "iOS"
def cross_uname():
return platform.uname_result(
system="iOS",
node="build",
release="13.0",
version="",
machine="arm64",
)
def cross_ios_ver(system="", release="", model="", is_simulator=False):
if system == "":
system = "iOS"
if release == "":
release = "13.0"
if model == "":
model = "iphonesimulator"
return platform.IOSVersionInfo(system, release, model, True)
platform.system = cross_system
platform.uname = cross_uname
platform.ios_ver = cross_ios_ver
###########################################################################
# sysconfig module patches
###########################################################################
def cross_get_platform():
return "ios-13.0-arm64-iphonesimulator"
def cross_get_sysconfigdata_name():
return "_sysconfigdata__ios_arm64-iphonesimulator"
sysconfig.get_platform = cross_get_platform
sysconfig._get_sysconfigdata_name = cross_get_sysconfigdata_name
# Ensure module-level values cached at time of import are updated.
sysconfig._BASE_PREFIX = sys.base_prefix
sysconfig._BASE_EXEC_PREFIX = sys.base_exec_prefix
# Force sysconfig data to be loaded (and cached).
sysconfig._init_config_vars()
@@ -0,0 +1,22 @@
# A site customization that can be used to trick pip into installing packages
# cross-platform. If the folder containing this file is on your PYTHONPATH when
# you invoke python, the interpreter will behave as if it were running on
# arm64 iphonesimulator.
import sys
import os
# Apply the cross-platform patch
import _cross_arm64_iphonesimulator
import _cross_venv
# Call the next sitecustomize script if there is one
# (https://nedbatchelder.com/blog/201001/running_code_at_python_startup.html).
del sys.modules["sitecustomize"]
this_dir = os.path.dirname(__file__)
path_index = sys.path.index(this_dir)
del sys.path[path_index]
try:
import sitecustomize # noqa: F401
finally:
sys.path.insert(path_index, this_dir)
@@ -0,0 +1,175 @@
from __future__ import annotations
import json
import pprint
import shutil
import sys
from importlib import util as importlib_util
from pathlib import Path
from typing import Any
def localized_vars(orig_vars: dict[str, Any], slice_path: Path) -> dict[str, Any]:
"""Update (where possible) any references to build-time variables with the
best guess of the installed location.
"""
# The host's sysconfigdata will include references to build-time variables.
# Update these to refer to the current known install location.
orig_prefix = orig_vars["prefix"]
localized_vars = {}
for key, value in orig_vars.items():
final = value
if isinstance(value, str):
# Replace any reference to the build installation prefix
final = final.replace(orig_prefix, str(slice_path))
# Replace any reference to the build-time Framework location
final = final.replace("-F .", f"-F {slice_path}")
localized_vars[key] = final
return localized_vars
def localize_sysconfigdata(sysconfig_path: Path, venv_site_packages: Path) -> None:
"""Localize a sysconfigdata python module.
:param sysconfig_path: The platform config that contains the
sysconfigdata module to localize.
:param venv_site_packages: The site packages folder where the localized
sysconfigdata module should be output.
"""
# Find the "_sysconfigdata_*.py" file in the platform config
sysconfigdata_path = next(sysconfig_path.glob("_sysconfigdata_*.py"))
# Import the sysconfigdata module
spec = importlib_util.spec_from_file_location(sysconfigdata_path.stem, sysconfigdata_path)
if spec is None:
msg = f"Unable to load spec for {sysconfigdata_path}"
raise ValueError(msg)
if spec.loader is None:
msg = f"Spec for {sysconfigdata_path} does not define a loader"
raise ValueError(msg)
sysconfigdata = importlib_util.module_from_spec(spec)
spec.loader.exec_module(sysconfigdata)
# Write the updated sysconfigdata module into the cross-platform site.
slice_path = sysconfigdata_path.parent.parent.parent
with (venv_site_packages / sysconfigdata_path.name).open("w") as f:
f.write(f"# Generated from {sysconfigdata_path}\n")
f.write("build_time_vars = ")
pprint.pprint(
localized_vars(sysconfigdata.build_time_vars, slice_path), stream=f, compact=True
)
def localize_sysconfig_vars(sysconfig_path: Path, venv_site_packages: Path) -> None:
"""Localize a sysconfig_vars.json file.
:param sysconfig_path: The platform config that contains the
sysconfigdata module to localize.
:param venv_site_packages: The site-packages folder where the localized
sysconfig_vars.json file should be output.
"""
# Find the "_sysconfig_vars_*.json" file in the platform config
sysconfig_vars_path = next(sysconfig_path.glob("_sysconfig_vars_*.json"))
with sysconfig_vars_path.open("rb") as f:
build_time_vars = json.load(f)
slice_path = sysconfig_vars_path.parent.parent.parent
with (venv_site_packages / sysconfig_vars_path.name).open("w") as f:
json.dump(localized_vars(build_time_vars, slice_path), f, indent=2)
def make_cross_venv(
venv_path: Path,
sysconfig_path: Path,
platform_config_path: Path,
) -> None:
"""Convert a virtual environment into a cross-platform environment.
:param venv_path: The path to the root of the venv.
:param sysconfig_path: The path containing sysconfigdata files for the
target platform.
:param platform_config_path: The path containing the cross-venv support
scripts (_cross_venv.py and the multiarch-specific _cross_*.py).
"""
if not venv_path.exists():
msg = f"Virtual environment {venv_path} does not exist."
raise ValueError(msg)
if not (venv_path / "bin/python3").exists():
msg = f"{venv_path} does not appear to be a virtual environment."
raise ValueError(msg)
LIB_PATH = f"lib/python{sys.version_info[0]}.{sys.version_info[1]}"
# Derive the multiarch tag from the sysconfigdata filename if available;
# otherwise fall back to the directory name (BeeWare convention).
sysconfigdata_files = list(sysconfig_path.glob("_sysconfigdata_*.py"))
if sysconfigdata_files:
multiarch_tag = sysconfigdata_files[0].stem.split("_ios_")[1]
cross_multiarch = f"_cross_{multiarch_tag.replace('-', '_')}"
else:
multiarch_tag = sysconfig_path.name
cross_multiarch = f"_cross_{multiarch_tag.replace('-', '_')}"
print(
f"Converting {venv_path} into a {multiarch_tag} environment... ",
end="",
)
# Update path references in the sysconfigdata to reflect local conditions.
venv_site_packages = venv_path / LIB_PATH / "site-packages"
localize_sysconfigdata(sysconfig_path, venv_site_packages)
localize_sysconfig_vars(sysconfig_path, venv_site_packages)
# The multiarch-specific script may be directly in platform_config_path (BeeWare
# convention, where all files are co-located) or in a named subdirectory
# (cibuildwheel resource layout, where each multiarch has its own subdir).
multiarch_script = platform_config_path / f"{cross_multiarch}.py"
if not multiarch_script.exists():
multiarch_script = platform_config_path / multiarch_tag / f"{cross_multiarch}.py"
shutil.copy(
multiarch_script,
venv_site_packages / f"{cross_multiarch}.py",
)
shutil.copy(
platform_config_path / "_cross_venv.py",
venv_site_packages / "_cross_venv.py",
)
# Write the .pth file that will enable the cross-env modifications
(venv_site_packages / "_cross_venv.pth").write_text(
f"import {cross_multiarch}; import _cross_venv\n"
)
print("done.")
if __name__ == "__main__":
try:
sysconfig_path = Path(sys.argv[2]).resolve()
except IndexError:
sysconfig_path = Path(__file__).parent
try:
platform_config_path = Path(sys.argv[3]).resolve()
except IndexError:
platform_config_path = sysconfig_path
try:
venv_path = Path(sys.argv[1]).resolve()
make_cross_venv(venv_path, sysconfig_path, platform_config_path)
except IndexError:
print("""
Convert a virtual environment into a cross-platform environment.
Usage:
make_cross_venv <venv> (<sysconfig path>) (<platform config path>)
The sysconfig path is the path that contains the `_sysconfigdata-*.py` file
for the platform being targeted. If an explicit sysconfig path isn't
provided, it is assumed the directory containing the make_cross_venv script
also contains the sysconfig data for the interpreter.
The platform config path is the path that contains the cross-venv support
scripts. If an explicit platform config path isn't provided, it is assumed
that the sysconfig path also contains the platform configuration files.
""")
@@ -0,0 +1,79 @@
# A site package that turns a macOS virtual environment
# into an x86_64 iphonesimulator cross-platform virtual environment
import platform
import subprocess
import sys
import sysconfig
###########################################################################
# sys module patches
###########################################################################
sys.cross_compiling = True
sys.platform = "ios"
sys.implementation._multiarch = "x86_64-iphonesimulator"
sys.base_prefix = sysconfig._get_sysconfigdata()["prefix"]
sys.base_exec_prefix = sysconfig._get_sysconfigdata()["prefix"]
###########################################################################
# subprocess module patches
###########################################################################
subprocess._can_fork_exec = True
###########################################################################
# platform module patches
###########################################################################
def cross_system():
return "iOS"
def cross_uname():
return platform.uname_result(
system="iOS",
node="build",
release="13.0",
version="",
machine="x86_64",
)
def cross_ios_ver(system="", release="", model="", is_simulator=False):
if system == "":
system = "iOS"
if release == "":
release = "13.0"
if model == "":
model = "iphonesimulator"
return platform.IOSVersionInfo(system, release, model, True)
platform.system = cross_system
platform.uname = cross_uname
platform.ios_ver = cross_ios_ver
###########################################################################
# sysconfig module patches
###########################################################################
def cross_get_platform():
return "ios-13.0-x86_64-iphonesimulator"
def cross_get_sysconfigdata_name():
return "_sysconfigdata__ios_x86_64-iphonesimulator"
sysconfig.get_platform = cross_get_platform
sysconfig._get_sysconfigdata_name = cross_get_sysconfigdata_name
# Ensure module-level values cached at time of import are updated.
sysconfig._BASE_PREFIX = sys.base_prefix
sysconfig._BASE_EXEC_PREFIX = sys.base_exec_prefix
# Force sysconfig data to be loaded (and cached).
sysconfig._init_config_vars()
@@ -0,0 +1,22 @@
# A site customization that can be used to trick pip into installing packages
# cross-platform. If the folder containing this file is on your PYTHONPATH when
# you invoke python, the interpreter will behave as if it were running on
# x86_64 iphonesimulator.
import sys
import os
# Apply the cross-platform patch
import _cross_x86_64_iphonesimulator
import _cross_venv
# Call the next sitecustomize script if there is one
# (https://nedbatchelder.com/blog/201001/running_code_at_python_startup.html).
del sys.modules["sitecustomize"]
this_dir = os.path.dirname(__file__)
path_index = sys.path.index(this_dir)
del sys.path[path_index]
try:
import sitecustomize # noqa: F401
finally:
sys.path.insert(path_index, this_dir)
+2 -2
View File
@@ -1,4 +1,4 @@
url = "https://nodejs.org/dist/"
v24 = "v24.15.0"
v22 = "v22.22.2"
v24 = "v24.16.0"
v22 = "v22.22.3"
v20 = "v20.20.2"
+34 -34
View File
@@ -1,54 +1,54 @@
[x86_64]
manylinux2014 = quay.io/pypa/manylinux2014_x86_64:2026.05.07-2
manylinux_2_28 = quay.io/pypa/manylinux_2_28_x86_64:2026.05.07-2
manylinux_2_34 = quay.io/pypa/manylinux_2_34_x86_64:2026.05.07-2
musllinux_1_2 = quay.io/pypa/musllinux_1_2_x86_64:2026.05.07-2
manylinux2014 = quay.io/pypa/manylinux2014_x86_64:2026.05.27-1
manylinux_2_28 = quay.io/pypa/manylinux_2_28_x86_64:2026.05.27-1
manylinux_2_34 = quay.io/pypa/manylinux_2_34_x86_64:2026.05.27-1
musllinux_1_2 = quay.io/pypa/musllinux_1_2_x86_64:2026.05.27-1
[i686]
manylinux2014 = quay.io/pypa/manylinux2014_i686:2026.05.07-2
manylinux_2_28 = quay.io/pypa/manylinux_2_28_i686:2026.05.07-2
manylinux_2_34 = quay.io/pypa/manylinux_2_34_i686:2026.05.07-2
musllinux_1_2 = quay.io/pypa/musllinux_1_2_i686:2026.05.07-2
manylinux2014 = quay.io/pypa/manylinux2014_i686:2026.05.27-1
manylinux_2_28 = quay.io/pypa/manylinux_2_28_i686:2026.05.27-1
manylinux_2_34 = quay.io/pypa/manylinux_2_34_i686:2026.05.27-1
musllinux_1_2 = quay.io/pypa/musllinux_1_2_i686:2026.05.27-1
[aarch64]
manylinux2014 = quay.io/pypa/manylinux2014_aarch64:2026.05.07-2
manylinux_2_28 = quay.io/pypa/manylinux_2_28_aarch64:2026.05.07-2
manylinux_2_34 = quay.io/pypa/manylinux_2_34_aarch64:2026.05.07-2
musllinux_1_2 = quay.io/pypa/musllinux_1_2_aarch64:2026.05.07-2
manylinux2014 = quay.io/pypa/manylinux2014_aarch64:2026.05.27-1
manylinux_2_28 = quay.io/pypa/manylinux_2_28_aarch64:2026.05.27-1
manylinux_2_34 = quay.io/pypa/manylinux_2_34_aarch64:2026.05.27-1
musllinux_1_2 = quay.io/pypa/musllinux_1_2_aarch64:2026.05.27-1
[ppc64le]
manylinux2014 = quay.io/pypa/manylinux2014_ppc64le:2026.05.07-2
manylinux_2_28 = quay.io/pypa/manylinux_2_28_ppc64le:2026.05.07-2
manylinux_2_34 = quay.io/pypa/manylinux_2_34_ppc64le:2026.05.07-2
musllinux_1_2 = quay.io/pypa/musllinux_1_2_ppc64le:2026.05.07-2
manylinux2014 = quay.io/pypa/manylinux2014_ppc64le:2026.05.27-1
manylinux_2_28 = quay.io/pypa/manylinux_2_28_ppc64le:2026.05.27-1
manylinux_2_34 = quay.io/pypa/manylinux_2_34_ppc64le:2026.05.27-1
musllinux_1_2 = quay.io/pypa/musllinux_1_2_ppc64le:2026.05.27-1
[s390x]
manylinux2014 = quay.io/pypa/manylinux2014_s390x:2026.05.07-2
manylinux_2_28 = quay.io/pypa/manylinux_2_28_s390x:2026.05.07-2
manylinux_2_34 = quay.io/pypa/manylinux_2_34_s390x:2026.05.07-2
musllinux_1_2 = quay.io/pypa/musllinux_1_2_s390x:2026.05.07-2
manylinux2014 = quay.io/pypa/manylinux2014_s390x:2026.05.27-1
manylinux_2_28 = quay.io/pypa/manylinux_2_28_s390x:2026.05.27-1
manylinux_2_34 = quay.io/pypa/manylinux_2_34_s390x:2026.05.27-1
musllinux_1_2 = quay.io/pypa/musllinux_1_2_s390x:2026.05.27-1
[pypy_x86_64]
manylinux2014 = quay.io/pypa/manylinux2014_x86_64:2026.05.07-2
manylinux_2_28 = quay.io/pypa/manylinux_2_28_x86_64:2026.05.07-2
manylinux_2_34 = quay.io/pypa/manylinux_2_34_x86_64:2026.05.07-2
manylinux2014 = quay.io/pypa/manylinux2014_x86_64:2026.05.27-1
manylinux_2_28 = quay.io/pypa/manylinux_2_28_x86_64:2026.05.27-1
manylinux_2_34 = quay.io/pypa/manylinux_2_34_x86_64:2026.05.27-1
[pypy_i686]
manylinux2014 = quay.io/pypa/manylinux2014_i686:2026.05.07-2
manylinux_2_28 = quay.io/pypa/manylinux_2_28_i686:2026.05.07-2
manylinux_2_34 = quay.io/pypa/manylinux_2_34_i686:2026.05.07-2
manylinux2014 = quay.io/pypa/manylinux2014_i686:2026.05.27-1
manylinux_2_28 = quay.io/pypa/manylinux_2_28_i686:2026.05.27-1
manylinux_2_34 = quay.io/pypa/manylinux_2_34_i686:2026.05.27-1
[pypy_aarch64]
manylinux2014 = quay.io/pypa/manylinux2014_aarch64:2026.05.07-2
manylinux_2_28 = quay.io/pypa/manylinux_2_28_aarch64:2026.05.07-2
manylinux_2_34 = quay.io/pypa/manylinux_2_34_aarch64:2026.05.07-2
manylinux2014 = quay.io/pypa/manylinux2014_aarch64:2026.05.27-1
manylinux_2_28 = quay.io/pypa/manylinux_2_28_aarch64:2026.05.27-1
manylinux_2_34 = quay.io/pypa/manylinux_2_34_aarch64:2026.05.27-1
[armv7l]
manylinux_2_31 = quay.io/pypa/manylinux_2_31_armv7l:2026.05.07-2
manylinux_2_35 = quay.io/pypa/manylinux_2_35_armv7l:2026.05.07-2
musllinux_1_2 = quay.io/pypa/musllinux_1_2_armv7l:2026.05.07-2
manylinux_2_31 = quay.io/pypa/manylinux_2_31_armv7l:2026.05.27-1
manylinux_2_35 = quay.io/pypa/manylinux_2_35_armv7l:2026.05.27-1
musllinux_1_2 = quay.io/pypa/musllinux_1_2_armv7l:2026.05.27-1
[riscv64]
manylinux_2_39 = quay.io/pypa/manylinux_2_39_riscv64:2026.05.07-2
musllinux_1_2 = quay.io/pypa/musllinux_1_2_riscv64:2026.05.07-2
manylinux_2_39 = quay.io/pypa/manylinux_2_39_riscv64:2026.05.27-1
musllinux_1_2 = quay.io/pypa/musllinux_1_2_riscv64:2026.05.27-1
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1 +1 @@
default = { version = "21.3.1", url = "https://github.com/pypa/get-virtualenv/releases/download/21.3.1/virtualenv.pyz" }
default = { version = "21.3.3", url = "https://github.com/pypa/get-virtualenv/releases/download/21.3.3/virtualenv.pyz", sha256 = "d3a7f4ee4c820c4247fe14ec1a478e19ef8f63f716c0f15bc10fab4d70ad9b69" }
+6 -1
View File
@@ -1,8 +1,13 @@
from __future__ import annotations
import json
from typing import Any
from cibuildwheel.util import resources
TYPE_CHECKING = False
if TYPE_CHECKING:
from typing import Any
def get_schema(tool_name: str = "cibuildwheel") -> dict[str, Any]:
"Get the stored complete schema for cibuildwheel settings."
+8 -2
View File
@@ -1,13 +1,19 @@
from __future__ import annotations
import dataclasses
import itertools
from enum import StrEnum
from fnmatch import fnmatch
from typing import Self
import bracex
from packaging.specifiers import SpecifierSet
from packaging.version import Version
TYPE_CHECKING = False
if TYPE_CHECKING:
from typing import Self
from packaging.specifiers import SpecifierSet
def selector_matches(patterns: str, string: str) -> bool:
"""
+9 -3
View File
@@ -1,14 +1,20 @@
from __future__ import annotations
import os
import shlex
import shutil
import subprocess
import sys
import typing
from collections.abc import Iterator, Mapping
from typing import Final, Literal
from cibuildwheel.errors import FatalError
from cibuildwheel.typing import PathOrStr
TYPE_CHECKING = False
if TYPE_CHECKING:
from collections.abc import Iterator, Mapping
from typing import Final, Literal
from cibuildwheel.typing import PathOrStr
_IS_WIN: Final[bool] = sys.platform.startswith("win")
+16 -3
View File
@@ -1,10 +1,12 @@
from __future__ import annotations
import hashlib
import os
import shutil
import ssl
import tarfile
import time
import urllib.request
from collections.abc import Callable
from pathlib import Path, PurePath
from typing import Final
from zipfile import ZipFile
@@ -14,6 +16,10 @@ from platformdirs import user_cache_path
from cibuildwheel.errors import FatalError
TYPE_CHECKING = False
if TYPE_CHECKING:
from collections.abc import Callable
DEFAULT_CIBW_CACHE_PATH: Final[Path] = user_cache_path(appname="cibuildwheel", appauthor="pypa")
CIBW_CACHE_PATH: Final[Path] = Path(
os.environ.get("CIBW_CACHE_PATH", DEFAULT_CIBW_CACHE_PATH)
@@ -40,7 +46,7 @@ def ensure_cache_sentinel(cache_path: Path) -> None:
)
def download(url: str, dest: Path) -> None:
def download(url: str, dest: Path, *, sha256: str | None = None) -> None:
print(f"+ Download {url} to {dest}")
dest_dir = dest.parent
dest_dir.mkdir(parents=True, exist_ok=True)
@@ -54,13 +60,20 @@ def download(url: str, dest: Path) -> None:
try:
with urllib.request.urlopen(url, context=context) as response:
dest.write_bytes(response.read())
return
break
except OSError:
if i == repeat_num - 1:
raise
time.sleep(3)
if sha256:
computed = hashlib.sha256(dest.read_bytes()).hexdigest()
if computed != sha256:
dest.unlink(missing_ok=True)
msg = f"SHA256 mismatch for {url}: expected {sha256!r}, got {computed!r}"
raise FatalError(msg)
def extract_zip(zip_src: Path, dest: Path) -> None:
"""Extracts a zip and correctly sets permissions on extracted files.
+8 -3
View File
@@ -1,13 +1,18 @@
from __future__ import annotations
import dataclasses
import itertools
import os
import re
import shlex
import textwrap
from collections import defaultdict
from collections.abc import Sequence
from cibuildwheel.typing import PathOrStr
TYPE_CHECKING = False
if TYPE_CHECKING:
import os
from collections.abc import Sequence
from cibuildwheel.typing import PathOrStr
def format_safe(template: str, **kwargs: str | os.PathLike[str]) -> str:
+8 -2
View File
@@ -1,8 +1,9 @@
from __future__ import annotations
import shlex
from collections.abc import Mapping, Sequence
from dataclasses import dataclass, field
from pathlib import Path, PurePath
from typing import Literal, Self, TypeVar
from typing import TypeVar
from packaging.utils import parse_wheel_filename
@@ -10,6 +11,11 @@ from cibuildwheel.util import resources
from cibuildwheel.util.cmd import call
from cibuildwheel.util.helpers import parse_key_value_string, unwrap
TYPE_CHECKING = False
if TYPE_CHECKING:
from collections.abc import Mapping, Sequence
from typing import Literal, Self
@dataclass(kw_only=True)
class DependencyConstraints:
+34 -11
View File
@@ -1,19 +1,26 @@
from __future__ import annotations
import fnmatch
import functools
import hashlib
import json
import platform
import typing
from pathlib import Path
from filelock import FileLock
from cibuildwheel.util.file import download, extract_tar
from cibuildwheel.util.resources import PYTHON_BUILD_STANDALONE_RELEASES
TYPE_CHECKING = False
if TYPE_CHECKING:
from pathlib import Path
class PythonBuildStandaloneAsset(typing.TypedDict):
name: str
url: str
sha256: str
class PythonBuildStandaloneRelease(typing.TypedDict):
@@ -81,8 +88,8 @@ def _get_pbs_asset(
arch_identifier: str,
platform_identifier: str,
libc_identifier: str | None,
) -> tuple[str, str, str]:
"""Finds the asset, returning (tag, filename, url)."""
) -> tuple[str, str, str, str]:
"""Finds the asset, returning (tag, url, filename, sha256)."""
release_data = get_python_build_standalone_release_data()
expected_suffix = f"{arch_identifier}-{platform_identifier}"
@@ -100,22 +107,36 @@ def _get_pbs_asset(
continue
asset_url = asset["url"]
return release["tag"], asset_url, asset_name
asset_sha256 = asset.get("sha256", "")
return release["tag"], asset_url, asset_name, asset_sha256
# If loop completes without finding a match
msg = f"Could not find python-build-standalone release asset matching {asset_pattern!r}."
raise PythonBuildStandaloneError(msg)
def _download_or_get_from_cache(asset_url: str, asset_filename: str, cache_dir: Path) -> Path:
def _download_or_get_from_cache(
asset_url: str, asset_filename: str, cache_dir: Path, sha256: str = ""
) -> Path:
with FileLock(cache_dir / (asset_filename + ".lock")):
asset_cache_path = cache_dir / asset_filename
if asset_cache_path.is_file():
print(f"Using cached python_build_standalone: {asset_cache_path}")
return asset_cache_path
if sha256:
computed = hashlib.sha256(asset_cache_path.read_bytes()).hexdigest()
if computed != sha256:
print(
f"Cached python_build_standalone SHA256 mismatch for {asset_cache_path}; redownloading."
)
asset_cache_path.unlink(missing_ok=True)
else:
print(f"Using cached python_build_standalone: {asset_cache_path}")
return asset_cache_path
else:
print(f"Using cached python_build_standalone: {asset_cache_path}")
return asset_cache_path
print(f"Downloading python_build_standalone: {asset_url} to {asset_cache_path}")
download(asset_url, asset_cache_path)
download(asset_url, asset_cache_path, sha256=sha256 or None)
return asset_cache_path
@@ -161,7 +182,7 @@ def create_python_build_standalone_environment(
arch_id, platform_id, libc_id = _get_platform_identifiers()
pbs_tag, asset_url, asset_filename = _get_pbs_asset(
pbs_tag, asset_url, asset_filename, asset_sha256 = _get_pbs_asset(
python_version=python_version,
arch_identifier=arch_id,
platform_identifier=platform_id,
@@ -171,11 +192,13 @@ def create_python_build_standalone_environment(
print(f"Using python-build-standalone release: {pbs_tag}")
archive_path = _download_or_get_from_cache(
asset_url=asset_url, asset_filename=asset_filename, cache_dir=cache_dir
asset_url=asset_url, asset_filename=asset_filename, cache_dir=cache_dir, sha256=asset_sha256
)
python_base_dir = temp_dir / "pbs"
assert not python_base_dir.exists()
if python_base_dir.exists():
msg = f"python-build-standalone directory already exists: {python_base_dir}"
raise PythonBuildStandaloneError(msg)
extract_tar(archive_path, python_base_dir)
return _find_python_executable(python_base_dir)
+8 -2
View File
@@ -1,9 +1,14 @@
from __future__ import annotations
import functools
import tomllib
from pathlib import Path
from typing import Final
from cibuildwheel.typing import PlatformName
TYPE_CHECKING = False
if TYPE_CHECKING:
from typing import Final
from cibuildwheel.typing import PlatformName
PATH: Final[Path] = Path(__file__).parent.parent / "resources"
INSTALL_CERTIFI_SCRIPT: Final[Path] = PATH / "install_certifi.py"
@@ -18,6 +23,7 @@ VIRTUALENV: Final[Path] = PATH / "virtualenv.toml"
CIBUILDWHEEL_SCHEMA: Final[Path] = PATH / "cibuildwheel.schema.json"
PYTHON_BUILD_STANDALONE_RELEASES: Final[Path] = PATH / "python-build-standalone-releases.json"
TEST_FAIL_CWD_FILE: Final[Path] = PATH / "testing_temp_dir_file.py"
IOS_SUPPORT_FILES: Final[Path] = PATH / "ios-support"
# this value is cached because it's used a lot in unit tests
+10 -3
View File
@@ -1,12 +1,13 @@
from __future__ import annotations
import contextlib
import functools
import os
import shutil
import sys
import tomllib
from collections.abc import Sequence
from pathlib import Path
from typing import Final, cast
from typing import cast
from filelock import FileLock
from packaging.markers import default_environment
@@ -17,6 +18,11 @@ from cibuildwheel.util import resources
from cibuildwheel.util.cmd import call
from cibuildwheel.util.file import CIBW_CACHE_PATH, download
TYPE_CHECKING = False
if TYPE_CHECKING:
from collections.abc import Sequence
from typing import Final
_IS_WIN: Final[bool] = sys.platform.startswith("win")
@@ -47,10 +53,11 @@ def _ensure_virtualenv(version: str) -> tuple[Path, Version]:
configuration = loaded_file.get(key, loaded_file["default"])
version = str(configuration["version"])
url = str(configuration["url"])
sha256 = str(configuration.get("sha256", ""))
path = CIBW_CACHE_PATH / f"virtualenv-{version}.pyz"
with FileLock(str(path) + ".lock"):
if not path.exists():
download(url, path)
download(url, path, sha256=sha256 or None)
return (path, Version(version))
+2 -2
View File
@@ -28,10 +28,10 @@ For example, consider a scenario when Pyodide ships a new `315.0.0a1` with cp315
In `cibuildwheel/resources/build-platforms.toml`, add an entry under `[pyodide]`:
```toml
{ identifier = "cp315-pyodide_wasm32", version = "3.15", default_pyodide_version = "315.0.0a1", node_version = "v24" },
{ identifier = "cp315-pyodide_wasm32", version = "3.15", default_pyodide_version = "315.0.0a1", node_version = "v24", sha256 = "SHA256" },
```
`version` is the CPython version string, `default_pyodide_version` is the Pyodide release to use when the user does not pin one explicitly (use the latest available alpha/beta for a prerelease entry), and `node_version` is the minimum Node.js major required by that Pyodide release — check the [pyodide-build FAQ](https://pyodide-build.readthedocs.io/en/latest/faq.html#what-node-js-version-do-i-need) for a rudimentary idea of what the correct value is.
`version` is the CPython version string, `default_pyodide_version` is the Pyodide release to use when the user does not pin one explicitly (use the latest available alpha/beta for a prerelease entry), and `node_version` is the minimum Node.js major required by that Pyodide release — check the [pyodide-build FAQ](https://pyodide-build.readthedocs.io/en/latest/faq.html#what-node-js-version-do-i-need) for a rudimentary idea of what the correct value is. `sha256` is the checksum of the Pyodide xbuildenv tarball.
### 2. Update the prerelease guards in the selector
+1 -14
View File
@@ -51,20 +51,7 @@ myextension = Extension(
### Building with NumPy
If using NumPy, there are a couple of things that can help.
First, if you require the `numpy` package at build-time (some binding tools, like `pybind11` and `nanobind`, do not), then the backward compatibility for your `build-backend.build-requires` is a little complicated for Python <3.9:
* NumPy <1.25: You must build with the oldest version of NumPy you want to support at runtime.
* NumPy 1.25 and 1.26: Anything you build will be compatible with 1.19+ by default, and you can set the minimum target to, for example, 1.22 with `#define NPY_TARGET_VERSION NPY_1_22_API_VERSION`.
* NumPy 2.x: You must build with NumPy 2 to support NumPy 2; otherwise the same as 1.25+.
So the rule is:
* Python <3.8: Use the oldest supported NumPy (via helper `oldest-supported-numpy` if you want)
* Python 3.9+: Use latest supported NumPy (2+).
Second, there might be platforms you want to ship for that NumPy (or some other scientific Python libraries) are not shipping yet for. This is often true for beta candidates of new Python releases, for example. To work with this, you can use the Scientific Python Nightly wheels. Here's an example, depending on what frontend you use:
If using NumPy, there might be platforms you want to ship for that NumPy (or some other scientific Python libraries) are not shipping yet for. This is often true for beta candidates of new Python releases, for example. To work with this, you can use the Scientific Python Nightly wheels. Here's an example, depending on what frontend you use:
!!! tab "pip based"
For frontends like `build` (the default) and `pip`:
+4 -4
View File
@@ -40,8 +40,8 @@ title: Working examples
| [OpenSpiel][] | ![github icon][] | ![apple icon][] ![linux icon][] | OpenSpiel is a collection of environments and algorithms for research in general reinforcement learning and search/planning in games. |
| [aiortc][] | ![github icon][] | ![apple icon][] ![linux icon][] | WebRTC and ORTC implementation for Python using asyncio. |
| [Dependency Injector][] | ![travisci icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Dependency injection framework for Python, uses Windows TravisCI |
| [UltraJSON][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Ultra fast JSON decoder and encoder written in C with Python bindings |
| [CTranslate2][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Includes libraries from the [Intel oneAPI toolkit](https://www.intel.com/content/www/us/en/developer/tools/oneapi/base-toolkit.html) and CUDA kernels compiled for multiple GPU architectures. |
| [UltraJSON][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Ultra fast JSON decoder and encoder written in C with Python bindings |
| [pyzmq][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] ![android icon][] | Python bindings for zeromq, the networking library. Uses Cython on CPython and CFFI on PyPy. ARM wheels for linux are built natively on GitHub Actions. |
| [tinyobjloader][] | ![azurepipelines icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Tiny but powerful single file wavefront obj loader |
| [Implicit][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Includes GPU support for linux wheels |
@@ -95,8 +95,8 @@ title: Working examples
| [power-grid-model][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Python/C++ library for distribution power system analysis |
| [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. |
| [pybase64][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] ![android icon][] ![ios icon][] ![pyodide icon][] | setuptools backend with custom CMake step for external sources. SBOM embedded via a custom repair step. |
| [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. |
| [boost-histogram][] | ![github icon][] ![travisci icon][] | ![windows icon][] ![apple icon][] ![linux icon][] ![pyodide icon][] | Supports full range of wheels, including PyPy and alternate archs. |
| [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 |
@@ -151,8 +151,8 @@ title: Working examples
[OpenSpiel]: https://github.com/google-deepmind/open_spiel
[aiortc]: https://github.com/aiortc/aiortc
[Dependency Injector]: https://github.com/ets-labs/python-dependency-injector
[UltraJSON]: https://github.com/ultrajson/ultrajson
[CTranslate2]: https://github.com/OpenNMT/CTranslate2
[UltraJSON]: https://github.com/ultrajson/ultrajson
[pyzmq]: https://github.com/zeromq/pyzmq
[tinyobjloader]: https://github.com/tinyobjloader/tinyobjloader
[Implicit]: https://github.com/benfred/implicit
@@ -206,8 +206,8 @@ title: Working examples
[power-grid-model]: https://github.com/PowerGridModel/power-grid-model
[TgCrypto]: https://github.com/pyrogram/tgcrypto
[streaming-form-data]: https://github.com/siddhantgoel/streaming-form-data
[Imagecodecs (fork)]: https://github.com/czaki/imagecodecs_build
[pybase64]: https://github.com/mayeut/pybase64
[Imagecodecs (fork)]: https://github.com/czaki/imagecodecs_build
[boost-histogram]: https://github.com/scikit-hep/boost-histogram
[bx-python]: https://github.com/bxlab/bx-python
[Python-WebRTC]: https://github.com/MarshalX/python-webrtc
+6 -1
View File
@@ -224,6 +224,7 @@ flake8-unused-arguments.ignore-variadic-names = true
flake8-tidy-imports.ban-relative-imports = "all"
flake8-annotations.allow-star-arg-any = true
flake8-annotations.mypy-init-return = true
future-annotations = true
[tool.ruff.lint.flake8-tidy-imports.banned-api]
"typing.Mapping".msg = "Use collections.abc.Mapping instead."
@@ -231,6 +232,7 @@ flake8-annotations.mypy-init-return = true
"typing.Iterator".msg = "Use collections.abc.Iterator instead."
"typing.Sequence".msg = "Use collections.abc.Sequence instead."
"typing.Set".msg = "Use collections.abc.Set instead."
"typing.TYPE_CHECKING".msg = "Use TYPE_CHECKING=False instead"
[tool.ruff.lint.per-file-ignores]
"unit_test/*" = ["PLC1901", "TID252"]
@@ -242,7 +244,10 @@ flake8-annotations.mypy-init-return = true
ignore = ["PC170", "PP303"]
[tool.check-wheel-contents]
ignore = ["W002"] # constraints-*.txt are allowed to be duplicates of one another
ignore = [
"W002", # constraints-*.txt are allowed to be duplicates of one another
"W004", # "-" is fine for our iOS support files for now
]
[tool.codespell]
ignore-words-list = [
+6 -1
View File
@@ -1,7 +1,8 @@
from __future__ import annotations
import json
import os
import subprocess
from collections.abc import Generator
import pytest
from filelock import FileLock
@@ -16,6 +17,10 @@ from cibuildwheel.venv import find_uv
from . import utils
from .utils import DEFAULT_CIBW_ENABLE, EMULATED_ARCHS, get_platform
TYPE_CHECKING = False
if TYPE_CHECKING:
from collections.abc import Generator
def pytest_addoption(parser: pytest.Parser) -> None:
parser.addoption(
+6 -1
View File
@@ -1,5 +1,6 @@
from __future__ import annotations
import textwrap
from pathlib import Path
import packaging.utils
import pytest
@@ -9,6 +10,10 @@ from cibuildwheel.selector import EnableGroup
from . import test_projects, utils
TYPE_CHECKING = False
if TYPE_CHECKING:
from pathlib import Path
basic_project = test_projects.new_c_project(
setup_py_add=textwrap.dedent(
"""
+6 -1
View File
@@ -1,11 +1,16 @@
from __future__ import annotations
import subprocess
import textwrap
from pathlib import Path
import pytest
from . import test_projects, utils
TYPE_CHECKING = False
if TYPE_CHECKING:
from pathlib import Path
pyproject_toml = r"""
[build-system]
requires = ["setuptools", "wheel"]
+8 -3
View File
@@ -1,10 +1,15 @@
import textwrap
from pathlib import Path
from __future__ import annotations
import pytest
import textwrap
from . import test_projects, utils
TYPE_CHECKING = False
if TYPE_CHECKING:
from pathlib import Path
import pytest
pyproject_toml = r"""
[build-system]
requires = ["setuptools", "wheel"]
+6 -1
View File
@@ -1,11 +1,16 @@
from __future__ import annotations
import subprocess
import textwrap
from pathlib import Path
import pytest
from . import test_projects, utils
TYPE_CHECKING = False
if TYPE_CHECKING:
from pathlib import Path
project_with_before_build_asserts = test_projects.new_c_project(
setup_py_add=textwrap.dedent(
r"""
+6 -1
View File
@@ -1,11 +1,16 @@
from __future__ import annotations
import subprocess
import textwrap
from pathlib import Path
import pytest
from . import test_projects, utils
TYPE_CHECKING = False
if TYPE_CHECKING:
from pathlib import Path
# pyodide does not support building without isolation, need to check the base_prefix
SYS_PREFIX = f"sys.{'base_' if utils.get_platform() == 'pyodide' else ''}prefix"
+5 -1
View File
@@ -1,7 +1,11 @@
from pathlib import Path
from __future__ import annotations
from . import test_projects, utils
TYPE_CHECKING = False
if TYPE_CHECKING:
from pathlib import Path
before_test_project = test_projects.new_c_project()
before_test_project.files["test/spam_test.py"] = r"""
import sys
+6 -1
View File
@@ -1,10 +1,15 @@
from __future__ import annotations
import subprocess
from pathlib import Path
import pytest
from . import test_projects, utils
TYPE_CHECKING = False
if TYPE_CHECKING:
from pathlib import Path
@pytest.mark.parametrize(
"frontend_name",
+6 -1
View File
@@ -1,8 +1,13 @@
from __future__ import annotations
import textwrap
from pathlib import Path
from . import test_projects, utils
TYPE_CHECKING = False
if TYPE_CHECKING:
from pathlib import Path
project_with_skip_asserts = test_projects.new_c_project(
setup_py_add=textwrap.dedent(
rf"""
+5 -1
View File
@@ -1,9 +1,13 @@
from pathlib import Path
from __future__ import annotations
import pytest
from . import test_projects, utils
TYPE_CHECKING = False
if TYPE_CHECKING:
from pathlib import Path
basic_project = test_projects.new_c_project()
+6 -1
View File
@@ -1,11 +1,16 @@
from __future__ import annotations
import platform
import textwrap
from pathlib import Path
import pytest
from . import test_projects, utils
TYPE_CHECKING = False
if TYPE_CHECKING:
from pathlib import Path
dockcross_only_project = test_projects.new_c_project(
setup_py_add=textwrap.dedent(
r"""
+5 -1
View File
@@ -1,10 +1,14 @@
from pathlib import Path
from __future__ import annotations
import jinja2
from . import utils
from .test_projects import TestProject
TYPE_CHECKING = False
if TYPE_CHECKING:
from pathlib import Path
cpp_test_project = TestProject()
setup_py_template = r"""
+6 -1
View File
@@ -1,7 +1,8 @@
from __future__ import annotations
import subprocess
import textwrap
from contextlib import nullcontext as does_not_raise
from pathlib import Path
import pytest
@@ -9,6 +10,10 @@ from test import test_projects
from . import utils
TYPE_CHECKING = False
if TYPE_CHECKING:
from pathlib import Path
basic_project = test_projects.new_c_project()
basic_project.files["repair.py"] = """
import shutil
+6 -1
View File
@@ -1,8 +1,9 @@
from __future__ import annotations
import json
import re
import subprocess
import textwrap
from pathlib import Path
import pytest
@@ -10,6 +11,10 @@ from cibuildwheel.util import resources
from . import test_projects, utils
TYPE_CHECKING = False
if TYPE_CHECKING:
from pathlib import Path
VERSION_REGEX = r"([\w-]+)==([^;\s]+)"
CHECK_VERSIONS_SCRIPT = """\
+6 -1
View File
@@ -1,11 +1,16 @@
from __future__ import annotations
import itertools
import subprocess
from pathlib import Path
import pytest
from . import test_projects, utils
TYPE_CHECKING = False
if TYPE_CHECKING:
from pathlib import Path
project_with_a_test = test_projects.new_c_project()
project_with_a_test.files["test/spam_test.py"] = r"""
+6 -1
View File
@@ -1,13 +1,18 @@
from __future__ import annotations
import os
import subprocess
import sys
import textwrap
from pathlib import Path
import pytest
from . import test_projects, utils
TYPE_CHECKING = False
if TYPE_CHECKING:
from pathlib import Path
project_with_environment_asserts = test_projects.new_c_project(
setup_py_add=textwrap.dedent(
r"""
+10 -5
View File
@@ -1,17 +1,22 @@
from __future__ import annotations
import os
import subprocess
import sys
import textwrap
from collections.abc import Mapping
from pathlib import Path
from tempfile import TemporaryDirectory
import pytest
from test.test_projects.base import TestProject
from . import test_projects, utils
TYPE_CHECKING = False
if TYPE_CHECKING:
from collections.abc import Mapping
import pytest
from test.test_projects.base import TestProject
# utilities
+6 -1
View File
@@ -1,14 +1,19 @@
from __future__ import annotations
import os
import platform
import shutil
import subprocess
import textwrap
from pathlib import Path
import pytest
from . import test_projects, utils
TYPE_CHECKING = False
if TYPE_CHECKING:
from pathlib import Path
pytestmark = pytest.mark.ios
CIBW_PLATFORM = os.environ.get("CIBW_PLATFORM", "ios")
+6 -1
View File
@@ -1,11 +1,16 @@
from __future__ import annotations
import platform
import subprocess
from pathlib import Path
import pytest
from . import test_projects, utils
TYPE_CHECKING = False
if TYPE_CHECKING:
from pathlib import Path
def test_python_exist(tmp_path: Path, capfd: pytest.CaptureFixture[str]) -> None:
if utils.get_platform() != "linux":
+6 -1
View File
@@ -1,10 +1,15 @@
from __future__ import annotations
import platform
from pathlib import Path
import pytest
from . import test_projects, utils
TYPE_CHECKING = False
if TYPE_CHECKING:
from pathlib import Path
basic_project = test_projects.new_c_project()
basic_project.files["tests/test_suite.py"] = r"""
import platform
+6 -1
View File
@@ -1,11 +1,16 @@
from __future__ import annotations
import platform
import textwrap
from pathlib import Path
import pytest
from . import test_projects, utils
TYPE_CHECKING = False
if TYPE_CHECKING:
from pathlib import Path
# TODO: specify these at runtime according to manylinux_image
project_with_manylinux_symbols = test_projects.new_c_project(
spam_c_top_level_add=textwrap.dedent(
+5 -1
View File
@@ -1,9 +1,13 @@
from pathlib import Path
from __future__ import annotations
import packaging.utils
from . import test_projects, utils
TYPE_CHECKING = False
if TYPE_CHECKING:
from pathlib import Path
meson_project = test_projects.new_meson_project()
+6 -1
View File
@@ -1,10 +1,15 @@
from __future__ import annotations
import textwrap
from pathlib import Path
import pytest
from . import test_projects, utils
TYPE_CHECKING = False
if TYPE_CHECKING:
from pathlib import Path
project_with_manylinux_symbols = test_projects.new_c_project(
spam_c_top_level_add=textwrap.dedent(
r"""
+6
View File
@@ -1,8 +1,14 @@
from __future__ import annotations
import textwrap
from pathlib import Path
from . import test_projects, utils
TYPE_CHECKING = False
if TYPE_CHECKING:
from pathlib import Path
basic_project = test_projects.new_c_project(
setup_py_add=textwrap.dedent(
"""
+6 -1
View File
@@ -1,8 +1,13 @@
from pathlib import Path
from __future__ import annotations
from typing import Any, Self
import jinja2
TYPE_CHECKING = False
if TYPE_CHECKING:
from pathlib import Path
FilesDict = dict[str, str | jinja2.Template]
TemplateContext = dict[str, Any]
+6
View File
@@ -1,3 +1,5 @@
from __future__ import annotations
import subprocess
from pathlib import Path
@@ -7,6 +9,10 @@ from test import test_projects
from . import utils
TYPE_CHECKING = False
if TYPE_CHECKING:
from pathlib import Path
pure_python_project = test_projects.TestProject()
pure_python_project.files["setup.py"] = """
from setuptools import Extension, setup
+6 -1
View File
@@ -1,9 +1,10 @@
from __future__ import annotations
import contextlib
import os
import subprocess
import sys
import textwrap
from pathlib import Path
import pytest
@@ -11,6 +12,10 @@ from cibuildwheel.util.file import CIBW_CACHE_PATH
from . import test_projects, utils
TYPE_CHECKING = False
if TYPE_CHECKING:
from pathlib import Path
pytestmark = pytest.mark.pyodide
CIBW_PLATFORM = os.environ.get("CIBW_PLATFORM", "pyodide")

Some files were not shown because too many files have changed in this diff Show More