Compare commits

..
Author SHA1 Message Date
cibuildwheel-bot[bot] 7e53c3d952 Update dependencies 2026-03-14 16:57:21 +00:00
187 changed files with 2334 additions and 6262 deletions
-122
View File
@@ -1,122 +0,0 @@
---
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.
+91
View File
@@ -0,0 +1,91 @@
only_if: changesInclude('.cirrus.yml') || ($CIRRUS_BRANCH == "main" && !changesIncludeOnly('.github/*', 'bin/*', 'docs/*', '.circleci/*', '.travis.yml', '.pre-commit-config.yaml', '.readthedocs.yml', 'azure-pipelines.yml', 'README.md', 'mkdocs.yml', 'noxfile.py')) || $CIRRUS_BRANCH =~ 'cirrus.*'
run_tests: &RUN_TESTS
install_cibuildwheel_script:
- python -m pip install -U pip
- python -m pip install -e. --group test
run_cibuildwheel_tests_script:
- python ./bin/run_tests.py
linux_x86_task:
timeout_in: 120m
compute_engine_instance:
image_project: cirrus-images
image: family/docker-builder
platform: linux
cpu: 8
memory: 8G
env:
VENV_ROOT: ${HOME}/venv-cibuildwheel
PATH: ${VENV_ROOT}/bin:${PATH}
install_pre_requirements_script:
- docker run --rm --privileged docker.io/tonistiigi/binfmt:latest --install all
- add-apt-repository -y ppa:deadsnakes/ppa
- apt-get update
- apt-get install -y python3.12-venv
- python3.12 -m venv ${VENV_ROOT}
<<: *RUN_TESTS
linux_aarch64_task:
compute_engine_instance:
image_project: cirrus-images
image: family/docker-builder-arm64
architecture: arm64
platform: linux
cpu: 4
memory: 4G
env:
VENV_ROOT: ${HOME}/venv-cibuildwheel
PATH: ${VENV_ROOT}/bin:${PATH}
install_pre_requirements_script:
- docker run --rm --privileged docker.io/tonistiigi/binfmt:latest --install all
- add-apt-repository -y ppa:deadsnakes/ppa
- apt-get update
- apt-get install -y python3.12-venv
- python3.12 -m venv ${VENV_ROOT}
<<: *RUN_TESTS
windows_x86_task:
# The task takes ~55 minutes while the timeout happens after 60 minutes by default,
# let's allow some wiggle room.
timeout_in: 120m
windows_container:
image: cirrusci/windowsservercore:visualstudio2022
cpu: 8
memory: 8G
install_pre_requirements_script:
- certutil -generateSSTFromWU roots.sst
- certutil -addstore -f root roots.sst
- del roots.sst
- choco install -y --no-progress python3 --version 3.12.4
- refreshenv
- powershell -Command "$cleanPath = $env:PATH -replace ';+$', ''; Add-Content -Path $env:CIRRUS_ENV -Value ('PATH=' + $cleanPath)"
<<: *RUN_TESTS
macos_arm64_task:
macos_instance:
image: ghcr.io/cirruslabs/macos-runner:sequoia
env:
VENV_ROOT: ${HOME}/venv-cibuildwheel
PATH: ${VENV_ROOT}/bin:${PATH}
install_pre_requirements_script:
- brew install python@3.12
- python3.12 -m venv ${VENV_ROOT}
<<: *RUN_TESTS
macos_arm64_cp38_task:
macos_instance:
image: ghcr.io/cirruslabs/macos-runner:sequoia
env:
VENV_ROOT: ${HOME}/venv-cibuildwheel
PATH: ${VENV_ROOT}/bin:${PATH}
PYTEST_ADDOPTS: --run-cp38-universal2 -k 'test_cp38_arm64_testing_universal2_installer or test_arch_auto or test_dummy_serial'
install_pre_requirements_script:
- brew install python@3.12
- python3.12 -m venv ${VENV_ROOT}
- curl -fsSLO https://www.python.org/ftp/python/3.8.10/python-3.8.10-macos11.pkg
- sudo installer -pkg python-3.8.10-macos11.pkg -target /
- rm python-3.8.10-macos11.pkg
- sh "/Applications/Python 3.8/Install Certificates.command"
<<: *RUN_TESTS
-40
View File
@@ -1,40 +0,0 @@
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
+4 -4
View File
@@ -13,11 +13,11 @@ jobs:
dist: dist:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@v6
with: with:
persist-credentials: false persist-credentials: false
- uses: hynek/build-and-inspect-python-package@d44ca7d91762de7a7d5436ddae667c6da6d1c3df # v2.18.0 - uses: hynek/build-and-inspect-python-package@v2
publish: publish:
needs: [dist] needs: [dist]
@@ -31,7 +31,7 @@ jobs:
attestations: write attestations: write
steps: steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - uses: actions/download-artifact@v8
with: with:
name: Packages name: Packages
path: dist path: dist
@@ -41,6 +41,6 @@ jobs:
with: with:
subject-path: "dist/cibuildwheel-*" subject-path: "dist/cibuildwheel-*"
- uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0 - uses: pypa/gh-action-pypi-publish@release/v1
with: with:
attestations: true attestations: true
+33 -37
View File
@@ -39,35 +39,35 @@ jobs:
name: Linters (mypy, ruff, etc.) name: Linters (mypy, ruff, etc.)
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@v6
with: with:
persist-credentials: false persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - uses: actions/setup-python@v6
id: python
with: with:
python-version: "3.x" python-version: "3.x"
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 - uses: j178/prek-action@v1
- uses: j178/prek-action@bdca6f102f98e2b4c7029491a53dfd366469e33d # v2.0.4
- name: PyLint checks - name: PyLint checks
run: uvx nox -s pylint -- --output-format=github run: pipx run --python "${{ steps.python.outputs.python-path }}" nox -s pylint -- --output-format=github
sample: sample:
name: Generate a sample project name: Generate a sample project
needs: lint needs: lint
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@v6
with: with:
persist-credentials: false persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - uses: actions/setup-python@v6
name: Install Python name: Install Python
with: with:
python-version: "3.x" python-version: "3.x"
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 - uses: astral-sh/setup-uv@v7
- name: Install dependencies - name: Install dependencies
run: uv sync --no-dev --group test run: uv sync --no-dev --group test
- name: Generate a sample project - name: Generate a sample project
run: uv run --no-sync -m test.test_projects test.test_0_basic.basic_project sample_proj run: uv run --no-sync -m test.test_projects test.test_0_basic.basic_project sample_proj
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - uses: actions/upload-artifact@v7
with: with:
name: sample_proj name: sample_proj
path: sample_proj path: sample_proj
@@ -84,10 +84,10 @@ jobs:
- os: ubuntu-latest - os: ubuntu-latest
python_version: '3.11' python_version: '3.11'
# Max Python # Max Python
- os: ubuntu-latest
python_version: '3.15'
- os: ubuntu-latest - os: ubuntu-latest
python_version: '3.14' python_version: '3.14'
- os: ubuntu-latest
python_version: '3.13'
test_select: android test_select: android
- os: ubuntu-24.04-arm - os: ubuntu-24.04-arm
python_version: '3.13' python_version: '3.13'
@@ -117,16 +117,16 @@ jobs:
test_select: android test_select: android
timeout-minutes: 180 timeout-minutes: 180
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@v6
with: with:
persist-credentials: false persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - uses: actions/setup-python@v6
name: Install Python ${{ matrix.python_version }} name: Install Python ${{ matrix.python_version }}
with: with:
python-version: ${{ matrix.python_version }} python-version: ${{ matrix.python_version }}
allow-prereleases: true allow-prereleases: true
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 - uses: astral-sh/setup-uv@v7
- name: Free up disk space - name: Free up disk space
if: runner.os == 'Linux' && matrix.test_select != 'android' if: runner.os == 'Linux' && matrix.test_select != 'android'
@@ -139,7 +139,7 @@ jobs:
# for oci_container unit tests # for oci_container unit tests
- name: Set up QEMU - name: Set up QEMU
if: runner.os == 'Linux' if: runner.os == 'Linux'
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0 uses: docker/setup-qemu-action@v4
- name: Set up Xcode - name: Set up Xcode
if: ${{ startsWith(matrix.os, 'macos-15') }} if: ${{ startsWith(matrix.os, 'macos-15') }}
@@ -150,12 +150,12 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: uv sync --no-dev --group test run: uv sync --no-dev --group test
- uses: joerick/pr-labels-action@0543b277721e852d821c6738d449f2f4dea03d5f # v1.0.9 - uses: joerick/pr-labels-action@v1.0.9
- name: Set CIBW_ENABLE - name: Set CIBW_ENABLE
shell: bash shell: bash
run: | run: |
if [[ "$GITHUB_REF_NAME" == "main" ]]; then if [[ "${{ github.ref_name }}" == "main" ]]; then
CIBW_ENABLE=all CIBW_ENABLE=all
else else
# get the default CIBW_ENABLE value from the test module # get the default CIBW_ENABLE value from the test module
@@ -172,7 +172,7 @@ jobs:
echo "CIBW_ENABLE=${CIBW_ENABLE}" >> $GITHUB_ENV echo "CIBW_ENABLE=${CIBW_ENABLE}" >> $GITHUB_ENV
- name: Download a sample project - name: Download a sample project
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 uses: actions/download-artifact@v8
with: with:
name: sample_proj name: sample_proj
path: sample_proj path: sample_proj
@@ -230,7 +230,7 @@ jobs:
run: | run: |
test $(find wheelhouse_only -name '*.whl' | wc -l) -eq 1 test $(find wheelhouse_only -name '*.whl' | wc -l) -eq 1
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - uses: actions/upload-artifact@v7
with: with:
name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }} name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }}
path: wheelhouse/*.whl path: wheelhouse/*.whl
@@ -247,13 +247,13 @@ jobs:
outputs: outputs:
archs: ${{ steps.archs.outputs.archs }} archs: ${{ steps.archs.outputs.archs }}
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@v6
with: with:
persist-credentials: false persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - uses: actions/setup-python@v6
with: with:
python-version: "3.x" python-version: "3.x"
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 - uses: astral-sh/setup-uv@v7
- name: Install dependencies - name: Install dependencies
run: uv sync --no-dev --group test run: uv sync --no-dev --group test
- name: Get qemu emulated architectures - name: Get qemu emulated architectures
@@ -272,23 +272,21 @@ jobs:
matrix: matrix:
arch: ${{ fromJSON(needs.emulated-archs.outputs.archs) }} arch: ${{ fromJSON(needs.emulated-archs.outputs.archs) }}
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@v6
with: with:
persist-credentials: false persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - uses: actions/setup-python@v6
with: with:
python-version: "3.x" python-version: "3.x"
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 - uses: astral-sh/setup-uv@v7
- name: Install dependencies - name: Install dependencies
run: uv sync --no-dev --group test run: uv sync --no-dev --group test
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0 uses: docker/setup-qemu-action@v4
- name: Run the emulation tests - name: Run the emulation tests
env: run: uv run --no-sync pytest --run-emulation ${{ matrix.arch }} test/test_emulation.py
MATRIX_ARCH: ${{ matrix.arch }}
run: uv run --no-sync pytest --run-emulation "$MATRIX_ARCH" test/test_emulation.py
test-pyodide: test-pyodide:
name: Test pyodide name: Test pyodide
@@ -296,20 +294,20 @@ jobs:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
timeout-minutes: 180 timeout-minutes: 180
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@v6
with: with:
persist-credentials: false persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - uses: actions/setup-python@v6
name: Install Python 3.12 name: Install Python 3.12
with: with:
python-version: '3.12' python-version: '3.12'
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 - uses: astral-sh/setup-uv@v7
- name: Install dependencies - name: Install dependencies
run: uv sync --no-dev --group test run: uv sync --no-dev --group test
- name: Download a sample project - name: Download a sample project
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 uses: actions/download-artifact@v8
with: with:
name: sample_proj name: sample_proj
path: sample_proj path: sample_proj
@@ -321,13 +319,11 @@ jobs:
output-dir: wheelhouse output-dir: wheelhouse
env: env:
CIBW_PLATFORM: pyodide CIBW_PLATFORM: pyodide
CIBW_ENABLE: pyodide-prerelease
- name: Run tests with 'CIBW_PLATFORM' set to 'pyodide' - name: Run tests with 'CIBW_PLATFORM' set to 'pyodide'
run: uv run --no-sync ./bin/run_tests.py run: uv run --no-sync ./bin/run_tests.py
env: env:
CIBW_PLATFORM: pyodide CIBW_PLATFORM: pyodide
CIBW_ENABLE: pyodide-prerelease
test-uv-extras: test-uv-extras:
name: Test uv extra on ${{ matrix.os }} ${{ matrix.test_select }} name: Test uv extra on ${{ matrix.os }} ${{ matrix.test_select }}
@@ -348,11 +344,11 @@ jobs:
- os: macos-15 - os: macos-15
test_select: android test_select: android
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@v6
with: with:
persist-credentials: false persist-credentials: false
- name: Download a sample project - name: Download a sample project
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 uses: actions/download-artifact@v8
with: with:
name: sample_proj name: sample_proj
path: sample_proj path: sample_proj
+8 -13
View File
@@ -23,38 +23,33 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
contents: write contents: write
environment: ${{ github.ref == 'refs/heads/main' && github.repository == 'pypa/cibuildwheel' && 'update-dependencies-workflow' || '' }}
steps: steps:
# we use this step to grab a Github App auth token, so that PRs generated by this workflow # we use this step to grab a Github App auth token, so that PRs generated by this workflow
# run the GHA tests. # run the GHA tests.
- uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 - uses: actions/create-github-app-token@v2
id: generate-token id: generate-token
if: github.ref == 'refs/heads/main' && github.repository == 'pypa/cibuildwheel' if: github.ref == 'refs/heads/main' && github.repository == 'pypa/cibuildwheel'
with: with:
client-id: ${{ secrets.CIBUILDWHEEL_BOT_APP_ID }} app-id: ${{ secrets.CIBUILDWHEEL_BOT_APP_ID }}
private-key: ${{ secrets.CIBUILDWHEEL_BOT_APP_PRIVATE_KEY }} private-key: ${{ secrets.CIBUILDWHEEL_BOT_APP_PRIVATE_KEY }}
permission-contents: write
permission-pull-requests: write
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@v6
with: with:
persist-credentials: false persist-credentials: false
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 - uses: wntrblm/nox@2026.02.09
with:
python-version: "3.14"
- name: "Run update: dependencies" - name: "Run update: dependencies"
run: uvx nox --force-color -s update_constraints run: nox --force-color -s update_constraints
- name: "Run update: python configs" - name: "Run update: python configs"
run: uvx nox --force-color -s update_pins run: nox --force-color -s update_pins
- name: "Run update: docs user projects" - name: "Run update: docs user projects"
run: uvx nox --force-color -s update_proj -- --auth=${{ secrets.GITHUB_TOKEN }} run: nox --force-color -s update_proj -- --auth=${{ secrets.GITHUB_TOKEN }}
- name: Create Pull Request - name: Create Pull Request
if: github.ref == 'refs/heads/main' && github.repository == 'pypa/cibuildwheel' if: github.ref == 'refs/heads/main' && github.repository == 'pypa/cibuildwheel'
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 uses: peter-evans/create-pull-request@v8
with: with:
commit-message: Update dependencies commit-message: Update dependencies
title: '[Bot] Update dependencies' title: '[Bot] Update dependencies'
+2 -2
View File
@@ -21,12 +21,12 @@ jobs:
permissions: permissions:
contents: write contents: write
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@v6
with: with:
persist-credentials: false persist-credentials: false
- name: Update the ${{ env.TAG_NAME }} tag - name: Update the ${{ env.TAG_NAME }} tag
id: update-major-minor-tag id: update-major-minor-tag
uses: joerick/update-vX.Y-tag-action@c4cefb60c33b82e4aa73a469e4acf30ee48d5812 # v1.0.2 uses: joerick/update-vX.Y-tag-action@v1.0
with: with:
source-tag: ${{ env.TAG_NAME }} source-tag: ${{ env.TAG_NAME }}
-24
View File
@@ -1,24 +0,0 @@
name: zizmor - GitHub Actions Security Analysis
on:
push:
branches: ["main"]
pull_request:
branches: ["**"]
permissions: {}
jobs:
zizmor:
name: Run zizmor 🌈
runs-on: ubuntu-latest
permissions:
security-events: write
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Run zizmor 🌈
uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6
-10
View File
@@ -112,13 +112,3 @@ site/
# PyCharm # PyCharm
.idea/ .idea/
# Lockfiles
*.lock
*.pylock
# OS files
.DS_Store
# This file should be a symlink or contain "See @AGENTS.md"
CLAUDE.md
+10 -11
View File
@@ -1,10 +1,9 @@
ci: ci:
autoupdate_schedule: monthly autoupdate_schedule: monthly
exclude: "^cibuildwheel/resources/ios-support/(?!make_cross_venv\\.py)"
repos: repos:
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: 3e8a8703264a2f4a69428a0aa4dcb512790b2c8c # frozen: v6.0.0 rev: v6.0.0
hooks: hooks:
- id: check-case-conflict - id: check-case-conflict
- id: check-merge-conflict - id: check-merge-conflict
@@ -15,22 +14,21 @@ repos:
exclude: (cibuildwheel/resources/pinned_docker_images.cfg)|(.svg$) exclude: (cibuildwheel/resources/pinned_docker_images.cfg)|(.svg$)
- id: mixed-line-ending - id: mixed-line-ending
- id: trailing-whitespace - id: trailing-whitespace
exclude: ^cibuildwheel/resources/android/android.patch$
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
rev: 0c7b6c989466a93942def1f84baf36ddfcd60c83 # frozen: v0.15.14 rev: v0.15.5
hooks: hooks:
- id: ruff-check - id: ruff-check
args: ["--fix"] args: ["--fix", "--show-fixes"]
- id: ruff-format - id: ruff-format
- repo: https://github.com/pre-commit/mirrors-mypy - repo: https://github.com/pre-commit/mirrors-mypy
rev: fc0f09a29bb495f4a91f00266155d6282d52485d # frozen: v1.20.2 rev: v1.19.1
hooks: hooks:
- id: mypy - id: mypy
name: mypy 3.11 on cibuildwheel/ name: mypy 3.11 on cibuildwheel/
args: ["--python-version=3.11"] args: ["--python-version=3.11"]
exclude: ^cibuildwheel/resources/android/_cross_venv.py$ # Requires Python 3.13 or later exclude: ^cibuildwheel/resources/_cross_venv.py$ # Requires Python 3.13 or later
additional_dependencies: &mypy-dependencies additional_dependencies: &mypy-dependencies
- bracex - bracex
- build - build
@@ -58,7 +56,7 @@ repos:
additional_dependencies: *mypy-dependencies additional_dependencies: *mypy-dependencies
- repo: https://github.com/shellcheck-py/shellcheck-py - repo: https://github.com/shellcheck-py/shellcheck-py
rev: 745eface02aef23e168a8afb6b5737818efbea95 # frozen: v0.11.0.1 rev: v0.11.0.1
hooks: hooks:
- id: shellcheck - id: shellcheck
@@ -79,7 +77,7 @@ repos:
additional_dependencies: [cogapp>=3.5] additional_dependencies: [cogapp>=3.5]
- repo: https://github.com/codespell-project/codespell - repo: https://github.com/codespell-project/codespell
rev: 2ccb47ff45ad361a21071a7eedda4c37e6ae8c5a # frozen: v2.4.2 rev: v2.4.2
hooks: hooks:
- id: codespell - id: codespell
args: ["-w"] args: ["-w"]
@@ -87,13 +85,14 @@ repos:
- repo: https://github.com/python-jsonschema/check-jsonschema - repo: https://github.com/python-jsonschema/check-jsonschema
rev: 943377262562a12b57292fc98fabd7dbf81451fe # frozen: 0.37.2 rev: 0.37.0
hooks: hooks:
- id: check-dependabot - id: check-dependabot
- id: check-github-actions - id: check-github-actions
- id: check-github-workflows - id: check-github-workflows
- id: check-gitlab-ci - id: check-gitlab-ci
- id: check-readthedocs # Disabled due to schema issue for now:
# - id: check-readthedocs
- id: check-travis - id: check-travis
- id: check-jsonschema - id: check-jsonschema
name: Check projects name: Check projects
-57
View File
@@ -1,57 +0,0 @@
# 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.
+5 -5
View File
@@ -1,9 +1,9 @@
This is a summary of the host Python versions and platforms covered by the different CI platforms: This is a summary of the host Python versions and platforms covered by the different CI platforms:
| | 3.11 | 3.12 | 3.13 | 3.14 | | | 3.11 | 3.12 | 3.13 | 3.14 |
|---------|----------------------------------|-----------------------|----------------|----------------| |---------|----------------------------------|---------------------------------|----------------|----------------|
| Linux | Azure Pipelines / GitHub Actions | CircleCI¹ / GitLab¹ | GitHub Actions | GitHub Actions | | Linux | Azure Pipelines / GitHub Actions | CircleCI¹ / Cirrus CI / GitLab¹ | GitHub Actions | GitHub Actions |
| macOS | Azure Pipelines | CircleCI¹ / GitLab¹ | GitHub Actions | | | macOS | Azure Pipelines | CircleCI¹ / Cirrus CI / GitLab¹ | GitHub Actions | |
| Windows | Azure Pipelines | GitLab¹ | GitHub Actions | | | Windows | Azure Pipelines | Cirrus CI / GitLab¹ | GitHub Actions | |
> ¹ Runs a reduced set of tests to reduce CI load > ¹ Runs a reduced set of tests to reduce CI load
+64 -90
View File
@@ -22,32 +22,31 @@ What does it do?
While cibuildwheel itself requires a recent Python version to run (we support the last three releases), it can target the following versions to build wheels: While cibuildwheel itself requires a recent Python version to run (we support the last three releases), it can target the following versions to build wheels:
| | 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 | | | macOS Intel | macOS Apple Silicon | Windows 64bit | Windows 32bit | Windows Arm64 | manylinux<br/>musllinux x86_64 | manylinux<br/>musllinux i686 | manylinux<br/>musllinux aarch64 | manylinux<br/>musllinux ppc64le | manylinux<br/>musllinux s390x | manylinux<br/>musllinux armv7l | Android | iOS | Pyodide |
| ------------------------ | ----------- | ------------------- | ------------- | ------------- | -------------- | ------------------------------ | ---------------------------- | ------------------------------- | ------------------------------- | ----------------------------- | ------------------------------ | ------- | --- | -------------- | |--------------------|----|-----|----|-----|-----|----|-----|----|-----|-----|---|-----|-----|-----|
| CPython 3.9 | ✅ | ✅ | ✅ | ✅ | ✅<sup>2</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅<sup>5</sup> | N/A | N/A | N/A | | CPython 3.8 | ✅ | ✅ | ✅ | ✅ | N/A | ✅ | ✅ | ✅ | ✅ | ✅ | ✅⁵ | N/A | N/A | N/A |
| CPython 3.10 | ✅ | ✅ | ✅ | ✅ | ✅<sup>2</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅<sup>5</sup> | N/A | N/A | N/A | | CPython 3.9 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | ✅⁵ | N/A | N/A | N/A |
| CPython 3.11 | ✅ | ✅ | ✅ | ✅ | ✅<sup>2</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅<sup>5</sup> | N/A | N/A | N/A | | CPython 3.10 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | ✅⁵ | N/A | N/A | N/A |
| CPython 3.12 | ✅ | ✅ | ✅ | ✅ | ✅<sup>2</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅<sup>5</sup> | N/A | N/A | ✅<sup>3</sup> | | CPython 3.11 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | ✅⁵ | N/A | N/A | N/A |
| CPython 3.13 | ✅ | ✅ | ✅ | ✅ | ✅<sup>2</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅<sup>5</sup> | ✅ | ✅ | ✅ | | CPython 3.12 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | ✅⁵ | N/A | N/A | ✅⁴ |
| CPython 3.14 | ✅ | ✅ | ✅ | ✅ | ✅<sup>2</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅<sup>5</sup> | ✅ | ✅ | ✅<sup>4</sup> | | CPython 3.1 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | ✅⁵ | ✅ | ✅ | ✅ |
| CPython 3.15<sup>6</sup> | ✅ | ✅ | ✅ | ✅ | ✅<sup>2</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅<sup>5</sup> | ✅ | ✅ | N/A | | CPython 3.14 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | ✅⁵ | ✅ | ✅ | 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.8 v7.3 | ✅ | ✅ | ✅ | N/A | N/A | ✅¹ | ✅¹ | ✅¹ | 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.9 v7.3 | ✅ | ✅ | ✅ | N/A | N/A | ✅¹ | ✅¹ | ✅¹ | 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 | | PyPy 3.10 v7.3 | ✅ | ✅ | ✅ | N/A | N/A | ✅¹ | ✅¹ | ✅¹ | N/A | N/A | N/A | N/A | N/A | N/A |
| GraalPy 3.11 v24.2 | ✅ | ✅ | ✅ | N/A | N/A | ✅<sup>1</sup> | N/A | ✅<sup>1</sup> | N/A | N/A | N/A | N/A | N/A | N/A | | PyPy 3.11 v7.3 | ✅ | ✅ | ✅ | N/A | N/A | ✅¹ | ✅¹ | ✅¹ | N/A | N/A | N/A | N/A | N/A | N/A |
| GraalPy 3.12 v25.0 | ✅ | ✅ | ✅ | N/A | N/A | ✅<sup>1</sup> | N/A | ✅<sup>1</sup> | N/A | N/A | N/A | N/A | N/A | N/A | | GraalPy 3.11 v24.2 | ✅ | ✅ | ✅ | N/A | N/A | ✅¹ | N/A | ✅¹ | N/A | N/A | N/A | N/A | N/A | N/A |
| GraalPy 3.12 v25.0 | ✅ | ✅ | ✅ | N/A | N/A | ✅¹ | N/A | ✅¹ | N/A | N/A | N/A | N/A | N/A | N/A |
<sup>**1** PyPy & GraalPy are only supported for manylinux wheels.</sup><br> <sup>¹ PyPy & GraalPy are only supported for manylinux wheels.</sup><br>
<sup>**2** Windows arm64 support is experimental.</sup><br> <sup>² Windows arm64 support is experimental.</sup><br>
<sup>**3** Not supported on PyPI, uses old `pyodide` tag instead of `pyemscripten`. Requires `pyodide-eol` [`enable`](https://cibuildwheel.pypa.io/en/stable/options/#enable).</sup><br> <sup>³ Free-threaded mode requires opt-in on 3.13 using [`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> Experimental, not yet supported on PyPI, but can be used directly in web deployment. Use `--platform pyodide` to build.</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> 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, Windows, pyemscripten, iOS, and Android wheels - Builds manylinux, musllinux, macOS, and Windows wheels for CPython, PyPy, and GraalPy
- Supports CPython, PyPy, and GraalPy - Works on GitHub Actions, Azure Pipelines, CircleCI, GitLab CI, and Cirrus CI
- 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 - 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. 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.
@@ -57,18 +56,18 @@ Usage
`cibuildwheel` runs inside a CI service. Supported platforms depend on which service you're using: `cibuildwheel` runs inside a CI service. Supported platforms depend on which service you're using:
| | Linux | macOS | Windows | Linux ARM | macOS ARM | Windows ARM | Android | iOS | Pyodide | | | Linux | macOS | Windows | Linux ARM | macOS ARM | Windows ARM | Android | iOS |
| --------------- | ----- | ----- | ------- | -------------- | --------- | -------------- | -------------------------- | -------------------------- | -------------- | |-----------------|-------|-------|---------|-----------|-----------|-------------|---------|-----|
| GitHub Actions | ✅ | ✅ | ✅ | ✅ | ✅ | ✅<sup>2</sup> | ✅<sup>4</sup> | ✅<sup>3</sup> | ✅ | | GitHub Actions | ✅ | ✅ | ✅ | ✅ | ✅ | ✅² | ✅⁴ | ✅³ |
| Azure Pipelines | ✅ | ✅ | ✅ | | ✅ | ✅<sup>2</sup> | ✅<sup>4</sup> | ✅<sup>3</sup> | ✅<sup>5</sup> | | Azure Pipelines | ✅ | ✅ | ✅ | | ✅ | ✅² | ✅⁴ | ✅³ |
| CircleCI | ✅ | ✅ | | ✅ | ✅ | | ✅<sup>4</sup><sup>5</sup> | ✅<sup>3</sup><sup>5</sup> | ✅<sup>5</sup> | | CircleCI | ✅ | ✅ | | ✅ | ✅ | | ✅⁴ | ✅³ |
| GitLab CI | ✅ | ✅ | ✅ | ✅<sup>1</sup> | ✅ | | ✅<sup>4</sup><sup>5</sup> | ✅<sup>3</sup><sup>5</sup> | ✅<sup>5</sup> | | Gitlab CI | ✅ | ✅ | ✅ | ✅¹ | ✅ | | ✅⁴ | ✅³ |
| Cirrus CI | ✅ | ✅ | ✅ | ✅ | ✅ | | ✅⁴ | |
<sup>**1** [Requires emulation](https://cibuildwheel.pypa.io/en/stable/faq/#emulation), distributed separately. Other services may also support Linux ARM through emulation or third-party build hosts, but these are not tested in our CI.</sup><br> <sup>¹ [Requires emulation](https://cibuildwheel.pypa.io/en/stable/faq/#emulation), distributed separately. Other services may also support Linux ARM through emulation or third-party build hosts, but these are not tested in our CI.</sup><br>
<sup>**2** [Uses cross-compilation](https://cibuildwheel.pypa.io/en/stable/faq/#windows-arm64). It is not possible to test `arm64` on this CI platform.</sup><br> <sup>² [Uses cross-compilation](https://cibuildwheel.pypa.io/en/stable/faq/#windows-arm64). It is not possible to test `arm64` on this CI platform.</sup><br>
<sup>**3** Requires a macOS runner; runs tests on the simulator for the runner's architecture. </sup><br> <sup>³ Requires a macOS runner; runs tests on the simulator for the runner's architecture. </sup><br>
<sup>**4** Building for Android requires the runner to be Linux x86_64, macOS ARM64 or macOS x86_64. Testing has [additional requirements](https://cibuildwheel.pypa.io/en/stable/platforms/#android).</sup><br> <sup> Building for Android requires the runner to be Linux x86_64, macOS ARM64 or macOS x86_64. Testing has [additional requirements](https://cibuildwheel.pypa.io/en/stable/platforms/#android).</sup><br>
<sup>**5** Builds may work, but are untested in cibuildwheel's CI.</sup><br>
<!--intro-end--> <!--intro-end-->
@@ -100,7 +99,7 @@ jobs:
- uses: actions/setup-python@v6 - uses: actions/setup-python@v6
- name: Install cibuildwheel - name: Install cibuildwheel
run: python -m pip install cibuildwheel==4.0.0rc1 run: python -m pip install cibuildwheel==3.4.0
- name: Build wheels - name: Build wheels
run: python -m cibuildwheel --output-dir wheelhouse run: python -m cibuildwheel --output-dir wheelhouse
@@ -157,8 +156,6 @@ The following diagram summarises the steps that cibuildwheel takes on each platf
| | [`container-engine`](https://cibuildwheel.pypa.io/en/stable/options/#container-engine) | Specify the container engine to use when building Linux wheels | | | [`container-engine`](https://cibuildwheel.pypa.io/en/stable/options/#container-engine) | Specify the container engine to use when building Linux wheels |
| | [`dependency-versions`](https://cibuildwheel.pypa.io/en/stable/options/#dependency-versions) | Control the versions of the tools cibuildwheel uses | | | [`dependency-versions`](https://cibuildwheel.pypa.io/en/stable/options/#dependency-versions) | Control the versions of the tools cibuildwheel uses |
| | [`pyodide-version`](https://cibuildwheel.pypa.io/en/stable/options/#pyodide-version) | Specify the Pyodide version to use for `pyodide` platform builds | | | [`pyodide-version`](https://cibuildwheel.pypa.io/en/stable/options/#pyodide-version) | Specify the Pyodide version to use for `pyodide` platform builds |
| **Auditing** | [`audit-requires`](https://cibuildwheel.pypa.io/en/stable/options/#audit-requires) | Install Python dependencies for the audit step |
| | [`audit-command`](https://cibuildwheel.pypa.io/en/stable/options/#audit-command) | Use a tool to check wheels before the end of the run |
| **Testing** | [`test-command`](https://cibuildwheel.pypa.io/en/stable/options/#test-command) | The command to test each built wheel | | **Testing** | [`test-command`](https://cibuildwheel.pypa.io/en/stable/options/#test-command) | The command to test each built wheel |
| | [`before-test`](https://cibuildwheel.pypa.io/en/stable/options/#before-test) | Execute a shell command before testing each wheel | | | [`before-test`](https://cibuildwheel.pypa.io/en/stable/options/#before-test) | Execute a shell command before testing each wheel |
| | [`test-sources`](https://cibuildwheel.pypa.io/en/stable/options/#test-sources) | Paths that are copied into the working directory of the tests | | | [`test-sources`](https://cibuildwheel.pypa.io/en/stable/options/#test-sources) | Paths that are copied into the working directory of the tests |
@@ -173,7 +170,7 @@ The following diagram summarises the steps that cibuildwheel takes on each platf
| | [`build-verbosity`](https://cibuildwheel.pypa.io/en/stable/options/#build-verbosity) | Increase/decrease the output of the build | | | [`build-verbosity`](https://cibuildwheel.pypa.io/en/stable/options/#build-verbosity) | Increase/decrease the output of the build |
<!--[[[end]]] (sum: b7YIjCyCkf) --> <!--[[[end]]] (sum: dbfwOkj/k/) -->
These options can be specified in a pyproject.toml file, or as environment variables, see [configuration docs](https://cibuildwheel.pypa.io/en/latest/configuration/). These options can be specified in a pyproject.toml file, or as environment variables, see [configuration docs](https://cibuildwheel.pypa.io/en/latest/configuration/).
@@ -188,16 +185,16 @@ Here are some repos that use cibuildwheel.
| Name | CI | OS | Notes | | Name | CI | OS | Notes |
|-----------------------------------|----|----|:------| |-----------------------------------|----|----|:------|
| [scikit-learn][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] ![pyodide icon][] | The machine learning library. A complex but clean config using many of cibuildwheel's features to build a large project with Cython and C++ extensions. | | [scikit-learn][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | The machine learning library. A complex but clean config using many of cibuildwheel's features to build a large project with Cython and C++ extensions. |
| [duckdb][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | DuckDB is an analytical in-process SQL database management system | | [duckdb][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | DuckDB is an analytical in-process SQL database management system |
| [pytorch-fairseq][] | ![github icon][] | ![apple icon][] ![linux icon][] | Facebook AI Research Sequence-to-Sequence Toolkit written in Python. | | [pytorch-fairseq][] | ![github icon][] | ![apple icon][] ![linux icon][] | Facebook AI Research Sequence-to-Sequence Toolkit written in Python. |
| [NumPy][] | ![github icon][] ![travisci icon][] | ![windows icon][] ![apple icon][] ![linux icon][] ![pyodide icon][] | The fundamental package for scientific computing with Python. | | [NumPy][] | ![github icon][] ![travisci icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | The fundamental package for scientific computing with Python. |
| [NCNN][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | ncnn is a high-performance neural network inference framework optimized for the mobile platform | | [NCNN][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | ncnn is a high-performance neural network inference framework optimized for the mobile platform |
| [Matplotlib][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] ![pyodide icon][] | The venerable Matplotlib, a Python library with C++ portions | | [Matplotlib][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | The venerable Matplotlib, a Python library with C++ portions |
| [Tornado][] | ![github icon][] | ![linux icon][] ![apple icon][] ![windows icon][] | Tornado is a Python web framework and asynchronous networking library. Uses stable ABI for a small C extension. | | [Tornado][] | ![github icon][] | ![linux icon][] ![apple icon][] ![windows icon][] | Tornado is a Python web framework and asynchronous networking library. Uses stable ABI for a small C extension. |
| [MyPy][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | The compiled version of MyPy using MyPyC. | | [MyPy][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | The compiled version of MyPy using MyPyC. |
| [Prophet][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Tool for producing high quality forecasts for time series data that has multiple seasonality with linear or non-linear growth. | | [Prophet][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Tool for producing high quality forecasts for time series data that has multiple seasonality with linear or non-linear growth. |
| [Triton][] | ![github icon][] | ![linux icon][] | Self hosted runners | | [Kivy][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Open source UI framework written in Python, running on Windows, Linux, macOS, Android and iOS |
[scikit-learn]: https://github.com/scikit-learn/scikit-learn [scikit-learn]: https://github.com/scikit-learn/scikit-learn
[duckdb]: https://github.com/duckdb/duckdb [duckdb]: https://github.com/duckdb/duckdb
@@ -208,19 +205,17 @@ Here are some repos that use cibuildwheel.
[Tornado]: https://github.com/tornadoweb/tornado [Tornado]: https://github.com/tornadoweb/tornado
[MyPy]: https://github.com/mypyc/mypy_mypyc-wheels [MyPy]: https://github.com/mypyc/mypy_mypyc-wheels
[Prophet]: https://github.com/facebook/prophet [Prophet]: https://github.com/facebook/prophet
[Triton]: https://github.com/openai/triton [Kivy]: https://github.com/kivy/kivy
[github icon]: docs/data/readme_icons/github.svg [github icon]: docs/data/readme_icons/github.svg
[azurepipelines icon]: docs/data/readme_icons/azurepipelines.svg [azurepipelines icon]: docs/data/readme_icons/azurepipelines.svg
[circleci icon]: docs/data/readme_icons/circleci.svg [circleci icon]: docs/data/readme_icons/circleci.svg
[gitlab icon]: docs/data/readme_icons/gitlab.svg [gitlab icon]: docs/data/readme_icons/gitlab.svg
[travisci icon]: docs/data/readme_icons/travisci.svg [travisci icon]: docs/data/readme_icons/travisci.svg
[cirrusci icon]: docs/data/readme_icons/cirrusci.svg
[windows icon]: docs/data/readme_icons/windows.svg [windows icon]: docs/data/readme_icons/windows.svg
[apple icon]: docs/data/readme_icons/apple.svg [apple icon]: docs/data/readme_icons/apple.svg
[linux icon]: docs/data/readme_icons/linux.svg [linux icon]: docs/data/readme_icons/linux.svg
[android icon]: docs/data/readme_icons/android.svg
[ios icon]: docs/data/readme_icons/ios.svg
[pyodide icon]: docs/data/readme_icons/pyodide.svg
<!-- END bin/projects.py --> <!-- END bin/projects.py -->
@@ -229,7 +224,7 @@ Here are some repos that use cibuildwheel.
Legal note Legal note
---------- ----------
Since `cibuildwheel` repairs the wheel with `delocate`, `auditwheel`, or `delvewheel`, it might automatically bundle dynamically linked libraries from the build machine. Since `cibuildwheel` repairs the wheel with `delocate` or `auditwheel`, 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. It helps ensure that the library can run without any dependencies outside of the pip toolchain.
@@ -240,45 +235,6 @@ Changelog
<!-- [[[cog from readme_changelog import mini_changelog; print(mini_changelog()) ]]] --> <!-- [[[cog from readme_changelog import mini_changelog; print(mini_changelog()) ]]] -->
### v4.0.0rc1
_14 May 2026_
- 🌟 Adds wheel auditing with `abi3audit` as a default after the repair step, with new [`audit-requires`](https://cibuildwheel.pypa.io/en/stable/options/#audit-requires) and [`audit-command`](https://cibuildwheel.pypa.io/en/stable/options/#audit-command) options (#2805)
- 🌟 Adds `pyemscripten` platform tag support (PEP 783), updates Pyodide to 314.0.0a1, and adds a `pyodide-eol` [`enable`](https://cibuildwheel.pypa.io/en/stable/options/#enable) flag for building end-of-life Pyodide versions (#2812, #2848)
- 🌟 Sets up `delvewheel` as the default [`repair-wheel-command`](https://cibuildwheel.pypa.io/en/stable/options/#repair-wheel-command) for Windows, so extension module DLLs are now bundled automatically. Skip by setting it to empty if not needed. (#2831)
- ✨ Adds CPython 3.15 support, under the [`enable` option](https://cibuildwheel.pypa.io/en/stable/options/#enable) `cpython-prerelease`. This version of cibuildwheel uses 3.15.0b1. (#2833, #2850)
_While CPython is in beta, the ABI can change, so your wheels might not be compatible with the final release. For this reason, we don't recommend distributing wheels until RC1, at which point 3.14 will be available in cibuildwheel without the flag._ (#2390)
- ✨ Adds `{project}` and `{package}` placeholders to [`config-settings`](https://cibuildwheel.pypa.io/en/stable/options/#config-settings) (#2827)
- ⚠️ Drops support for Python 3.8 (#2686)
- ⚠️ Removes the experimental CPython 3.13 free-threading builds and the `cpython-freethreading` [`enable`](https://cibuildwheel.pypa.io/en/stable/options/#enable) option. CPython 3.14+ free-threading support remains available without the enable flag. (#2684)
- ⚠️ Drops support for Cirrus CI, which is shutting down June 1, 2026 (#2817)
- 🐛 Fixes `UV_PYTHON` not being set for [`before-build`](https://cibuildwheel.pypa.io/en/stable/options/#before-build) on Linux when using `uv` as the [`build-frontend`](https://cibuildwheel.pypa.io/en/stable/options/#build-frontend) (#2830)
- 🛠 Updates Android to Python 3.13.13 and 3.14.4 (#2821)
- 🛠 Applies Pyodide-specific patches to the Emscripten toolchain installation (#2800)
- 🛠 Updates dependencies and container pins (#2845, #2837, #2821, #2818, #2810, #2838, #2813)
- 🛠 Uses `python -V -V` for Windows build diagnostics (#2832)
- 📚 Documents platform-specific [`before-build`](https://cibuildwheel.pypa.io/en/stable/options/#before-build) configuration (#2834)
- 📚 Updates the "How it works" diagram with details of Android, iOS, and Pyodide builds (#2816)
- 📚 Adds Pyodide icon and regenerates working examples data for Android, iOS, and Pyodide (#2815, #2811)
- 📚 Links back to source in docs (#2806)
- 💼 Adds PEP 723 metadata for `bin/` scripts and drops the `bin` dependency group (#2819)
- 💼 Updates CI action pins and dev dependencies (#2851, #2843, #2826, #2823, #2820, #2807)
- 🧪 Fixes Android tests using the `uv` frontend (#2809)
- 🧪 Fixes the update-dependencies workflow to use `uv` to run `nox` (#2808)
### v3.4.1
_2 April 2026_
- ⚠️ Building for the experimental CPython 3.13 free-threading variant is now deprecated. That functionality will be removed in the next minor release. The [`enable`](https://cibuildwheel.pypa.io/en/stable/options/#enable) option `cpython-freethreading` is therefore also deprecated. Builds specifying `enable = "all"` no longer select `cpython-freethreading`. CPython 3.14 free-threading support remains available without the `enable` flag. (#2787)
- 🐛 iOS builds will no longer skip `repair-wheel-command` if it's defined in config (#2761)
- 🐛 Fix bug causing `uv` to fail when environments define PYTHON_VERSION or UV_PYTHON, conflicting with our venvs (#2795)
- ✨ cibuildwheel prints the selected build identifiers at the start of the build. (#2785)
- 🔐 The GitHub Action now references other actions with a full SHA (#2744)
### v3.4.0 ### v3.4.0
_5 March 2026_ _5 March 2026_
@@ -286,7 +242,6 @@ _5 March 2026_
- 🌟 You can now build wheels using `uv` as a build frontend. This should improve performance, especially if your project has lots of build dependencies. To use, set [`build-frontend`](https://cibuildwheel.pypa.io/en/stable/options/#build-frontend) to `uv`. (#2322) - 🌟 You can now build wheels using `uv` as a build frontend. This should improve performance, especially if your project has lots of build dependencies. To use, set [`build-frontend`](https://cibuildwheel.pypa.io/en/stable/options/#build-frontend) to `uv`. (#2322)
- ⚠️ We no longer support running on Travis CI. It may continue working but we don't run tests there anymore so we can't be sure. (#2682) - ⚠️ We no longer support running on Travis CI. It may continue working but we don't run tests there anymore so we can't be sure. (#2682)
- ✨ Improvements to building rust wheels on Android (#2650) - ✨ Improvements to building rust wheels on Android (#2650)
- 🛠 Update Pyodide to 0.29.3 (#2719, #2733)
- 🐛 Fix bug with the GitHub Action on Windows, where PATH was getting unnecessarily changed, causing issues with meson builds. (#2723) - 🐛 Fix bug with the GitHub Action on Windows, where PATH was getting unnecessarily changed, causing issues with meson builds. (#2723)
- ✨ Add support for quiet setting on `build` and `uv` from the cibuildwheel `build-verbosity` setting. (#2737) - ✨ Add support for quiet setting on `build` and `uv` from the cibuildwheel `build-verbosity` setting. (#2737)
- 📚 Docs updates, including guidance on using Meson on Windows (#2718) - 📚 Docs updates, including guidance on using Meson on Windows (#2718)
@@ -311,7 +266,26 @@ _12 November 2025_
- 🛠 Improve the handling of `test-command` on Android, enabling more options to be passed (#2590) - 🛠 Improve the handling of `test-command` on Android, enabling more options to be passed (#2590)
- 📚 Docs improvements (#2618) - 📚 Docs improvements (#2618)
<!-- [[[end]]] (sum: ZD0EjcyC0B) --> ### v3.2.1
_12 October 2025_
- 🛠 Update to CPython 3.14.0 final (#2614)
- 🐛 Fix the default MACOSX_DEPLOYMENT_TARGET on Python 3.14 (#2613)
- 📚 Docs improvements (#2617)
### v3.2.0
_22 September 2025_
- ✨ Adds GraalPy v25 (Python 3.12) support (#2597)
- 🛠 Update to CPython 3.14.0rc3 (#2602)
- 🛠 Adds CPython 3.14.0 prerelease support for Android, and a number of improvements to Android builds (#2568, #2591)
- 🛠 Improvements to testing on Android, passing environment markers when installing the venv, and providing more debug output when build-verbosity is set (#2575)
- ⚠️ PyPy 3.10 was moved to `pypy-eol` in the `enable` option, as it is now end-of-life. (#2521)
- 📚 Docs improvements (#2574, #2601, #2598)
<!-- [[[end]]] (sum: aO1wNWSgKa) -->
--- ---
@@ -338,11 +312,11 @@ Core:
- Matthieu Darbois [@mayeut](https://github.com/mayeut) - Matthieu Darbois [@mayeut](https://github.com/mayeut)
- Henry Schreiner [@henryiii](https://github.com/henryiii) - Henry Schreiner [@henryiii](https://github.com/henryiii)
- Grzegorz Bokota [@Czaki](https://github.com/Czaki) - Grzegorz Bokota [@Czaki](https://github.com/Czaki)
- Agriya Khetarpal [@agriyakhetarpal](https://github.com/agriyakhetarpal) (also Pyodide)
Platform maintainers: Platform maintainers:
- Russell Keith-Magee [@freakboy3742](https://github.com/freakboy3742) (iOS) - Russell Keith-Magee [@freakboy3742](https://github.com/freakboy3742) (iOS)
- Agriya Khetarpal [@agriyakhetarpal](https://github.com/agriyakhetarpal) (Pyodide)
- Hood Chatham [@hoodmane](https://github.com/hoodmane) (Pyodide) - Hood Chatham [@hoodmane](https://github.com/hoodmane) (Pyodide)
- Gyeongjae Choi [@ryanking13](https://github.com/ryanking13) (Pyodide) - Gyeongjae Choi [@ryanking13](https://github.com/ryanking13) (Pyodide)
- Tim Felgentreff [@timfel](https://github.com/timfel) (GraalPy) - Tim Felgentreff [@timfel](https://github.com/timfel) (GraalPy)
+20 -46
View File
@@ -36,10 +36,9 @@ runs:
- id: cibw - id: cibw
run: | run: |
# Install cibuildwheel and build the command line # Install cibuildwheel
"$PYTHON" -u << "EOF" "${{ steps.python.outputs.python-path }}" -u << "EOF"
import os import os
import shlex
import shutil import shutil
import sys import sys
import venv import venv
@@ -47,7 +46,7 @@ runs:
from pathlib import Path from pathlib import Path
from subprocess import run from subprocess import run
EXTRAS = set(e.strip() for e in os.environ.get("INPUT_EXTRAS", "").split(",") if e.strip()) EXTRAS = set(e.strip() for e in "${{ inputs.extras }}".split(",") if e.strip())
class EnvBuilder(venv.EnvBuilder): class EnvBuilder(venv.EnvBuilder):
@@ -60,14 +59,14 @@ runs:
def post_setup(self, context): def post_setup(self, context):
super().post_setup(context) super().post_setup(context)
self.bin_path = Path(context.env_exe).parent self.bin_path = Path(context.env_exe).parent
install_spec = os.environ["GITHUB_ACTION_PATH"] install_spec = r"${{ github.action_path }}"
if EXTRAS: if EXTRAS:
install_spec += f"[{','.join(sorted(EXTRAS))}]" install_spec += f"[{','.join(sorted(EXTRAS))}]"
run([sys.executable, "-m", "pip", "--python", context.env_exe, "install", install_spec], check=True) run([sys.executable, "-m", "pip", "--python", context.env_exe, "install", install_spec], check=True)
print("::group::Install cibuildwheel") print("::group::Install cibuildwheel")
venv_path = Path(os.environ["RUNNER_TEMP"]) / "cibw" venv_path = Path(r"${{ runner.temp }}") / "cibw"
if venv_path.exists(): if venv_path.exists():
shutil.rmtree(venv_path) shutil.rmtree(venv_path)
@@ -89,59 +88,34 @@ runs:
cibw_bin = [p for p in builder.bin_path.glob("cibuildwheel*") if p.stem == "cibuildwheel"][0] cibw_bin = [p for p in builder.bin_path.glob("cibuildwheel*") if p.stem == "cibuildwheel"][0]
# Build the command line
cmd_args = [str(cibw_bin), os.environ["INPUT_PACKAGE_DIR"]]
if output_dir := os.environ.get("INPUT_OUTPUT_DIR"):
cmd_args += ["--output-dir", output_dir]
if config_file := os.environ.get("INPUT_CONFIG_FILE"):
cmd_args += ["--config-file", config_file]
if only := os.environ.get("INPUT_ONLY"):
cmd_args += ["--only", only]
cmd_bash = shlex.join(cmd_args)
def pwsh_quote(text):
# Wrap in single quotes and double-up any existing single quotes
return "'" + str(text).replace("'", "''") + "'"
# Prepend '& ' so PowerShell executes the quoted binary path
cmd_pwsh = "& " + " ".join(pwsh_quote(arg) for arg in cmd_args)
with open(os.environ["GITHUB_OUTPUT"], "at") as f: with open(os.environ["GITHUB_OUTPUT"], "at") as f:
f.write(f"cibw-bin={cibw_bin}\n")
f.write(f"prepend-path={clean_bin_path}\n") f.write(f"prepend-path={clean_bin_path}\n")
f.write(f"cmd-bash={cmd_bash}\n")
f.write(f"cmd-pwsh={cmd_pwsh}\n")
print("::endgroup::") print("::endgroup::")
EOF EOF
shell: bash shell: bash
env:
PYTHON: ${{ steps.python.outputs.python-path }}
INPUT_PACKAGE_DIR: ${{ inputs.package-dir }}
INPUT_OUTPUT_DIR: ${{ inputs.output-dir }}
INPUT_CONFIG_FILE: ${{ inputs.config-file }}
INPUT_ONLY: ${{ inputs.only }}
INPUT_EXTRAS: ${{ inputs.extras }}
# Redirecting stderr to stdout to fix interleaving issue in Actions. # Redirecting stderr to stdout to fix interleaving issue in Actions.
- run: | - run: |
export PATH="$CIBW_PREPEND_PATH:$PATH" export PATH="${{ steps.cibw.outputs.prepend-path }}:$PATH"
eval "$CIBW_CMD_BASH" 2>&1
"${{ steps.cibw.outputs.cibw-bin }}" \
"${{ inputs.package-dir }}" \
${{ inputs.output-dir != '' && format('--output-dir "{0}"', inputs.output-dir) || ''}} \
${{ inputs.config-file != '' && format('--config-file "{0}"', inputs.config-file) || ''}} \
${{ inputs.only != '' && format('--only "{0}"', inputs.only) || ''}} \
2>&1
shell: bash shell: bash
if: runner.os != 'Windows' if: runner.os != 'Windows'
env:
CIBW_PREPEND_PATH: ${{ steps.cibw.outputs.prepend-path }}
CIBW_CMD_BASH: ${{ steps.cibw.outputs.cmd-bash }}
# Windows needs powershell to interact nicely with Meson # Windows needs powershell to interact nicely with Meson
- run: | - run: |
$env:PATH = "$env:CIBW_PREPEND_PATH;$env:PATH" $env:PATH = "${{ steps.cibw.outputs.prepend-path }};$env:PATH"
Invoke-Expression $env:CIBW_CMD_PWSH & "${{ steps.cibw.outputs.cibw-bin }}" `
"${{ inputs.package-dir }}" `
${{ inputs.output-dir != '' && format('--output-dir "{0}"', inputs.output-dir) || ''}} `
${{ inputs.config-file != '' && format('--config-file "{0}"', inputs.config-file) || ''}} `
${{ inputs.only != '' && format('--only "{0}"', inputs.only) || ''}}
shell: pwsh shell: pwsh
if: runner.os == 'Windows' if: runner.os == 'Windows'
env:
CIBW_PREPEND_PATH: ${{ steps.cibw.outputs.prepend-path }}
CIBW_CMD_PWSH: ${{ steps.cibw.outputs.cmd-pwsh }}
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env -S uv run --script #!/usr/bin/env python3
# /// script # /// script
# dependencies = ["click", "packaging", "prek"] # dependencies = ["click", "packaging", "prek"]
+1 -11
View File
@@ -1,14 +1,4 @@
#!/usr/bin/env -S uv run --script #!/usr/bin/env python3
# /// script
# dependencies = [
# "click",
# "cibuildwheel",
# ]
#
# [tool.uv.sources]
# cibuildwheel = { path = ".." }
# ///
import sys import sys
import textwrap import textwrap
+2 -9
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env -S uv run --script #!/usr/bin/env -S uv run -q
# /// script # /// script
# dependencies = ["pyyaml"] # dependencies = ["pyyaml"]
@@ -28,6 +28,7 @@ $defs:
description: How to inherit the parent's value. description: How to inherit the parent's value.
enable: enable:
enum: enum:
- cpython-freethreading
- cpython-prerelease - cpython-prerelease
- graalpy - graalpy
- pyodide-prerelease - pyodide-prerelease
@@ -38,12 +39,6 @@ additionalProperties: false
description: cibuildwheel's settings. description: cibuildwheel's settings.
type: object type: object
properties: properties:
audit-command:
description: Execute a shell command to audit each wheel after it is repaired. Use {wheel} for each wheel path, or {abi3_wheel} to only audit abi3 wheels.
type: string_array
audit-requires:
description: Install Python dependencies for the audit step.
type: string_array
archs: archs:
description: Change the architectures built on your machine by default. description: Change the architectures built on your machine by default.
type: string_array type: string_array
@@ -314,8 +309,6 @@ items:
type: object type: object
additionalProperties: false additionalProperties: false
properties: properties:
audit-command: {"$ref": "#/$defs/inherit"}
audit-requires: {"$ref": "#/$defs/inherit"}
before-all: {"$ref": "#/$defs/inherit"} before-all: {"$ref": "#/$defs/inherit"}
before-build: {"$ref": "#/$defs/inherit"} before-build: {"$ref": "#/$defs/inherit"}
xbuild-tools: {"$ref": "#/$defs/inherit"} xbuild-tools: {"$ref": "#/$defs/inherit"}
+2 -20
View File
@@ -1,17 +1,4 @@
#!/usr/bin/env -S uv run --script #!/usr/bin/env python3
# /// script
# dependencies = [
# "click",
# "pygithub",
# "pyyaml",
# "rich",
# "cibuildwheel",
# ]
#
# [tool.uv.sources]
# cibuildwheel = { path = ".." }
# ///
""" """
Check known projects for usage of requires-python. Check known projects for usage of requires-python.
@@ -24,9 +11,8 @@ This will cache the results to all_known_setup.yaml; you can reprint
the results without the `--online` setting. the results without the `--online` setting.
""" """
from __future__ import annotations
import ast import ast
from collections.abc import Iterable, Iterator
from pathlib import Path from pathlib import Path
import click import click
@@ -36,10 +22,6 @@ from rich import print
from cibuildwheel.projectfiles import Analyzer from cibuildwheel.projectfiles import Analyzer
TYPE_CHECKING = False
if TYPE_CHECKING:
from collections.abc import Iterable, Iterator
DIR = Path(__file__).parent.resolve() DIR = Path(__file__).parent.resolve()
+1 -7
View File
@@ -1,10 +1,4 @@
#!/usr/bin/env -S uv run --script #!/usr/bin/env python3
# /// script
# dependencies = [
# "click",
# ]
# ///
import os import os
+7 -20
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env -S uv run --script #!/usr/bin/env python3
# /// script # /// script
# dependencies = [ # dependencies = [
@@ -17,14 +17,12 @@ Suggested usage:
git diff git diff
""" """
from __future__ import annotations
import builtins import builtins
import functools import functools
import textwrap import textwrap
import urllib.error
import urllib.request import urllib.request
import xml.dom.minidom import xml.dom.minidom
from collections.abc import Iterable, Mapping, Sequence
from datetime import UTC, datetime from datetime import UTC, datetime
from io import StringIO from io import StringIO
from pathlib import Path from pathlib import Path
@@ -32,11 +30,7 @@ from typing import Any, Self, TextIO
import click import click
import yaml import yaml
from github import Auth, Github, GithubException from github import Github, GithubException
TYPE_CHECKING = False
if TYPE_CHECKING:
from collections.abc import Iterable, Mapping, Sequence
ICONS = ( ICONS = (
"github", "github",
@@ -44,12 +38,10 @@ ICONS = (
"circleci", "circleci",
"gitlab", "gitlab",
"travisci", "travisci",
"cirrusci",
"windows", "windows",
"apple", "apple",
"linux", "linux",
"android",
"ios",
"pyodide",
) )
@@ -138,13 +130,8 @@ class Project:
def fetch_icon(icon_name: str) -> None: def fetch_icon(icon_name: str) -> None:
url = f"https://cdn.jsdelivr.net/npm/simple-icons@v4/icons/{icon_name}.svg" url = f"https://cdn.jsdelivr.net/npm/simple-icons@v4/icons/{icon_name}.svg"
try: with urllib.request.urlopen(url) as f:
with urllib.request.urlopen(url) as f: original_svg_data = f.read()
original_svg_data = f.read()
except urllib.error.HTTPError as e:
if e.code == 404 and path_for_icon(icon_name).exists():
return
raise
document = xml.dom.minidom.parseString(original_svg_data) document = xml.dom.minidom.parseString(original_svg_data)
svgElement = document.documentElement svgElement = document.documentElement
@@ -178,7 +165,7 @@ def get_projects(
for icon in ICONS: for icon in ICONS:
fetch_icon(icon) fetch_icon(icon)
github = Github(auth=Auth.Token(auth)) if (online and auth) else None github = Github(auth) if online else None
return sorted((Project(item, github) for item in config), reverse=online) return sorted((Project(item, github) for item in config), reverse=online)
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env -S uv run --script #!/usr/bin/env python3
import re import re
from pathlib import Path from pathlib import Path
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env -S uv run --script #!/usr/bin/env python3
import dataclasses import dataclasses
import re import re
+6 -7
View File
@@ -1,10 +1,4 @@
#!/usr/bin/env -S uv run --script #!/usr/bin/env python3
# /// script
# dependencies = [
# "click",
# ]
# ///
import os import os
@@ -121,6 +115,11 @@ services = [
dst_config_path=".gitlab-ci.yml", dst_config_path=".gitlab-ci.yml",
badge_md="[![Gitlab](https://gitlab.com/joerick/cibuildwheel/badges/{branch}/pipeline.svg)](https://gitlab.com/joerick/cibuildwheel/-/commits/{branch})", badge_md="[![Gitlab](https://gitlab.com/joerick/cibuildwheel/badges/{branch}/pipeline.svg)](https://gitlab.com/joerick/cibuildwheel/-/commits/{branch})",
), ),
CIService(
name="cirrus-ci",
dst_config_path=".cirrus.yml",
badge_md="[![Cirrus CI](https://api.cirrus-ci.com/github/pypa/cibuildwheel.svg?branch={branch})](https://cirrus-ci.com/github/pypa/cibuildwheel/{branch})",
),
] ]
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env -S uv run --script #!/usr/bin/env python3
import argparse import argparse
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env -S uv run --script #!/usr/bin/env python3
import argparse import argparse
+1 -8
View File
@@ -1,11 +1,4 @@
#!/usr/bin/env -S uv run --script #!/usr/bin/env python3
# /// script
# dependencies = [
# "packaging",
# "requests",
# ]
# ///
import configparser import configparser
import dataclasses import dataclasses
+32 -43
View File
@@ -1,60 +1,49 @@
#!/usr/bin/env -S uv run --script #!/usr/bin/env python3
# /// script
# dependencies = [
# "playwright",
# ]
# ///
import subprocess import subprocess
import sys import sys
import tempfile
from pathlib import Path from pathlib import Path
from playwright.sync_api import sync_playwright # type: ignore[import-not-found] try:
from html2image import Html2Image # type: ignore[import-not-found]
CSS = """ except ImportError:
<link href="https://fonts.googleapis.com/css2?family=Lato&display=swap" rel="stylesheet"> sys.exit(
<style> """
html, body { html2image not found. Ensure you have Chrome (on Mac/Windows) or
font-family: Lato, "Helvetica Neue", Helvetica, Arial, sans-serif; Chromium (on Linux) installed, and then do:
font-weight: 400; pip install html2image
font-size: 16px; """
color: #404040; )
background: white;
margin: 0;
padding: 0;
}
* {
box-sizing: border-box;
}
</style>
"""
def main() -> None: def main() -> None:
subprocess.run([sys.executable, "-m", "playwright", "install", "chromium"], check=True) subprocess.run(["mkdocs", "build"], check=True)
html_str = Path("docs/diagram.html").read_text() hti = Html2Image(custom_flags=["--force-device-scale-factor=2"])
html_str = f"<html><head>{CSS}</head><body>{html_str}</body></html>"
with tempfile.TemporaryDirectory() as tmp_dir_str: html_str = Path("docs/diagram.md").read_text()
html_path = Path(tmp_dir_str) / "diagram_screenshot.html" css_tags = f"""
html_path.write_text(html_str) <style>{Path("site/css/theme.css").read_text()}</style>
<style>{Path("site/css/theme_extra.css").read_text()}</style>
<style>{Path("site/extra.css").read_text()}</style>
<style>
body {{
background: white;
}}
</style>
"""
html_str = css_tags + html_str
dest_path = Path("docs/data/how-it-works.png") [screenshot, *_] = hti.screenshot(
html_str=html_str,
size=(830, 405),
)
with sync_playwright() as p: dest_path = Path("docs/data/how-it-works.png")
browser = p.chromium.launch() dest_path.unlink(missing_ok=True)
page = browser.new_page(device_scale_factor=2, viewport={"width": 830, "height": 600})
page.goto(html_path.as_uri())
page.wait_for_load_state("networkidle")
height = page.evaluate("document.body.scrollHeight") Path(screenshot).rename(dest_path)
page.set_viewport_size({"width": 830, "height": height})
page.screenshot(path=str(dest_path), full_page=True)
browser.close()
if __name__ == "__main__": if __name__ == "__main__":
+1 -10
View File
@@ -1,13 +1,4 @@
#!/usr/bin/env -S uv run --script #!/usr/bin/env python3
# /// script
# dependencies = [
# "click",
# "packaging",
# "requests",
# "rich",
# ]
# ///
import dataclasses import dataclasses
+3 -42
View File
@@ -1,33 +1,13 @@
#!/usr/bin/env -S uv run --script #!/usr/bin/env python3
# /// script
# dependencies = [
# "cibuildwheel",
# "requests",
# ]
#
# [tool.uv.sources]
# cibuildwheel = { path = ".." }
# ///
import json import json
from pathlib import Path
from typing import Final
import requests
from cibuildwheel.extra import github_api_request from cibuildwheel.extra import github_api_request
from cibuildwheel.util.python_build_standalone import ( from cibuildwheel.util.python_build_standalone import (
PythonBuildStandaloneAsset, PythonBuildStandaloneAsset,
PythonBuildStandaloneReleaseData, 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: def main() -> None:
@@ -45,27 +25,8 @@ def main() -> None:
f"repos/astral-sh/python-build-standalone/releases/tags/{latest_tag}" f"repos/astral-sh/python-build-standalone/releases/tags/{latest_tag}"
)["assets"] )["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 = [ assets = [
PythonBuildStandaloneAsset( PythonBuildStandaloneAsset(name=ga["name"], url=ga["browser_download_url"])
name=ga["name"],
url=ga["browser_download_url"],
sha256=name_to_sha256.get(ga["name"], ""),
)
for ga in github_assets for ga in github_assets
if ga["name"].endswith("install_only.tar.gz") if ga["name"].endswith("install_only.tar.gz")
] ]
+7 -75
View File
@@ -1,27 +1,14 @@
#!/usr/bin/env -S uv run --script #!/usr/bin/env python3
# /// script
# dependencies = [
# "click",
# "packaging",
# "requests",
# "rich",
# "cibuildwheel",
# ]
#
# [tool.uv.sources]
# cibuildwheel = { path = ".." }
# ///
from __future__ import annotations
import difflib import difflib
import hashlib
import logging import logging
import operator import operator
import re import re
import tomllib import tomllib
from collections.abc import Mapping, MutableMapping
from pathlib import Path from pathlib import Path
from typing import Any, Final, Literal, NotRequired, TypedDict from typing import Any, Final, Literal, TypedDict
from xml.etree import ElementTree as ET from xml.etree import ElementTree as ET
import click import click
@@ -35,10 +22,6 @@ from rich.syntax import Syntax
from cibuildwheel.extra import dump_python_configurations, get_pyodide_xbuildenv_info from cibuildwheel.extra import dump_python_configurations, get_pyodide_xbuildenv_info
from cibuildwheel.platforms.android import android_triplet from cibuildwheel.platforms.android import android_triplet
TYPE_CHECKING = False
if TYPE_CHECKING:
from collections.abc import Mapping, MutableMapping
log = logging.getLogger("cibw") log = logging.getLogger("cibw")
# Looking up the dir instead of using utils.resources_dir # Looking up the dir instead of using utils.resources_dir
@@ -57,13 +40,11 @@ class Config(TypedDict):
class ConfigUrl(Config): class ConfigUrl(Config):
url: str url: str
sha256: NotRequired[str]
class ConfigPyodide(Config): class ConfigPyodide(Config):
default_pyodide_version: str default_pyodide_version: str
node_version: str node_version: str
sha256: str
# The following set of "Versions" classes allow the initial call to the APIs to # The following set of "Versions" classes allow the initial call to the APIs to
@@ -182,23 +163,10 @@ class GraalPyVersions:
] ]
if urls: if urls:
(url,) = 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( return ConfigUrl(
identifier=identifier, identifier=identifier,
version=f"{version.major}.{version.minor}", version=f"{version.major}.{version.minor}",
url=url, url=url,
sha256=sha256,
) )
return None return None
@@ -314,14 +282,12 @@ class CPythonVersions:
uri = self.versions_dict[new_version] uri = self.versions_dict[new_version]
files = [rf for rf in self.files_info if rf["release"] == uri] files = [rf for rf in self.files_info if rf["release"] == uri]
matching = [rf for rf in files if file_ident in rf["url"]] urls = [rf["url"] for rf in files if file_ident in rf["url"]]
if matching: if urls:
rf = matching[0]
return ConfigUrl( return ConfigUrl(
identifier=identifier, identifier=identifier,
version=f"{new_version.major}.{new_version.minor}", version=f"{new_version.major}.{new_version.minor}",
url=rf["url"], url=urls[0],
sha256=rf.get("sha256_sum", ""),
) )
return None return None
@@ -438,7 +404,6 @@ class PyodideVersions:
version=str(version), version=str(version),
default_pyodide_version=release["version"], default_pyodide_version=release["version"],
node_version=node_version, node_version=node_version,
sha256=release["sha256"],
) )
@@ -467,16 +432,6 @@ class AllVersions:
self.pyodide = PyodideVersions() 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: def update_config(self, config: MutableMapping[str, str]) -> None:
identifier = config["identifier"] identifier = config["identifier"]
version = Version(config["version"]) version = Version(config["version"])
@@ -520,36 +475,13 @@ class AllVersions:
versions = self.maven if identifier.startswith("cp313") else self.cpython versions = self.maven if identifier.startswith("cp313") else self.cpython
config_update = versions.update_version_android(identifier, spec) config_update = versions.update_version_android(identifier, spec)
elif "ios" in identifier: elif "ios" in identifier:
# Python 3.13 and 3.14 are released by BeeWare on GitHub. config_update = self.ios_cpython.update_version_ios(identifier, version)
# 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: elif "pyodide" in identifier:
config_update = self.pyodide.update_version_pyodide( config_update = self.pyodide.update_version_pyodide(
identifier, version, spec, config["node_version"] identifier, version, spec, config["node_version"]
) )
assert config_update is not None, f"{identifier} not found!" 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: if config_update != config:
log.info(" Updated %s to %s", config, config_update) log.info(" Updated %s to %s", config, config_update)
config.clear() config.clear()
+2 -30
View File
@@ -1,29 +1,14 @@
#!/usr/bin/env -S uv run --script #!/usr/bin/env python3
# /// script
# dependencies = [
# "click",
# "packaging",
# "requests",
# "rich",
# "cibuildwheel",
# ]
#
# [tool.uv.sources]
# cibuildwheel = { path = ".." }
# ///
import dataclasses import dataclasses
import difflib import difflib
import hashlib
import logging import logging
import tomllib import tomllib
from pathlib import Path from pathlib import Path
from typing import Final from typing import Final
import click import click
import requests
import rich import rich
from packaging.version import Version from packaging.version import Version
from rich.logging import RichHandler from rich.logging import RichHandler
@@ -95,29 +80,16 @@ def update_virtualenv(force: bool, level: str) -> None:
if latest_release.version > Version(local_version): if latest_release.version > Version(local_version):
version = latest_release.name version = latest_release.name
url = latest_release.download_url url = latest_release.download_url
sha256 = "" # recomputed below
else: else:
version = local_version version = local_version
url = default["url"] 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"] = { configurations["default"] = {
"version": version, "version": version,
"url": url, "url": url,
"sha256": sha256,
} }
result_toml = "".join( result_toml = "".join(
f'{key} = {{ version = "{value["version"]}", url = "{value["url"]}", sha256 = "{value.get("sha256", "")}" }}\n' f'{key} = {{ version = "{value["version"]}", url = "{value["url"]}" }}\n'
for key, value in configurations.items() for key, value in configurations.items()
) )
+1 -1
View File
@@ -1 +1 @@
__version__ = "4.0.0rc1" __version__ = "3.4.0"
+14 -24
View File
@@ -1,5 +1,3 @@
from __future__ import annotations
import argparse import argparse
import contextlib import contextlib
import dataclasses import dataclasses
@@ -7,15 +5,18 @@ import functools
import os import os
import shutil import shutil
import sys import sys
import tarfile
import textwrap import textwrap
import traceback import traceback
import typing import typing
from collections.abc import Generator, Iterable, Sequence
from pathlib import Path from pathlib import Path
from tempfile import mkdtemp from tempfile import mkdtemp
from typing import Any, Literal, TextIO
import cibuildwheel import cibuildwheel
import cibuildwheel.util
from cibuildwheel import errors from cibuildwheel import errors
from cibuildwheel._compat.tarfile import TarFile, safe_extractall
from cibuildwheel.architecture import Architecture, allowed_architectures_check from cibuildwheel.architecture import Architecture, allowed_architectures_check
from cibuildwheel.ci import CIProvider, detect_ci_provider, fix_ansi_codes_for_github_actions from cibuildwheel.ci import CIProvider, detect_ci_provider, fix_ansi_codes_for_github_actions
from cibuildwheel.logger import log from cibuildwheel.logger import log
@@ -27,11 +28,6 @@ from cibuildwheel.util.file import CIBW_CACHE_PATH, ensure_cache_sentinel
from cibuildwheel.util.helpers import strtobool from cibuildwheel.util.helpers import strtobool
from cibuildwheel.util.resources import read_all_configs 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 @dataclasses.dataclass
class GlobalOptions: class GlobalOptions:
@@ -57,7 +53,7 @@ class Unbuffered:
self.stream.writelines(data) self.stream.writelines(data)
self.stream.flush() self.stream.flush()
def __getattr__(self, attr: str) -> Any: # noqa: ANN401 def __getattr__(self, attr: str) -> Any:
return getattr(self.stream, attr) return getattr(self.stream, attr)
@@ -267,8 +263,8 @@ def main_inner(global_options: GlobalOptions) -> None:
# Tarfile builds require extraction and changing the directory # Tarfile builds require extraction and changing the directory
temp_dir = Path(mkdtemp(prefix="cibw-sdist-")).resolve(strict=True) temp_dir = Path(mkdtemp(prefix="cibw-sdist-")).resolve(strict=True)
try: try:
with TarFile.open(args.package_dir) as tar: with tarfile.open(args.package_dir) as tar:
safe_extractall(tar, temp_dir) tar.extractall(path=temp_dir)
# The extract directory is now the project dir # The extract directory is now the project dir
try: try:
@@ -436,17 +432,17 @@ def print_preamble(platform: str, options: Options, identifiers: Sequence[str])
msg = "\n".join(error_list) msg = "\n".join(error_list)
raise errors.ConfigurationError(msg) raise errors.ConfigurationError(msg)
n = len(identifiers)
print(f"{n} build{'s' if n != 1 else ''} selected:")
print(", ".join(identifiers))
print()
print("Here we go!\n") print("Here we go!\n")
def detect_errors(*, options: Options, identifiers: Iterable[str]) -> Generator[str, None, None]: def detect_errors(*, options: Options, identifiers: Iterable[str]) -> Generator[str, None, None]:
# Check for deprecated CIBW_FREE_THREADED_SUPPORT environment variable # Check for deprecated CIBW_FREE_THREADED_SUPPORT environment variable
if "CIBW_FREE_THREADED_SUPPORT" in os.environ: if "CIBW_FREE_THREADED_SUPPORT" in os.environ:
yield "CIBW_FREE_THREADED_SUPPORT environment variable is no longer supported." yield (
"CIBW_FREE_THREADED_SUPPORT environment variable is no longer supported. "
'Use tool.cibuildwheel.enable = ["cpython-freethreading"] in pyproject.toml '
"or set CIBW_ENABLE=cpython-freethreading instead."
)
# Deprecated {python} and {pip} # Deprecated {python} and {pip}
for option_name in ["test_command", "before_build"]: for option_name in ["test_command", "before_build"]:
@@ -538,16 +534,10 @@ def check_for_invalid_selectors(
msg += "This selector matches a group that wasn't enabled. Enable it using the `enable` option or remove this selector. " msg += "This selector matches a group that wasn't enabled. Enable it using the `enable` option or remove this selector. "
if "p2" in selector_ or "p35" in selector_: if "p2" in selector_ or "p35" in selector_:
msg += f"cibuildwheel 4.x no longer supports Python < 3.9. Please use the 1.x series or update `{selector_name}`. " msg += f"cibuildwheel 3.x no longer supports Python < 3.8. Please use the 1.x series or update `{selector_name}`. "
error_type = errors.DeprecationError error_type = errors.DeprecationError
if "p36" in selector_ or "p37" in selector_: if "p36" in selector_ or "p37" in selector_:
msg += f"cibuildwheel 4.x no longer supports Python < 3.9. Please use the 2.x series or update `{selector_name}`. " msg += f"cibuildwheel 3.x no longer supports Python < 3.8. Please use the 2.x series or update `{selector_name}`. "
error_type = errors.DeprecationError
if "p38" in selector_:
msg += f"cibuildwheel 4.x no longer supports Python < 3.9. Please use the 3.x series or update `{selector_name}`. "
error_type = errors.DeprecationError
if "cp313t" in selector_:
msg += f"cibuildwheel 4.x no longer supports Python 3.13 free-threading. Please use the 3.x series or update `{selector_name}`. "
error_type = errors.DeprecationError error_type = errors.DeprecationError
if selector_name == "build": if selector_name == "build":
View File
-71
View File
@@ -1,71 +0,0 @@
# 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",
]
+50 -55
View File
@@ -1,21 +1,16 @@
from __future__ import annotations
import platform as platform_module import platform as platform_module
import re import re
import shutil import shutil
import subprocess import subprocess
import sys import sys
import typing import typing
from collections.abc import Set
from enum import StrEnum, auto from enum import StrEnum, auto
from typing import Final, Literal
from cibuildwheel import errors from cibuildwheel import errors
TYPE_CHECKING = False from .typing import PlatformName
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]] = { PRETTY_NAMES: Final[dict[PlatformName, str]] = {
"linux": "Linux", "linux": "Linux",
@@ -88,34 +83,34 @@ class Architecture(StrEnum):
arm64_iphonesimulator = auto() arm64_iphonesimulator = auto()
x86_64_iphonesimulator = auto() x86_64_iphonesimulator = auto()
@classmethod @staticmethod
def parse_config(cls, config: str, platform: PlatformName) -> set[Self]: def parse_config(config: str, platform: PlatformName) -> "set[Architecture]":
result = set() result = set()
for arch_str in re.split(r"[\s,]+", config): for arch_str in re.split(r"[\s,]+", config):
match arch_str: match arch_str:
case "auto": case "auto":
result |= cls.auto_archs(platform=platform) result |= Architecture.auto_archs(platform=platform)
case "native": case "native":
if native_arch := cls.native_arch(platform=platform): if native_arch := Architecture.native_arch(platform=platform):
result.add(native_arch) result.add(native_arch)
case "all": case "all":
result |= cls.all_archs(platform=platform) result |= Architecture.all_archs(platform=platform)
case "auto64": case "auto64":
result |= cls.bitness_archs(platform=platform, bitness="64") result |= Architecture.bitness_archs(platform=platform, bitness="64")
case "auto32": case "auto32":
result |= cls.bitness_archs(platform=platform, bitness="32") result |= Architecture.bitness_archs(platform=platform, bitness="32")
case _: case _:
try: try:
result.add(cls(arch_str)) result.add(Architecture(arch_str))
except ValueError as e: except ValueError as e:
msg = f"Invalid architecture '{arch_str}'" msg = f"Invalid architecture '{arch_str}'"
raise errors.ConfigurationError(msg) from e raise errors.ConfigurationError(msg) from e
return result return result
@classmethod @staticmethod
def native_arch(cls, platform: PlatformName) -> Self | None: def native_arch(platform: PlatformName) -> "Architecture | None":
native_machine = platform_module.machine() native_machine = platform_module.machine()
native_architecture = cls(native_machine) native_architecture = Architecture(native_machine)
# Cross-platform support. Used for --print-build-identifiers or docker builds. # Cross-platform support. Used for --print-build-identifiers or docker builds.
host_platform: PlatformName = ( host_platform: PlatformName = (
@@ -125,15 +120,15 @@ class Architecture(StrEnum):
) )
if platform == "pyodide": if platform == "pyodide":
return cls.wasm32 return Architecture.wasm32
elif platform == "ios": elif platform == "ios":
# Can only build for iOS on macOS. The "native" architecture is the # Can only build for iOS on macOS. The "native" architecture is the
# simulator for the macOS native platform. # simulator for the macOS native platform.
if host_platform == "macos": if host_platform == "macos":
if native_architecture == cls.x86_64: if native_architecture == Architecture.x86_64:
return cls.x86_64_iphonesimulator return Architecture.x86_64_iphonesimulator
else: else:
return cls.arm64_iphonesimulator return Architecture.arm64_iphonesimulator
else: else:
return None return None
@@ -145,64 +140,64 @@ class Architecture(StrEnum):
# can't build anything on this platform # can't build anything on this platform
return None return None
native_architecture = cls(synonym) native_architecture = Architecture(synonym)
return native_architecture return native_architecture
@classmethod @staticmethod
def auto_archs(cls, platform: PlatformName) -> set[Self]: def auto_archs(platform: PlatformName) -> "set[Architecture]":
native_arch = cls.native_arch(platform) native_arch = Architecture.native_arch(platform)
if native_arch is None: if native_arch is None:
return set() # can't build anything on this platform return set() # can't build anything on this platform
result = {native_arch} result = {native_arch}
match platform: match platform:
case "windows" if cls.AMD64 in result: case "windows" if Architecture.AMD64 in result:
result.add(cls.x86) result.add(Architecture.x86)
case "ios" if native_arch == cls.arm64_iphonesimulator: case "ios" if native_arch == Architecture.arm64_iphonesimulator:
# Also build the device wheel if we're on ARM64. # Also build the device wheel if we're on ARM64.
result.add(cls.arm64_iphoneos) result.add(Architecture.arm64_iphoneos)
return result return result
@classmethod @staticmethod
def all_archs(cls, platform: PlatformName) -> set[Self]: def all_archs(platform: PlatformName) -> "set[Architecture]":
all_archs_map = { all_archs_map = {
"linux": { "linux": {
cls.x86_64, Architecture.x86_64,
cls.i686, Architecture.i686,
cls.aarch64, Architecture.aarch64,
cls.ppc64le, Architecture.ppc64le,
cls.s390x, Architecture.s390x,
cls.armv7l, Architecture.armv7l,
cls.riscv64, Architecture.riscv64,
}, },
"macos": {cls.x86_64, cls.arm64, cls.universal2}, "macos": {Architecture.x86_64, Architecture.arm64, Architecture.universal2},
"windows": {cls.x86, cls.AMD64, cls.ARM64}, "windows": {Architecture.x86, Architecture.AMD64, Architecture.ARM64},
"pyodide": {cls.wasm32}, "pyodide": {Architecture.wasm32},
"android": {cls.x86_64, cls.arm64_v8a}, "android": {Architecture.x86_64, Architecture.arm64_v8a},
"ios": { "ios": {
cls.x86_64_iphonesimulator, Architecture.x86_64_iphonesimulator,
cls.arm64_iphonesimulator, Architecture.arm64_iphonesimulator,
cls.arm64_iphoneos, Architecture.arm64_iphoneos,
}, },
} }
return all_archs_map[platform] return all_archs_map[platform]
@classmethod @staticmethod
def bitness_archs(cls, platform: PlatformName, bitness: Literal["64", "32"]) -> set[Self]: def bitness_archs(platform: PlatformName, bitness: Literal["64", "32"]) -> "set[Architecture]":
# This map maps 64-bit architectures to their 32-bit equivalents. # This map maps 64-bit architectures to their 32-bit equivalents.
archs_map = { archs_map = {
cls.x86_64: cls.i686, Architecture.x86_64: Architecture.i686,
cls.AMD64: cls.x86, Architecture.AMD64: Architecture.x86,
cls.aarch64: cls.armv7l, Architecture.aarch64: Architecture.armv7l,
} }
native_arch = cls.native_arch(platform) native_arch = Architecture.native_arch(platform)
if native_arch is None: if native_arch is None:
return set() # can't build anything on this platform return set() # can't build anything on this platform
if native_arch == cls.wasm32: if native_arch == Architecture.wasm32:
return {native_arch} if bitness == "32" else set() return {native_arch} if bitness == "32" else set()
match bitness: match bitness:
@@ -212,7 +207,7 @@ class Architecture(StrEnum):
if native_arch in archs_map.values(): if native_arch in archs_map.values():
return {native_arch} return {native_arch}
elif native_arch in archs_map and platform in {"linux", "windows"}: elif native_arch in archs_map and platform in {"linux", "windows"}:
if native_arch == cls.aarch64 and not _check_aarch32_el0(): if native_arch == Architecture.aarch64 and not _check_aarch32_el0():
# If we're on aarch64, skip if we cannot build armv7l wheels. # If we're on aarch64, skip if we cannot build armv7l wheels.
return set() return set()
return {archs_map[native_arch]} return {archs_map[native_arch]}
-137
View File
@@ -1,137 +0,0 @@
from __future__ import annotations
import subprocess
import sys
from pathlib import Path
from cibuildwheel import errors
from cibuildwheel.logger import log
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(
*,
tmp_dir: Path,
build_options: BuildOptions,
wheel: Path,
) -> None:
"""
Run the audit commands on a single wheel.
Creates a virtualenv (or reuses an existing one) and installs any
audit requirements, then runs each audit command template against
the wheel. Commands containing {abi3_wheel} are skipped for
non-abi3 wheels.
"""
if not needs_audit(build_options.audit_command, wheel.name):
return
log.step("Auditing wheel...")
use_uv = build_options.build_frontend.name in {"build[uv]", "uv"}
version = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
dependency_constraint = build_options.dependency_constraints.get_for_python_version(
version=version, tmp_dir=tmp_dir
)
# Use the base interpreter, not the venv python, to avoid nested-venv
# issues where pip can't be found (seen on Windows + Python 3.13).
host_python = Path(getattr(sys, "_base_executable", sys.executable))
audit_venv_dir = tmp_dir / "audit_venv"
if not (audit_venv_dir / "pyvenv.cfg").exists():
env = virtualenv(
version,
host_python,
audit_venv_dir,
dependency_constraint=dependency_constraint,
use_uv=use_uv,
)
else:
env = activate_virtualenv(audit_venv_dir)
# install audit requirements. This is run every time in case the user has
# defined overrides.
audit_requires = build_options.audit_requires
if audit_requires:
print(f"Installing audit dependencies: {', '.join(audit_requires)}")
pip: list[str]
if use_uv:
uv_path = find_uv()
assert uv_path is not None
pip = [str(uv_path), "pip"]
else:
pip = ["pip"]
# we pin if the audit-requires is left as the default "abi3audit"
should_pin = audit_requires == ["abi3audit"] and dependency_constraint
call(
*pip,
"install",
*(["--constraint", str(dependency_constraint)] if should_pin else []),
*audit_requires,
env=env,
)
audit_command = build_options.audit_command
for command_template in audit_command:
if "{abi3_wheel}" in command_template and "{wheel}" in command_template:
msg = (
f"Invalid audit command {command_template!r}: cannot contain both {{abi3_wheel}} "
"and {{wheel}} placeholders"
)
raise errors.ConfigurationError(msg)
if "{abi3_wheel}" in command_template and not is_abi3_wheel(wheel.name):
continue
prepared_command = prepare_command(
command_template,
abi3_wheel=wheel,
wheel=wheel,
project=".",
package=build_options.package_dir,
)
print(f"Running audit command: {prepared_command}")
try:
shell(prepared_command, env=env)
except subprocess.CalledProcessError as e:
print(f"Audit command failed with exit code {e.returncode}")
msg = f"Audit command failed: {prepared_command}"
raise errors.AuditCommandFailedError(msg) from e
def needs_audit(audit_commands: list[str], wheel_name: str) -> bool:
saw_abi3_placeholder = False
for audit_command in audit_commands:
if "{abi3_wheel}" not in audit_command and "{wheel}" not in audit_command:
msg = (
f"Invalid audit command {audit_command!r}: must contain either "
"{{abi3_wheel}} or {{wheel}} placeholder"
)
raise errors.ConfigurationError(msg)
if "{abi3_wheel}" in audit_command:
saw_abi3_placeholder = True
if is_abi3_wheel(wheel_name):
return True
elif "{wheel}" in audit_command:
return True
if saw_abi3_placeholder:
print("No audit required for this wheel, as it is not abi3")
else:
print("No audit configured")
return False
+8 -13
View File
@@ -1,21 +1,16 @@
from __future__ import annotations
import dataclasses import dataclasses
import subprocess import subprocess
from collections.abc import (
Callable,
Iterable,
Mapping,
Sequence,
)
import bashlex import bashlex
TYPE_CHECKING = False # a function that takes a command and the environment, and returns the result
if TYPE_CHECKING: EnvironmentExecutor = Callable[[list[str], dict[str, str]], str]
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: def local_environment_executor(command: Sequence[str], env: Mapping[str, str]) -> str:
+4 -1
View File
@@ -2,7 +2,7 @@ import os
import re import re
from enum import Enum from enum import Enum
from cibuildwheel.util.helpers import strtobool from .util.helpers import strtobool
ANSI_CODE_REGEX = re.compile(r"(\033\[[0-9;]*m)") ANSI_CODE_REGEX = re.compile(r"(\033\[[0-9;]*m)")
@@ -14,6 +14,7 @@ class CIProvider(Enum):
azure_pipelines = "azure_pipelines" azure_pipelines = "azure_pipelines"
github_actions = "github_actions" github_actions = "github_actions"
gitlab = "gitlab" gitlab = "gitlab"
cirrus_ci = "cirrus_ci"
# unofficial support # unofficial support
appveyor = "appveyor" appveyor = "appveyor"
@@ -34,6 +35,8 @@ def detect_ci_provider() -> CIProvider | None:
return CIProvider.github_actions return CIProvider.github_actions
elif "GITLAB_CI" in os.environ: elif "GITLAB_CI" in os.environ:
return CIProvider.gitlab return CIProvider.gitlab
elif "CIRRUS_CI" in os.environ:
return CIProvider.cirrus_ci
elif strtobool(os.environ.get("CI", "false")): elif strtobool(os.environ.get("CI", "false")):
return CIProvider.other return CIProvider.other
else: else:
+4 -10
View File
@@ -1,17 +1,11 @@
from __future__ import annotations
import dataclasses import dataclasses
from typing import Protocol from collections.abc import Mapping, Sequence
from typing import Any, Protocol
import bashlex import bashlex
import bashlex.errors import bashlex.errors
from cibuildwheel import bashlex_eval from . import bashlex_eval
TYPE_CHECKING = False
if TYPE_CHECKING:
from collections.abc import Mapping, Sequence
from typing import Any
class EnvironmentParseError(Exception): class EnvironmentParseError(Exception):
@@ -137,7 +131,7 @@ class ParsedEnvironment:
def __repr__(self) -> str: def __repr__(self) -> str:
return f"{self.__class__.__name__}({[repr(a) for a in self.assignments]!r})" return f"{self.__class__.__name__}({[repr(a) for a in self.assignments]!r})"
def options_summary(self) -> list[EnvironmentAssignment]: def options_summary(self) -> Any:
return self.assignments return self.assignments
+2 -8
View File
@@ -76,7 +76,7 @@ class RepairStepProducedNoWheelError(FatalError):
Build failed because the repair step completed successfully but Build failed because the repair step completed successfully but
did not produce a wheel. did not produce a wheel.
Your `repair-wheel-command` must place the repaired Your `repair-wheel-command` is expected to place the repaired
wheel in the {dest_dir} directory. See the documentation for wheel in the {dest_dir} directory. See the documentation for
example configurations: example configurations:
@@ -94,7 +94,7 @@ class RepairStepProducedMultipleWheelsError(FatalError):
Build failed because the repair step completed successfully but Build failed because the repair step completed successfully but
produced multiple wheels: {wheels} produced multiple wheels: {wheels}
Your `repair-wheel-command` must place exactly one repaired Your `repair-wheel-command` is expected to place one repaired
wheel in the {{dest_dir}} directory. See the documentation for wheel in the {{dest_dir}} directory. See the documentation for
example configurations: example configurations:
@@ -103,9 +103,3 @@ class RepairStepProducedMultipleWheelsError(FatalError):
) )
super().__init__(message) super().__init__(message)
self.return_code = 8 self.return_code = 8
class AuditCommandFailedError(FatalError):
def __init__(self, message: str) -> None:
super().__init__(message)
self.return_code = 9
+3 -11
View File
@@ -2,24 +2,17 @@
These are utilities for the `/bin` scripts, not for the `cibuildwheel` program. These are utilities for the `/bin` scripts, not for the `cibuildwheel` program.
""" """
from __future__ import annotations
import json import json
import time import time
import typing import typing
import urllib.error import urllib.error
import urllib.request import urllib.request
from collections.abc import Mapping, Sequence
from io import StringIO from io import StringIO
from typing import NotRequired, Protocol from typing import Any, NotRequired, Protocol
from cibuildwheel import __version__ as cibw_version 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") __all__ = ("Printable", "dump_python_configurations")
@@ -69,7 +62,7 @@ def github_api_request(path: str, *, max_retries: int = 3) -> dict[str, Any]:
# pylint: disable=E1101 # pylint: disable=E1101
if ( if (
isinstance(e, urllib.error.HTTPError) isinstance(e, urllib.error.HTTPError)
and (e.code in {403, 429}) and (e.code == 403 or e.code == 429)
and e.headers.get("x-ratelimit-remaining") == "0" and e.headers.get("x-ratelimit-remaining") == "0"
): ):
reset_time = int(e.headers.get("x-ratelimit-reset", 0)) reset_time = int(e.headers.get("x-ratelimit-reset", 0))
@@ -90,7 +83,6 @@ def github_api_request(path: str, *, max_retries: int = 3) -> dict[str, Any]:
class PyodideXBuildEnvRelease(typing.TypedDict): class PyodideXBuildEnvRelease(typing.TypedDict):
version: str version: str
sha256: str
python_version: str python_version: str
emscripten_version: str emscripten_version: str
min_pyodide_build_version: NotRequired[str] min_pyodide_build_version: NotRequired[str]
+11 -18
View File
@@ -1,18 +1,11 @@
from __future__ import annotations
import dataclasses import dataclasses
import shlex import shlex
import typing import typing
from typing import Literal, get_args from collections.abc import Sequence
from typing import Literal, Self, get_args
from cibuildwheel.util.helpers import parse_key_value_string, prepare_command from .logger import log
from .util.helpers import parse_key_value_string
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"] BuildFrontendName = Literal["pip", "build", "build[uv]", "uv"]
@@ -43,8 +36,12 @@ class BuildFrontendConfig:
return {"name": self.name, "args": repr(self.args)} return {"name": self.name, "args": repr(self.args)}
def _get_verbosity_flags(level: int, frontend: BuildFrontendName) -> list[str]: def _get_verbosity_flags(level: int, frontend: BuildFrontendName, *, py38: bool) -> list[str]:
if level < 0: if level < 0:
if frontend.startswith("build") and py38:
msg = f"build_verbosity {level} is not supported for {frontend} frontend. Ignoring."
log.warning(msg)
return []
return ["-" + -level * "q"] return ["-" + -level * "q"]
if level > 0: if level > 0:
@@ -61,10 +58,6 @@ def _split_config_settings(config_settings: str) -> list[str]:
return [f"-C{setting}" for setting in config_settings_list] return [f"-C{setting}" for setting in config_settings_list]
def prepare_config_settings(config_settings: str, *, project: PathOrStr, package: PathOrStr) -> str:
return prepare_command(config_settings, project=project, package=package)
# Based on build.__main__.main. # Based on build.__main__.main.
def parse_config_settings(config_settings_str: str) -> dict[str, str | list[str]]: def parse_config_settings(config_settings_str: str) -> dict[str, str | list[str]]:
config_settings: dict[str, str | list[str]] = {} config_settings: dict[str, str | list[str]] = {}
@@ -82,10 +75,10 @@ def parse_config_settings(config_settings_str: str) -> dict[str, str | list[str]
def get_build_frontend_extra_flags( def get_build_frontend_extra_flags(
build_frontend: BuildFrontendConfig, verbosity_level: int, config_settings: str build_frontend: BuildFrontendConfig, verbosity_level: int, config_settings: str, *, py38: bool
) -> list[str]: ) -> list[str]:
return [ return [
*_split_config_settings(config_settings), *_split_config_settings(config_settings),
*build_frontend.args, *build_frontend.args,
*_get_verbosity_flags(verbosity_level, build_frontend.name), *_get_verbosity_flags(verbosity_level, build_frontend.name, py38=py38),
] ]
+7 -12
View File
@@ -1,5 +1,3 @@
from __future__ import annotations
import codecs import codecs
import contextlib import contextlib
import dataclasses import dataclasses
@@ -11,21 +9,18 @@ import re
import sys import sys
import textwrap import textwrap
import time import time
from collections.abc import Generator
from pathlib import Path from pathlib import Path
from typing import IO, TYPE_CHECKING, AnyStr, Final, Literal
import humanize import humanize
from cibuildwheel.ci import CIProvider, detect_ci_provider, filter_ansi_codes from .ci import CIProvider, detect_ci_provider, filter_ansi_codes
TYPE_CHECKING = False
if TYPE_CHECKING: if TYPE_CHECKING:
from collections.abc import Generator from .options import Options
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}", "") DEFAULT_FOLD_PATTERN: Final[FoldPattern] = ("{name}", "")
FOLD_PATTERNS: Final[dict[str, FoldPattern]] = { FOLD_PATTERNS: Final[dict[str, FoldPattern]] = {
"azure": ("##[group]{name}", "##[endgroup]"), "azure": ("##[group]{name}", "##[endgroup]"),
@@ -239,7 +234,7 @@ class Logger:
print(f"cibuildwheel: {c.bright_red}error{c.end}: {error}\n", file=sys.stderr) print(f"cibuildwheel: {c.bright_red}error{c.end}: {error}\n", file=sys.stderr)
@contextlib.contextmanager @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() start = time.time()
yield yield
duration = time.time() - start duration = time.time() - start
@@ -297,7 +292,7 @@ class Logger:
# lowercase, shorten # lowercase, shorten
return identifier.lower()[:20] 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. Returns the GitHub step summary, in markdown format.
""" """
+15 -22
View File
@@ -1,5 +1,3 @@
from __future__ import annotations
import dataclasses import dataclasses
import io import io
import json import json
@@ -12,24 +10,18 @@ import sys
import textwrap import textwrap
import typing import typing
import uuid import uuid
from collections.abc import Mapping, Sequence
from enum import Enum from enum import Enum
from pathlib import PurePosixPath from pathlib import Path, PurePath, PurePosixPath
from typing import Literal, assert_never from types import TracebackType
from typing import IO, Literal, Self, assert_never
from cibuildwheel.ci import CIProvider, detect_ci_provider from .ci import CIProvider, detect_ci_provider
from cibuildwheel.errors import OCIEngineTooOldError from .errors import OCIEngineTooOldError
from cibuildwheel.logger import log from .logger import log
from cibuildwheel.util.cmd import call from .typing import PathOrStr
from cibuildwheel.util.helpers import FlexibleVersion, parse_key_value_string, strtobool from .util.cmd import call
from .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"] ContainerEngineName = Literal["docker", "podman"]
@@ -45,7 +37,7 @@ class OCIPlatform(Enum):
S390X = "linux/s390x" S390X = "linux/s390x"
@classmethod @classmethod
def native(cls) -> Self: def native(cls) -> "OCIPlatform":
"""Return the current OCI platform, or raise ValueError if unknown.""" """Return the current OCI platform, or raise ValueError if unknown."""
arch = platform.machine().lower() arch = platform.machine().lower()
mapping = { mapping = {
@@ -533,12 +525,13 @@ class OCIContainer:
return self.call(command, env=environment, capture_output=True) return self.call(command, env=environment, capture_output=True)
def debug_info(self) -> str: def debug_info(self) -> str:
command = [self.engine.name, "info"]
if self.engine.name == "podman": if self.engine.name == "podman":
command.append("--debug") command = f"{self.engine.name} info --debug"
else:
command = f"{self.engine.name} info"
completed = subprocess.run( completed = subprocess.run(
command, command,
shell=True,
check=True, check=True,
cwd=self.cwd, cwd=self.cwd,
stdin=subprocess.PIPE, stdin=subprocess.PIPE,
+18 -39
View File
@@ -1,5 +1,3 @@
from __future__ import annotations
import collections import collections
import configparser import configparser
import contextlib import contextlib
@@ -10,29 +8,24 @@ import functools
import shlex import shlex
import textwrap import textwrap
import tomllib import tomllib
from collections.abc import Mapping, Sequence from collections.abc import Callable, Generator, Iterable, Mapping, Sequence, Set
from pathlib import Path from pathlib import Path
from typing import assert_never from typing import Any, Final, Literal, Self, assert_never
from packaging.specifiers import SpecifierSet from packaging.specifiers import SpecifierSet
from cibuildwheel import errors from . import errors
from cibuildwheel.architecture import Architecture from .architecture import Architecture
from cibuildwheel.environment import EnvironmentParseError, ParsedEnvironment, parse_environment from .environment import EnvironmentParseError, ParsedEnvironment, parse_environment
from cibuildwheel.frontend import BuildFrontendConfig from .frontend import BuildFrontendConfig
from cibuildwheel.logger import log from .logger import log
from cibuildwheel.oci_container import OCIContainerEngineConfig from .oci_container import OCIContainerEngineConfig
from cibuildwheel.projectfiles import get_requires_python_str, resolve_dependency_groups from .projectfiles import get_requires_python_str, resolve_dependency_groups
from cibuildwheel.selector import BuildSelector, EnableGroup, TestSelector, selector_matches from .selector import BuildSelector, EnableGroup, TestSelector, selector_matches
from cibuildwheel.typing import PLATFORMS, PlatformName from .typing import PLATFORMS, PlatformName
from cibuildwheel.util import resources from .util import resources
from cibuildwheel.util.helpers import format_safe, parse_key_value_string, strtobool, unwrap from .util.helpers import format_safe, parse_key_value_string, strtobool, unwrap
from cibuildwheel.util.packaging import DependencyConstraints from .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, ...]] = ( MANYLINUX_ARCHS: Final[tuple[str, ...]] = (
"x86_64", "x86_64",
@@ -132,8 +125,6 @@ class BuildOptions:
test_groups: list[str] test_groups: list[str]
test_environment: ParsedEnvironment test_environment: ParsedEnvironment
test_runtime: TestRuntimeConfig test_runtime: TestRuntimeConfig
audit_requires: list[str]
audit_command: list[str]
build_verbosity: int build_verbosity: int
build_frontend: BuildFrontendConfig build_frontend: BuildFrontendConfig
config_settings: str config_settings: str
@@ -715,12 +706,11 @@ class Options:
def _check_pinned_image(self, value: str, pinned_images: Mapping[str, str]) -> None: def _check_pinned_image(self, value: str, pinned_images: Mapping[str, str]) -> None:
error_set = {"manylinux1", "manylinux2010", "manylinux_2_24", "musllinux_1_1"} 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() warning_set: set[str] = set()
if value in error_set: if value in error_set:
msg = ( msg = (
f"cibuildwheel 4.x does not support the image {value!r}. Either upgrade to a " f"cibuildwheel 3.x does not support the image {value!r}. Either upgrade to a "
"supported image or continue using the image by pinning it directly with" "supported image or continue using the image by pinning it directly with"
" its full OCI registry '<name>{:<tag>|@<digest>}'." " its full OCI registry '<name>{:<tag>|@<digest>}'."
) )
@@ -902,15 +892,6 @@ class Options:
pyodide_version = self.reader.get("pyodide-version", env_plat=False) pyodide_version = self.reader.get("pyodide-version", env_plat=False)
audit_command_str = self.reader.get(
"audit-command", option_format=ListFormat(sep=" && ")
)
audit_command = audit_command_str.split(" && ") if audit_command_str else []
audit_requires = self.reader.get(
"audit-requires", option_format=ListFormat(sep=" ")
).split()
return BuildOptions( return BuildOptions(
globals=self.globals, globals=self.globals,
test_command=test_command, test_command=test_command,
@@ -934,8 +915,6 @@ class Options:
config_settings=config_settings, config_settings=config_settings,
container_engine=container_engine, container_engine=container_engine,
pyodide_version=pyodide_version or None, pyodide_version=pyodide_version or None,
audit_command=audit_command,
audit_requires=audit_requires,
) )
def check_for_invalid_configuration(self, identifiers: Iterable[str]) -> None: def check_for_invalid_configuration(self, identifiers: Iterable[str]) -> None:
@@ -1008,8 +987,8 @@ class Options:
def option_summary( def option_summary(
self, self,
option_name: str, option_name: str,
option_value: Any, # noqa: ANN401 option_value: Any,
default_value: Any, # noqa: ANN401 default_value: Any,
overrides: Mapping[str, Any] | None = None, overrides: Mapping[str, Any] | None = None,
skip_unset: bool = False, skip_unset: bool = False,
) -> str | None: ) -> str | None:
@@ -1061,7 +1040,7 @@ class Options:
return value return value
@staticmethod @staticmethod
def option_summary_value(option_value: Any) -> str: # noqa: ANN401 def option_summary_value(option_value: Any) -> str:
if hasattr(option_value, "options_summary"): if hasattr(option_value, "options_summary"):
option_value = option_value.options_summary() option_value = option_value.options_summary()
+1 -3
View File
@@ -1,16 +1,14 @@
from __future__ import annotations from __future__ import annotations
import sys import sys
from typing import Protocol from typing import TYPE_CHECKING, Final, Protocol
from cibuildwheel import errors from cibuildwheel import errors
from cibuildwheel.platforms import android, ios, linux, macos, pyodide, windows from cibuildwheel.platforms import android, ios, linux, macos, pyodide, windows
TYPE_CHECKING = False
if TYPE_CHECKING: if TYPE_CHECKING:
from collections.abc import Sequence from collections.abc import Sequence
from pathlib import Path from pathlib import Path
from typing import Final
from cibuildwheel.architecture import Architecture from cibuildwheel.architecture import Architecture
from cibuildwheel.options import Options from cibuildwheel.options import Options
+23 -51
View File
@@ -1,5 +1,3 @@
from __future__ import annotations
import csv import csv
import hashlib import hashlib
import os import os
@@ -9,6 +7,7 @@ import shlex
import shutil import shutil
import subprocess import subprocess
import sysconfig import sysconfig
from collections.abc import Iterable, Iterator, MutableMapping
from dataclasses import dataclass from dataclasses import dataclass
from os.path import relpath from os.path import relpath
from pathlib import Path from pathlib import Path
@@ -23,31 +22,20 @@ from elftools.common.exceptions import ELFError
from elftools.elf.elffile import ELFFile from elftools.elf.elffile import ELFFile
from filelock import FileLock from filelock import FileLock
from cibuildwheel import errors, platforms # pylint: disable=cyclic-import from .. import errors, platforms # pylint: disable=cyclic-import
from cibuildwheel.architecture import Architecture, arch_synonym from ..architecture import Architecture, arch_synonym
from cibuildwheel.audit import run_audit from ..frontend import get_build_frontend_extra_flags, parse_config_settings
from cibuildwheel.frontend import ( from ..logger import log
get_build_frontend_extra_flags, from ..options import BuildOptions, Options
parse_config_settings, from ..selector import BuildSelector
prepare_config_settings, from ..util import resources
) from ..util.cmd import call, shell
from cibuildwheel.logger import log from ..util.file import CIBW_CACHE_PATH, copy_test_sources, download, move_file
from cibuildwheel.util import resources from ..util.helpers import prepare_command
from cibuildwheel.util.cmd import call, shell from ..util.packaging import find_compatible_wheel
from cibuildwheel.util.file import CIBW_CACHE_PATH, copy_test_sources, download, move_file from ..util.python_build_standalone import create_python_build_standalone_environment
from cibuildwheel.util.helpers import prepare_command from ..venv import constraint_flags, find_uv, virtualenv
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
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 = { ANDROID_TRIPLET = {
"arm64_v8a": "aarch64-linux-android", "arm64_v8a": "aarch64-linux-android",
"x86_64": "x86_64-linux-android", "x86_64": "x86_64-linux-android",
@@ -72,7 +60,6 @@ class PythonConfiguration:
version: str version: str
identifier: str identifier: str
url: str url: str
sha256: str = ""
@property @property
def arch(self) -> str: def arch(self) -> str:
@@ -161,7 +148,6 @@ def build(options: Options, tmp_path: Path) -> None:
before_build(state) before_build(state)
built_wheel = build_wheel(state) built_wheel = build_wheel(state)
repaired_wheel = repair_wheel(state, built_wheel) repaired_wheel = repair_wheel(state, built_wheel)
run_audit(tmp_dir=tmp_path, build_options=build_options, wheel=repaired_wheel)
test_wheel(state, repaired_wheel, build_frontend=build_options.build_frontend.name) test_wheel(state, repaired_wheel, build_frontend=build_options.build_frontend.name)
@@ -185,16 +171,11 @@ def setup_target_python(config: PythonConfiguration, build_path: Path) -> Path:
python_tgz = CIBW_CACHE_PATH / config.url.rpartition("/")[-1] python_tgz = CIBW_CACHE_PATH / config.url.rpartition("/")[-1]
with FileLock(f"{python_tgz}.lock"): with FileLock(f"{python_tgz}.lock"):
if not python_tgz.exists(): if not python_tgz.exists():
download(config.url, python_tgz, sha256=config.sha256 or None) download(config.url, python_tgz)
python_dir = build_path / "python" python_dir = build_path / "python"
python_dir.mkdir() python_dir.mkdir()
shutil.unpack_archive(python_tgz, python_dir) shutil.unpack_archive(python_tgz, python_dir)
# Patch a testbed bug. This code and the patch file can both be removed once we've
# updated to Python versions that include the fix.
call("patch", "-p1", "-i", RESOURCES_ANDROID / "android.patch", cwd=python_dir)
return python_dir return python_dir
@@ -241,10 +222,7 @@ def setup_env(
f"or insert {command} above it." f"or insert {command} above it."
) )
raise errors.FatalError(msg) raise errors.FatalError(msg)
if command == "python": call(command, "--version", env=build_env)
call(command, "-V", "-V", env=build_env)
else:
call(command, "--version", env=build_env)
# Construct an altered environment which simulates running on Android. # Construct an altered environment which simulates running on Android.
android_env = setup_android_env(config, python_dir, venv_dir, build_env) android_env = setup_android_env(config, python_dir, venv_dir, build_env)
@@ -372,7 +350,7 @@ def setup_android_env(
) -> dict[str, str]: ) -> dict[str, str]:
site_packages = next(venv_dir.glob("lib/python*/site-packages")) site_packages = next(venv_dir.glob("lib/python*/site-packages"))
for suffix in ["pth", "py"]: for suffix in ["pth", "py"]:
shutil.copy(RESOURCES_ANDROID / f"_cross_venv.{suffix}", site_packages) shutil.copy(resources.PATH / f"_cross_venv.{suffix}", site_packages)
sysconfigdata_path = Path( sysconfigdata_path = Path(
shutil.copy( shutil.copy(
@@ -447,7 +425,7 @@ def setup_rust(
venv_bin = Path(env["VIRTUAL_ENV"]) / "bin" venv_bin = Path(env["VIRTUAL_ENV"]) / "bin"
for tool in ["cargo", "rustup"]: for tool in ["cargo", "rustup"]:
shim_path = venv_bin / tool shim_path = venv_bin / tool
shutil.copy(RESOURCES_ANDROID / "rust_shim.py", shim_path) shutil.copy(resources.PATH / "_rust_shim.py", shim_path)
shim_path.chmod(0o755) shim_path.chmod(0o755)
@@ -478,11 +456,8 @@ def build_wheel(state: BuildState) -> Path:
*get_build_frontend_extra_flags( *get_build_frontend_extra_flags(
state.options.build_frontend, state.options.build_frontend,
state.options.build_verbosity, state.options.build_verbosity,
prepare_config_settings( state.options.config_settings,
state.options.config_settings, py38=False,
project=".",
package=state.options.package_dir,
),
), ),
env=state.android_env, env=state.android_env,
) )
@@ -499,11 +474,8 @@ def build_wheel(state: BuildState) -> Path:
*get_build_frontend_extra_flags( *get_build_frontend_extra_flags(
state.options.build_frontend, state.options.build_frontend,
state.options.build_verbosity, state.options.build_verbosity,
prepare_config_settings( state.options.config_settings,
state.options.config_settings, py38=False,
project=".",
package=state.options.package_dir,
),
), ),
env=state.android_env, env=state.android_env,
) )
@@ -660,7 +632,7 @@ def test_wheel(state: BuildState, wheel: Path, *, build_frontend: str) -> None:
return return
log.step("Testing wheel...") log.step("Testing wheel...")
use_uv = build_frontend in {"build[uv]", "uv"} use_uv = build_frontend == "build[uv]"
uv_path = find_uv() uv_path = find_uv()
if use_uv and uv_path is None: if use_uv and uv_path is None:
msg = "uv not found" msg = "uv not found"
+40 -64
View File
@@ -9,35 +9,38 @@ import subprocess
import sys import sys
import textwrap import textwrap
from pathlib import Path from pathlib import Path
from typing import assert_never from typing import TYPE_CHECKING, assert_never
from filelock import FileLock from filelock import FileLock
from packaging.version import Version
from cibuildwheel import errors from .. import errors
from cibuildwheel.audit import run_audit from ..frontend import (
from cibuildwheel.frontend import (
BuildFrontendName, BuildFrontendName,
get_build_frontend_extra_flags, get_build_frontend_extra_flags,
prepare_config_settings,
) )
from cibuildwheel.logger import log from ..logger import log
from cibuildwheel.platforms.macos import install_cpython as install_build_cpython from ..util import resources
from cibuildwheel.util import resources from ..util.cmd import call, shell, split_command
from cibuildwheel.util.cmd import call, shell, split_command from ..util.file import (
from cibuildwheel.util.file import CIBW_CACHE_PATH, copy_test_sources, download, move_file CIBW_CACHE_PATH,
from cibuildwheel.util.helpers import prepare_command, unwrap_preserving_paragraphs copy_test_sources,
from cibuildwheel.util.packaging import find_compatible_wheel download,
from cibuildwheel.venv import constraint_flags, virtualenv move_file,
)
from ..util.helpers import prepare_command, unwrap_preserving_paragraphs
from ..util.packaging import (
find_compatible_wheel,
)
from ..venv import constraint_flags, virtualenv
from .macos import install_cpython as install_build_cpython
TYPE_CHECKING = False
if TYPE_CHECKING: if TYPE_CHECKING:
from collections.abc import Sequence, Set from collections.abc import Sequence, Set
from cibuildwheel.architecture import Architecture from ..architecture import Architecture
from cibuildwheel.environment import ParsedEnvironment from ..environment import ParsedEnvironment
from cibuildwheel.options import Options from ..options import Options
from cibuildwheel.selector import BuildSelector from ..selector import BuildSelector
@dataclasses.dataclass(frozen=True, kw_only=True) @dataclasses.dataclass(frozen=True, kw_only=True)
@@ -46,7 +49,6 @@ class PythonConfiguration:
identifier: str identifier: str
url: str url: str
build_url: str build_url: str
sha256: str = ""
@property @property
def sdk(self) -> str: def sdk(self) -> str:
@@ -134,7 +136,7 @@ def install_target_cpython(tmp: Path, config: PythonConfiguration, free_threadin
with FileLock(str(installation_path) + ".lock"): with FileLock(str(installation_path) + ".lock"):
if not installation_path.exists(): if not installation_path.exists():
downloaded_tar_gz = tmp / ios_python_tar_gz downloaded_tar_gz = tmp / ios_python_tar_gz
download(config.url, downloaded_tar_gz, sha256=config.sha256 or None) download(config.url, downloaded_tar_gz)
installation_path.mkdir(parents=True, exist_ok=True) installation_path.mkdir(parents=True, exist_ok=True)
call("tar", "-C", installation_path, "-xf", downloaded_tar_gz) call("tar", "-C", installation_path, "-xf", downloaded_tar_gz)
downloaded_tar_gz.unlink() downloaded_tar_gz.unlink()
@@ -191,37 +193,18 @@ def cross_virtualenv(
) )
# Convert the macOS virtual environment into an iOS virtual environment # Convert the macOS virtual environment into an iOS virtual environment
# using make_cross_venv.py. # using the cross-platform conversion script in the iOS distribution.
# target_python is the path to the Python binary; # target_python is the path to the Python binary;
# determine the root of the XCframework slice that is being used. # determine the root of the XCframework slice that is being used.
slice_path = target_python.parent.parent slice_path = target_python.parent.parent
if Version(py_version) >= Version("3.15"): call(
# python.org 3.15+ distributions: sysconfig data lives in the "python",
# stdlib directory (lib-<arch>/python<version>); cross-venv scripts str(slice_path / f"platform-config/{multiarch}/make_cross_venv.py"),
# come from cibuildwheel resources. str(venv_path),
arch = multiarch.split("-", maxsplit=1)[0] env=env,
stdlib_path = slice_path / f"lib-{arch}" / f"python{py_version}" cwd=venv_path,
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 # When running on macOS, it's easy for the build environment to leak into
# the target environment, especially when building for ARM64 (because the # the target environment, especially when building for ARM64 (because the
@@ -301,8 +284,7 @@ def setup_python(
build_frontend: BuildFrontendName, build_frontend: BuildFrontendName,
xbuild_tools: Sequence[str] | None, xbuild_tools: Sequence[str] | None,
) -> tuple[Path, dict[str, str]]: ) -> tuple[Path, dict[str, str]]:
# Not using set because mypy can't narrow it if build_frontend == "build[uv]" or build_frontend == "uv":
if build_frontend == "build[uv]" or build_frontend == "uv": # noqa: PLR1714
msg = "uv doesn't support iOS" msg = "uv doesn't support iOS"
raise errors.FatalError(msg) raise errors.FatalError(msg)
@@ -339,8 +321,8 @@ def setup_python(
/ f"python{python_configuration.version}" / f"python{python_configuration.version}"
) )
assert target_python.parent.exists(), ( assert target_python.exists(), (
f"{target_python.parent} not found, has {list(target_install_path.iterdir())}" f"{target_python.name} not found, has {list(target_install_path.iterdir())}"
) )
log.step("Creating cross build environment...") log.step("Creating cross build environment...")
@@ -387,7 +369,7 @@ def setup_python(
"entry or insert python above it." "entry or insert python above it."
) )
raise errors.FatalError(msg) raise errors.FatalError(msg)
call("python", "-V", "-V", env=env) call("python", "--version", env=env)
# Check what pip version we're on # Check what pip version we're on
assert (venv_bin_path / "pip").exists() assert (venv_bin_path / "pip").exists()
@@ -459,8 +441,7 @@ def build(options: Options, tmp_path: Path) -> None:
build_options = options.build_options(config.identifier) build_options = options.build_options(config.identifier)
build_frontend = build_options.build_frontend build_frontend = build_options.build_frontend
# uv doesn't support iOS # uv doesn't support iOS
# Not using set because mypy can't narrow it if build_frontend.name == "build[uv]" or build_frontend.name == "uv":
if build_frontend.name == "build[uv]" or build_frontend.name == "uv": # noqa: PLR1714
msg = "uv doesn't support iOS" msg = "uv doesn't support iOS"
raise errors.FatalError(msg) raise errors.FatalError(msg)
@@ -509,11 +490,8 @@ def build(options: Options, tmp_path: Path) -> None:
extra_flags = get_build_frontend_extra_flags( extra_flags = get_build_frontend_extra_flags(
build_frontend, build_frontend,
build_options.build_verbosity, build_options.build_verbosity,
prepare_config_settings( build_options.config_settings,
build_options.config_settings, py38=False,
project=".",
package=build_options.package_dir,
),
) )
match build_frontend.name: match build_frontend.name:
@@ -574,12 +552,10 @@ def build(options: Options, tmp_path: Path) -> None:
if repaired_wheel.name in {wheel.name for wheel in built_wheels}: if repaired_wheel.name in {wheel.name for wheel in built_wheels}:
raise errors.AlreadyBuiltWheelError(repaired_wheel.name) raise errors.AlreadyBuiltWheelError(repaired_wheel.name)
log.step_end()
run_audit(tmp_dir=tmp_path, build_options=build_options, wheel=repaired_wheel)
test_wheel = repaired_wheel test_wheel = repaired_wheel
log.step_end()
if build_options.test_command and build_options.test_selector(config.identifier): if build_options.test_command and build_options.test_selector(config.identifier):
if not config.is_simulator: if not config.is_simulator:
log.step("Skipping tests on non-simulator SDK") log.step("Skipping tests on non-simulator SDK")
+19 -49
View File
@@ -1,33 +1,27 @@
from __future__ import annotations
import contextlib import contextlib
import dataclasses import dataclasses
import shutil
import subprocess import subprocess
import sys import sys
import textwrap import textwrap
from collections import OrderedDict from collections import OrderedDict
from collections.abc import Iterable, Iterator, Sequence, Set
from pathlib import Path, PurePath, PurePosixPath from pathlib import Path, PurePath, PurePosixPath
from typing import assert_never from typing import TYPE_CHECKING, assert_never
from cibuildwheel import errors from .. import errors
from cibuildwheel.architecture import Architecture from ..architecture import Architecture
from cibuildwheel.audit import needs_audit, run_audit from ..frontend import get_build_frontend_extra_flags
from cibuildwheel.frontend import get_build_frontend_extra_flags, prepare_config_settings from ..logger import log
from cibuildwheel.logger import log from ..oci_container import OCIContainer, OCIContainerEngineConfig, OCIPlatform
from cibuildwheel.oci_container import OCIContainer, OCIContainerEngineConfig, OCIPlatform from ..options import BuildOptions, Options
from cibuildwheel.util import resources from ..selector import BuildSelector
from cibuildwheel.util.file import copy_test_sources from ..util import resources
from cibuildwheel.util.helpers import prepare_command, unwrap from ..util.file import copy_test_sources
from cibuildwheel.util.packaging import find_compatible_wheel from ..util.helpers import prepare_command, unwrap
from ..util.packaging import find_compatible_wheel
TYPE_CHECKING = False
if TYPE_CHECKING: if TYPE_CHECKING:
from collections.abc import Iterable, Iterator, Sequence, Set from ..typing import PathOrStr
from cibuildwheel.options import BuildOptions, Options
from cibuildwheel.selector import BuildSelector
from cibuildwheel.typing import PathOrStr
ARCHITECTURE_OCI_PLATFORM_MAP = { ARCHITECTURE_OCI_PLATFORM_MAP = {
Architecture.x86_64: OCIPlatform.AMD64, Architecture.x86_64: OCIPlatform.AMD64,
@@ -243,7 +237,6 @@ def build_in_container(
if PurePosixPath(which_python) != python_bin / "python": if PurePosixPath(which_python) != python_bin / "python":
msg = "python available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert python above it." msg = "python available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert python above it."
raise errors.FatalError(msg) raise errors.FatalError(msg)
container.call(["python", "-V", "-V"], env=env)
if use_uv: if use_uv:
which_uv = container.call(["which", "uv"], env=env, capture_output=True).strip() which_uv = container.call(["which", "uv"], env=env, capture_output=True).strip()
@@ -271,16 +264,7 @@ def build_in_container(
project=container_project_path, project=container_project_path,
package=container_package_dir, package=container_package_dir,
) )
before_build_env = env.copy() container.call(["sh", "-c", before_build_prepared], env=env)
if use_uv:
# On Linux, no virtualenv is created for the build environment
# (unlike macOS/Windows, where one is set up before before_build
# runs). uv requires either an active venv or an explicit Python
# target to install packages. Pin UV_PYTHON to the exact interpreter
# for this build so that `uv pip install` works in before_build
# without requiring users to pass --system.
before_build_env["UV_PYTHON"] = str(python_bin / "python")
container.call(["sh", "-c", before_build_prepared], env=before_build_env)
log.step("Building wheel...") log.step("Building wheel...")
@@ -292,11 +276,8 @@ def build_in_container(
extra_flags = get_build_frontend_extra_flags( extra_flags = get_build_frontend_extra_flags(
build_frontend, build_frontend,
build_options.build_verbosity, build_options.build_verbosity,
prepare_config_settings( build_options.config_settings,
build_options.config_settings, py38=config.identifier[1:].startswith("p38"),
project=container_project_path,
package=container_package_dir,
),
) )
match build_frontend.name: match build_frontend.name:
@@ -378,18 +359,6 @@ def build_in_container(
if repaired_wheel.name in {wheel.name for wheel in built_wheels}: if repaired_wheel.name in {wheel.name for wheel in built_wheels}:
raise errors.AlreadyBuiltWheelError(repaired_wheel.name) raise errors.AlreadyBuiltWheelError(repaired_wheel.name)
log.step_end()
if needs_audit(build_options.audit_command, repaired_wheel.name):
local_abi3audit_dir = local_identifier_tmp_dir / "audit"
local_abi3audit_dir.mkdir(parents=True, exist_ok=True)
try:
container.copy_out(repaired_wheel_dir, local_abi3audit_dir)
local_wheel = local_abi3audit_dir / repaired_wheel.name
run_audit(tmp_dir=local_tmp_dir, build_options=build_options, wheel=local_wheel)
finally:
shutil.rmtree(local_abi3audit_dir, ignore_errors=True)
if build_options.test_command and build_options.test_selector(config.identifier): if build_options.test_command and build_options.test_selector(config.identifier):
log.step("Testing wheel...") log.step("Testing wheel...")
@@ -512,7 +481,8 @@ def build(options: Options, tmp_path: Path) -> None:
Docker or Podman is required to run Linux builds. If you're Docker or Podman is required to run Linux builds. If you're
building on Travis CI, add `services: [docker]` to your building on Travis CI, add `services: [docker]` to your
.travis.yml. If you're building on Circle CI in Linux, add a .travis.yml. If you're building on Circle CI in Linux, add a
`setup_remote_docker` step to your .circleci/config.yml. `setup_remote_docker` step to your .circleci/config.yml. If
you're building on Cirrus CI, use `docker_builder` task.
""" """
) )
raise errors.ConfigurationError(msg) from error raise errors.ConfigurationError(msg) from error
+80 -65
View File
@@ -1,5 +1,3 @@
from __future__ import annotations
import dataclasses import dataclasses
import functools import functools
import inspect import inspect
@@ -10,37 +8,32 @@ import shutil
import subprocess import subprocess
import sys import sys
import typing import typing
from collections.abc import Set
from pathlib import Path from pathlib import Path
from typing import assert_never from typing import Literal, assert_never
from filelock import FileLock from filelock import FileLock
from packaging.version import Version from packaging.version import Version
from cibuildwheel import errors from .. import errors
from cibuildwheel.audit import run_audit from ..architecture import Architecture
from cibuildwheel.ci import detect_ci_provider from ..ci import detect_ci_provider
from cibuildwheel.frontend import ( from ..environment import ParsedEnvironment
BuildFrontendName, from ..frontend import BuildFrontendName, get_build_frontend_extra_flags
get_build_frontend_extra_flags, from ..logger import log
prepare_config_settings, from ..options import Options
from ..selector import BuildSelector
from ..util import resources
from ..util.cmd import call, shell
from ..util.file import (
CIBW_CACHE_PATH,
copy_test_sources,
download,
move_file,
) )
from cibuildwheel.logger import log from ..util.helpers import prepare_command, unwrap
from cibuildwheel.util import resources from ..util.packaging import find_compatible_wheel, get_pip_version
from cibuildwheel.util.cmd import call, shell from ..venv import constraint_flags, find_uv, virtualenv
from cibuildwheel.util.file import CIBW_CACHE_PATH, copy_test_sources, download, move_file
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 @functools.cache
@@ -88,7 +81,6 @@ class PythonConfiguration:
version: str version: str
identifier: str identifier: str
url: str url: str
sha256: str = ""
def all_python_configurations() -> list[PythonConfiguration]: def all_python_configurations() -> list[PythonConfiguration]:
@@ -136,9 +128,7 @@ def get_python_configurations(
return python_configurations return python_configurations
def install_cpython( def install_cpython(_tmp: Path, version: str, url: str, free_threading: bool) -> Path:
_tmp: Path, version: str, url: str, free_threading: bool, sha256: str = ""
) -> Path:
ft = "T" if free_threading else "" ft = "T" if free_threading else ""
installation_path = Path(f"/Library/Frameworks/Python{ft}.framework/Versions/{version}") installation_path = Path(f"/Library/Frameworks/Python{ft}.framework/Versions/{version}")
with FileLock(CIBW_CACHE_PATH / f"cpython{version}.lock"): with FileLock(CIBW_CACHE_PATH / f"cpython{version}.lock"):
@@ -164,12 +154,13 @@ def install_cpython(
python_filename = url.rsplit("/", maxsplit=1)[-1] python_filename = url.rsplit("/", maxsplit=1)[-1]
pkg_path = CIBW_CACHE_PATH / "cpython-installer" / python_filename pkg_path = CIBW_CACHE_PATH / "cpython-installer" / python_filename
if not pkg_path.exists(): if not pkg_path.exists():
download(url, pkg_path, sha256=sha256 or None) download(url, pkg_path)
args = [] args = []
if version.startswith("3.14"): if version.startswith("3.13"):
# Python 3.13 is the first version to have a free-threading option
args += ["-applyChoiceChangesXML", str(resources.FREE_THREAD_ENABLE_313.resolve())]
elif version.startswith("3.14"):
args += ["-applyChoiceChangesXML", str(resources.FREE_THREAD_ENABLE_314.resolve())] args += ["-applyChoiceChangesXML", str(resources.FREE_THREAD_ENABLE_314.resolve())]
elif version.startswith("3.15"):
args += ["-applyChoiceChangesXML", str(resources.FREE_THREAD_ENABLE_315.resolve())]
call("sudo", "installer", "-pkg", pkg_path, *args, "-target", "/") call("sudo", "installer", "-pkg", pkg_path, *args, "-target", "/")
pkg_path.unlink() pkg_path.unlink()
env = os.environ.copy() env = os.environ.copy()
@@ -188,7 +179,7 @@ def install_cpython(
return installation_path / "bin" / (f"python{version}t" if free_threading else "python3") return installation_path / "bin" / (f"python{version}t" if free_threading else "python3")
def install_pypy(tmp: Path, url: str, sha256: str = "") -> Path: def install_pypy(tmp: Path, url: str) -> Path:
pypy_tar_bz2 = url.rsplit("/", 1)[-1] pypy_tar_bz2 = url.rsplit("/", 1)[-1]
extension = ".tar.bz2" extension = ".tar.bz2"
assert pypy_tar_bz2.endswith(extension) assert pypy_tar_bz2.endswith(extension)
@@ -196,14 +187,14 @@ def install_pypy(tmp: Path, url: str, sha256: str = "") -> Path:
with FileLock(str(installation_path) + ".lock"): with FileLock(str(installation_path) + ".lock"):
if not installation_path.exists(): if not installation_path.exists():
downloaded_tar_bz2 = tmp / pypy_tar_bz2 downloaded_tar_bz2 = tmp / pypy_tar_bz2
download(url, downloaded_tar_bz2, sha256=sha256 or None) download(url, downloaded_tar_bz2)
installation_path.parent.mkdir(parents=True, exist_ok=True) installation_path.parent.mkdir(parents=True, exist_ok=True)
call("tar", "-C", installation_path.parent, "-xf", downloaded_tar_bz2) call("tar", "-C", installation_path.parent, "-xf", downloaded_tar_bz2)
downloaded_tar_bz2.unlink() downloaded_tar_bz2.unlink()
return installation_path / "bin" / "pypy3" return installation_path / "bin" / "pypy3"
def install_graalpy(tmp: Path, url: str, sha256: str = "") -> Path: def install_graalpy(tmp: Path, url: str) -> Path:
graalpy_archive = url.rsplit("/", 1)[-1] graalpy_archive = url.rsplit("/", 1)[-1]
extension = ".tar.gz" extension = ".tar.gz"
assert graalpy_archive.endswith(extension) assert graalpy_archive.endswith(extension)
@@ -211,7 +202,7 @@ def install_graalpy(tmp: Path, url: str, sha256: str = "") -> Path:
with FileLock(str(installation_path) + ".lock"): with FileLock(str(installation_path) + ".lock"):
if not installation_path.exists(): if not installation_path.exists():
downloaded_archive = tmp / graalpy_archive downloaded_archive = tmp / graalpy_archive
download(url, downloaded_archive, sha256=sha256 or None) download(url, downloaded_archive)
installation_path.mkdir(parents=True) installation_path.mkdir(parents=True)
# GraalPy top-folder name is inconsistent with archive name # GraalPy top-folder name is inconsistent with archive name
call("tar", "-C", installation_path, "--strip-components=1", "-xzf", downloaded_archive) call("tar", "-C", installation_path, "--strip-components=1", "-xzf", downloaded_archive)
@@ -235,17 +226,13 @@ def setup_python(
if implementation_id.startswith("cp"): if implementation_id.startswith("cp"):
free_threading = "t-macos" in python_configuration.identifier free_threading = "t-macos" in python_configuration.identifier
base_python = install_cpython( base_python = install_cpython(
tmp, tmp, python_configuration.version, python_configuration.url, free_threading
python_configuration.version,
python_configuration.url,
free_threading,
python_configuration.sha256,
) )
elif implementation_id.startswith("pp"): elif implementation_id.startswith("pp"):
base_python = install_pypy(tmp, python_configuration.url, python_configuration.sha256) base_python = install_pypy(tmp, python_configuration.url)
elif implementation_id.startswith("gp"): elif implementation_id.startswith("gp"):
base_python = install_graalpy(tmp, python_configuration.url, python_configuration.sha256) base_python = install_graalpy(tmp, python_configuration.url)
else: else:
msg = "Unknown Python implementation" msg = "Unknown Python implementation"
raise ValueError(msg) raise ValueError(msg)
@@ -261,7 +248,6 @@ def setup_python(
venv_path, venv_path,
dependency_constraint, dependency_constraint,
use_uv=use_uv, use_uv=use_uv,
marker_env=target_marker_env(implementation_id=implementation_id),
) )
venv_bin_path = venv_path / "bin" venv_bin_path = venv_path / "bin"
assert venv_bin_path.exists() assert venv_bin_path.exists()
@@ -272,10 +258,6 @@ def setup_python(
# https://github.com/pypa/virtualenv/issues/620 # https://github.com/pypa/virtualenv/issues/620
# Also see https://github.com/python/cpython/pull/9516 # Also see https://github.com/python/cpython/pull/9516
env.pop("__PYVENV_LAUNCHER__", None) env.pop("__PYVENV_LAUNCHER__", None)
# uv uses this over the current environment's python, so remove it to avoid confusion
env.pop("PYTHON_VERSION", None)
env.pop("PYTHON_ARCH", None)
env.pop("UV_PYTHON", None)
# we version pip ourselves, so we don't care about pip version checking # we version pip ourselves, so we don't care about pip version checking
env["PIP_DISABLE_PIP_VERSION_CHECK"] = "1" env["PIP_DISABLE_PIP_VERSION_CHECK"] = "1"
@@ -289,7 +271,7 @@ def setup_python(
if which_python != str(venv_bin_path / "python"): if which_python != str(venv_bin_path / "python"):
msg = "python available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert python above it." msg = "python available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert python above it."
raise errors.FatalError(msg) raise errors.FatalError(msg)
call("python", "-V", "-V", env=env) call("python", "--version", env=env)
# check what pip version we're on # check what pip version we're on
if not use_uv: if not use_uv:
@@ -393,8 +375,6 @@ def setup_python(
uv_path, uv_path,
"pip", "pip",
"install", "install",
"--python",
which_python,
"--upgrade", "--upgrade",
"delocate", "delocate",
"build[virtualenv, uv]", "build[virtualenv, uv]",
@@ -407,8 +387,6 @@ def setup_python(
uv_path, uv_path,
"pip", "pip",
"install", "install",
"--python",
which_python,
"--upgrade", "--upgrade",
"delocate", "delocate",
*constraint_flags(dependency_constraint), *constraint_flags(dependency_constraint),
@@ -496,11 +474,8 @@ def build(options: Options, tmp_path: Path) -> None:
extra_flags = get_build_frontend_extra_flags( extra_flags = get_build_frontend_extra_flags(
build_frontend, build_frontend,
build_options.build_verbosity, build_options.build_verbosity,
prepare_config_settings( build_options.config_settings,
build_options.config_settings, py38=config.identifier[1:].startswith("p38"),
project=".",
package=build_options.package_dir,
),
) )
build_env = env.copy() build_env = env.copy()
@@ -591,10 +566,15 @@ def build(options: Options, tmp_path: Path) -> None:
log.step_end() log.step_end()
run_audit(tmp_dir=tmp_path, build_options=build_options, wheel=repaired_wheel)
if build_options.test_command and build_options.test_selector(config.identifier): if build_options.test_command and build_options.test_selector(config.identifier):
machine_arch = platform.machine() machine_arch = platform.machine()
python_arch = call(
"python",
"-sSc",
"import platform; print(platform.machine())",
env=env,
capture_stdout=True,
).strip()
testing_archs: list[Literal["x86_64", "arm64"]] testing_archs: list[Literal["x86_64", "arm64"]]
if config_is_arm64: if config_is_arm64:
@@ -642,6 +622,24 @@ def build(options: Options, tmp_path: Path) -> None:
# skip this test # skip this test
continue continue
is_cp38 = config.identifier.startswith("cp38-")
if testing_arch == "arm64" and is_cp38 and python_arch != "arm64":
log.warning(
unwrap(
"""
While cibuildwheel can build CPython 3.8 universal2/arm64 wheels, we
cannot test the arm64 part of them, even when running on an Apple
Silicon machine. This is because we use the x86_64 installer of
CPython 3.8. See the discussion in
https://github.com/pypa/cibuildwheel/pull/1169 for the details. To
silence this warning, set `CIBW_TEST_SKIP: "cp38-macosx_*:arm64"`.
"""
)
)
# skip this test
continue
log.step( log.step(
"Testing wheel..." "Testing wheel..."
if testing_arch == machine_arch if testing_arch == machine_arch
@@ -698,16 +696,33 @@ def build(options: Options, tmp_path: Path) -> None:
shell_with_arch(before_test_prepared, env=virtualenv_env) shell_with_arch(before_test_prepared, env=virtualenv_env)
# install the wheel # install the wheel
if is_cp38 and python_arch == "x86_64":
virtualenv_env_install_wheel = virtualenv_env.copy()
virtualenv_env_install_wheel["SYSTEM_VERSION_COMPAT"] = "0"
log.notice(
unwrap(
"""
Setting SYSTEM_VERSION_COMPAT=0 to ensure CPython 3.8 can get
correct macOS version and allow installation of wheels with
MACOSX_DEPLOYMENT_TARGET >= 11.0.
See https://github.com/pypa/cibuildwheel/issues/1767 for the
details.
"""
)
)
else:
virtualenv_env_install_wheel = virtualenv_env
pip_install( pip_install(
f"{repaired_wheel}{build_options.test_extras}", f"{repaired_wheel}{build_options.test_extras}",
env=virtualenv_env, env=virtualenv_env_install_wheel,
) )
# test the wheel # test the wheel
if build_options.test_requires: if build_options.test_requires:
pip_install( pip_install(
*build_options.test_requires, *build_options.test_requires,
env=virtualenv_env, env=virtualenv_env_install_wheel,
) )
# run the tests from a temp dir, with an absolute path in the command # run the tests from a temp dir, with an absolute path in the command
+46 -64
View File
@@ -1,5 +1,3 @@
from __future__ import annotations
import dataclasses import dataclasses
import functools import functools
import json import json
@@ -9,20 +7,23 @@ import subprocess
import sys import sys
import tomllib import tomllib
import typing import typing
from collections.abc import Set
from pathlib import Path from pathlib import Path
from tempfile import TemporaryDirectory from tempfile import TemporaryDirectory
from typing import Final, TypedDict from typing import Final, TypedDict
from filelock import FileLock from filelock import FileLock
from cibuildwheel import errors from .. import errors
from cibuildwheel.architecture import Architecture from ..architecture import Architecture
from cibuildwheel.audit import run_audit from ..environment import ParsedEnvironment
from cibuildwheel.frontend import get_build_frontend_extra_flags, prepare_config_settings from ..frontend import get_build_frontend_extra_flags
from cibuildwheel.logger import log from ..logger import log
from cibuildwheel.util import resources from ..options import Options
from cibuildwheel.util.cmd import call, shell from ..selector import BuildSelector
from cibuildwheel.util.file import ( from ..util import resources
from ..util.cmd import call, shell
from ..util.file import (
CIBW_CACHE_PATH, CIBW_CACHE_PATH,
copy_test_sources, copy_test_sources,
download, download,
@@ -30,21 +31,13 @@ from cibuildwheel.util.file import (
extract_zip, extract_zip,
move_file, move_file,
) )
from cibuildwheel.util.helpers import prepare_command, unwrap, unwrap_preserving_paragraphs from ..util.helpers import prepare_command, unwrap, unwrap_preserving_paragraphs
from cibuildwheel.util.packaging import find_compatible_wheel, get_pip_version from ..util.packaging import find_compatible_wheel, get_pip_version
from cibuildwheel.util.python_build_standalone import ( from ..util.python_build_standalone import (
PythonBuildStandaloneError, PythonBuildStandaloneError,
create_python_build_standalone_environment, create_python_build_standalone_environment,
) )
from cibuildwheel.venv import constraint_flags, virtualenv from ..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") IS_WIN: Final[bool] = sys.platform.startswith("win")
@@ -55,7 +48,6 @@ class PythonConfiguration:
identifier: str identifier: str
default_pyodide_version: str default_pyodide_version: str
node_version: str node_version: str
sha256: str = ""
class PyodideXBuildEnvInfoVersionRange(TypedDict): class PyodideXBuildEnvInfoVersionRange(TypedDict):
@@ -102,28 +94,22 @@ def ensure_node(major_version: str) -> Path:
return path return path
def install_emscripten(env: dict[str, str], version: str, xbuildenv_cache_path: Path) -> Path: def install_emscripten(tmp: Path, version: str) -> Path:
"""Install Emscripten via pyodide-build, which also applies Pyodide-specific patches.""" url = f"https://github.com/emscripten-core/emsdk/archive/refs/tags/{version}.zip"
emscripten_dir = Path( installation_path = CIBW_CACHE_PATH / f"emsdk-{version}"
call("pyodide", "config", "get", "emscripten_dir", env=env, capture_stdout=True).strip() emsdk_path = installation_path / f"emsdk-{version}/emsdk"
) emcc_path = installation_path / f"emsdk-{version}/upstream/emscripten/emcc"
with FileLock(CIBW_CACHE_PATH / "emscripten.lock"): with FileLock(f"{installation_path}.lock"):
if emscripten_dir.exists(): if installation_path.exists():
return emscripten_dir return emcc_path
call( emsdk_zip = tmp / "emsdk.zip"
"pyodide", download(url, emsdk_zip)
"xbuildenv", installation_path.mkdir()
"install-emscripten", extract_zip(emsdk_zip, installation_path)
"--force", call(emsdk_path, "install", version)
"--version", call(emsdk_path, "activate", version)
version,
"--path", return emcc_path
str(xbuildenv_cache_path),
env=env,
cwd=CIBW_CACHE_PATH,
)
assert emscripten_dir.exists()
return emscripten_dir
def get_all_xbuildenv_version_info(env: dict[str, str]) -> list[PyodideXBuildEnvInfo]: def get_all_xbuildenv_version_info(env: dict[str, str]) -> list[PyodideXBuildEnvInfo]:
@@ -194,8 +180,12 @@ def validate_pyodide_build_version(
raise errors.FatalError(msg) raise errors.FatalError(msg)
def install_xbuildenv(env: dict[str, str], xbuildenv_cache_path: Path, pyodide_version: str) -> str: def install_xbuildenv(env: dict[str, str], pyodide_build_version: str, pyodide_version: str) -> str:
"""Install a particular Pyodide xbuildenv version and set a path to the Pyodide root.""" """Install a particular Pyodide xbuildenv version and set a path to the Pyodide root."""
# Since pyodide-build was unvendored from Pyodide v0.27.0, the versions of
# pyodide-build are uncoupled from the versions of Pyodide. So, we specify
# both the pyodide-build version and the Pyodide version in the temp path.
xbuildenv_cache_path = CIBW_CACHE_PATH / f"pyodide-build-{pyodide_build_version}"
pyodide_root = xbuildenv_cache_path / pyodide_version / "xbuildenv" / "pyodide-root" pyodide_root = xbuildenv_cache_path / pyodide_version / "xbuildenv" / "pyodide-root"
with FileLock(CIBW_CACHE_PATH / "xbuildenv.lock"): with FileLock(CIBW_CACHE_PATH / "xbuildenv.lock"):
@@ -278,7 +268,7 @@ def setup_python(
if which_python != str(venv_bin_path / "python"): if which_python != str(venv_bin_path / "python"):
msg = "python available on PATH doesn't match our venv instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert python above it." msg = "python available on PATH doesn't match our venv instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert python above it."
raise errors.FatalError(msg) raise errors.FatalError(msg)
call("python", "-V", "-V", env=env) call("python", "--version", env=env)
# check what pip version we're on # check what pip version we're on
assert (venv_bin_path / "pip").exists() assert (venv_bin_path / "pip").exists()
@@ -295,6 +285,7 @@ def setup_python(
"install", "install",
"--upgrade", "--upgrade",
"pyodide-build", "pyodide-build",
"build[virtualenv]",
*constraint_flags(constraints_path), *constraint_flags(constraints_path),
env=env, env=env,
) )
@@ -313,18 +304,14 @@ def setup_python(
pyodide_build_version=pyodide_build_version, pyodide_build_version=pyodide_build_version,
) )
xbuildenv_cache_path = CIBW_CACHE_PATH / f"pyodide-build-{pyodide_build_version}" emscripten_version = xbuildenv_info["emscripten"]
log.step(f"Installing Emscripten version: {emscripten_version} ...")
emcc_path = install_emscripten(tmp, emscripten_version)
env["PATH"] = os.pathsep.join([str(emcc_path.parent), env["PATH"]])
log.step(f"Installing Pyodide xbuildenv version: {pyodide_version} ...") log.step(f"Installing Pyodide xbuildenv version: {pyodide_version} ...")
env["PYODIDE_ROOT"] = install_xbuildenv(env, xbuildenv_cache_path, pyodide_version) env["PYODIDE_ROOT"] = install_xbuildenv(env, pyodide_build_version, pyodide_version)
emscripten_version = xbuildenv_info["emscripten"]
log.step(
f"Installing Emscripten {emscripten_version} and applying Pyodide-specific patches ..."
)
emscripten_dir = install_emscripten(env, emscripten_version, xbuildenv_cache_path)
env["PATH"] = os.pathsep.join([str(emscripten_dir), env["PATH"]])
return env return env
@@ -431,11 +418,8 @@ def build(options: Options, tmp_path: Path) -> None:
extra_flags = get_build_frontend_extra_flags( extra_flags = get_build_frontend_extra_flags(
build_frontend, build_frontend,
build_options.build_verbosity, build_options.build_verbosity,
prepare_config_settings( build_options.config_settings,
build_options.config_settings, py38=False,
project=".",
package=build_options.package_dir,
),
) )
call( call(
@@ -471,8 +455,6 @@ def build(options: Options, tmp_path: Path) -> None:
if repaired_wheel.name in {wheel.name for wheel in built_wheels}: if repaired_wheel.name in {wheel.name for wheel in built_wheels}:
raise errors.AlreadyBuiltWheelError(repaired_wheel.name) raise errors.AlreadyBuiltWheelError(repaired_wheel.name)
run_audit(tmp_dir=tmp_path, build_options=build_options, wheel=repaired_wheel)
if build_options.test_command and build_options.test_selector(config.identifier): if build_options.test_command and build_options.test_selector(config.identifier):
log.step("Testing wheel...") log.step("Testing wheel...")
+33 -83
View File
@@ -1,5 +1,3 @@
from __future__ import annotations
import dataclasses import dataclasses
import json import json
import os import os
@@ -7,41 +5,26 @@ import platform as platform_module
import shutil import shutil
import subprocess import subprocess
import textwrap import textwrap
from collections.abc import MutableMapping, Sequence, Set
from functools import cache from functools import cache
from pathlib import Path from pathlib import Path
from typing import assert_never from typing import assert_never
from filelock import FileLock from filelock import FileLock
from cibuildwheel import errors from .. import errors
from cibuildwheel.architecture import Architecture from ..architecture import Architecture
from cibuildwheel.audit import run_audit from ..environment import ParsedEnvironment
from cibuildwheel.frontend import ( from ..frontend import BuildFrontendName, get_build_frontend_extra_flags
BuildFrontendName, from ..logger import log
get_build_frontend_extra_flags, from ..options import Options
prepare_config_settings, from ..selector import BuildSelector
) from ..util import resources
from cibuildwheel.logger import log from ..util.cmd import call, shell
from cibuildwheel.util import resources from ..util.file import CIBW_CACHE_PATH, copy_test_sources, download, extract_zip, move_file
from cibuildwheel.util.cmd import call, shell from ..util.helpers import prepare_command, unwrap
from cibuildwheel.util.file import ( from ..util.packaging import find_compatible_wheel, get_pip_version
CIBW_CACHE_PATH, from ..venv import constraint_flags, find_uv, virtualenv
copy_test_sources,
download,
extract_zip,
move_file,
)
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( def get_nuget_args(
@@ -73,7 +56,6 @@ class PythonConfiguration:
version: str version: str
identifier: str identifier: str
url: str | None = None url: str | None = None
sha256: str = ""
@property @property
def arch(self) -> str: def arch(self) -> str:
@@ -132,7 +114,7 @@ def install_cpython(configuration: PythonConfiguration, arch: str | None = None)
return installation_path / "python.exe" return installation_path / "python.exe"
def install_pypy(tmp: Path, arch: str, url: str, sha256: str = "") -> Path: def install_pypy(tmp: Path, arch: str, url: str) -> Path:
assert arch == "64" assert arch == "64"
assert "win64" in url assert "win64" in url
# Inside the PyPy zip file is a directory with the same name # Inside the PyPy zip file is a directory with the same name
@@ -143,13 +125,13 @@ def install_pypy(tmp: Path, arch: str, url: str, sha256: str = "") -> Path:
with FileLock(str(installation_path) + ".lock"): with FileLock(str(installation_path) + ".lock"):
if not installation_path.exists(): if not installation_path.exists():
pypy_zip = tmp / zip_filename pypy_zip = tmp / zip_filename
download(url, pypy_zip, sha256=sha256 or None) download(url, pypy_zip)
# Extract to the parent directory because the zip file still contains a directory # Extract to the parent directory because the zip file still contains a directory
extract_zip(pypy_zip, installation_path.parent) extract_zip(pypy_zip, installation_path.parent)
return installation_path / "python.exe" return installation_path / "python.exe"
def install_graalpy(tmp: Path, url: str, sha256: str = "") -> Path: def install_graalpy(tmp: Path, url: str) -> Path:
zip_filename = url.rsplit("/", 1)[-1] zip_filename = url.rsplit("/", 1)[-1]
extension = ".zip" extension = ".zip"
assert zip_filename.endswith(extension) assert zip_filename.endswith(extension)
@@ -157,7 +139,7 @@ def install_graalpy(tmp: Path, url: str, sha256: str = "") -> Path:
with FileLock(str(installation_path) + ".lock"): with FileLock(str(installation_path) + ".lock"):
if not installation_path.exists(): if not installation_path.exists():
graalpy_zip = tmp / zip_filename graalpy_zip = tmp / zip_filename
download(url, graalpy_zip, sha256=sha256 or None) download(url, graalpy_zip)
# Extract to the parent directory because the zip file still contains a directory # Extract to the parent directory because the zip file still contains a directory
extract_zip(graalpy_zip, installation_path.parent) extract_zip(graalpy_zip, installation_path.parent)
return installation_path / "bin" / "graalpy.exe" return installation_path / "bin" / "graalpy.exe"
@@ -250,6 +232,11 @@ def setup_rust_cross_compile(
) )
def can_use_uv(python_configuration: PythonConfiguration) -> bool:
conditions = (not python_configuration.identifier.startswith("pp38-"),)
return all(conditions)
def setup_python( def setup_python(
tmp: Path, tmp: Path,
python_configuration: PythonConfiguration, python_configuration: PythonConfiguration,
@@ -273,18 +260,17 @@ def setup_python(
base_python = install_cpython(python_configuration, arch=native_arch) base_python = install_cpython(python_configuration, arch=native_arch)
elif implementation_id.startswith("pp"): elif implementation_id.startswith("pp"):
assert python_configuration.url is not None assert python_configuration.url is not None
base_python = install_pypy( base_python = install_pypy(tmp, python_configuration.arch, python_configuration.url)
tmp, python_configuration.arch, python_configuration.url, python_configuration.sha256
)
elif implementation_id.startswith("gp"): elif implementation_id.startswith("gp"):
base_python = install_graalpy( base_python = install_graalpy(tmp, python_configuration.url or "")
tmp, python_configuration.url or "", python_configuration.sha256
)
else: else:
msg = "Unknown Python implementation" msg = "Unknown Python implementation"
raise ValueError(msg) raise ValueError(msg)
assert base_python.exists() assert base_python.exists()
if build_frontend == "build[uv]" and not can_use_uv(python_configuration):
build_frontend = "build"
use_uv = build_frontend in {"build[uv]", "uv"} use_uv = build_frontend in {"build[uv]", "uv"}
uv_path = find_uv() uv_path = find_uv()
@@ -296,15 +282,12 @@ def setup_python(
venv_path, venv_path,
dependency_constraint, dependency_constraint,
use_uv=use_uv, use_uv=use_uv,
marker_env=target_marker_env(implementation_id=implementation_id),
) )
# set up environment variables for run_with_env # set up environment variables for run_with_env
env["PYTHON_VERSION"] = python_configuration.version env["PYTHON_VERSION"] = python_configuration.version
env["PYTHON_ARCH"] = python_configuration.arch env["PYTHON_ARCH"] = python_configuration.arch
env.pop("UV_PYTHON", None) env["PIP_DISABLE_PIP_VERSION_CHECK"] = "1"
if not use_uv:
env["PIP_DISABLE_PIP_VERSION_CHECK"] = "1"
# update env with results from CIBW_ENVIRONMENT # update env with results from CIBW_ENVIRONMENT
env = environment.as_dictionary(prev_environment=env) env = environment.as_dictionary(prev_environment=env)
@@ -315,7 +298,7 @@ def setup_python(
if where_python != str(venv_path / "Scripts" / "python.exe"): if where_python != str(venv_path / "Scripts" / "python.exe"):
msg = "python available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert python above it." msg = "python available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert python above it."
raise errors.FatalError(msg) raise errors.FatalError(msg)
call("python", "-V", "-V", env=env) call("python", "--version", env=env)
call("python", "-c", "\"import struct; print(struct.calcsize('P') * 8)\"", env=env) call("python", "-c", "\"import struct; print(struct.calcsize('P') * 8)\"", env=env)
# check what pip version we're on # check what pip version we're on
@@ -330,22 +313,12 @@ def setup_python(
log.step("Installing build tools...") log.step("Installing build tools...")
match build_frontend: match build_frontend:
case "pip":
call(
"pip",
"install",
"--upgrade",
"delvewheel",
*constraint_flags(dependency_constraint),
env=env,
)
case "build": case "build":
call( call(
"pip", "pip",
"install", "install",
"--upgrade", "--upgrade",
"build[virtualenv]", "build[virtualenv]",
"delvewheel",
*constraint_flags(dependency_constraint), *constraint_flags(dependency_constraint),
env=env, env=env,
) )
@@ -355,29 +328,11 @@ def setup_python(
uv_path, uv_path,
"pip", "pip",
"install", "install",
"--python",
where_python,
"--upgrade", "--upgrade",
"build[virtualenv]", "build[virtualenv]",
"delvewheel",
*constraint_flags(dependency_constraint), *constraint_flags(dependency_constraint),
env=env, env=env,
) )
case "uv":
assert uv_path is not None
call(
uv_path,
"pip",
"install",
"--python",
where_python,
"--upgrade",
"delvewheel",
*constraint_flags(dependency_constraint),
env=env,
)
case _:
assert_never(build_frontend)
if python_libs_base: if python_libs_base:
# Set up the environment for various backends to enable cross-compilation # Set up the environment for various backends to enable cross-compilation
@@ -450,7 +405,7 @@ def build(options: Options, tmp_path: Path) -> None:
for config in python_configurations: for config in python_configurations:
build_options = options.build_options(config.identifier) build_options = options.build_options(config.identifier)
build_frontend = build_options.build_frontend build_frontend = build_options.build_frontend
use_uv = build_frontend.name in {"build[uv]", "uv"} use_uv = build_frontend.name in {"build[uv]", "uv"} and can_use_uv(config)
log.build_start(config.identifier) log.build_start(config.identifier)
identifier_tmp_dir = tmp_path / config.identifier identifier_tmp_dir = tmp_path / config.identifier
@@ -497,11 +452,8 @@ def build(options: Options, tmp_path: Path) -> None:
extra_flags = get_build_frontend_extra_flags( extra_flags = get_build_frontend_extra_flags(
build_frontend, build_frontend,
build_options.build_verbosity, build_options.build_verbosity,
prepare_config_settings( build_options.config_settings,
build_options.config_settings, py38=config.identifier[1:].startswith("p38"),
project=".",
package=options.globals.package_dir,
),
) )
if ( if (
@@ -597,8 +549,6 @@ def build(options: Options, tmp_path: Path) -> None:
if repaired_wheel.name in {wheel.name for wheel in built_wheels}: if repaired_wheel.name in {wheel.name for wheel in built_wheels}:
raise errors.AlreadyBuiltWheelError(repaired_wheel.name) raise errors.AlreadyBuiltWheelError(repaired_wheel.name)
run_audit(tmp_dir=tmp_path, build_options=build_options, wheel=repaired_wheel)
test_selected = options.globals.test_selector(config.identifier) test_selected = options.globals.test_selector(config.identifier)
if test_selected and config.arch == "ARM64" != platform_module.machine(): if test_selected and config.arch == "ARM64" != platform_module.machine():
log.warning( log.warning(
+2 -7
View File
@@ -1,16 +1,11 @@
from __future__ import annotations
import ast import ast
import configparser import configparser
import contextlib import contextlib
from pathlib import Path
from typing import Any
import dependency_groups 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: def get_parent(node: ast.AST | None, depth: int = 1) -> ast.AST | None:
for _ in range(depth): for _ in range(depth):
@@ -1,11 +0,0 @@
--- a/android.py
+++ b/android.py
@@ -1021,7 +1021,7 @@ def main():
context = parse_args()
# Set the CROSS_BUILD_DIR if an argument was provided
- if context.cross_build_dir:
+ if getattr(context, "cross_build_dir", None):
global CROSS_BUILD_DIR
CROSS_BUILD_DIR = context.cross_build_dir.resolve()
+102 -103
View File
@@ -1,189 +1,196 @@
[linux] [linux]
python_configurations = [ python_configurations = [
{ identifier = "cp38-manylinux_x86_64", version = "3.8", path_str = "/opt/python/cp38-cp38" },
{ identifier = "cp39-manylinux_x86_64", version = "3.9", path_str = "/opt/python/cp39-cp39" }, { identifier = "cp39-manylinux_x86_64", version = "3.9", path_str = "/opt/python/cp39-cp39" },
{ identifier = "cp310-manylinux_x86_64", version = "3.10", path_str = "/opt/python/cp310-cp310" }, { identifier = "cp310-manylinux_x86_64", version = "3.10", path_str = "/opt/python/cp310-cp310" },
{ identifier = "cp311-manylinux_x86_64", version = "3.11", path_str = "/opt/python/cp311-cp311" }, { identifier = "cp311-manylinux_x86_64", version = "3.11", path_str = "/opt/python/cp311-cp311" },
{ identifier = "cp312-manylinux_x86_64", version = "3.12", path_str = "/opt/python/cp312-cp312" }, { identifier = "cp312-manylinux_x86_64", version = "3.12", path_str = "/opt/python/cp312-cp312" },
{ identifier = "cp313-manylinux_x86_64", version = "3.13", path_str = "/opt/python/cp313-cp313" }, { identifier = "cp313-manylinux_x86_64", version = "3.13", path_str = "/opt/python/cp313-cp313" },
{ identifier = "cp313t-manylinux_x86_64", version = "3.13", path_str = "/opt/python/cp313-cp313t" },
{ identifier = "cp314-manylinux_x86_64", version = "3.14", path_str = "/opt/python/cp314-cp314" }, { identifier = "cp314-manylinux_x86_64", version = "3.14", path_str = "/opt/python/cp314-cp314" },
{ identifier = "cp314t-manylinux_x86_64", version = "3.14", path_str = "/opt/python/cp314-cp314t" }, { identifier = "cp314t-manylinux_x86_64", version = "3.14", path_str = "/opt/python/cp314-cp314t" },
{ identifier = "cp315-manylinux_x86_64", version = "3.15", path_str = "/opt/python/cp315-cp315" }, { identifier = "cp38-manylinux_i686", version = "3.8", path_str = "/opt/python/cp38-cp38" },
{ identifier = "cp315t-manylinux_x86_64", version = "3.15", path_str = "/opt/python/cp315-cp315t" },
{ identifier = "cp39-manylinux_i686", version = "3.9", path_str = "/opt/python/cp39-cp39" }, { identifier = "cp39-manylinux_i686", version = "3.9", path_str = "/opt/python/cp39-cp39" },
{ identifier = "cp310-manylinux_i686", version = "3.10", path_str = "/opt/python/cp310-cp310" }, { identifier = "cp310-manylinux_i686", version = "3.10", path_str = "/opt/python/cp310-cp310" },
{ identifier = "cp311-manylinux_i686", version = "3.11", path_str = "/opt/python/cp311-cp311" }, { identifier = "cp311-manylinux_i686", version = "3.11", path_str = "/opt/python/cp311-cp311" },
{ identifier = "cp312-manylinux_i686", version = "3.12", path_str = "/opt/python/cp312-cp312" }, { identifier = "cp312-manylinux_i686", version = "3.12", path_str = "/opt/python/cp312-cp312" },
{ identifier = "cp313-manylinux_i686", version = "3.13", path_str = "/opt/python/cp313-cp313" }, { identifier = "cp313-manylinux_i686", version = "3.13", path_str = "/opt/python/cp313-cp313" },
{ identifier = "cp313t-manylinux_i686", version = "3.13", path_str = "/opt/python/cp313-cp313t" },
{ identifier = "cp314-manylinux_i686", version = "3.14", path_str = "/opt/python/cp314-cp314" }, { identifier = "cp314-manylinux_i686", version = "3.14", path_str = "/opt/python/cp314-cp314" },
{ identifier = "cp314t-manylinux_i686", version = "3.14", path_str = "/opt/python/cp314-cp314t" }, { identifier = "cp314t-manylinux_i686", version = "3.14", path_str = "/opt/python/cp314-cp314t" },
{ identifier = "cp315-manylinux_i686", version = "3.15", path_str = "/opt/python/cp315-cp315" }, { identifier = "pp38-manylinux_x86_64", version = "3.8", path_str = "/opt/python/pp38-pypy38_pp73" },
{ identifier = "cp315t-manylinux_i686", version = "3.15", path_str = "/opt/python/cp315-cp315t" },
{ identifier = "pp39-manylinux_x86_64", version = "3.9", path_str = "/opt/python/pp39-pypy39_pp73" }, { identifier = "pp39-manylinux_x86_64", version = "3.9", path_str = "/opt/python/pp39-pypy39_pp73" },
{ identifier = "pp310-manylinux_x86_64", version = "3.10", path_str = "/opt/python/pp310-pypy310_pp73" }, { identifier = "pp310-manylinux_x86_64", version = "3.10", path_str = "/opt/python/pp310-pypy310_pp73" },
{ identifier = "pp311-manylinux_x86_64", version = "3.11", path_str = "/opt/python/pp311-pypy311_pp73" }, { identifier = "pp311-manylinux_x86_64", version = "3.11", path_str = "/opt/python/pp311-pypy311_pp73" },
{ identifier = "gp311_242-manylinux_x86_64", version = "3.11", path_str = "/opt/python/graalpy311-graalpy242_311_native" }, { identifier = "gp311_242-manylinux_x86_64", version = "3.11", path_str = "/opt/python/graalpy311-graalpy242_311_native" },
{ identifier = "gp312_250-manylinux_x86_64", version = "3.12", path_str = "/opt/python/graalpy312-graalpy250_312_native" }, { identifier = "gp312_250-manylinux_x86_64", version = "3.12", path_str = "/opt/python/graalpy312-graalpy250_312_native" },
{ identifier = "cp38-manylinux_aarch64", version = "3.8", path_str = "/opt/python/cp38-cp38" },
{ identifier = "cp39-manylinux_aarch64", version = "3.9", path_str = "/opt/python/cp39-cp39" }, { identifier = "cp39-manylinux_aarch64", version = "3.9", path_str = "/opt/python/cp39-cp39" },
{ identifier = "cp310-manylinux_aarch64", version = "3.10", path_str = "/opt/python/cp310-cp310" }, { identifier = "cp310-manylinux_aarch64", version = "3.10", path_str = "/opt/python/cp310-cp310" },
{ identifier = "cp311-manylinux_aarch64", version = "3.11", path_str = "/opt/python/cp311-cp311" }, { identifier = "cp311-manylinux_aarch64", version = "3.11", path_str = "/opt/python/cp311-cp311" },
{ identifier = "cp312-manylinux_aarch64", version = "3.12", path_str = "/opt/python/cp312-cp312" }, { identifier = "cp312-manylinux_aarch64", version = "3.12", path_str = "/opt/python/cp312-cp312" },
{ identifier = "cp313-manylinux_aarch64", version = "3.13", path_str = "/opt/python/cp313-cp313" }, { identifier = "cp313-manylinux_aarch64", version = "3.13", path_str = "/opt/python/cp313-cp313" },
{ identifier = "cp313t-manylinux_aarch64", version = "3.13", path_str = "/opt/python/cp313-cp313t" },
{ identifier = "cp314-manylinux_aarch64", version = "3.14", path_str = "/opt/python/cp314-cp314" }, { identifier = "cp314-manylinux_aarch64", version = "3.14", path_str = "/opt/python/cp314-cp314" },
{ identifier = "cp314t-manylinux_aarch64", version = "3.14", path_str = "/opt/python/cp314-cp314t" }, { identifier = "cp314t-manylinux_aarch64", version = "3.14", path_str = "/opt/python/cp314-cp314t" },
{ identifier = "cp315-manylinux_aarch64", version = "3.15", path_str = "/opt/python/cp315-cp315" }, { identifier = "cp38-manylinux_ppc64le", version = "3.8", path_str = "/opt/python/cp38-cp38" },
{ identifier = "cp315t-manylinux_aarch64", version = "3.15", path_str = "/opt/python/cp315-cp315t" },
{ identifier = "cp39-manylinux_ppc64le", version = "3.9", path_str = "/opt/python/cp39-cp39" }, { identifier = "cp39-manylinux_ppc64le", version = "3.9", path_str = "/opt/python/cp39-cp39" },
{ identifier = "cp310-manylinux_ppc64le", version = "3.10", path_str = "/opt/python/cp310-cp310" }, { identifier = "cp310-manylinux_ppc64le", version = "3.10", path_str = "/opt/python/cp310-cp310" },
{ identifier = "cp311-manylinux_ppc64le", version = "3.11", path_str = "/opt/python/cp311-cp311" }, { identifier = "cp311-manylinux_ppc64le", version = "3.11", path_str = "/opt/python/cp311-cp311" },
{ identifier = "cp312-manylinux_ppc64le", version = "3.12", path_str = "/opt/python/cp312-cp312" }, { identifier = "cp312-manylinux_ppc64le", version = "3.12", path_str = "/opt/python/cp312-cp312" },
{ identifier = "cp313-manylinux_ppc64le", version = "3.13", path_str = "/opt/python/cp313-cp313" }, { identifier = "cp313-manylinux_ppc64le", version = "3.13", path_str = "/opt/python/cp313-cp313" },
{ identifier = "cp313t-manylinux_ppc64le", version = "3.13", path_str = "/opt/python/cp313-cp313t" },
{ identifier = "cp314-manylinux_ppc64le", version = "3.14", path_str = "/opt/python/cp314-cp314" }, { identifier = "cp314-manylinux_ppc64le", version = "3.14", path_str = "/opt/python/cp314-cp314" },
{ identifier = "cp314t-manylinux_ppc64le", version = "3.14", path_str = "/opt/python/cp314-cp314t" }, { identifier = "cp314t-manylinux_ppc64le", version = "3.14", path_str = "/opt/python/cp314-cp314t" },
{ identifier = "cp315-manylinux_ppc64le", version = "3.15", path_str = "/opt/python/cp315-cp315" }, { identifier = "cp38-manylinux_s390x", version = "3.8", path_str = "/opt/python/cp38-cp38" },
{ identifier = "cp315t-manylinux_ppc64le", version = "3.15", path_str = "/opt/python/cp315-cp315t" },
{ identifier = "cp39-manylinux_s390x", version = "3.9", path_str = "/opt/python/cp39-cp39" }, { identifier = "cp39-manylinux_s390x", version = "3.9", path_str = "/opt/python/cp39-cp39" },
{ identifier = "cp310-manylinux_s390x", version = "3.10", path_str = "/opt/python/cp310-cp310" }, { identifier = "cp310-manylinux_s390x", version = "3.10", path_str = "/opt/python/cp310-cp310" },
{ identifier = "cp311-manylinux_s390x", version = "3.11", path_str = "/opt/python/cp311-cp311" }, { identifier = "cp311-manylinux_s390x", version = "3.11", path_str = "/opt/python/cp311-cp311" },
{ identifier = "cp312-manylinux_s390x", version = "3.12", path_str = "/opt/python/cp312-cp312" }, { identifier = "cp312-manylinux_s390x", version = "3.12", path_str = "/opt/python/cp312-cp312" },
{ identifier = "cp313-manylinux_s390x", version = "3.13", path_str = "/opt/python/cp313-cp313" }, { identifier = "cp313-manylinux_s390x", version = "3.13", path_str = "/opt/python/cp313-cp313" },
{ identifier = "cp313t-manylinux_s390x", version = "3.13", path_str = "/opt/python/cp313-cp313t" },
{ identifier = "cp314-manylinux_s390x", version = "3.14", path_str = "/opt/python/cp314-cp314" }, { identifier = "cp314-manylinux_s390x", version = "3.14", path_str = "/opt/python/cp314-cp314" },
{ identifier = "cp314t-manylinux_s390x", version = "3.14", path_str = "/opt/python/cp314-cp314t" }, { identifier = "cp314t-manylinux_s390x", version = "3.14", path_str = "/opt/python/cp314-cp314t" },
{ identifier = "cp315-manylinux_s390x", version = "3.15", path_str = "/opt/python/cp315-cp315" }, { identifier = "cp38-manylinux_armv7l", version = "3.8", path_str = "/opt/python/cp38-cp38" },
{ identifier = "cp315t-manylinux_s390x", version = "3.15", path_str = "/opt/python/cp315-cp315t" },
{ identifier = "cp39-manylinux_armv7l", version = "3.9", path_str = "/opt/python/cp39-cp39" }, { identifier = "cp39-manylinux_armv7l", version = "3.9", path_str = "/opt/python/cp39-cp39" },
{ identifier = "cp310-manylinux_armv7l", version = "3.10", path_str = "/opt/python/cp310-cp310" }, { identifier = "cp310-manylinux_armv7l", version = "3.10", path_str = "/opt/python/cp310-cp310" },
{ identifier = "cp311-manylinux_armv7l", version = "3.11", path_str = "/opt/python/cp311-cp311" }, { identifier = "cp311-manylinux_armv7l", version = "3.11", path_str = "/opt/python/cp311-cp311" },
{ identifier = "cp312-manylinux_armv7l", version = "3.12", path_str = "/opt/python/cp312-cp312" }, { identifier = "cp312-manylinux_armv7l", version = "3.12", path_str = "/opt/python/cp312-cp312" },
{ identifier = "cp313-manylinux_armv7l", version = "3.13", path_str = "/opt/python/cp313-cp313" }, { identifier = "cp313-manylinux_armv7l", version = "3.13", path_str = "/opt/python/cp313-cp313" },
{ identifier = "cp313t-manylinux_armv7l", version = "3.13", path_str = "/opt/python/cp313-cp313t" },
{ identifier = "cp314-manylinux_armv7l", version = "3.14", path_str = "/opt/python/cp314-cp314" }, { identifier = "cp314-manylinux_armv7l", version = "3.14", path_str = "/opt/python/cp314-cp314" },
{ identifier = "cp314t-manylinux_armv7l", version = "3.14", path_str = "/opt/python/cp314-cp314t" }, { identifier = "cp314t-manylinux_armv7l", version = "3.14", path_str = "/opt/python/cp314-cp314t" },
{ identifier = "cp315-manylinux_armv7l", version = "3.15", path_str = "/opt/python/cp315-cp315" }, { identifier = "cp38-manylinux_riscv64", version = "3.8", path_str = "/opt/python/cp38-cp38" },
{ identifier = "cp315t-manylinux_armv7l", version = "3.15", path_str = "/opt/python/cp315-cp315t" },
{ identifier = "cp39-manylinux_riscv64", version = "3.9", path_str = "/opt/python/cp39-cp39" }, { identifier = "cp39-manylinux_riscv64", version = "3.9", path_str = "/opt/python/cp39-cp39" },
{ identifier = "cp310-manylinux_riscv64", version = "3.10", path_str = "/opt/python/cp310-cp310" }, { identifier = "cp310-manylinux_riscv64", version = "3.10", path_str = "/opt/python/cp310-cp310" },
{ identifier = "cp311-manylinux_riscv64", version = "3.11", path_str = "/opt/python/cp311-cp311" }, { identifier = "cp311-manylinux_riscv64", version = "3.11", path_str = "/opt/python/cp311-cp311" },
{ identifier = "cp312-manylinux_riscv64", version = "3.12", path_str = "/opt/python/cp312-cp312" }, { identifier = "cp312-manylinux_riscv64", version = "3.12", path_str = "/opt/python/cp312-cp312" },
{ identifier = "cp313-manylinux_riscv64", version = "3.13", path_str = "/opt/python/cp313-cp313" }, { identifier = "cp313-manylinux_riscv64", version = "3.13", path_str = "/opt/python/cp313-cp313" },
{ identifier = "cp313t-manylinux_riscv64", version = "3.13", path_str = "/opt/python/cp313-cp313t" },
{ identifier = "cp314-manylinux_riscv64", version = "3.14", path_str = "/opt/python/cp314-cp314" }, { identifier = "cp314-manylinux_riscv64", version = "3.14", path_str = "/opt/python/cp314-cp314" },
{ identifier = "cp314t-manylinux_riscv64", version = "3.14", path_str = "/opt/python/cp314-cp314t" }, { identifier = "cp314t-manylinux_riscv64", version = "3.14", path_str = "/opt/python/cp314-cp314t" },
{ identifier = "cp315-manylinux_riscv64", version = "3.15", path_str = "/opt/python/cp315-cp315" }, { identifier = "pp38-manylinux_aarch64", version = "3.8", path_str = "/opt/python/pp38-pypy38_pp73" },
{ identifier = "cp315t-manylinux_riscv64", version = "3.15", path_str = "/opt/python/cp315-cp315t" },
{ identifier = "pp39-manylinux_aarch64", version = "3.9", path_str = "/opt/python/pp39-pypy39_pp73" }, { identifier = "pp39-manylinux_aarch64", version = "3.9", path_str = "/opt/python/pp39-pypy39_pp73" },
{ identifier = "pp310-manylinux_aarch64", version = "3.10", path_str = "/opt/python/pp310-pypy310_pp73" }, { identifier = "pp310-manylinux_aarch64", version = "3.10", path_str = "/opt/python/pp310-pypy310_pp73" },
{ identifier = "pp311-manylinux_aarch64", version = "3.11", path_str = "/opt/python/pp311-pypy311_pp73" }, { identifier = "pp311-manylinux_aarch64", version = "3.11", path_str = "/opt/python/pp311-pypy311_pp73" },
{ identifier = "gp311_242-manylinux_aarch64", version = "3.11", path_str = "/opt/python/graalpy311-graalpy242_311_native" }, { identifier = "gp311_242-manylinux_aarch64", version = "3.11", path_str = "/opt/python/graalpy311-graalpy242_311_native" },
{ identifier = "gp312_250-manylinux_aarch64", version = "3.12", path_str = "/opt/python/graalpy312-graalpy250_312_native" }, { identifier = "gp312_250-manylinux_aarch64", version = "3.12", path_str = "/opt/python/graalpy312-graalpy250_312_native" },
{ identifier = "pp38-manylinux_i686", version = "3.8", path_str = "/opt/python/pp38-pypy38_pp73" },
{ identifier = "pp39-manylinux_i686", version = "3.9", path_str = "/opt/python/pp39-pypy39_pp73" }, { identifier = "pp39-manylinux_i686", version = "3.9", path_str = "/opt/python/pp39-pypy39_pp73" },
{ identifier = "pp310-manylinux_i686", version = "3.10", path_str = "/opt/python/pp310-pypy310_pp73" }, { identifier = "pp310-manylinux_i686", version = "3.10", path_str = "/opt/python/pp310-pypy310_pp73" },
{ identifier = "pp311-manylinux_i686", version = "3.11", path_str = "/opt/python/pp311-pypy311_pp73" }, { identifier = "pp311-manylinux_i686", version = "3.11", path_str = "/opt/python/pp311-pypy311_pp73" },
{ identifier = "cp38-musllinux_x86_64", version = "3.8", path_str = "/opt/python/cp38-cp38" },
{ identifier = "cp39-musllinux_x86_64", version = "3.9", path_str = "/opt/python/cp39-cp39" }, { identifier = "cp39-musllinux_x86_64", version = "3.9", path_str = "/opt/python/cp39-cp39" },
{ identifier = "cp310-musllinux_x86_64", version = "3.10", path_str = "/opt/python/cp310-cp310" }, { identifier = "cp310-musllinux_x86_64", version = "3.10", path_str = "/opt/python/cp310-cp310" },
{ identifier = "cp311-musllinux_x86_64", version = "3.11", path_str = "/opt/python/cp311-cp311" }, { identifier = "cp311-musllinux_x86_64", version = "3.11", path_str = "/opt/python/cp311-cp311" },
{ identifier = "cp312-musllinux_x86_64", version = "3.12", path_str = "/opt/python/cp312-cp312" }, { identifier = "cp312-musllinux_x86_64", version = "3.12", path_str = "/opt/python/cp312-cp312" },
{ identifier = "cp313-musllinux_x86_64", version = "3.13", path_str = "/opt/python/cp313-cp313" }, { identifier = "cp313-musllinux_x86_64", version = "3.13", path_str = "/opt/python/cp313-cp313" },
{ identifier = "cp313t-musllinux_x86_64", version = "3.13", path_str = "/opt/python/cp313-cp313t" },
{ identifier = "cp314-musllinux_x86_64", version = "3.14", path_str = "/opt/python/cp314-cp314" }, { identifier = "cp314-musllinux_x86_64", version = "3.14", path_str = "/opt/python/cp314-cp314" },
{ identifier = "cp314t-musllinux_x86_64", version = "3.14", path_str = "/opt/python/cp314-cp314t" }, { identifier = "cp314t-musllinux_x86_64", version = "3.14", path_str = "/opt/python/cp314-cp314t" },
{ identifier = "cp315-musllinux_x86_64", version = "3.15", path_str = "/opt/python/cp315-cp315" }, { identifier = "cp38-musllinux_i686", version = "3.8", path_str = "/opt/python/cp38-cp38" },
{ identifier = "cp315t-musllinux_x86_64", version = "3.15", path_str = "/opt/python/cp315-cp315t" },
{ identifier = "cp39-musllinux_i686", version = "3.9", path_str = "/opt/python/cp39-cp39" }, { identifier = "cp39-musllinux_i686", version = "3.9", path_str = "/opt/python/cp39-cp39" },
{ identifier = "cp310-musllinux_i686", version = "3.10", path_str = "/opt/python/cp310-cp310" }, { identifier = "cp310-musllinux_i686", version = "3.10", path_str = "/opt/python/cp310-cp310" },
{ identifier = "cp311-musllinux_i686", version = "3.11", path_str = "/opt/python/cp311-cp311" }, { identifier = "cp311-musllinux_i686", version = "3.11", path_str = "/opt/python/cp311-cp311" },
{ identifier = "cp312-musllinux_i686", version = "3.12", path_str = "/opt/python/cp312-cp312" }, { identifier = "cp312-musllinux_i686", version = "3.12", path_str = "/opt/python/cp312-cp312" },
{ identifier = "cp313-musllinux_i686", version = "3.13", path_str = "/opt/python/cp313-cp313" }, { identifier = "cp313-musllinux_i686", version = "3.13", path_str = "/opt/python/cp313-cp313" },
{ identifier = "cp313t-musllinux_i686", version = "3.13", path_str = "/opt/python/cp313-cp313t" },
{ identifier = "cp314-musllinux_i686", version = "3.14", path_str = "/opt/python/cp314-cp314" }, { identifier = "cp314-musllinux_i686", version = "3.14", path_str = "/opt/python/cp314-cp314" },
{ identifier = "cp314t-musllinux_i686", version = "3.14", path_str = "/opt/python/cp314-cp314t" }, { identifier = "cp314t-musllinux_i686", version = "3.14", path_str = "/opt/python/cp314-cp314t" },
{ identifier = "cp315-musllinux_i686", version = "3.15", path_str = "/opt/python/cp315-cp315" }, { identifier = "cp38-musllinux_aarch64", version = "3.8", path_str = "/opt/python/cp38-cp38" },
{ identifier = "cp315t-musllinux_i686", version = "3.15", path_str = "/opt/python/cp315-cp315t" },
{ identifier = "cp39-musllinux_aarch64", version = "3.9", path_str = "/opt/python/cp39-cp39" }, { identifier = "cp39-musllinux_aarch64", version = "3.9", path_str = "/opt/python/cp39-cp39" },
{ identifier = "cp310-musllinux_aarch64", version = "3.10", path_str = "/opt/python/cp310-cp310" }, { identifier = "cp310-musllinux_aarch64", version = "3.10", path_str = "/opt/python/cp310-cp310" },
{ identifier = "cp311-musllinux_aarch64", version = "3.11", path_str = "/opt/python/cp311-cp311" }, { identifier = "cp311-musllinux_aarch64", version = "3.11", path_str = "/opt/python/cp311-cp311" },
{ identifier = "cp312-musllinux_aarch64", version = "3.12", path_str = "/opt/python/cp312-cp312" }, { identifier = "cp312-musllinux_aarch64", version = "3.12", path_str = "/opt/python/cp312-cp312" },
{ identifier = "cp313-musllinux_aarch64", version = "3.13", path_str = "/opt/python/cp313-cp313" }, { identifier = "cp313-musllinux_aarch64", version = "3.13", path_str = "/opt/python/cp313-cp313" },
{ identifier = "cp313t-musllinux_aarch64", version = "3.13", path_str = "/opt/python/cp313-cp313t" },
{ identifier = "cp314-musllinux_aarch64", version = "3.14", path_str = "/opt/python/cp314-cp314" }, { identifier = "cp314-musllinux_aarch64", version = "3.14", path_str = "/opt/python/cp314-cp314" },
{ identifier = "cp314t-musllinux_aarch64", version = "3.14", path_str = "/opt/python/cp314-cp314t" }, { identifier = "cp314t-musllinux_aarch64", version = "3.14", path_str = "/opt/python/cp314-cp314t" },
{ identifier = "cp315-musllinux_aarch64", version = "3.15", path_str = "/opt/python/cp315-cp315" }, { identifier = "cp38-musllinux_ppc64le", version = "3.8", path_str = "/opt/python/cp38-cp38" },
{ identifier = "cp315t-musllinux_aarch64", version = "3.15", path_str = "/opt/python/cp315-cp315t" },
{ identifier = "cp39-musllinux_ppc64le", version = "3.9", path_str = "/opt/python/cp39-cp39" }, { identifier = "cp39-musllinux_ppc64le", version = "3.9", path_str = "/opt/python/cp39-cp39" },
{ identifier = "cp310-musllinux_ppc64le", version = "3.10", path_str = "/opt/python/cp310-cp310" }, { identifier = "cp310-musllinux_ppc64le", version = "3.10", path_str = "/opt/python/cp310-cp310" },
{ identifier = "cp311-musllinux_ppc64le", version = "3.11", path_str = "/opt/python/cp311-cp311" }, { identifier = "cp311-musllinux_ppc64le", version = "3.11", path_str = "/opt/python/cp311-cp311" },
{ identifier = "cp312-musllinux_ppc64le", version = "3.12", path_str = "/opt/python/cp312-cp312" }, { identifier = "cp312-musllinux_ppc64le", version = "3.12", path_str = "/opt/python/cp312-cp312" },
{ identifier = "cp313-musllinux_ppc64le", version = "3.13", path_str = "/opt/python/cp313-cp313" }, { identifier = "cp313-musllinux_ppc64le", version = "3.13", path_str = "/opt/python/cp313-cp313" },
{ identifier = "cp313t-musllinux_ppc64le", version = "3.13", path_str = "/opt/python/cp313-cp313t" },
{ identifier = "cp314-musllinux_ppc64le", version = "3.14", path_str = "/opt/python/cp314-cp314" }, { identifier = "cp314-musllinux_ppc64le", version = "3.14", path_str = "/opt/python/cp314-cp314" },
{ identifier = "cp314t-musllinux_ppc64le", version = "3.14", path_str = "/opt/python/cp314-cp314t" }, { identifier = "cp314t-musllinux_ppc64le", version = "3.14", path_str = "/opt/python/cp314-cp314t" },
{ identifier = "cp315-musllinux_ppc64le", version = "3.15", path_str = "/opt/python/cp315-cp315" }, { identifier = "cp38-musllinux_s390x", version = "3.8", path_str = "/opt/python/cp38-cp38" },
{ identifier = "cp315t-musllinux_ppc64le", version = "3.15", path_str = "/opt/python/cp315-cp315t" },
{ identifier = "cp39-musllinux_s390x", version = "3.9", path_str = "/opt/python/cp39-cp39" }, { identifier = "cp39-musllinux_s390x", version = "3.9", path_str = "/opt/python/cp39-cp39" },
{ identifier = "cp310-musllinux_s390x", version = "3.10", path_str = "/opt/python/cp310-cp310" }, { identifier = "cp310-musllinux_s390x", version = "3.10", path_str = "/opt/python/cp310-cp310" },
{ identifier = "cp311-musllinux_s390x", version = "3.11", path_str = "/opt/python/cp311-cp311" }, { identifier = "cp311-musllinux_s390x", version = "3.11", path_str = "/opt/python/cp311-cp311" },
{ identifier = "cp312-musllinux_s390x", version = "3.12", path_str = "/opt/python/cp312-cp312" }, { identifier = "cp312-musllinux_s390x", version = "3.12", path_str = "/opt/python/cp312-cp312" },
{ identifier = "cp313-musllinux_s390x", version = "3.13", path_str = "/opt/python/cp313-cp313" }, { identifier = "cp313-musllinux_s390x", version = "3.13", path_str = "/opt/python/cp313-cp313" },
{ identifier = "cp313t-musllinux_s390x", version = "3.13", path_str = "/opt/python/cp313-cp313t" },
{ identifier = "cp314-musllinux_s390x", version = "3.14", path_str = "/opt/python/cp314-cp314" }, { identifier = "cp314-musllinux_s390x", version = "3.14", path_str = "/opt/python/cp314-cp314" },
{ identifier = "cp314t-musllinux_s390x", version = "3.14", path_str = "/opt/python/cp314-cp314t" }, { identifier = "cp314t-musllinux_s390x", version = "3.14", path_str = "/opt/python/cp314-cp314t" },
{ identifier = "cp315-musllinux_s390x", version = "3.15", path_str = "/opt/python/cp315-cp315" }, { identifier = "cp38-musllinux_armv7l", version = "3.8", path_str = "/opt/python/cp38-cp38" },
{ identifier = "cp315t-musllinux_s390x", version = "3.15", path_str = "/opt/python/cp315-cp315t" },
{ identifier = "cp39-musllinux_armv7l", version = "3.9", path_str = "/opt/python/cp39-cp39" }, { identifier = "cp39-musllinux_armv7l", version = "3.9", path_str = "/opt/python/cp39-cp39" },
{ identifier = "cp310-musllinux_armv7l", version = "3.10", path_str = "/opt/python/cp310-cp310" }, { identifier = "cp310-musllinux_armv7l", version = "3.10", path_str = "/opt/python/cp310-cp310" },
{ identifier = "cp311-musllinux_armv7l", version = "3.11", path_str = "/opt/python/cp311-cp311" }, { identifier = "cp311-musllinux_armv7l", version = "3.11", path_str = "/opt/python/cp311-cp311" },
{ identifier = "cp312-musllinux_armv7l", version = "3.12", path_str = "/opt/python/cp312-cp312" }, { identifier = "cp312-musllinux_armv7l", version = "3.12", path_str = "/opt/python/cp312-cp312" },
{ identifier = "cp313-musllinux_armv7l", version = "3.13", path_str = "/opt/python/cp313-cp313" }, { identifier = "cp313-musllinux_armv7l", version = "3.13", path_str = "/opt/python/cp313-cp313" },
{ identifier = "cp313t-musllinux_armv7l", version = "3.13", path_str = "/opt/python/cp313-cp313t" },
{ identifier = "cp314-musllinux_armv7l", version = "3.14", path_str = "/opt/python/cp314-cp314" }, { identifier = "cp314-musllinux_armv7l", version = "3.14", path_str = "/opt/python/cp314-cp314" },
{ identifier = "cp314t-musllinux_armv7l", version = "3.14", path_str = "/opt/python/cp314-cp314t" }, { identifier = "cp314t-musllinux_armv7l", version = "3.14", path_str = "/opt/python/cp314-cp314t" },
{ identifier = "cp315-musllinux_armv7l", version = "3.15", path_str = "/opt/python/cp315-cp315" }, { identifier = "cp38-musllinux_riscv64", version = "3.8", path_str = "/opt/python/cp38-cp38" },
{ identifier = "cp315t-musllinux_armv7l", version = "3.15", path_str = "/opt/python/cp315-cp315t" },
{ identifier = "cp39-musllinux_riscv64", version = "3.9", path_str = "/opt/python/cp39-cp39" }, { identifier = "cp39-musllinux_riscv64", version = "3.9", path_str = "/opt/python/cp39-cp39" },
{ identifier = "cp310-musllinux_riscv64", version = "3.10", path_str = "/opt/python/cp310-cp310" }, { identifier = "cp310-musllinux_riscv64", version = "3.10", path_str = "/opt/python/cp310-cp310" },
{ identifier = "cp311-musllinux_riscv64", version = "3.11", path_str = "/opt/python/cp311-cp311" }, { identifier = "cp311-musllinux_riscv64", version = "3.11", path_str = "/opt/python/cp311-cp311" },
{ identifier = "cp312-musllinux_riscv64", version = "3.12", path_str = "/opt/python/cp312-cp312" }, { identifier = "cp312-musllinux_riscv64", version = "3.12", path_str = "/opt/python/cp312-cp312" },
{ identifier = "cp313-musllinux_riscv64", version = "3.13", path_str = "/opt/python/cp313-cp313" }, { identifier = "cp313-musllinux_riscv64", version = "3.13", path_str = "/opt/python/cp313-cp313" },
{ identifier = "cp313t-musllinux_riscv64", version = "3.13", path_str = "/opt/python/cp313-cp313t" },
{ identifier = "cp314-musllinux_riscv64", version = "3.14", path_str = "/opt/python/cp314-cp314" }, { identifier = "cp314-musllinux_riscv64", version = "3.14", path_str = "/opt/python/cp314-cp314" },
{ identifier = "cp314t-musllinux_riscv64", version = "3.14", path_str = "/opt/python/cp314-cp314t" }, { identifier = "cp314t-musllinux_riscv64", version = "3.14", path_str = "/opt/python/cp314-cp314t" },
{ identifier = "cp315-musllinux_riscv64", version = "3.15", path_str = "/opt/python/cp315-cp315" },
{ identifier = "cp315t-musllinux_riscv64", version = "3.15", path_str = "/opt/python/cp315-cp315t" },
] ]
[macos] [macos]
python_configurations = [ 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", sha256 = "351fe18f4fb03be7afac5e4012fc0a51345f43202af43ef620cf1eee5ee36578" }, { identifier = "cp38-macosx_x86_64", version = "3.8", url = "https://www.python.org/ftp/python/3.8.10/python-3.8.10-macosx10.9.pkg" },
{ 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 = "cp38-macosx_arm64", version = "3.8", url = "https://www.python.org/ftp/python/3.8.10/python-3.8.10-macosx10.9.pkg" },
{ 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 = "cp38-macosx_universal2", version = "3.8", url = "https://www.python.org/ftp/python/3.8.10/python-3.8.10-macosx10.9.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", sha256 = "767ed35ad688d28ea4494081ae96408a0318d0d5bb9ca0139d74d6247b231cfc" }, { 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 = "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 = "cp39-macosx_arm64", version = "3.9", url = "https://www.python.org/ftp/python/3.9.13/python-3.9.13-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", sha256 = "767ed35ad688d28ea4494081ae96408a0318d0d5bb9ca0139d74d6247b231cfc" }, { identifier = "cp39-macosx_universal2", version = "3.9", url = "https://www.python.org/ftp/python/3.9.13/python-3.9.13-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", sha256 = "b6cfdee2571ca56ee895043ca1e7110fb78a878cee3eb0c21accb2de34d24b55" }, { 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 = "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 = "cp310-macosx_arm64", version = "3.10", url = "https://www.python.org/ftp/python/3.10.11/python-3.10.11-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", sha256 = "b6cfdee2571ca56ee895043ca1e7110fb78a878cee3eb0c21accb2de34d24b55" }, { identifier = "cp310-macosx_universal2", version = "3.10", url = "https://www.python.org/ftp/python/3.10.11/python-3.10.11-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", sha256 = "8373e58da4ea146b3eb1c1f9834f19a319440b6b679b06050b1f9ee3237aa8e4" }, { 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 = "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 = "cp311-macosx_arm64", version = "3.11", url = "https://www.python.org/ftp/python/3.11.9/python-3.11.9-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", sha256 = "8373e58da4ea146b3eb1c1f9834f19a319440b6b679b06050b1f9ee3237aa8e4" }, { identifier = "cp311-macosx_universal2", version = "3.11", url = "https://www.python.org/ftp/python/3.11.9/python-3.11.9-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", sha256 = "a909cb655af5db67d5a90b3603437a1d58bec3446d624e4034e278ac62023cc9" }, { 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 = "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 = "cp312-macosx_arm64", version = "3.12", url = "https://www.python.org/ftp/python/3.12.10/python-3.12.10-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", sha256 = "a909cb655af5db67d5a90b3603437a1d58bec3446d624e4034e278ac62023cc9" }, { identifier = "cp312-macosx_universal2", version = "3.12", url = "https://www.python.org/ftp/python/3.12.10/python-3.12.10-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", sha256 = "b28a8dc33c456dd06c97024697d63ca916cfb494594c06fa3e4ef4d41fa82335" }, { identifier = "cp313-macosx_x86_64", version = "3.13", url = "https://www.python.org/ftp/python/3.13.12/python-3.13.12-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", sha256 = "b28a8dc33c456dd06c97024697d63ca916cfb494594c06fa3e4ef4d41fa82335" }, { identifier = "cp313-macosx_arm64", version = "3.13", url = "https://www.python.org/ftp/python/3.13.12/python-3.13.12-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", sha256 = "b28a8dc33c456dd06c97024697d63ca916cfb494594c06fa3e4ef4d41fa82335" }, { identifier = "cp313-macosx_universal2", version = "3.13", url = "https://www.python.org/ftp/python/3.13.12/python-3.13.12-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", sha256 = "b28a8dc33c456dd06c97024697d63ca916cfb494594c06fa3e4ef4d41fa82335" }, { identifier = "cp313t-macosx_x86_64", version = "3.13", url = "https://www.python.org/ftp/python/3.13.12/python-3.13.12-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", sha256 = "b28a8dc33c456dd06c97024697d63ca916cfb494594c06fa3e4ef4d41fa82335" }, { identifier = "cp313t-macosx_arm64", version = "3.13", url = "https://www.python.org/ftp/python/3.13.12/python-3.13.12-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", sha256 = "b28a8dc33c456dd06c97024697d63ca916cfb494594c06fa3e4ef4d41fa82335" }, { identifier = "cp313t-macosx_universal2", version = "3.13", url = "https://www.python.org/ftp/python/3.13.12/python-3.13.12-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", sha256 = "d9e6de70d8bb02c9300b9d8e8b56aadb8dd426073d97e6225ac5319b3eb6d84e" }, { identifier = "cp314-macosx_x86_64", version = "3.14", url = "https://www.python.org/ftp/python/3.14.3/python-3.14.3-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", sha256 = "d9e6de70d8bb02c9300b9d8e8b56aadb8dd426073d97e6225ac5319b3eb6d84e" }, { identifier = "cp314-macosx_arm64", version = "3.14", url = "https://www.python.org/ftp/python/3.14.3/python-3.14.3-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", sha256 = "d9e6de70d8bb02c9300b9d8e8b56aadb8dd426073d97e6225ac5319b3eb6d84e" }, { identifier = "cp314-macosx_universal2", version = "3.14", url = "https://www.python.org/ftp/python/3.14.3/python-3.14.3-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", sha256 = "d9e6de70d8bb02c9300b9d8e8b56aadb8dd426073d97e6225ac5319b3eb6d84e" }, { identifier = "cp314t-macosx_x86_64", version = "3.14", url = "https://www.python.org/ftp/python/3.14.3/python-3.14.3-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", sha256 = "d9e6de70d8bb02c9300b9d8e8b56aadb8dd426073d97e6225ac5319b3eb6d84e" }, { identifier = "cp314t-macosx_arm64", version = "3.14", url = "https://www.python.org/ftp/python/3.14.3/python-3.14.3-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", sha256 = "d9e6de70d8bb02c9300b9d8e8b56aadb8dd426073d97e6225ac5319b3eb6d84e" }, { identifier = "cp314t-macosx_universal2", version = "3.14", url = "https://www.python.org/ftp/python/3.14.3/python-3.14.3-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", sha256 = "fda015431621e7e5aa16359d114f2c45a77ed936992c1efff86302e768a6b21c" }, { identifier = "pp38-macosx_x86_64", version = "3.8", url = "https://downloads.python.org/pypy/pypy3.8-v7.3.11-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", sha256 = "88f824e7a2d676440d09bc90fc959ae0fd3557d7e2f14bfbbe53d41d159a47fe" }, { identifier = "pp38-macosx_arm64", version = "3.8", url = "https://downloads.python.org/pypy/pypy3.8-v7.3.11-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", sha256 = "2c5e5c2a33ac882551d7f28b98d19d486b8995aa50824a73b4edcc6aeec35c63" }, { 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 = "pp310-macosx_arm64", version = "3.10", url = "https://downloads.python.org/pypy/pypy3.10-v7.3.19-macos_arm64.tar.bz2", sha256 = "3db8a03fc496164801646844d7f3b12baa0adb3ad9a0b7cb124521bc2e168a36" }, { identifier = "pp39-macosx_arm64", version = "3.9", url = "https://downloads.python.org/pypy/pypy3.9-v7.3.16-macos_arm64.tar.bz2" },
{ 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 = "pp310-macosx_x86_64", version = "3.10", url = "https://downloads.python.org/pypy/pypy3.10-v7.3.19-macos_x86_64.tar.bz2" },
{ 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 = "pp310-macosx_arm64", version = "3.10", url = "https://downloads.python.org/pypy/pypy3.10-v7.3.19-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", sha256 = "" }, { identifier = "pp311-macosx_x86_64", version = "3.11", url = "https://downloads.python.org/pypy/pypy3.11-v7.3.21-macos_x86_64.tar.bz2" },
{ 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 = "pp311-macosx_arm64", version = "3.11", url = "https://downloads.python.org/pypy/pypy3.11-v7.3.21-macos_arm64.tar.bz2" },
{ 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 = "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 = "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" }, { 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" },
] ]
[windows] [windows]
python_configurations = [ python_configurations = [
{ identifier = "cp38-win32", version = "3.8.10" },
{ identifier = "cp38-win_amd64", version = "3.8.10" },
{ identifier = "cp39-win32", version = "3.9.13" }, { identifier = "cp39-win32", version = "3.9.13" },
{ identifier = "cp39-win_amd64", version = "3.9.13" }, { identifier = "cp39-win_amd64", version = "3.9.13" },
{ identifier = "cp310-win32", version = "3.10.11" }, { identifier = "cp310-win32", version = "3.10.11" },
@@ -192,58 +199,50 @@ python_configurations = [
{ identifier = "cp311-win_amd64", version = "3.11.9" }, { identifier = "cp311-win_amd64", version = "3.11.9" },
{ identifier = "cp312-win32", version = "3.12.10" }, { identifier = "cp312-win32", version = "3.12.10" },
{ identifier = "cp312-win_amd64", version = "3.12.10" }, { identifier = "cp312-win_amd64", version = "3.12.10" },
{ identifier = "cp313-win32", version = "3.13.13" }, { identifier = "cp313-win32", version = "3.13.12" },
{ identifier = "cp313-win_amd64", version = "3.13.13" }, { identifier = "cp313t-win32", version = "3.13.12" },
{ identifier = "cp314-win32", version = "3.14.5" }, { identifier = "cp313-win_amd64", version = "3.13.12" },
{ identifier = "cp314t-win32", version = "3.14.5" }, { identifier = "cp313t-win_amd64", version = "3.13.12" },
{ identifier = "cp314-win_amd64", version = "3.14.5" }, { identifier = "cp314-win32", version = "3.14.3" },
{ identifier = "cp314t-win_amd64", version = "3.14.5" }, { identifier = "cp314t-win32", version = "3.14.3" },
{ identifier = "cp315-win32", version = "3.15.0-b1" }, { identifier = "cp314-win_amd64", version = "3.14.3" },
{ identifier = "cp315t-win32", version = "3.15.0-b1" }, { identifier = "cp314t-win_amd64", version = "3.14.3" },
{ identifier = "cp315-win_amd64", version = "3.15.0-b1" },
{ identifier = "cp315t-win_amd64", version = "3.15.0-b1" },
{ identifier = "cp39-win_arm64", version = "3.9.10" }, { identifier = "cp39-win_arm64", version = "3.9.10" },
{ identifier = "cp310-win_arm64", version = "3.10.11" }, { identifier = "cp310-win_arm64", version = "3.10.11" },
{ identifier = "cp311-win_arm64", version = "3.11.9" }, { identifier = "cp311-win_arm64", version = "3.11.9" },
{ identifier = "cp312-win_arm64", version = "3.12.10" }, { identifier = "cp312-win_arm64", version = "3.12.10" },
{ identifier = "cp313-win_arm64", version = "3.13.13" }, { identifier = "cp313-win_arm64", version = "3.13.12" },
{ identifier = "cp314-win_arm64", version = "3.14.5" }, { identifier = "cp313t-win_arm64", version = "3.13.12" },
{ identifier = "cp314t-win_arm64", version = "3.14.5" }, { identifier = "cp314-win_arm64", version = "3.14.3" },
{ identifier = "cp315-win_arm64", version = "3.15.0-b1" }, { identifier = "cp314t-win_arm64", version = "3.14.3" },
{ identifier = "cp315t-win_arm64", version = "3.15.0-b1" }, { identifier = "pp38-win_amd64", version = "3.8", url = "https://downloads.python.org/pypy/pypy3.8-v7.3.11-win64.zip" },
{ identifier = "pp39-win_amd64", version = "3.9", url = "https://downloads.python.org/pypy/pypy3.9-v7.3.16-win64.zip", sha256 = "06ec12a5e964dc0ad33e6f380185a4d295178dce6d6df512f508e7aee00a1323" }, { 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", sha256 = "c0d07bba6c8fb4e5804f4a8b3f8ef07cc3d89f6ad1db42a45ffb9be60bbb7cc2" }, { 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.23-win64.zip", sha256 = "948b8ea58dea5b9917210fe4afd242c788fbfaba1c3f1a25e696a404f703389a" }, { identifier = "pp311-win_amd64", version = "3.11", url = "https://downloads.python.org/pypy/pypy3.11-v7.3.21-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", sha256 = "9606134284d4d95b2f9d69c3087cd3e9e488f46355b419f5e66588a3281df6a3" }, { 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.3/graalpy-25.0.3-windows-amd64.zip", sha256 = "2ae5c42b93e08d9e106a017634a3442b272ccae6d08ace157244de0449e937d4" }, { 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" },
] ]
[pyodide] [pyodide]
python_configurations = [ python_configurations = [
{ identifier = "cp312-pyodide_wasm32", version = "3.12", default_pyodide_version = "0.27.7", node_version = "v22", sha256 = "0c2e7db42efa5d1dac38b50f8b3d659a1e3885d0a233494831b8206281307d52" }, { 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", sha256 = "a29fc4a076408a18fc29eb4b280f80c6dddc95c19514c874c29e590d0931c02a" }, { identifier = "cp313-pyodide_wasm32", version = "3.13", default_pyodide_version = "0.29.3", node_version = "v22" },
{ identifier = "cp314-pyodide_wasm32", version = "3.14", default_pyodide_version = "314.0.0a2", node_version = "v24", sha256 = "ac7bbcdf289ab3ae25621c436b1d013099ef6bec644ad0538dae5d65552c5c95" },
] ]
[android] [android]
python_configurations = [ python_configurations = [
{ identifier = "cp313-android_arm64_v8a", version = "3.13", url = "https://repo.maven.apache.org/maven2/com/chaquo/python/python/3.13.13/python-3.13.13-aarch64-linux-android.tar.gz", sha256 = "a21250c246b36eb704be096be51e40ab24cafa3ae1d7ca4c396e6bf780cf87cf" }, { identifier = "cp313-android_arm64_v8a", version = "3.13", url = "https://repo.maven.apache.org/maven2/com/chaquo/python/python/3.13.12/python-3.13.12-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", sha256 = "b021b76df2c8d64e41c76d18a2d91f27652356bd932c06f9076bac1b664c0e37" }, { identifier = "cp313-android_x86_64", version = "3.13", url = "https://repo.maven.apache.org/maven2/com/chaquo/python/python/3.13.12/python-3.13.12-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", sha256 = "f008321abf837fcaec569df143283ece0e764b18d8c75763200160553f906af1" }, { identifier = "cp314-android_arm64_v8a", version = "3.14", url = "https://www.python.org/ftp/python/3.14.3/python-3.14.3-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", sha256 = "36184e31464b2b959d92c81ded8d025834342cc080623bced282a2a9a57bd47c" }, { identifier = "cp314-android_x86_64", version = "3.14", url = "https://www.python.org/ftp/python/3.14.3/python-3.14.3-x86_64-linux-android.tar.gz" },
{ 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] [ios]
python_configurations = [ python_configurations = [
{ identifier = "cp313-ios_arm64_iphoneos", version = "3.13", url = "https://github.com/beeware/Python-Apple-support/releases/download/3.13-b13/Python-3.13-iOS-support.b13.tar.gz", sha256 = "d1f95f95137a4b91dc0cbe9b99ddfc0a78918dde883c7fbe7147074a5e715274" }, { identifier = "cp313-ios_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", 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" },
{ 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 = "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", sha256 = "8e4123b543bf17fdae2e2c6c033434487752438431014eb12e6d833aa35927a8" }, { 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", 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" },
{ 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 = "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 = "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" },
] ]
@@ -13,6 +13,7 @@
}, },
"enable": { "enable": {
"enum": [ "enum": [
"cpython-freethreading",
"cpython-prerelease", "cpython-prerelease",
"graalpy", "graalpy",
"pyodide-prerelease", "pyodide-prerelease",
@@ -26,36 +27,6 @@
"description": "cibuildwheel's settings.", "description": "cibuildwheel's settings.",
"type": "object", "type": "object",
"properties": { "properties": {
"audit-command": {
"description": "Execute a shell command to audit each wheel after it is repaired. Use {wheel} for each wheel path, or {abi3_wheel} to only audit abi3 wheels.",
"oneOf": [
{
"type": "string"
},
{
"type": "array",
"items": {
"type": "string"
}
}
],
"title": "CIBW_AUDIT_COMMAND"
},
"audit-requires": {
"description": "Install Python dependencies for the audit step.",
"oneOf": [
{
"type": "string"
},
{
"type": "array",
"items": {
"type": "string"
}
}
],
"title": "CIBW_AUDIT_REQUIRES"
},
"archs": { "archs": {
"description": "Change the architectures built on your machine by default.", "description": "Change the architectures built on your machine by default.",
"oneOf": [ "oneOf": [
@@ -664,12 +635,6 @@
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"properties": { "properties": {
"audit-command": {
"$ref": "#/$defs/inherit"
},
"audit-requires": {
"$ref": "#/$defs/inherit"
},
"before-all": { "before-all": {
"$ref": "#/$defs/inherit" "$ref": "#/$defs/inherit"
}, },
@@ -717,12 +682,6 @@
} }
} }
}, },
"audit-command": {
"$ref": "#/properties/audit-command"
},
"audit-requires": {
"$ref": "#/properties/audit-requires"
},
"before-all": { "before-all": {
"$ref": "#/properties/before-all" "$ref": "#/properties/before-all"
}, },
@@ -841,12 +800,6 @@
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"properties": { "properties": {
"audit-command": {
"$ref": "#/properties/audit-command"
},
"audit-requires": {
"$ref": "#/properties/audit-requires"
},
"archs": { "archs": {
"$ref": "#/properties/archs" "$ref": "#/properties/archs"
}, },
@@ -977,12 +930,6 @@
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"properties": { "properties": {
"audit-command": {
"$ref": "#/properties/audit-command"
},
"audit-requires": {
"$ref": "#/properties/audit-requires"
},
"archs": { "archs": {
"$ref": "#/properties/archs" "$ref": "#/properties/archs"
}, },
@@ -1046,12 +993,6 @@
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"properties": { "properties": {
"audit-command": {
"$ref": "#/properties/audit-command"
},
"audit-requires": {
"$ref": "#/properties/audit-requires"
},
"archs": { "archs": {
"$ref": "#/properties/archs" "$ref": "#/properties/archs"
}, },
@@ -1128,12 +1069,6 @@
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"properties": { "properties": {
"audit-command": {
"$ref": "#/properties/audit-command"
},
"audit-requires": {
"$ref": "#/properties/audit-requires"
},
"archs": { "archs": {
"$ref": "#/properties/archs" "$ref": "#/properties/archs"
}, },
@@ -1197,12 +1132,6 @@
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"properties": { "properties": {
"audit-command": {
"$ref": "#/properties/audit-command"
},
"audit-requires": {
"$ref": "#/properties/audit-requires"
},
"archs": { "archs": {
"$ref": "#/properties/archs" "$ref": "#/properties/archs"
}, },
@@ -1266,12 +1195,6 @@
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"properties": { "properties": {
"audit-command": {
"$ref": "#/properties/audit-command"
},
"audit-requires": {
"$ref": "#/properties/audit-requires"
},
"archs": { "archs": {
"$ref": "#/properties/archs" "$ref": "#/properties/archs"
}, },
@@ -2,15 +2,15 @@
# nox -s update_constraints # nox -s update_constraints
annotated-types==0.7.0 annotated-types==0.7.0
# via pydantic # via pydantic
auditwheel-emscripten==0.2.4 auditwheel-emscripten==0.2.3
# via pyodide-build # via pyodide-build
build==1.5.0 build==1.2.2.post1
# via # via
# -r .nox/update_constraints/tmp/constraints-pyodide.in # -r .nox/update_constraints/tmp/constraints-pyodide.in
# pyodide-build # pyodide-build
certifi==2026.5.20 certifi==2026.2.25
# via requests # via requests
charset-normalizer==3.4.7 charset-normalizer==3.4.5
# via requests # via requests
click==8.1.8 click==8.1.8
# via # via
@@ -19,54 +19,54 @@ click==8.1.8
# pyodide-cli # pyodide-cli
distlib==0.4.0 distlib==0.4.0
# via virtualenv # via virtualenv
filelock==3.29.0 filelock==3.25.2
# via # via
# python-discovery # python-discovery
# virtualenv # virtualenv
idna==3.16 idna==3.11
# via requests # via requests
leb128==1.0.9 leb128==1.0.9
# via auditwheel-emscripten # via auditwheel-emscripten
markdown-it-py==4.2.0 markdown-it-py==4.0.0
# via rich # via rich
mdurl==0.1.2 mdurl==0.1.2
# via markdown-it-py # via markdown-it-py
packaging==26.2 packaging==26.0
# via # via
# auditwheel-emscripten # auditwheel-emscripten
# build # build
# pyodide-build # pyodide-build
# wheel # wheel
pip==26.1.1 pip==26.0.1
# via -r .nox/update_constraints/tmp/constraints-pyodide.in # via -r .nox/update_constraints/tmp/constraints-pyodide.in
platformdirs==4.9.6 platformdirs==4.9.4
# via # via
# pyodide-build # pyodide-build
# python-discovery # python-discovery
# virtualenv # virtualenv
pydantic==2.13.4 pydantic==2.12.5
# via # via
# pyodide-build # pyodide-build
# pyodide-lock # pyodide-lock
pydantic-core==2.46.4 pydantic-core==2.41.5
# via pydantic # via pydantic
pygments==2.20.0 pygments==2.19.2
# via rich # via rich
pyodide-build==0.34.4 pyodide-build==0.33.0
# via -r .nox/update_constraints/tmp/constraints-pyodide.in # via -r .nox/update_constraints/tmp/constraints-pyodide.in
pyodide-cli==0.5.0 pyodide-cli==0.5.0
# via # via
# auditwheel-emscripten # auditwheel-emscripten
# pyodide-build # pyodide-build
pyodide-lock==0.1.3 pyodide-lock==0.1.2
# via pyodide-build # via pyodide-build
pyproject-hooks==1.2.0 pyproject-hooks==1.2.0
# via build # via build
python-discovery==1.3.1 python-discovery==1.1.3
# via virtualenv # via virtualenv
requests==2.34.2 requests==2.32.5
# via pyodide-build # via pyodide-build
rich==15.0.0 rich==14.3.3
# via # via
# pyodide-build # pyodide-build
# pyodide-cli # pyodide-cli
@@ -79,13 +79,13 @@ typing-extensions==4.15.0
# typing-inspection # typing-inspection
typing-inspection==0.4.2 typing-inspection==0.4.2
# via pydantic # via pydantic
urllib3==2.7.0 urllib3==2.6.3
# via requests # via requests
virtualenv==21.3.3 virtualenv==21.2.0
# via # via
# build # build
# pyodide-build # pyodide-build
wheel==0.47.0 wheel==0.46.3
# via # via
# auditwheel-emscripten # auditwheel-emscripten
# pyodide-build # pyodide-build
@@ -2,15 +2,15 @@
# nox -s update_constraints # nox -s update_constraints
annotated-types==0.7.0 annotated-types==0.7.0
# via pydantic # via pydantic
auditwheel-emscripten==0.2.4 auditwheel-emscripten==0.2.3
# via pyodide-build # via pyodide-build
build==1.5.0 build==1.2.2.post1
# via # via
# -r .nox/update_constraints/tmp/constraints-pyodide.in # -r .nox/update_constraints/tmp/constraints-pyodide.in
# pyodide-build # pyodide-build
certifi==2026.5.20 certifi==2026.2.25
# via requests # via requests
charset-normalizer==3.4.7 charset-normalizer==3.4.5
# via requests # via requests
click==8.1.8 click==8.1.8
# via # via
@@ -19,54 +19,54 @@ click==8.1.8
# pyodide-cli # pyodide-cli
distlib==0.4.0 distlib==0.4.0
# via virtualenv # via virtualenv
filelock==3.29.0 filelock==3.25.2
# via # via
# python-discovery # python-discovery
# virtualenv # virtualenv
idna==3.16 idna==3.11
# via requests # via requests
leb128==1.0.9 leb128==1.0.9
# via auditwheel-emscripten # via auditwheel-emscripten
markdown-it-py==4.2.0 markdown-it-py==4.0.0
# via rich # via rich
mdurl==0.1.2 mdurl==0.1.2
# via markdown-it-py # via markdown-it-py
packaging==26.2 packaging==26.0
# via # via
# auditwheel-emscripten # auditwheel-emscripten
# build # build
# pyodide-build # pyodide-build
# wheel # wheel
pip==26.1.1 pip==26.0.1
# via -r .nox/update_constraints/tmp/constraints-pyodide.in # via -r .nox/update_constraints/tmp/constraints-pyodide.in
platformdirs==4.9.6 platformdirs==4.9.4
# via # via
# pyodide-build # pyodide-build
# python-discovery # python-discovery
# virtualenv # virtualenv
pydantic==2.13.4 pydantic==2.12.5
# via # via
# pyodide-build # pyodide-build
# pyodide-lock # pyodide-lock
pydantic-core==2.46.4 pydantic-core==2.41.5
# via pydantic # via pydantic
pygments==2.20.0 pygments==2.19.2
# via rich # via rich
pyodide-build==0.34.4 pyodide-build==0.33.0
# via -r .nox/update_constraints/tmp/constraints-pyodide.in # via -r .nox/update_constraints/tmp/constraints-pyodide.in
pyodide-cli==0.5.0 pyodide-cli==0.5.0
# via # via
# auditwheel-emscripten # auditwheel-emscripten
# pyodide-build # pyodide-build
pyodide-lock==0.1.3 pyodide-lock==0.1.2
# via pyodide-build # via pyodide-build
pyproject-hooks==1.2.0 pyproject-hooks==1.2.0
# via build # via build
python-discovery==1.3.1 python-discovery==1.1.3
# via virtualenv # via virtualenv
requests==2.34.2 requests==2.32.5
# via pyodide-build # via pyodide-build
rich==15.0.0 rich==14.3.3
# via # via
# pyodide-build # pyodide-build
# pyodide-cli # pyodide-cli
@@ -79,13 +79,13 @@ typing-extensions==4.15.0
# typing-inspection # typing-inspection
typing-inspection==0.4.2 typing-inspection==0.4.2
# via pydantic # via pydantic
urllib3==2.7.0 urllib3==2.6.3
# via requests # via requests
virtualenv==21.3.3 virtualenv==21.2.0
# via # via
# build # build
# pyodide-build # pyodide-build
wheel==0.47.0 wheel==0.46.3
# via # via
# auditwheel-emscripten # auditwheel-emscripten
# pyodide-build # pyodide-build
@@ -1,91 +0,0 @@
# This file was autogenerated by uv via the following command:
# nox -s update_constraints
annotated-types==0.7.0
# via pydantic
auditwheel-emscripten==0.2.4
# via pyodide-build
build==1.5.0
# via
# -r .nox/update_constraints/tmp/constraints-pyodide.in
# pyodide-build
certifi==2026.5.20
# via requests
charset-normalizer==3.4.7
# via requests
click==8.1.8
# via
# -r .nox/update_constraints/tmp/constraints-pyodide.in
# pyodide-build
# pyodide-cli
distlib==0.4.0
# via virtualenv
filelock==3.29.0
# via
# python-discovery
# virtualenv
idna==3.16
# via requests
leb128==1.0.9
# via auditwheel-emscripten
markdown-it-py==4.2.0
# via rich
mdurl==0.1.2
# via markdown-it-py
packaging==26.2
# via
# auditwheel-emscripten
# build
# pyodide-build
# wheel
pip==26.1.1
# via -r .nox/update_constraints/tmp/constraints-pyodide.in
platformdirs==4.9.6
# via
# pyodide-build
# python-discovery
# virtualenv
pydantic==2.13.4
# via
# pyodide-build
# pyodide-lock
pydantic-core==2.46.4
# via pydantic
pygments==2.20.0
# via rich
pyodide-build==0.34.4
# via -r .nox/update_constraints/tmp/constraints-pyodide.in
pyodide-cli==0.5.0
# via
# auditwheel-emscripten
# pyodide-build
pyodide-lock==0.1.3
# via pyodide-build
pyproject-hooks==1.2.0
# via build
python-discovery==1.3.1
# via virtualenv
requests==2.34.2
# via pyodide-build
rich==15.0.0
# via
# pyodide-build
# pyodide-cli
ruamel-yaml==0.19.1
# via pyodide-build
typing-extensions==4.15.0
# via
# pydantic
# pydantic-core
# typing-inspection
typing-inspection==0.4.2
# via pydantic
urllib3==2.7.0
# via requests
virtualenv==21.3.3
# via
# build
# pyodide-build
wheel==0.47.0
# via
# auditwheel-emscripten
# pyodide-build
@@ -1,96 +1,42 @@
# This file was autogenerated by uv via the following command: # This file was autogenerated by uv via the following command:
# nox -s update_constraints # nox -s update_constraints
abi3audit==0.0.26
# via -r cibuildwheel/resources/constraints.in
abi3info==2025.11.29
# via abi3audit
altgraph==0.17.5 altgraph==0.17.5
# via macholib # via macholib
attrs==26.1.0 build==1.4.0
# via
# cattrs
# requests-cache
build==1.5.0
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
cattrs==26.1.0
# via requests-cache
certifi==2026.5.20
# via requests
charset-normalizer==3.4.7
# via requests
delocate==0.13.0 delocate==0.13.0
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
delvewheel==1.12.1
# via -r cibuildwheel/resources/constraints.in
distlib==0.4.0 distlib==0.4.0
# via virtualenv # via virtualenv
exceptiongroup==1.3.1 filelock==3.25.2
# via cattrs
filelock==3.29.0
# via # via
# python-discovery # python-discovery
# virtualenv # virtualenv
idna==3.16 importlib-metadata==8.7.1
# via
# requests
# url-normalize
importlib-metadata==9.0.0
# via build # via build
kaitaistruct==0.11
# via abi3audit
macholib==1.16.4 macholib==1.16.4
# via delocate # via delocate
markdown-it-py==4.2.0 packaging==26.0
# via rich
mdurl==0.1.2
# via markdown-it-py
packaging==26.2
# via # via
# abi3audit
# build # build
# delocate # delocate
pefile==2024.8.26 pip==26.0.1
# via
# abi3audit
# delvewheel
pip==26.1.1
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
platformdirs==4.9.6 platformdirs==4.9.4
# via # via
# python-discovery # python-discovery
# requests-cache
# virtualenv # virtualenv
pyelftools==0.32
# via abi3audit
pygments==2.20.0
# via rich
pyproject-hooks==1.2.0 pyproject-hooks==1.2.0
# via build # via build
python-discovery==1.3.1 python-discovery==1.1.3
# via virtualenv # via virtualenv
requests==2.34.2 tomli==2.4.0
# via
# abi3audit
# requests-cache
requests-cache==1.3.2
# via abi3audit
rich==15.0.0
# via abi3audit
tomli==2.4.1
# via build # via build
typing-extensions==4.15.0 typing-extensions==4.15.0
# via # via
# cattrs
# delocate # delocate
# exceptiongroup
# virtualenv # virtualenv
url-normalize==3.0.0 virtualenv==21.2.0
# via requests-cache
urllib3==2.7.0
# via
# requests
# requests-cache
virtualenv==21.3.3
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
zipp==4.1.0 zipp==3.23.0
# via importlib-metadata # via importlib-metadata
@@ -1,87 +1,34 @@
# This file was autogenerated by uv via the following command: # This file was autogenerated by uv via the following command:
# nox -s update_constraints # nox -s update_constraints
abi3audit==0.0.26
# via -r cibuildwheel/resources/constraints.in
abi3info==2025.11.29
# via abi3audit
altgraph==0.17.5 altgraph==0.17.5
# via macholib # via macholib
attrs==26.1.0 build==1.4.0
# via
# cattrs
# requests-cache
build==1.5.0
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
cattrs==26.1.0
# via requests-cache
certifi==2026.5.20
# via requests
charset-normalizer==3.4.7
# via requests
delocate==0.13.0 delocate==0.13.0
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
delvewheel==1.12.1
# via -r cibuildwheel/resources/constraints.in
distlib==0.4.0 distlib==0.4.0
# via virtualenv # via virtualenv
filelock==3.29.0 filelock==3.25.2
# via # via
# python-discovery # python-discovery
# virtualenv # virtualenv
idna==3.16
# via
# requests
# url-normalize
kaitaistruct==0.11
# via abi3audit
macholib==1.16.4 macholib==1.16.4
# via delocate # via delocate
markdown-it-py==4.2.0 packaging==26.0
# via rich
mdurl==0.1.2
# via markdown-it-py
packaging==26.2
# via # via
# abi3audit
# build # build
# delocate # delocate
pefile==2024.8.26 pip==26.0.1
# via
# abi3audit
# delvewheel
pip==26.1.1; implementation_name != "graalpy" or platform_system != "Windows"
pip==26.0.1; implementation_name == "graalpy" and platform_system == "Windows"
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
platformdirs==4.9.6 platformdirs==4.9.4
# via # via
# python-discovery # python-discovery
# requests-cache
# virtualenv # virtualenv
pyelftools==0.32
# via abi3audit
pygments==2.20.0
# via rich
pyproject-hooks==1.2.0 pyproject-hooks==1.2.0
# via build # via build
python-discovery==1.3.1 python-discovery==1.1.3
# via virtualenv # via virtualenv
requests==2.34.2
# via
# abi3audit
# requests-cache
requests-cache==1.3.2
# via abi3audit
rich==15.0.0
# via abi3audit
typing-extensions==4.15.0 typing-extensions==4.15.0
# via # via delocate
# cattrs virtualenv==21.2.0
# delocate
url-normalize==3.0.0
# via requests-cache
urllib3==2.7.0
# via
# requests
# requests-cache
virtualenv==21.3.3
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
@@ -1,87 +1,34 @@
# This file was autogenerated by uv via the following command: # This file was autogenerated by uv via the following command:
# nox -s update_constraints # nox -s update_constraints
abi3audit==0.0.26
# via -r cibuildwheel/resources/constraints.in
abi3info==2025.11.29
# via abi3audit
altgraph==0.17.5 altgraph==0.17.5
# via macholib # via macholib
attrs==26.1.0 build==1.4.0
# via
# cattrs
# requests-cache
build==1.5.0
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
cattrs==26.1.0
# via requests-cache
certifi==2026.5.20
# via requests
charset-normalizer==3.4.7
# via requests
delocate==0.13.0 delocate==0.13.0
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
delvewheel==1.12.1
# via -r cibuildwheel/resources/constraints.in
distlib==0.4.0 distlib==0.4.0
# via virtualenv # via virtualenv
filelock==3.29.0 filelock==3.25.2
# via # via
# python-discovery # python-discovery
# virtualenv # virtualenv
idna==3.16
# via
# requests
# url-normalize
kaitaistruct==0.11
# via abi3audit
macholib==1.16.4 macholib==1.16.4
# via delocate # via delocate
markdown-it-py==4.2.0 packaging==26.0
# via rich
mdurl==0.1.2
# via markdown-it-py
packaging==26.2
# via # via
# abi3audit
# build # build
# delocate # delocate
pefile==2024.8.26 pip==26.0.1
# via
# abi3audit
# delvewheel
pip==26.1.1; implementation_name != "graalpy" or platform_system != "Windows"
pip==26.0.1; implementation_name == "graalpy" and platform_system == "Windows"
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
platformdirs==4.9.6 platformdirs==4.9.4
# via # via
# python-discovery # python-discovery
# requests-cache
# virtualenv # virtualenv
pyelftools==0.32
# via abi3audit
pygments==2.20.0
# via rich
pyproject-hooks==1.2.0 pyproject-hooks==1.2.0
# via build # via build
python-discovery==1.3.1 python-discovery==1.1.3
# via virtualenv # via virtualenv
requests==2.34.2
# via
# abi3audit
# requests-cache
requests-cache==1.3.2
# via abi3audit
rich==15.0.0
# via abi3audit
typing-extensions==4.15.0 typing-extensions==4.15.0
# via # via delocate
# cattrs virtualenv==21.2.0
# delocate
url-normalize==3.0.0
# via requests-cache
urllib3==2.7.0
# via
# requests
# requests-cache
virtualenv==21.3.3
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
@@ -1,86 +1,34 @@
# This file was autogenerated by uv via the following command: # This file was autogenerated by uv via the following command:
# nox -s update_constraints # nox -s update_constraints
abi3audit==0.0.26
# via -r cibuildwheel/resources/constraints.in
abi3info==2025.11.29
# via abi3audit
altgraph==0.17.5 altgraph==0.17.5
# via macholib # via macholib
attrs==26.1.0 build==1.4.0
# via
# cattrs
# requests-cache
build==1.5.0
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
cattrs==26.1.0
# via requests-cache
certifi==2026.5.20
# via requests
charset-normalizer==3.4.7
# via requests
delocate==0.13.0 delocate==0.13.0
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
delvewheel==1.12.1
# via -r cibuildwheel/resources/constraints.in
distlib==0.4.0 distlib==0.4.0
# via virtualenv # via virtualenv
filelock==3.29.0 filelock==3.25.2
# via # via
# python-discovery # python-discovery
# virtualenv # virtualenv
idna==3.16
# via
# requests
# url-normalize
kaitaistruct==0.11
# via abi3audit
macholib==1.16.4 macholib==1.16.4
# via delocate # via delocate
markdown-it-py==4.2.0 packaging==26.0
# via rich
mdurl==0.1.2
# via markdown-it-py
packaging==26.2
# via # via
# abi3audit
# build # build
# delocate # delocate
pefile==2024.8.26 pip==26.0.1
# via
# abi3audit
# delvewheel
pip==26.1.1
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
platformdirs==4.9.6 platformdirs==4.9.4
# via # via
# python-discovery # python-discovery
# requests-cache
# virtualenv # virtualenv
pyelftools==0.32
# via abi3audit
pygments==2.20.0
# via rich
pyproject-hooks==1.2.0 pyproject-hooks==1.2.0
# via build # via build
python-discovery==1.3.1 python-discovery==1.1.3
# via virtualenv # via virtualenv
requests==2.34.2
# via
# abi3audit
# requests-cache
requests-cache==1.3.2
# via abi3audit
rich==15.0.0
# via abi3audit
typing-extensions==4.15.0 typing-extensions==4.15.0
# via # via delocate
# cattrs virtualenv==21.2.0
# delocate
url-normalize==3.0.0
# via requests-cache
urllib3==2.7.0
# via
# requests
# requests-cache
virtualenv==21.3.3
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
@@ -1,86 +1,34 @@
# This file was autogenerated by uv via the following command: # This file was autogenerated by uv via the following command:
# nox -s update_constraints # nox -s update_constraints
abi3audit==0.0.26
# via -r cibuildwheel/resources/constraints.in
abi3info==2025.11.29
# via abi3audit
altgraph==0.17.5 altgraph==0.17.5
# via macholib # via macholib
attrs==26.1.0 build==1.4.0
# via
# cattrs
# requests-cache
build==1.5.0
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
cattrs==26.1.0
# via requests-cache
certifi==2026.5.20
# via requests
charset-normalizer==3.4.7
# via requests
delocate==0.13.0 delocate==0.13.0
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
delvewheel==1.12.1
# via -r cibuildwheel/resources/constraints.in
distlib==0.4.0 distlib==0.4.0
# via virtualenv # via virtualenv
filelock==3.29.0 filelock==3.25.2
# via # via
# python-discovery # python-discovery
# virtualenv # virtualenv
idna==3.16
# via
# requests
# url-normalize
kaitaistruct==0.11
# via abi3audit
macholib==1.16.4 macholib==1.16.4
# via delocate # via delocate
markdown-it-py==4.2.0 packaging==26.0
# via rich
mdurl==0.1.2
# via markdown-it-py
packaging==26.2
# via # via
# abi3audit
# build # build
# delocate # delocate
pefile==2024.8.26 pip==26.0.1
# via
# abi3audit
# delvewheel
pip==26.1.1
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
platformdirs==4.9.6 platformdirs==4.9.4
# via # via
# python-discovery # python-discovery
# requests-cache
# virtualenv # virtualenv
pyelftools==0.32
# via abi3audit
pygments==2.20.0
# via rich
pyproject-hooks==1.2.0 pyproject-hooks==1.2.0
# via build # via build
python-discovery==1.3.1 python-discovery==1.1.3
# via virtualenv # via virtualenv
requests==2.34.2
# via
# abi3audit
# requests-cache
requests-cache==1.3.2
# via abi3audit
rich==15.0.0
# via abi3audit
typing-extensions==4.15.0 typing-extensions==4.15.0
# via # via delocate
# cattrs virtualenv==21.2.0
# delocate
url-normalize==3.0.0
# via requests-cache
urllib3==2.7.0
# via
# requests
# requests-cache
virtualenv==21.3.3
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
@@ -1,86 +0,0 @@
# This file was autogenerated by uv via the following command:
# nox -s update_constraints
abi3audit==0.0.26
# via -r cibuildwheel/resources/constraints.in
abi3info==2025.11.29
# via abi3audit
altgraph==0.17.5
# via macholib
attrs==26.1.0
# via
# cattrs
# requests-cache
build==1.5.0
# via -r cibuildwheel/resources/constraints.in
cattrs==26.1.0
# via requests-cache
certifi==2026.5.20
# via requests
charset-normalizer==3.4.7
# via requests
delocate==0.13.0
# via -r cibuildwheel/resources/constraints.in
delvewheel==1.12.1
# via -r cibuildwheel/resources/constraints.in
distlib==0.4.0
# via virtualenv
filelock==3.29.0
# via
# python-discovery
# virtualenv
idna==3.16
# via
# requests
# url-normalize
kaitaistruct==0.11
# via abi3audit
macholib==1.16.4
# via delocate
markdown-it-py==4.2.0
# via rich
mdurl==0.1.2
# via markdown-it-py
packaging==26.2
# via
# abi3audit
# build
# delocate
pefile==2024.8.26
# via
# abi3audit
# delvewheel
pip==26.1.1
# via -r cibuildwheel/resources/constraints.in
platformdirs==4.9.6
# via
# python-discovery
# requests-cache
# virtualenv
pyelftools==0.32
# via abi3audit
pygments==2.20.0
# via rich
pyproject-hooks==1.2.0
# via build
python-discovery==1.3.1
# via virtualenv
requests==2.34.2
# via
# abi3audit
# requests-cache
requests-cache==1.3.2
# via abi3audit
rich==15.0.0
# via abi3audit
typing-extensions==4.15.0
# via
# cattrs
# delocate
url-normalize==3.0.0
# via requests-cache
urllib3==2.7.0
# via
# requests
# requests-cache
virtualenv==21.3.3
# via -r cibuildwheel/resources/constraints.in
@@ -0,0 +1,42 @@
# This file was autogenerated by uv via the following command:
# nox -s update_constraints
altgraph==0.17.5
# via macholib
build==1.2.2.post1
# via -r cibuildwheel/resources/constraints.in
delocate==0.12.0
# via -r cibuildwheel/resources/constraints.in
distlib==0.4.0
# via virtualenv
filelock==3.16.1
# via
# python-discovery
# virtualenv
importlib-metadata==8.5.0
# via build
macholib==1.16.4
# via delocate
packaging==26.0
# via
# build
# delocate
pip==25.0.1
# via -r cibuildwheel/resources/constraints.in
platformdirs==4.3.6
# via
# python-discovery
# virtualenv
pyproject-hooks==1.2.0
# via build
python-discovery==1.1.3
# via virtualenv
tomli==2.4.0
# via build
typing-extensions==4.13.2
# via
# delocate
# virtualenv
virtualenv==21.2.0
# via -r cibuildwheel/resources/constraints.in
zipp==3.20.2
# via importlib-metadata
@@ -1,96 +1,42 @@
# This file was autogenerated by uv via the following command: # This file was autogenerated by uv via the following command:
# nox -s update_constraints # nox -s update_constraints
abi3audit==0.0.25
# via -r cibuildwheel/resources/constraints.in
abi3info==2025.4.29
# via abi3audit
altgraph==0.17.5 altgraph==0.17.5
# via macholib # via macholib
attrs==26.1.0 build==1.4.0
# via
# cattrs
# requests-cache
build==1.4.4
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
cattrs==25.3.0
# via requests-cache
certifi==2026.5.20
# via requests
charset-normalizer==3.4.7
# via requests
delocate==0.13.0 delocate==0.13.0
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
delvewheel==1.12.1
# via -r cibuildwheel/resources/constraints.in
distlib==0.4.0 distlib==0.4.0
# via virtualenv # via virtualenv
exceptiongroup==1.3.1
# via cattrs
filelock==3.19.1 filelock==3.19.1
# via # via
# python-discovery # python-discovery
# virtualenv # virtualenv
idna==3.16
# via
# requests
# url-normalize
importlib-metadata==8.7.1 importlib-metadata==8.7.1
# via build # via build
kaitaistruct==0.11
# via abi3audit
macholib==1.16.4 macholib==1.16.4
# via delocate # via delocate
markdown-it-py==3.0.0 packaging==26.0
# via rich
mdurl==0.1.2
# via markdown-it-py
packaging==25.0
# via # via
# abi3audit
# build # build
# delocate # delocate
pefile==2024.8.26
# via
# abi3audit
# delvewheel
pip==26.0.1 pip==26.0.1
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
platformdirs==4.4.0 platformdirs==4.4.0
# via # via
# python-discovery # python-discovery
# requests-cache
# virtualenv # virtualenv
pyelftools==0.32
# via abi3audit
pygments==2.20.0
# via rich
pyproject-hooks==1.2.0 pyproject-hooks==1.2.0
# via build # via build
python-discovery==1.3.1 python-discovery==1.1.3
# via virtualenv # via virtualenv
requests==2.32.5 tomli==2.4.0
# via
# abi3audit
# requests-cache
requests-cache==1.2.1
# via abi3audit
rich==14.2.0
# via abi3audit
tomli==2.4.1
# via build # via build
typing-extensions==4.15.0 typing-extensions==4.15.0
# via # via
# cattrs
# delocate # delocate
# exceptiongroup
# virtualenv # virtualenv
url-normalize==2.2.1 virtualenv==21.2.0
# via requests-cache
urllib3==2.6.3
# via
# requests
# requests-cache
virtualenv==21.3.3
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
zipp==3.23.1 zipp==3.23.0
# via importlib-metadata # via importlib-metadata
-2
View File
@@ -1,6 +1,4 @@
pip pip
build build
delocate delocate
delvewheel
virtualenv virtualenv
abi3audit
+8 -60
View File
@@ -1,86 +1,34 @@
# This file was autogenerated by uv via the following command: # This file was autogenerated by uv via the following command:
# nox -s update_constraints # nox -s update_constraints
abi3audit==0.0.26
# via -r cibuildwheel/resources/constraints.in
abi3info==2025.11.29
# via abi3audit
altgraph==0.17.5 altgraph==0.17.5
# via macholib # via macholib
attrs==26.1.0 build==1.4.0
# via
# cattrs
# requests-cache
build==1.5.0
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
cattrs==26.1.0
# via requests-cache
certifi==2026.5.20
# via requests
charset-normalizer==3.4.7
# via requests
delocate==0.13.0 delocate==0.13.0
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
delvewheel==1.12.1
# via -r cibuildwheel/resources/constraints.in
distlib==0.4.0 distlib==0.4.0
# via virtualenv # via virtualenv
filelock==3.29.0 filelock==3.25.2
# via # via
# python-discovery # python-discovery
# virtualenv # virtualenv
idna==3.16
# via
# requests
# url-normalize
kaitaistruct==0.11
# via abi3audit
macholib==1.16.4 macholib==1.16.4
# via delocate # via delocate
markdown-it-py==4.2.0 packaging==26.0
# via rich
mdurl==0.1.2
# via markdown-it-py
packaging==26.2
# via # via
# abi3audit
# build # build
# delocate # delocate
pefile==2024.8.26 pip==26.0.1
# via
# abi3audit
# delvewheel
pip==26.1.1
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
platformdirs==4.9.6 platformdirs==4.9.4
# via # via
# python-discovery # python-discovery
# requests-cache
# virtualenv # virtualenv
pyelftools==0.32
# via abi3audit
pygments==2.20.0
# via rich
pyproject-hooks==1.2.0 pyproject-hooks==1.2.0
# via build # via build
python-discovery==1.3.1 python-discovery==1.1.3
# via virtualenv # via virtualenv
requests==2.34.2
# via
# abi3audit
# requests-cache
requests-cache==1.3.2
# via abi3audit
rich==15.0.0
# via abi3audit
typing-extensions==4.15.0 typing-extensions==4.15.0
# via # via delocate
# cattrs virtualenv==21.2.0
# delocate
url-normalize==3.0.0
# via requests-cache
urllib3==2.7.0
# via
# requests
# requests-cache
virtualenv==21.3.3
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
-4
View File
@@ -5,8 +5,6 @@ test-skip = ""
enable = [] enable = []
archs = ["auto"] archs = ["auto"]
audit-requires = ["abi3audit"]
audit-command = "abi3audit --strict --report {abi3_wheel}"
build-frontend = "default" build-frontend = "default"
config-settings = {} config-settings = {}
dependency-versions = "pinned" dependency-versions = "pinned"
@@ -60,11 +58,9 @@ repair-wheel-command = "auditwheel repair -w {dest_dir} {wheel}"
repair-wheel-command = "delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel}" repair-wheel-command = "delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel}"
[tool.cibuildwheel.windows] [tool.cibuildwheel.windows]
repair-wheel-command = "delvewheel repair -w {dest_dir} -v {wheel}"
[tool.cibuildwheel.android] [tool.cibuildwheel.android]
[tool.cibuildwheel.ios] [tool.cibuildwheel.ios]
[tool.cibuildwheel.pyodide] [tool.cibuildwheel.pyodide]
audit-command = ""
@@ -8,7 +8,7 @@
<key>choiceAttribute</key> <key>choiceAttribute</key>
<string>selected</string> <string>selected</string>
<key>choiceIdentifier</key> <key>choiceIdentifier</key>
<string>org.python.Python.PythonTFramework-3.15</string> <string>org.python.Python.PythonTFramework-3.13</string>
</dict> </dict>
</array> </array>
</plist> </plist>
@@ -1,103 +0,0 @@
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())
@@ -1,79 +0,0 @@
# 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()
@@ -1,22 +0,0 @@
# 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)
@@ -1,79 +0,0 @@
# 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()
@@ -1,22 +0,0 @@
# 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)
@@ -1,175 +0,0 @@
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.
""")
@@ -1,79 +0,0 @@
# 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()
@@ -1,22 +0,0 @@
# 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)
+3 -3
View File
@@ -1,4 +1,4 @@
url = "https://nodejs.org/dist/" url = "https://nodejs.org/dist/"
v24 = "v24.16.0" v24 = "v24.14.0"
v22 = "v22.22.3" v22 = "v22.22.1"
v20 = "v20.20.2" v20 = "v20.20.1"
+34 -34
View File
@@ -1,54 +1,54 @@
[x86_64] [x86_64]
manylinux2014 = quay.io/pypa/manylinux2014_x86_64:2026.05.27-1 manylinux2014 = quay.io/pypa/manylinux2014_x86_64:2026.03.14-2
manylinux_2_28 = quay.io/pypa/manylinux_2_28_x86_64:2026.05.27-1 manylinux_2_28 = quay.io/pypa/manylinux_2_28_x86_64:2026.03.14-2
manylinux_2_34 = quay.io/pypa/manylinux_2_34_x86_64:2026.05.27-1 manylinux_2_34 = quay.io/pypa/manylinux_2_34_x86_64:2026.03.14-2
musllinux_1_2 = quay.io/pypa/musllinux_1_2_x86_64:2026.05.27-1 musllinux_1_2 = quay.io/pypa/musllinux_1_2_x86_64:2026.03.14-2
[i686] [i686]
manylinux2014 = quay.io/pypa/manylinux2014_i686:2026.05.27-1 manylinux2014 = quay.io/pypa/manylinux2014_i686:2026.03.14-2
manylinux_2_28 = quay.io/pypa/manylinux_2_28_i686:2026.05.27-1 manylinux_2_28 = quay.io/pypa/manylinux_2_28_i686:2026.03.14-2
manylinux_2_34 = quay.io/pypa/manylinux_2_34_i686:2026.05.27-1 manylinux_2_34 = quay.io/pypa/manylinux_2_34_i686:2026.03.14-2
musllinux_1_2 = quay.io/pypa/musllinux_1_2_i686:2026.05.27-1 musllinux_1_2 = quay.io/pypa/musllinux_1_2_i686:2026.03.14-2
[aarch64] [aarch64]
manylinux2014 = quay.io/pypa/manylinux2014_aarch64:2026.05.27-1 manylinux2014 = quay.io/pypa/manylinux2014_aarch64:2026.03.14-2
manylinux_2_28 = quay.io/pypa/manylinux_2_28_aarch64:2026.05.27-1 manylinux_2_28 = quay.io/pypa/manylinux_2_28_aarch64:2026.03.14-2
manylinux_2_34 = quay.io/pypa/manylinux_2_34_aarch64:2026.05.27-1 manylinux_2_34 = quay.io/pypa/manylinux_2_34_aarch64:2026.03.14-2
musllinux_1_2 = quay.io/pypa/musllinux_1_2_aarch64:2026.05.27-1 musllinux_1_2 = quay.io/pypa/musllinux_1_2_aarch64:2026.03.14-2
[ppc64le] [ppc64le]
manylinux2014 = quay.io/pypa/manylinux2014_ppc64le:2026.05.27-1 manylinux2014 = quay.io/pypa/manylinux2014_ppc64le:2026.03.14-2
manylinux_2_28 = quay.io/pypa/manylinux_2_28_ppc64le:2026.05.27-1 manylinux_2_28 = quay.io/pypa/manylinux_2_28_ppc64le:2026.03.14-2
manylinux_2_34 = quay.io/pypa/manylinux_2_34_ppc64le:2026.05.27-1 manylinux_2_34 = quay.io/pypa/manylinux_2_34_ppc64le:2026.03.14-2
musllinux_1_2 = quay.io/pypa/musllinux_1_2_ppc64le:2026.05.27-1 musllinux_1_2 = quay.io/pypa/musllinux_1_2_ppc64le:2026.03.14-2
[s390x] [s390x]
manylinux2014 = quay.io/pypa/manylinux2014_s390x:2026.05.27-1 manylinux2014 = quay.io/pypa/manylinux2014_s390x:2026.03.14-2
manylinux_2_28 = quay.io/pypa/manylinux_2_28_s390x:2026.05.27-1 manylinux_2_28 = quay.io/pypa/manylinux_2_28_s390x:2026.03.14-2
manylinux_2_34 = quay.io/pypa/manylinux_2_34_s390x:2026.05.27-1 manylinux_2_34 = quay.io/pypa/manylinux_2_34_s390x:2026.03.14-2
musllinux_1_2 = quay.io/pypa/musllinux_1_2_s390x:2026.05.27-1 musllinux_1_2 = quay.io/pypa/musllinux_1_2_s390x:2026.03.14-2
[pypy_x86_64] [pypy_x86_64]
manylinux2014 = quay.io/pypa/manylinux2014_x86_64:2026.05.27-1 manylinux2014 = quay.io/pypa/manylinux2014_x86_64:2026.03.14-2
manylinux_2_28 = quay.io/pypa/manylinux_2_28_x86_64:2026.05.27-1 manylinux_2_28 = quay.io/pypa/manylinux_2_28_x86_64:2026.03.14-2
manylinux_2_34 = quay.io/pypa/manylinux_2_34_x86_64:2026.05.27-1 manylinux_2_34 = quay.io/pypa/manylinux_2_34_x86_64:2026.03.14-2
[pypy_i686] [pypy_i686]
manylinux2014 = quay.io/pypa/manylinux2014_i686:2026.05.27-1 manylinux2014 = quay.io/pypa/manylinux2014_i686:2026.03.14-2
manylinux_2_28 = quay.io/pypa/manylinux_2_28_i686:2026.05.27-1 manylinux_2_28 = quay.io/pypa/manylinux_2_28_i686:2026.03.14-2
manylinux_2_34 = quay.io/pypa/manylinux_2_34_i686:2026.05.27-1 manylinux_2_34 = quay.io/pypa/manylinux_2_34_i686:2026.03.14-2
[pypy_aarch64] [pypy_aarch64]
manylinux2014 = quay.io/pypa/manylinux2014_aarch64:2026.05.27-1 manylinux2014 = quay.io/pypa/manylinux2014_aarch64:2026.03.14-2
manylinux_2_28 = quay.io/pypa/manylinux_2_28_aarch64:2026.05.27-1 manylinux_2_28 = quay.io/pypa/manylinux_2_28_aarch64:2026.03.14-2
manylinux_2_34 = quay.io/pypa/manylinux_2_34_aarch64:2026.05.27-1 manylinux_2_34 = quay.io/pypa/manylinux_2_34_aarch64:2026.03.14-2
[armv7l] [armv7l]
manylinux_2_31 = quay.io/pypa/manylinux_2_31_armv7l:2026.05.27-1 manylinux_2_31 = quay.io/pypa/manylinux_2_31_armv7l:2026.03.14-2
manylinux_2_35 = quay.io/pypa/manylinux_2_35_armv7l:2026.05.27-1 manylinux_2_35 = quay.io/pypa/manylinux_2_35_armv7l:2026.03.14-2
musllinux_1_2 = quay.io/pypa/musllinux_1_2_armv7l:2026.05.27-1 musllinux_1_2 = quay.io/pypa/musllinux_1_2_armv7l:2026.03.14-2
[riscv64] [riscv64]
manylinux_2_39 = quay.io/pypa/manylinux_2_39_riscv64:2026.05.27-1 manylinux_2_39 = quay.io/pypa/manylinux_2_39_riscv64:2026.03.14-2
musllinux_1_2 = quay.io/pypa/musllinux_1_2_riscv64:2026.05.27-1 musllinux_1_2 = quay.io/pypa/musllinux_1_2_riscv64:2026.03.14-2
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1 +1 @@
default = { version = "21.3.3", url = "https://github.com/pypa/get-virtualenv/releases/download/21.3.3/virtualenv.pyz", sha256 = "d3a7f4ee4c820c4247fe14ec1a478e19ef8f63f716c0f15bc10fab4d70ad9b69" } default = { version = "21.2.0", url = "https://github.com/pypa/get-virtualenv/releases/download/21.2.0/virtualenv.pyz" }
+2 -7
View File
@@ -1,12 +1,7 @@
from __future__ import annotations
import json import json
from typing import Any
from cibuildwheel.util import resources from .util import resources
TYPE_CHECKING = False
if TYPE_CHECKING:
from typing import Any
def get_schema(tool_name: str = "cibuildwheel") -> dict[str, Any]: def get_schema(tool_name: str = "cibuildwheel") -> dict[str, Any]:
+19 -27
View File
@@ -1,19 +1,13 @@
from __future__ import annotations
import dataclasses import dataclasses
import itertools import itertools
from enum import StrEnum from enum import StrEnum
from fnmatch import fnmatch from fnmatch import fnmatch
from typing import Any
import bracex import bracex
from packaging.specifiers import SpecifierSet
from packaging.version import Version 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: def selector_matches(patterns: str, string: str) -> bool:
""" """
@@ -35,19 +29,19 @@ class EnableGroup(StrEnum):
Groups of build selectors that are not enabled by default. Groups of build selectors that are not enabled by default.
""" """
CPythonFreeThreading = "cpython-freethreading"
CPythonPrerelease = "cpython-prerelease" CPythonPrerelease = "cpython-prerelease"
GraalPy = "graalpy" GraalPy = "graalpy"
PyPy = "pypy" PyPy = "pypy"
PyPyEoL = "pypy-eol" PyPyEoL = "pypy-eol"
PyodideEoL = "pyodide-eol"
PyodidePrerelease = "pyodide-prerelease" PyodidePrerelease = "pyodide-prerelease"
@classmethod @classmethod
def all_groups(cls) -> frozenset[Self]: def all_groups(cls) -> frozenset["EnableGroup"]:
return frozenset(cls) return frozenset(cls)
@classmethod @classmethod
def parse_option_value(cls, value: str) -> frozenset[Self]: def parse_option_value(cls, value: str) -> frozenset["EnableGroup"]:
""" """
Parses a string of space-separated values into a set of EnableGroup Parses a string of space-separated values into a set of EnableGroup
members. The string may contain group names or "all". members. The string may contain group names or "all".
@@ -90,6 +84,8 @@ class BuildSelector:
return False return False
# filter out groups that are not enabled # filter out groups that are not enabled
if EnableGroup.CPythonFreeThreading not in self.enable and fnmatch(build_id, "cp313t-*"):
return False
if EnableGroup.CPythonPrerelease not in self.enable and fnmatch(build_id, "cp315*"): if EnableGroup.CPythonPrerelease not in self.enable and fnmatch(build_id, "cp315*"):
return False return False
is_pypy_eol = fnmatch(build_id, "pp3?-*") or fnmatch(build_id, "pp310-*") is_pypy_eol = fnmatch(build_id, "pp3?-*") or fnmatch(build_id, "pp310-*")
@@ -100,28 +96,24 @@ class BuildSelector:
return False return False
if EnableGroup.GraalPy not in self.enable and fnmatch(build_id, "gp*"): if EnableGroup.GraalPy not in self.enable and fnmatch(build_id, "gp*"):
return False return False
if EnableGroup.PyodideEoL not in self.enable and fnmatch(build_id, "cp312-pyodide_*"): # TODO: Re-enable this when we have Pyodide prereleases again (e.g., 0.29.0a1+)
return False # Python 3.13 support became stable in Pyodide 0.28.0, so it no longer needs a prerelease
# NOTE: Disable this when we don't have any Pyodide prereleases (e.g., 314.0.0a1+) # flag.
# When doing this, also: # Also update Pyodide tests in unit_test/build_selector_test.py accordingly.
# 1. update Pyodide tests in unit_test/build_selector_test.py and unit_test/options_test.py accordingly. # When re-enabling, update the pattern to match the experimental Python version in case
# 2. update Python versions for Pyodide identifiers in cibuildwheel/selector.py. # it is bumped to Python 3.14 (likely cp314-pyodide_* but could remain as 3.13 as well).
# 3. update constraints as necessary via bin/generate_pyodide_constraints.py and add/delete
# Pyodide constraints files in cibuildwheel/resources/constraints/ as necessary.
# When disabling, update the pattern to match the experimental Python version in case
# it is bumped to Python 3.15 (likely cp315-pyodide_* but could remain as 3.14 as well).
# This depends on the CPython version being used in the Pyodide runtime at the time. # This depends on the CPython version being used in the Pyodide runtime at the time.
if EnableGroup.PyodidePrerelease not in self.enable and fnmatch( # if EnableGroup.PyodidePrerelease not in self.enable and fnmatch(
build_id, "cp314-pyodide_*" # build_id, "cp313-pyodide_*"
): # ):
return False # return False
should_build = selector_matches(self.build_config, build_id) should_build = selector_matches(self.build_config, build_id)
should_skip = selector_matches(self.skip_config, build_id) should_skip = selector_matches(self.skip_config, build_id)
return should_build and not should_skip return should_build and not should_skip
def options_summary(self) -> dict[str, str | list[str]]: def options_summary(self) -> Any:
return { return {
"build_config": self.build_config, "build_config": self.build_config,
"skip_config": self.skip_config, "skip_config": self.skip_config,
@@ -142,5 +134,5 @@ class TestSelector:
should_skip = selector_matches(self.skip_config, build_id) should_skip = selector_matches(self.skip_config, build_id)
return not should_skip return not should_skip
def options_summary(self) -> dict[str, str]: def options_summary(self) -> Any:
return {"skip_config": self.skip_config} return {"skip_config": self.skip_config}
+4 -10
View File
@@ -1,20 +1,14 @@
from __future__ import annotations
import os import os
import shlex import shlex
import shutil import shutil
import subprocess import subprocess
import sys import sys
import typing import typing
from collections.abc import Iterator, Mapping
from typing import Final, Literal
from cibuildwheel.errors import FatalError from ..errors import FatalError
from ..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") _IS_WIN: Final[bool] = sys.platform.startswith("win")
+4 -17
View File
@@ -1,12 +1,10 @@
from __future__ import annotations
import hashlib
import os import os
import shutil import shutil
import ssl import ssl
import tarfile import tarfile
import time import time
import urllib.request import urllib.request
from collections.abc import Callable
from pathlib import Path, PurePath from pathlib import Path, PurePath
from typing import Final from typing import Final
from zipfile import ZipFile from zipfile import ZipFile
@@ -14,11 +12,7 @@ from zipfile import ZipFile
import certifi import certifi
from platformdirs import user_cache_path from platformdirs import user_cache_path
from cibuildwheel.errors import FatalError from ..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") DEFAULT_CIBW_CACHE_PATH: Final[Path] = user_cache_path(appname="cibuildwheel", appauthor="pypa")
CIBW_CACHE_PATH: Final[Path] = Path( CIBW_CACHE_PATH: Final[Path] = Path(
@@ -46,7 +40,7 @@ def ensure_cache_sentinel(cache_path: Path) -> None:
) )
def download(url: str, dest: Path, *, sha256: str | None = None) -> None: def download(url: str, dest: Path) -> None:
print(f"+ Download {url} to {dest}") print(f"+ Download {url} to {dest}")
dest_dir = dest.parent dest_dir = dest.parent
dest_dir.mkdir(parents=True, exist_ok=True) dest_dir.mkdir(parents=True, exist_ok=True)
@@ -60,20 +54,13 @@ def download(url: str, dest: Path, *, sha256: str | None = None) -> None:
try: try:
with urllib.request.urlopen(url, context=context) as response: with urllib.request.urlopen(url, context=context) as response:
dest.write_bytes(response.read()) dest.write_bytes(response.read())
break return
except OSError: except OSError:
if i == repeat_num - 1: if i == repeat_num - 1:
raise raise
time.sleep(3) 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: def extract_zip(zip_src: Path, dest: Path) -> None:
"""Extracts a zip and correctly sets permissions on extracted files. """Extracts a zip and correctly sets permissions on extracted files.
+3 -8
View File
@@ -1,18 +1,13 @@
from __future__ import annotations
import dataclasses import dataclasses
import itertools import itertools
import os
import re import re
import shlex import shlex
import textwrap import textwrap
from collections import defaultdict from collections import defaultdict
from collections.abc import Sequence
TYPE_CHECKING = False from ..typing import PathOrStr
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: def format_safe(template: str, **kwargs: str | os.PathLike[str]) -> str:
+10 -21
View File
@@ -1,20 +1,14 @@
from __future__ import annotations
import shlex import shlex
from collections.abc import Mapping, Sequence
from dataclasses import dataclass, field from dataclasses import dataclass, field
from pathlib import Path, PurePath from pathlib import Path, PurePath
from typing import TypeVar from typing import Any, Literal, Self, TypeVar
from packaging.utils import parse_wheel_filename from packaging.utils import parse_wheel_filename
from cibuildwheel.util import resources from . import resources
from cibuildwheel.util.cmd import call from .cmd import call
from cibuildwheel.util.helpers import parse_key_value_string, unwrap from .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) @dataclass(kw_only=True)
@@ -108,7 +102,7 @@ class DependencyConstraints:
return None return None
def options_summary(self) -> str | dict[str, str]: def options_summary(self) -> Any:
if self == DependencyConstraints.pinned(): if self == DependencyConstraints.pinned():
return "pinned" return "pinned"
elif self.packages: elif self.packages:
@@ -175,17 +169,12 @@ def find_compatible_wheel(wheels: Sequence[T], identifier: str) -> T | None:
elif platform.startswith("pyodide"): elif platform.startswith("pyodide"):
# each Pyodide version has its own platform tag # each Pyodide version has its own platform tag
continue continue
# Windows should exactly match else:
elif tag.platform != platform: # Windows should exactly match
continue if tag.platform != platform:
continue
# If all the filters above pass, then the wheel is a previously built compatible wheel. # If all the filters above pass, then the wheel is a previously built compatible wheel.
return wheel return wheel
return None return None
def is_abi3_wheel(wheel_name: str) -> bool:
"""Check if a wheel uses the abi3 stable ABI based on its filename."""
_, _, _, tags = parse_wheel_filename(wheel_name)
return any(tag.abi == "abi3" for tag in tags)
+11 -34
View File
@@ -1,26 +1,19 @@
from __future__ import annotations
import fnmatch import fnmatch
import functools import functools
import hashlib
import json import json
import platform import platform
import typing import typing
from pathlib import Path
from filelock import FileLock from filelock import FileLock
from cibuildwheel.util.file import download, extract_tar from cibuildwheel.util.file import download, extract_tar
from cibuildwheel.util.resources import PYTHON_BUILD_STANDALONE_RELEASES from cibuildwheel.util.resources import PYTHON_BUILD_STANDALONE_RELEASES
TYPE_CHECKING = False
if TYPE_CHECKING:
from pathlib import Path
class PythonBuildStandaloneAsset(typing.TypedDict): class PythonBuildStandaloneAsset(typing.TypedDict):
name: str name: str
url: str url: str
sha256: str
class PythonBuildStandaloneRelease(typing.TypedDict): class PythonBuildStandaloneRelease(typing.TypedDict):
@@ -88,8 +81,8 @@ def _get_pbs_asset(
arch_identifier: str, arch_identifier: str,
platform_identifier: str, platform_identifier: str,
libc_identifier: str | None, libc_identifier: str | None,
) -> tuple[str, str, str, str]: ) -> tuple[str, str, str]:
"""Finds the asset, returning (tag, url, filename, sha256).""" """Finds the asset, returning (tag, filename, url)."""
release_data = get_python_build_standalone_release_data() release_data = get_python_build_standalone_release_data()
expected_suffix = f"{arch_identifier}-{platform_identifier}" expected_suffix = f"{arch_identifier}-{platform_identifier}"
@@ -107,36 +100,22 @@ def _get_pbs_asset(
continue continue
asset_url = asset["url"] asset_url = asset["url"]
asset_sha256 = asset.get("sha256", "") return release["tag"], asset_url, asset_name
return release["tag"], asset_url, asset_name, asset_sha256
# If loop completes without finding a match # If loop completes without finding a match
msg = f"Could not find python-build-standalone release asset matching {asset_pattern!r}." msg = f"Could not find python-build-standalone release asset matching {asset_pattern!r}."
raise PythonBuildStandaloneError(msg) raise PythonBuildStandaloneError(msg)
def _download_or_get_from_cache( def _download_or_get_from_cache(asset_url: str, asset_filename: str, cache_dir: Path) -> Path:
asset_url: str, asset_filename: str, cache_dir: Path, sha256: str = ""
) -> Path:
with FileLock(cache_dir / (asset_filename + ".lock")): with FileLock(cache_dir / (asset_filename + ".lock")):
asset_cache_path = cache_dir / asset_filename asset_cache_path = cache_dir / asset_filename
if asset_cache_path.is_file(): if asset_cache_path.is_file():
if sha256: print(f"Using cached python_build_standalone: {asset_cache_path}")
computed = hashlib.sha256(asset_cache_path.read_bytes()).hexdigest() return asset_cache_path
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}") print(f"Downloading python_build_standalone: {asset_url} to {asset_cache_path}")
download(asset_url, asset_cache_path, sha256=sha256 or None) download(asset_url, asset_cache_path)
return asset_cache_path return asset_cache_path
@@ -182,7 +161,7 @@ def create_python_build_standalone_environment(
arch_id, platform_id, libc_id = _get_platform_identifiers() arch_id, platform_id, libc_id = _get_platform_identifiers()
pbs_tag, asset_url, asset_filename, asset_sha256 = _get_pbs_asset( pbs_tag, asset_url, asset_filename = _get_pbs_asset(
python_version=python_version, python_version=python_version,
arch_identifier=arch_id, arch_identifier=arch_id,
platform_identifier=platform_id, platform_identifier=platform_id,
@@ -192,13 +171,11 @@ def create_python_build_standalone_environment(
print(f"Using python-build-standalone release: {pbs_tag}") print(f"Using python-build-standalone release: {pbs_tag}")
archive_path = _download_or_get_from_cache( archive_path = _download_or_get_from_cache(
asset_url=asset_url, asset_filename=asset_filename, cache_dir=cache_dir, sha256=asset_sha256 asset_url=asset_url, asset_filename=asset_filename, cache_dir=cache_dir
) )
python_base_dir = temp_dir / "pbs" python_base_dir = temp_dir / "pbs"
if python_base_dir.exists(): assert not 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) extract_tar(archive_path, python_base_dir)
return _find_python_executable(python_base_dir) return _find_python_executable(python_base_dir)
+3 -9
View File
@@ -1,19 +1,14 @@
from __future__ import annotations
import functools import functools
import tomllib import tomllib
from pathlib import Path from pathlib import Path
from typing import Final
TYPE_CHECKING = False from ..typing import PlatformName
if TYPE_CHECKING:
from typing import Final
from cibuildwheel.typing import PlatformName
PATH: Final[Path] = Path(__file__).parent.parent / "resources" PATH: Final[Path] = Path(__file__).parent.parent / "resources"
INSTALL_CERTIFI_SCRIPT: Final[Path] = PATH / "install_certifi.py" INSTALL_CERTIFI_SCRIPT: Final[Path] = PATH / "install_certifi.py"
FREE_THREAD_ENABLE_313: Final[Path] = PATH / "free-threaded-enable-313.xml"
FREE_THREAD_ENABLE_314: Final[Path] = PATH / "free-threaded-enable-314.xml" FREE_THREAD_ENABLE_314: Final[Path] = PATH / "free-threaded-enable-314.xml"
FREE_THREAD_ENABLE_315: Final[Path] = PATH / "free-threaded-enable-315.xml"
NODEJS: Final[Path] = PATH / "nodejs.toml" NODEJS: Final[Path] = PATH / "nodejs.toml"
DEFAULTS: Final[Path] = PATH / "defaults.toml" DEFAULTS: Final[Path] = PATH / "defaults.toml"
PINNED_DOCKER_IMAGES: Final[Path] = PATH / "pinned_docker_images.cfg" PINNED_DOCKER_IMAGES: Final[Path] = PATH / "pinned_docker_images.cfg"
@@ -23,7 +18,6 @@ VIRTUALENV: Final[Path] = PATH / "virtualenv.toml"
CIBUILDWHEEL_SCHEMA: Final[Path] = PATH / "cibuildwheel.schema.json" CIBUILDWHEEL_SCHEMA: Final[Path] = PATH / "cibuildwheel.schema.json"
PYTHON_BUILD_STANDALONE_RELEASES: Final[Path] = PATH / "python-build-standalone-releases.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" 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 # this value is cached because it's used a lot in unit tests
+12 -57
View File
@@ -1,49 +1,24 @@
from __future__ import annotations
import contextlib import contextlib
import functools import functools
import os import os
import shutil import shutil
import sys import sys
import tomllib import tomllib
from collections.abc import Sequence
from pathlib import Path from pathlib import Path
from typing import cast from typing import Final
from filelock import FileLock from filelock import FileLock
from packaging.markers import default_environment
from packaging.requirements import InvalidRequirement, Requirement from packaging.requirements import InvalidRequirement, Requirement
from packaging.version import Version from packaging.version import Version
from cibuildwheel.util import resources from .util import resources
from cibuildwheel.util.cmd import call from .util.cmd import call
from cibuildwheel.util.file import CIBW_CACHE_PATH, download from .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") _IS_WIN: Final[bool] = sys.platform.startswith("win")
def target_marker_env(
*,
implementation_id: str,
) -> dict[str, str]:
"""
Build a PEP 508 marker environment dict for the target Python,
overriding the host's values with the target implementation info.
"""
env = cast("dict[str, str]", default_environment())
if implementation_id.startswith("gp"):
env["implementation_name"] = "graalpy"
env["platform_python_implementation"] = "GraalPy"
elif implementation_id.startswith("pp"):
env["implementation_name"] = "pypy"
env["platform_python_implementation"] = "PyPy"
return env
@functools.cache @functools.cache
def _ensure_virtualenv(version: str) -> tuple[Path, Version]: def _ensure_virtualenv(version: str) -> tuple[Path, Version]:
version_parts = version.split(".") version_parts = version.split(".")
@@ -53,11 +28,10 @@ def _ensure_virtualenv(version: str) -> tuple[Path, Version]:
configuration = loaded_file.get(key, loaded_file["default"]) configuration = loaded_file.get(key, loaded_file["default"])
version = str(configuration["version"]) version = str(configuration["version"])
url = str(configuration["url"]) url = str(configuration["url"])
sha256 = str(configuration.get("sha256", ""))
path = CIBW_CACHE_PATH / f"virtualenv-{version}.pyz" path = CIBW_CACHE_PATH / f"virtualenv-{version}.pyz"
with FileLock(str(path) + ".lock"): with FileLock(str(path) + ".lock"):
if not path.exists(): if not path.exists():
download(url, path, sha256=sha256 or None) download(url, path)
return (path, Version(version)) return (path, Version(version))
@@ -73,7 +47,6 @@ def constraint_flags(
def _parse_pip_constraint_for_virtualenv( def _parse_pip_constraint_for_virtualenv(
constraint_path: Path | None, constraint_path: Path | None,
marker_env: dict[str, str] | None = None,
) -> str: ) -> str:
""" """
Parses the constraints file referenced by `dependency_constraint_flags` and returns a dict where Parses the constraints file referenced by `dependency_constraint_flags` and returns a dict where
@@ -83,12 +56,7 @@ def _parse_pip_constraint_for_virtualenv(
The function does not try to be too smart and just handles basic constraints. The function does not try to be too smart and just handles basic constraints.
If it can't get an exact version, the real constraint will be handled by the If it can't get an exact version, the real constraint will be handled by the
{macos|windows}.setup_python function. {macos|windows}.setup_python function.
If marker_env is provided, marker-bearing constraints are evaluated against it;
otherwise, marker-bearing constraints are skipped.
""" """
env: dict[str, str] = (
marker_env if marker_env is not None else cast("dict[str, str]", default_environment())
)
if constraint_path: if constraint_path:
assert constraint_path.exists() assert constraint_path.exists()
with constraint_path.open(encoding="utf-8") as constraint_file: with constraint_path.open(encoding="utf-8") as constraint_file:
@@ -104,12 +72,11 @@ def _parse_pip_constraint_for_virtualenv(
if ( if (
package != "pip" package != "pip"
or requirement.url is not None or requirement.url is not None
or requirement.marker is not None
or len(requirement.extras) != 0 or len(requirement.extras) != 0
or len(requirement.specifier) != 1 or len(requirement.specifier) != 1
): ):
continue continue
if requirement.marker is not None and not requirement.marker.evaluate(env):
continue
specifier = next(iter(requirement.specifier)) specifier = next(iter(requirement.specifier))
if specifier.operator != "==": if specifier.operator != "==":
continue continue
@@ -128,7 +95,6 @@ def virtualenv(
use_uv: bool, use_uv: bool,
env: dict[str, str] | None = None, env: dict[str, str] | None = None,
pip_version: str | None = None, pip_version: str | None = None,
marker_env: dict[str, str] | None = None,
) -> dict[str, str]: ) -> dict[str, str]:
""" """
Create a virtual environment. If `use_uv` is True, Create a virtual environment. If `use_uv` is True,
@@ -148,7 +114,7 @@ def virtualenv(
else: else:
virtualenv_app, virtualenv_version = _ensure_virtualenv(version) virtualenv_app, virtualenv_version = _ensure_virtualenv(version)
if pip_version is None: if pip_version is None:
pip_version = _parse_pip_constraint_for_virtualenv(dependency_constraint, marker_env) pip_version = _parse_pip_constraint_for_virtualenv(dependency_constraint)
additional_flags = [f"--pip={pip_version}", "--no-setuptools"] additional_flags = [f"--pip={pip_version}", "--no-setuptools"]
if virtualenv_version < Version("20.31") or Version(version) < Version("3.9"): if virtualenv_version < Version("20.31") or Version(version) < Version("3.9"):
additional_flags.append("--no-wheel") additional_flags.append("--no-wheel")
@@ -173,7 +139,10 @@ def virtualenv(
python, python,
venv_path, venv_path,
) )
venv_env = activate_virtualenv(venv_path, env=env) paths = [str(venv_path), str(venv_path / "Scripts")] if _IS_WIN else [str(venv_path / "bin")]
venv_env = os.environ.copy() if env is None else env.copy()
venv_env["PATH"] = os.pathsep.join([*paths, venv_env["PATH"]])
venv_env["VIRTUAL_ENV"] = str(venv_path)
if not use_uv and pip_version == "embed": if not use_uv and pip_version == "embed":
call( call(
"python", "python",
@@ -189,20 +158,6 @@ def virtualenv(
return venv_env return venv_env
def activate_virtualenv(
venv_path: Path,
env: dict[str, str] | None = None,
) -> dict[str, str]:
"""
Return a copy of the environment with the virtualenv at `venv_path` activated.
"""
paths = [str(venv_path), str(venv_path / "Scripts")] if _IS_WIN else [str(venv_path / "bin")]
venv_env = os.environ.copy() if env is None else env.copy()
venv_env["PATH"] = os.pathsep.join([*paths, venv_env["PATH"]])
venv_env["VIRTUAL_ENV"] = str(venv_path)
return venv_env
def find_uv() -> Path | None: def find_uv() -> Path | None:
# Prefer uv in our environment # Prefer uv in our environment
with contextlib.suppress(ImportError, FileNotFoundError): with contextlib.suppress(ImportError, FileNotFoundError):
-113
View File
@@ -1,113 +0,0 @@
# Maintaining Pyodide support
Last updated: May 2026
This page describes how to update cibuildwheel's Pyodide platform code when either:
- a new Pyodide alpha release arrives with support for a new [PyEmscripten Platform](https://pyodide.org/en/latest/development/abi.html) (which is tied to updates in Emscripten and CPython versions, compiler/linker flags, and so on), or
- when that alpha release graduates to a stable one.
## Background
Pyodide has three types of releases that matter to cibuildwheel:
- **Stable** the most recent full Pyodide release (e.g., `0.29.x` / cp313). This is enabled by default with no special `CIBW_ENABLE` flag needed.
- **Prerelease** an alpha/beta/rc Pyodide release that uses the _next_ CPython version (e.g., `314.0.0a1` / cp314). Users must opt in with `CIBW_ENABLE: pyodide-prerelease` to build against this version. This may or may not be available at any given time, depending on the Pyodide release cycle.
- **End-of-life (EoL)** older Pyodide stable releases that are no longer the current stable. These are kept available behind `CIBW_ENABLE: pyodide-eol` so that users who still need to build for older Pyodide versions can do so.
The guards in `cibuildwheel/selector.py` enforce this distinction. The constraints files under `cibuildwheel/resources/` pin the exact tool versions that go with each build.
---
## When a new Pyodide prerelease becomes available
For example, consider a scenario when Pyodide ships a new `315.0.0a1` with cp315 support.
### 1. Add the new Python configuration
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", 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. `sha256` is the checksum of the Pyodide xbuildenv tarball.
### 2. Update the prerelease guards in the selector
In `cibuildwheel/selector.py`, update the patterns in the `PyodidePrerelease` guards to match the new identifier:
```python
if EnableGroup.PyodidePrerelease not in self.enable and fnmatch(
build_id, "cp315-pyodide_*"
):
return False
```
### 3. Generate and pin a constraints file
Run the `update_constraints` `nox` session, which reads `build-platforms.toml` and regenerates all Pyodide constraints files automatically:
```bash
nox -s update_constraints
```
### 4. Update tests
Update the unit tests so the new identifier is accepted by the selector with `PyodidePrerelease` enabled and rejected without it. Pyodide-specific integration tests may also need their hardcoded expected-wheel lists extended.
## When a Pyodide prerelease becomes stable
Pyodide uses a versioning scheme where the stable release for a given CPython version is named `[PythonMajorMinor].0.0`, so the first stable release shipping cp314 will be **`314.0.0`**. See [pyodide/pyodide#6084](https://github.com/pyodide/pyodide/issues/6084) for a rationale of this versioning scheme.
### 1. Update the stable entries, and remove (or replace) the prerelease entry
In `build-platforms.toml`, update the former prerelease entry's `default_pyodide_version` to the new stable release (e.g. `314.0.0`) and remove the prerelease marker from the identifier if present. Remove previous prerelease entries if they are now obsolete, or update them to the next prerelease if one is available. Move the previous stable version behind `pyodide-eol` rather than dropping it outright (see below).
### 2. Disable or update the prerelease guards
In `selector.py`:
- **If a new prerelease is available**: update the `fnmatch` pattern to the next identifier (e.g. `cp315-pyodide_*`) as described above.
- **If there is no new prerelease**: comment out the `PyodidePrerelease` logic.
### 3. Update the constraints file
Run `nox -s update_constraints` to regenerate the constraints file for the newly stable version. If the entry was already in `build-platforms.toml` as a prerelease, its constraints file already exists, and this step just refreshes it against the stable Pyodide release and updates the dependencies' versions.
### 4. Update tests
Update the unit tests so the newly stable identifier is accepted by the selector without needing `PyodidePrerelease` in the enable set. Pyodide-specific integration tests may also need their hardcoded expected-wheel lists extended.
## When an old Pyodide version is to be moved to end-of-life
When a Pyodide version is superseded by a new stable release, move it behind the `pyodide-eol` enable flag. We want to allow users who still build for older Pyodide ABIs time to upgrade.
### 1. Add the `pyodide-eol` guard in the selector
In `cibuildwheel/selector.py`, add (or update) the `PyodideEoL` guard to include the old identifier:
```python
if EnableGroup.PyodideEoL not in self.enable and fnmatch(build_id, "cp312-pyodide_*"):
return False
```
### 2. Update tests
Update the unit tests so the EoL identifier requires `PyodideEoL` to be included in the enable set. The default (no `CIBW_ENABLE`) should exclude it.
## When an old Pyodide version is to be fully retired
Retirement is not expected to happen on a routine basis. It is only warranted when the Pyodide ecosystem itself has evolved to the point where an older ABI version is considered obsolete for example, if the surrounding toolchain, packaging standards, or runtime infrastructure have moved on so substantially that building for the older version no longer makes practical sense. Any retirement is to be discussed and agreed upon by Pyodide maintainers before proceeding.
### 1. Remove the Python configuration
Delete the entry from `build-platforms.toml` and remove the `PyodideEoL` guard for that identifier in `selector.py`.
### 2. Delete the constraints file
Remove `cibuildwheel/resources/constraints-pyodideXYZ.txt`.
### 3. Update tests
Remove references to the old identifier from the unit tests, integration tests, and drop any expected-wheel entries for it from the test helper.
-46
View File
@@ -4,45 +4,6 @@ title: Changelog
# Changelog # Changelog
### v4.0.0rc1
_14 May 2026_
- 🌟 Adds wheel auditing with `abi3audit` as a default after the repair step, with new [`audit-requires`](https://cibuildwheel.pypa.io/en/stable/options/#audit-requires) and [`audit-command`](https://cibuildwheel.pypa.io/en/stable/options/#audit-command) options (#2805)
- 🌟 Adds `pyemscripten` platform tag support (PEP 783), updates Pyodide to 314.0.0a1, and adds a `pyodide-eol` [`enable`](https://cibuildwheel.pypa.io/en/stable/options/#enable) flag for building end-of-life Pyodide versions (#2812, #2848)
- 🌟 Sets up `delvewheel` as the default [`repair-wheel-command`](https://cibuildwheel.pypa.io/en/stable/options/#repair-wheel-command) for Windows, so extension module DLLs are now bundled automatically. Skip by setting it to empty if not needed. (#2831)
- ✨ Adds CPython 3.15 support, under the [`enable` option](https://cibuildwheel.pypa.io/en/stable/options/#enable) `cpython-prerelease`. This version of cibuildwheel uses 3.15.0b1. (#2833, #2850)
_While CPython is in beta, the ABI can change, so your wheels might not be compatible with the final release. For this reason, we don't recommend distributing wheels until RC1, at which point 3.14 will be available in cibuildwheel without the flag._ (#2390)
- ✨ Adds `{project}` and `{package}` placeholders to [`config-settings`](https://cibuildwheel.pypa.io/en/stable/options/#config-settings) (#2827)
- ⚠️ Drops support for Python 3.8 (#2686)
- ⚠️ Removes the experimental CPython 3.13 free-threading builds and the `cpython-freethreading` [`enable`](https://cibuildwheel.pypa.io/en/stable/options/#enable) option. CPython 3.14+ free-threading support remains available without the enable flag. (#2684)
- ⚠️ Drops support for Cirrus CI, which is shutting down June 1, 2026 (#2817)
- 🐛 Fixes `UV_PYTHON` not being set for [`before-build`](https://cibuildwheel.pypa.io/en/stable/options/#before-build) on Linux when using `uv` as the [`build-frontend`](https://cibuildwheel.pypa.io/en/stable/options/#build-frontend) (#2830)
- 🛠 Updates Android to Python 3.13.13 and 3.14.4 (#2821)
- 🛠 Applies Pyodide-specific patches to the Emscripten toolchain installation (#2800)
- 🛠 Updates dependencies and container pins (#2845, #2837, #2821, #2818, #2810, #2838, #2813)
- 🛠 Uses `python -V -V` for Windows build diagnostics (#2832)
- 📚 Documents platform-specific [`before-build`](https://cibuildwheel.pypa.io/en/stable/options/#before-build) configuration (#2834)
- 📚 Updates the "How it works" diagram with details of Android, iOS, and Pyodide builds (#2816)
- 📚 Adds Pyodide icon and regenerates working examples data for Android, iOS, and Pyodide (#2815, #2811)
- 📚 Links back to source in docs (#2806)
- 💼 Adds PEP 723 metadata for `bin/` scripts and drops the `bin` dependency group (#2819)
- 💼 Updates CI action pins and dev dependencies (#2851, #2843, #2826, #2823, #2820, #2807)
- 🧪 Fixes Android tests using the `uv` frontend (#2809)
- 🧪 Fixes the update-dependencies workflow to use `uv` to run `nox` (#2808)
### v3.4.1
_2 April 2026_
- ⚠️ Building for the experimental CPython 3.13 free-threading variant is now deprecated. That functionality will be removed in the next minor release. The [`enable`](https://cibuildwheel.pypa.io/en/stable/options/#enable) option `cpython-freethreading` is therefore also deprecated. Builds specifying `enable = "all"` no longer select `cpython-freethreading`. CPython 3.14 free-threading support remains available without the `enable` flag. (#2787)
- 🐛 iOS builds will no longer skip `repair-wheel-command` if it's defined in config (#2761)
- 🐛 Fix bug causing `uv` to fail when environments define PYTHON_VERSION or UV_PYTHON, conflicting with our venvs (#2795)
- ✨ cibuildwheel prints the selected build identifiers at the start of the build. (#2785)
- 🔐 The GitHub Action now references other actions with a full SHA (#2744)
### v3.4.0 ### v3.4.0
_5 March 2026_ _5 March 2026_
@@ -50,7 +11,6 @@ _5 March 2026_
- 🌟 You can now build wheels using `uv` as a build frontend. This should improve performance, especially if your project has lots of build dependencies. To use, set [`build-frontend`](https://cibuildwheel.pypa.io/en/stable/options/#build-frontend) to `uv`. (#2322) - 🌟 You can now build wheels using `uv` as a build frontend. This should improve performance, especially if your project has lots of build dependencies. To use, set [`build-frontend`](https://cibuildwheel.pypa.io/en/stable/options/#build-frontend) to `uv`. (#2322)
- ⚠️ We no longer support running on Travis CI. It may continue working but we don't run tests there anymore so we can't be sure. (#2682) - ⚠️ We no longer support running on Travis CI. It may continue working but we don't run tests there anymore so we can't be sure. (#2682)
- ✨ Improvements to building rust wheels on Android (#2650) - ✨ Improvements to building rust wheels on Android (#2650)
- 🛠 Update Pyodide to 0.29.3 (#2719, #2733)
- 🐛 Fix bug with the GitHub Action on Windows, where PATH was getting unnecessarily changed, causing issues with meson builds. (#2723) - 🐛 Fix bug with the GitHub Action on Windows, where PATH was getting unnecessarily changed, causing issues with meson builds. (#2723)
- ✨ Add support for quiet setting on `build` and `uv` from the cibuildwheel `build-verbosity` setting. (#2737) - ✨ Add support for quiet setting on `build` and `uv` from the cibuildwheel `build-verbosity` setting. (#2737)
- 📚 Docs updates, including guidance on using Meson on Windows (#2718) - 📚 Docs updates, including guidance on using Meson on Windows (#2718)
@@ -207,12 +167,6 @@ See @henryiii's [release post](https://iscinumpy.dev/post/cibuildwheel-3-0-0/) f
- 📚 Docs now primarily use the pyproject.toml name of options, rather than the environment variable name. (#2389) - 📚 Docs now primarily use the pyproject.toml name of options, rather than the environment variable name. (#2389)
- 📚 README table now matches docs and auto-updates. (#2427, #2428) - 📚 README table now matches docs and auto-updates. (#2427, #2428)
### v2.23.4
_16 March 2026_
- 🐛 Fix HTTP 429 errors while downloading virtualenv from GitHub blob URLs uses a https://bootstrap.pypa.io/ URL instead. (#2775)
### v2.23.3 ### v2.23.3
_26 April 2025_ _26 April 2025_
+14 -4
View File
@@ -103,6 +103,20 @@ Commit this file, and push to Gitlab. The pipeline should start automatically.
Gitlab will store the built wheels for you - you can access them from the Pipelines view. Check out the Gitlab [docs](https://docs.gitlab.com/ee/ci/yaml/) for more info on this config file. Gitlab will store the built wheels for you - you can access them from the Pipelines view. Check out the Gitlab [docs](https://docs.gitlab.com/ee/ci/yaml/) for more info on this config file.
### Cirrus CI [linux/mac/windows] {: #cirrus-ci}
To build Linux, Mac, and Windows wheels on Cirrus CI, create a `.cirrus.yml` file in your repo,
> .cirrus.yml
```yaml
{% include "../examples/cirrus-ci-minimal.yml" %}
```
Commit this file, enable building of your repo on Cirrus CI, and push.
Cirrus CI will store the built wheels for you - you can access them from the individual task view. Check out the Cirrus CI [docs](https://cirrus-ci.org/guide/writing-tasks/) for more info on this config file.
> ⚠️ Got an error? Check the [FAQ](faq.md). > ⚠️ Got an error? Check the [FAQ](faq.md).
### Other CI services ### Other CI services
@@ -111,10 +125,6 @@ Gitlab will store the built wheels for you - you can access them from the Pipeli
Appveyor official support was dropped in cibuildwheel v3.0, due to a lack of CI credits. However, it can probably still be used as-is. Check the Appveyor example from the cibuildwheel v2.0 branch: [appveyor-minimal.yml](https://github.com/pypa/cibuildwheel/blob/v2.23.3/examples/appveyor-minimal.yml). Appveyor official support was dropped in cibuildwheel v3.0, due to a lack of CI credits. However, it can probably still be used as-is. Check the Appveyor example from the cibuildwheel v2.0 branch: [appveyor-minimal.yml](https://github.com/pypa/cibuildwheel/blob/v2.23.3/examples/appveyor-minimal.yml).
### Cirrus CI [linux/mac/windows] {: #cirrus-ci}
Due to Cirrus CI [end-of-life](https://cirruslabs.org) on June 1, 2026, we no longer officially support this service.
## Next steps ## Next steps
Once you've got the wheel building successfully, you might want to set up [testing](options.md#test-command) or [automatic releases to PyPI](deliver-to-pypi.md#automatic-method). Once you've got the wheel building successfully, you might want to set up [testing](options.md#test-command) or [automatic releases to PyPI](deliver-to-pypi.md#automatic-method).
+14 -4
View File
@@ -53,6 +53,16 @@ cibuildwheel to run tests, add the following YAML to your CI config file:
CIBW_TEST_COMMAND: "pytest {project}/tests" CIBW_TEST_COMMAND: "pytest {project}/tests"
``` ```
!!! tab "Cirrus CI"
> .cirrus.yml ([docs](https://cirrus-ci.org/guide/writing-tasks/#environment-variables))
```yaml
env:
CIBW_TEST_REQUIRES: pytest
CIBW_TEST_COMMAND: "pytest {project}/tests"
```
## Configuration file {: #configuration-file} ## Configuration file {: #configuration-file}
You can configure cibuildwheel with a config file, such as `pyproject.toml`. You can configure cibuildwheel with a config file, such as `pyproject.toml`.
@@ -132,17 +142,17 @@ which means it can be given multiple times.
manylinux-x86_64-image = "manylinux_2_34" manylinux-x86_64-image = "manylinux_2_34"
[[tool.cibuildwheel.overrides]] [[tool.cibuildwheel.overrides]]
select = "cp39-*" select = "cp38-*"
manylinux-x86_64-image = "manylinux2014" manylinux-x86_64-image = "manylinux2014"
[[tool.cibuildwheel.overrides]] [[tool.cibuildwheel.overrides]]
select = "cp3{10,11}-*" select = "cp3{9,10}-*"
manylinux-x86_64-image = "manylinux_2_28" manylinux-x86_64-image = "manylinux_2_28"
``` ```
This example will build CPython 3.9 wheels on manylinux2014, CPython 3.10-3.11 This example will build CPython 3.8 wheels on manylinux2014, CPython 3.9-3.10
wheels on manylinux_2_28, and manylinux_2_34 wheels for any newer Python wheels on manylinux_2_28, and manylinux_2_34 wheels for any newer Python
(like 3.14). (like 3.10).
```toml ```toml
[tool.cibuildwheel] [tool.cibuildwheel]

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