Compare commits

..
Author SHA1 Message Date
Joe Rickerby 50fef6ffbf Remove the DOCKER_HOST variable from gitlab configs 2024-09-06 13:26:01 +01:00
173 changed files with 4568 additions and 8104 deletions
+29 -34
View File
@@ -1,30 +1,7 @@
version: 2.1 version: 2
commands:
cibw_prepare_environment:
description: "Prepare the environment for testing."
steps:
- run:
name: Prepare the environment.
command: bash .circleci/prepare.sh
cibw_run_tests:
description: "Runs tests, with CIBW_ENABLE=all on the main branch"
steps:
- run:
name: Test
command: |
if [ "${CIRCLE_BRANCH}" == "main" ]; then
echo "INFO: Exporting CIBW_ENABLE=all for main branch test run."
export CIBW_ENABLE=all
else
echo "INFO: CIBW_ENABLE not set for this branch test run."
fi
venv/bin/python ./bin/run_tests.py
no_output_timeout: 30m
jobs: jobs:
osx-python312: osx-python3.12:
macos: macos:
xcode: 15.4.0 xcode: 15.4.0
resource_class: macos.m1.medium.gen1 resource_class: macos.m1.medium.gen1
@@ -32,10 +9,16 @@ jobs:
PYTHON: python3 PYTHON: python3
steps: steps:
- checkout - checkout
- cibw_prepare_environment
- cibw_run_tests
linux-python312: - run:
name: Prepare the environment.
command: bash .circleci/prepare.sh
- run:
name: Test.
command: venv/bin/python ./bin/run_tests.py
no_output_timeout: 30m
linux-python3.12:
docker: docker:
- image: cimg/python:3.12 - image: cimg/python:3.12
environment: environment:
@@ -46,8 +29,14 @@ jobs:
steps: steps:
- checkout - checkout
- setup_remote_docker - setup_remote_docker
- cibw_prepare_environment
- cibw_run_tests - run:
name: Prepare the environment.
command: bash .circleci/prepare.sh
- run:
name: Test.
command: venv/bin/python ./bin/run_tests.py
no_output_timeout: 30m
linux-aarch64: linux-aarch64:
machine: machine:
@@ -60,13 +49,19 @@ jobs:
PYTEST_ADDOPTS: -k "unit_test or main_tests or test_0_basic or test_docker_images" PYTEST_ADDOPTS: -k "unit_test or main_tests or test_0_basic or test_docker_images"
steps: steps:
- checkout - checkout
- cibw_prepare_environment
- cibw_run_tests - run:
name: Prepare the environment.
command: bash .circleci/prepare.sh
- run:
name: Test.
command: venv/bin/python ./bin/run_tests.py
no_output_timeout: 30m
workflows: workflows:
version: 2 version: 2
all-tests: all-tests:
jobs: jobs:
- osx-python312 - osx-python3.12
- linux-python312 - linux-python3.12
- linux-aarch64 - linux-aarch64
+2 -4
View File
@@ -4,13 +4,11 @@ set -o xtrace
if [ "$(uname -s)" == "Darwin" ]; then if [ "$(uname -s)" == "Darwin" ]; then
sudo softwareupdate --install-rosetta --agree-to-license sudo softwareupdate --install-rosetta --agree-to-license
else
docker run --rm --privileged docker.io/tonistiigi/binfmt:latest --install all
fi fi
$PYTHON --version $PYTHON --version
$PYTHON -m venv venv $PYTHON -m venv venv
venv/bin/python -m pip install -U pip dependency-groups venv/bin/python -m pip install -U pip
venv/bin/python -m dependency_groups test | xargs venv/bin/python -m pip install -e. venv/bin/python -m pip install -e ".[dev]"
venv/bin/python -m pip freeze venv/bin/python -m pip freeze
venv/bin/python --version venv/bin/python --version
+16 -30
View File
@@ -2,11 +2,11 @@ only_if: changesInclude('.cirrus.yml') || ($CIRRUS_BRANCH == "main" && !changesI
run_tests: &RUN_TESTS run_tests: &RUN_TESTS
install_cibuildwheel_script: install_cibuildwheel_script:
- python -m pip install dependency-groups - python -m pip install -e ".[dev]" pytest-custom-exit-code
- python -m dependency_groups test | xargs python -m pip install -e.
run_cibuildwheel_tests_script: run_cibuildwheel_tests_script:
- python ./bin/run_tests.py - python ./bin/run_tests.py
linux_x86_task: linux_x86_task:
timeout_in: 120m timeout_in: 120m
compute_engine_instance: compute_engine_instance:
@@ -15,15 +15,9 @@ linux_x86_task:
platform: linux platform: linux
cpu: 8 cpu: 8
memory: 8G memory: 8G
env:
VENV_ROOT: ${HOME}/venv-cibuildwheel
PATH: ${VENV_ROOT}/bin:${PATH}
install_pre_requirements_script: install_pre_requirements_script:
- docker run --rm --privileged docker.io/tonistiigi/binfmt:latest --install all - apt install -y python3-venv python-is-python3
- 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 <<: *RUN_TESTS
linux_aarch64_task: linux_aarch64_task:
@@ -34,15 +28,9 @@ linux_aarch64_task:
platform: linux platform: linux
cpu: 4 cpu: 4
memory: 4G memory: 4G
env:
VENV_ROOT: ${HOME}/venv-cibuildwheel
PATH: ${VENV_ROOT}/bin:${PATH}
install_pre_requirements_script: install_pre_requirements_script:
- docker run --rm --privileged docker.io/tonistiigi/binfmt:latest --install all - apt install -y python3-venv python-is-python3
- 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 <<: *RUN_TESTS
windows_x86_task: windows_x86_task:
@@ -55,32 +43,30 @@ windows_x86_task:
memory: 8G memory: 8G
install_pre_requirements_script: install_pre_requirements_script:
- choco install -y --no-progress python3 --version 3.12.4 - choco install -y --no-progress python3 --version 3.10.6
- refreshenv - refreshenv
- echo PATH=%PATH% >> "%CIRRUS_ENV%" - echo PATH=%PATH% >> "%CIRRUS_ENV%"
<<: *RUN_TESTS <<: *RUN_TESTS
macos_arm64_task: macos_arm64_task:
macos_instance: macos_instance:
image: ghcr.io/cirruslabs/macos-runner:sequoia image: ghcr.io/cirruslabs/macos-sonoma-xcode
env: env:
VENV_ROOT: ${HOME}/venv-cibuildwheel PATH: /opt/homebrew/opt/python@3.10/libexec/bin:$PATH
PATH: ${VENV_ROOT}/bin:${PATH}
install_pre_requirements_script: install_pre_requirements_script:
- brew install python@3.12 - brew install python@3.10
- python3.12 -m venv ${VENV_ROOT}
<<: *RUN_TESTS <<: *RUN_TESTS
macos_arm64_cp38_task: macos_arm64_cp38_task:
macos_instance: macos_instance:
image: ghcr.io/cirruslabs/macos-runner:sequoia image: ghcr.io/cirruslabs/macos-sonoma-xcode
env: env:
VENV_ROOT: ${HOME}/venv-cibuildwheel PATH: /opt/homebrew/opt/python@3.10/libexec/bin:$PATH
PATH: ${VENV_ROOT}/bin:${PATH} PYTEST_ADDOPTS: --run-cp38-universal2 -k 'test_cp38_arm64_testing_universal2_installer or test_arch_auto'
PYTEST_ADDOPTS: --run-cp38-universal2 -k 'test_cp38_arm64_testing_universal2_installer or test_arch_auto or test_dummy_serial'
install_pre_requirements_script: install_pre_requirements_script:
- brew install python@3.12 - brew install python@3.10
- python3.12 -m venv ${VENV_ROOT}
- curl -fsSLO https://www.python.org/ftp/python/3.8.10/python-3.8.10-macos11.pkg - 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 / - sudo installer -pkg python-3.8.10-macos11.pkg -target /
- rm python-3.8.10-macos11.pkg - rm python-3.8.10-macos11.pkg
+13 -5
View File
@@ -13,7 +13,16 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: hynek/build-and-inspect-python-package@v2 - name: Build SDist and wheel
run: pipx run build
- uses: actions/upload-artifact@v4
with:
name: cibw-sdist
path: dist/*
- name: Check metadata
run: pipx run twine check dist/*
publish: publish:
needs: [dist] needs: [dist]
@@ -29,14 +38,13 @@ jobs:
steps: steps:
- uses: actions/download-artifact@v4 - uses: actions/download-artifact@v4
with: with:
name: Packages pattern: cibw-*
path: dist path: dist
merge-multiple: true
- name: Generate artifact attestation for sdist and wheel - name: Generate artifact attestation for sdist and wheel
uses: actions/attest-build-provenance@db473fddc028af60658334401dc6fa3ffd8669fd # v2.3.0 uses: actions/attest-build-provenance@6149ea5740be74af77f260b9db67e633f6b0a9a1 # v1.4.2
with: with:
subject-path: "dist/cibuildwheel-*" subject-path: "dist/cibuildwheel-*"
- uses: pypa/gh-action-pypi-publish@release/v1 - uses: pypa/gh-action-pypi-publish@release/v1
with:
attestations: true
+25 -54
View File
@@ -4,13 +4,7 @@ on:
push: push:
branches: branches:
- main - main
- 2.x
pull_request: pull_request:
types:
- opened
- synchronize
- reopened
- labeled
paths-ignore: paths-ignore:
- 'docs/**' - 'docs/**'
- .pre-commit-config.yaml - .pre-commit-config.yaml
@@ -32,6 +26,8 @@ jobs:
with: with:
python-version: "3.x" python-version: "3.x"
- uses: pre-commit/action@v3.0.1 - uses: pre-commit/action@v3.0.1
- name: Check manifest
run: pipx run --python "${{ steps.python.outputs.python-path }}" nox -s check_manifest
- name: PyLint checks - name: PyLint checks
run: pipx run --python "${{ steps.python.outputs.python-path }}" nox -s pylint -- --output-format=github run: pipx run --python "${{ steps.python.outputs.python-path }}" nox -s pylint -- --output-format=github
@@ -41,13 +37,11 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, windows-11-arm, macos-13, macos-15] os: [ubuntu-latest, windows-latest, macos-13, macos-14]
python_version: ['3.13'] python_version: ['3.12']
include: include:
- os: ubuntu-latest - os: ubuntu-latest
python_version: '3.11' python_version: '3.8'
- os: ubuntu-latest
python_version: '3.14'
timeout-minutes: 180 timeout-minutes: 180
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -55,9 +49,8 @@ jobs:
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
- uses: astral-sh/setup-uv@v6 - uses: yezz123/setup-uv@v4
# free some space to prevent reaching GHA disk space limits # free some space to prevent reaching GHA disk space limits
- name: Clean docker images - name: Clean docker images
@@ -66,38 +59,13 @@ jobs:
docker system prune -a -f docker system prune -a -f
df -h df -h
# for oci_container unit tests
- name: Set up QEMU
if: runner.os == 'Linux'
uses: docker/setup-qemu-action@v3
- name: Install dependencies - name: Install dependencies
run: | run: |
uv sync --no-dev --group test uv pip install --system ".[test]"
- uses: joerick/pr-labels-action@v1.0.9
- name: Set CIBW_ENABLE
shell: bash
run: |
if [[ "${{ github.ref_name }}" == "main" ]]; then
CIBW_ENABLE=all
else
# get the default CIBW_ENABLE value from the test module
CIBW_ENABLE=$(uv run --no-sync python -c 'import sys, test.utils as c; sys.stdout.write(c.DEFAULT_CIBW_ENABLE)')
# if this is a PR, check for labels
if [[ -n "$GITHUB_PR_LABEL_CI_PYPY" ]]; then
CIBW_ENABLE+=" pypy"
fi
if [[ -n "$GITHUB_PR_LABEL_CI_GRAALPY" ]]; then
CIBW_ENABLE+=" graalpy"
fi
fi
echo "CIBW_ENABLE=${CIBW_ENABLE}" >> $GITHUB_ENV
- name: Generate a sample project - name: Generate a sample project
run: | run: |
uv run --no-sync -m test.test_projects test.test_0_basic.basic_project sample_proj python -m test.test_projects test.test_0_basic.basic_project sample_proj
- name: Run a sample build (GitHub Action) - name: Run a sample build (GitHub Action)
uses: ./ uses: ./
@@ -107,13 +75,15 @@ jobs:
env: env:
CIBW_ARCHS_MACOS: x86_64 universal2 arm64 CIBW_ARCHS_MACOS: x86_64 universal2 arm64
CIBW_BUILD_FRONTEND: 'build[uv]' CIBW_BUILD_FRONTEND: 'build[uv]'
CIBW_FREE_THREADED_SUPPORT: 1
CIBW_PRERELEASE_PYTHONS: 1
- name: Run a sample build (GitHub Action, only) - name: Run a sample build (GitHub Action, only)
uses: ./ uses: ./
with: with:
package-dir: sample_proj package-dir: sample_proj
output-dir: wheelhouse_only output-dir: wheelhouse_only
only: cp312-${{ runner.os == 'Linux' && (runner.arch == 'ARM64' && 'manylinux_aarch64' || 'manylinux_x86_64') || (runner.os == 'Windows' && 'win_amd64' || 'macosx_x86_64') }} only: cp312-${{ runner.os == 'Linux' && 'manylinux_x86_64' || (runner.os == 'Windows' && 'win_amd64' || 'macosx_x86_64') }}
- name: Create custom configuration file - name: Create custom configuration file
shell: bash shell: bash
@@ -134,7 +104,7 @@ jobs:
output-dir: wheelhouse_config_file output-dir: wheelhouse_config_file
config-file: sample_proj/cibw.toml config-file: sample_proj/cibw.toml
- name: Check Action artifacts - name: Check Action artefacts
shell: bash shell: bash
run: | run: |
test $(find wheelhouse -name '*.whl' | wc -l) -ge 1 test $(find wheelhouse -name '*.whl' | wc -l) -ge 1
@@ -148,7 +118,7 @@ jobs:
- name: Test cibuildwheel - name: Test cibuildwheel
run: | run: |
uv run --no-sync bin/run_tests.py ${{ (runner.os == 'Linux' && runner.arch == 'X64') && '--run-podman' || '' }} python ./bin/run_tests.py --run-podman
emulated-archs: emulated-archs:
name: Get qemu emulated architectures name: Get qemu emulated architectures
@@ -161,13 +131,12 @@ jobs:
- uses: actions/setup-python@v5 - uses: actions/setup-python@v5
with: with:
python-version: "3.x" python-version: "3.x"
- uses: astral-sh/setup-uv@v6
- name: Install dependencies - name: Install dependencies
run: uv sync --no-dev --group test run: python -m pip install ".[test]"
- name: Get qemu emulated architectures - name: Get qemu emulated architectures
id: archs id: archs
run: | run: |
OUTPUT=$(uv run --no-sync python -c "from json import dumps; from test.utils import EMULATED_ARCHS; print(dumps(EMULATED_ARCHS))") OUTPUT=$(python -c "from json import dumps; from test.utils import EMULATED_ARCHS; print(dumps(EMULATED_ARCHS))")
echo "${OUTPUT}" echo "${OUTPUT}"
echo "archs=${OUTPUT}" >> "$GITHUB_OUTPUT" echo "archs=${OUTPUT}" >> "$GITHUB_OUTPUT"
@@ -184,18 +153,20 @@ jobs:
- uses: actions/setup-python@v5 - uses: actions/setup-python@v5
with: with:
python-version: "3.x" python-version: "3.x"
- uses: astral-sh/setup-uv@v6
- name: Install dependencies - name: Install dependencies
run: uv sync --no-dev --group test run: python -m pip install ".[test,uv]"
- name: Set up QEMU - name: Set up QEMU
id: qemu
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v3
with:
platforms: all
- name: Run the emulation tests - name: Run the emulation tests
run: uv run --no-sync pytest --run-emulation ${{ matrix.arch }} test/test_emulation.py run: pytest --run-emulation ${{ matrix.arch }} test/test_emulation.py
test-pyodide: test-pyodide:
name: Test cibuildwheel building Pyodide wheels name: Test cibuildwheel building pyodide wheels
needs: lint needs: lint
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
timeout-minutes: 180 timeout-minutes: 180
@@ -205,14 +176,14 @@ jobs:
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@v6
- name: Install dependencies - name: Install dependencies
run: uv sync --no-dev --group test run: |
python -m pip install ".[test]"
- name: Generate a sample project - name: Generate a sample project
run: | run: |
uv run --no-sync -m test.test_projects test.test_0_basic.basic_project sample_proj python -m test.test_projects test.test_0_basic.basic_project sample_proj
- name: Run a sample build (GitHub Action) - name: Run a sample build (GitHub Action)
uses: ./ uses: ./
@@ -224,6 +195,6 @@ jobs:
- name: Run tests with 'CIBW_PLATFORM' set to 'pyodide' - name: Run tests with 'CIBW_PLATFORM' set to 'pyodide'
run: | run: |
uv run --no-sync ./bin/run_tests.py python ./bin/run_tests.py
env: env:
CIBW_PLATFORM: pyodide CIBW_PLATFORM: pyodide
+15 -13
View File
@@ -20,19 +20,11 @@ jobs:
if: github.repository_owner == 'pypa' || github.event_name != 'schedule' if: github.repository_owner == 'pypa' || github.event_name != 'schedule'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
# we use this step to grab a Github App auth token, so that PRs generated by this workflow
# run the GHA tests.
- uses: actions/create-github-app-token@v2
id: generate-token
if: github.ref == 'refs/heads/main' && github.repository == 'pypa/cibuildwheel'
with:
app-id: ${{ secrets.CIBUILDWHEEL_BOT_APP_ID }}
private-key: ${{ secrets.CIBUILDWHEEL_BOT_APP_PRIVATE_KEY }}
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: wntrblm/nox@2025.05.01 - uses: wntrblm/nox@2024.04.15
with:
python-versions: "3.7, 3.8, 3.9, 3.10, 3.11, 3.12, 3.13"
- name: "Run update: dependencies" - name: "Run update: dependencies"
run: nox --force-color -s update_constraints run: nox --force-color -s update_constraints
@@ -41,9 +33,18 @@ jobs:
- name: "Run update: docs user projects" - name: "Run update: docs user projects"
run: nox --force-color -s update_proj -- --auth=${{ secrets.GITHUB_TOKEN }} run: nox --force-color -s update_proj -- --auth=${{ secrets.GITHUB_TOKEN }}
# we use this step to grab a Github App auth token, so that PRs generated by this workflow
# run the GHA tests.
- uses: tibdex/github-app-token@v2
id: generate-token
if: github.ref == 'refs/heads/main' && github.repository == 'pypa/cibuildwheel'
with:
app_id: ${{ secrets.CIBUILDWHEEL_BOT_APP_ID }}
private_key: ${{ secrets.CIBUILDWHEEL_BOT_APP_PRIVATE_KEY }}
- 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@v7 uses: peter-evans/create-pull-request@v6
with: with:
commit-message: Update dependencies commit-message: Update dependencies
title: '[Bot] Update dependencies' title: '[Bot] Update dependencies'
@@ -52,6 +53,7 @@ jobs:
PR generated by "Update dependencies" [workflow](https://github.com/${{github.repository}}/actions/runs/${{github.run_id}}). PR generated by "Update dependencies" [workflow](https://github.com/${{github.repository}}/actions/runs/${{github.run_id}}).
branch: update-dependencies-pr branch: update-dependencies-pr
sign-commits: true committer: "cibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com>"
author: "cibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com>"
token: ${{ steps.generate-token.outputs.token }} token: ${{ steps.generate-token.outputs.token }}
delete-branch: true delete-branch: true
+6 -1
View File
@@ -84,7 +84,9 @@ celerybeat-schedule
# virtualenv # virtualenv
.venv .venv
venv*/ venv/
venv3/
venv2/
ENV/ ENV/
env/ env/
env2/ env2/
@@ -110,5 +112,8 @@ all_known_setup.yaml
# mkdocs # mkdocs
site/ site/
# Virtual environments
venv*
# PyCharm # PyCharm
.idea/ .idea/
+3 -20
View File
@@ -5,7 +5,6 @@ linux:
entrypoint: ["env", "-u", "DOCKER_HOST"] entrypoint: ["env", "-u", "DOCKER_HOST"]
command: ["dockerd-entrypoint.sh"] command: ["dockerd-entrypoint.sh"]
variables: variables:
DOCKER_HOST: tcp://docker:2375/
DOCKER_DRIVER: overlay2 DOCKER_DRIVER: overlay2
# See https://github.com/docker-library/docker/pull/166 # See https://github.com/docker-library/docker/pull/166
DOCKER_TLS_CERTDIR: "" DOCKER_TLS_CERTDIR: ""
@@ -13,15 +12,9 @@ linux:
# skip all but the basic tests # skip all but the basic tests
# (comment the below line in a PR to debug a Gitlab-specific issue) # (comment the below line in a PR to debug a Gitlab-specific issue)
PYTEST_ADDOPTS: -k "unit_test or test_0_basic" --suppress-no-test-exit-code PYTEST_ADDOPTS: -k "unit_test or test_0_basic" --suppress-no-test-exit-code
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
variables:
CIBW_ENABLE: "all"
script: script:
- curl -sSL https://get.docker.com/ | sh - curl -sSL https://get.docker.com/ | sh
- docker run --rm --privileged docker.io/tonistiigi/binfmt:latest --install all - python -m pip install -e ".[dev]" pytest-custom-exit-code
- python -m pip install dependency-groups
- python -m dependency_groups test | xargs python -m pip install -e. pytest-custom-exit-code
- python ./bin/run_tests.py - python ./bin/run_tests.py
windows: windows:
@@ -30,13 +23,8 @@ windows:
PYTEST_ADDOPTS: -k "unit_test or test_0_basic" --suppress-no-test-exit-code PYTEST_ADDOPTS: -k "unit_test or test_0_basic" --suppress-no-test-exit-code
before_script: before_script:
- choco install python -y --version 3.12.4 - choco install python -y --version 3.12.4
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
variables:
CIBW_ENABLE: "all"
script: script:
- py -m pip install dependency-groups - py -m pip install -e ".[dev]" pytest-custom-exit-code
- py -m pip install -e. pytest-custom-exit-code $(py -m dependency_groups test)
- py bin\run_tests.py - py bin\run_tests.py
tags: tags:
- saas-windows-medium-amd64 - saas-windows-medium-amd64
@@ -45,13 +33,8 @@ macos:
image: macos-14-xcode-15 image: macos-14-xcode-15
variables: variables:
PYTEST_ADDOPTS: -k "unit_test or test_0_basic" --suppress-no-test-exit-code PYTEST_ADDOPTS: -k "unit_test or test_0_basic" --suppress-no-test-exit-code
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
variables:
CIBW_ENABLE: "all"
script: script:
- python3 -m pip install dependency-groups - python3 -m pip install -e ".[dev]" pytest-custom-exit-code
- python3 -m dependency_groups test | xargs python3 -m pip install -e. pytest-custom-exit-code
- python3 ./bin/run_tests.py - python3 ./bin/run_tests.py
tags: tags:
- saas-macos-medium-m1 - saas-macos-medium-m1
+12 -15
View File
@@ -1,7 +1,7 @@
repos: repos:
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0 rev: v4.6.0
hooks: hooks:
- id: check-case-conflict - id: check-case-conflict
- id: check-merge-conflict - id: check-merge-conflict
@@ -14,26 +14,24 @@ repos:
- id: trailing-whitespace - id: trailing-whitespace
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.11.9 rev: v0.6.3
hooks: hooks:
- id: ruff - id: ruff
args: ["--fix", "--show-fixes"] 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: v1.15.0 rev: v1.11.2
hooks: hooks:
- id: mypy - id: mypy
name: mypy 3.11 on cibuildwheel/ name: mypy 3.8 on cibuildwheel/
args: ["--python-version=3.11"] exclude: ^cibuildwheel/resources/.*py|bin/generate_schema.py$
args: ["--python-version=3.8"]
additional_dependencies: &mypy-dependencies additional_dependencies: &mypy-dependencies
- bracex - bracex
- dependency-groups>=1.2 - nox
- nox>=2025.2.9
- orjson
- packaging - packaging
- pygithub - pygithub
- pytest
- rich - rich
- tomli - tomli
- tomli_w - tomli_w
@@ -42,13 +40,12 @@ repos:
- types-jinja2 - types-jinja2
- types-pyyaml - types-pyyaml
- types-requests - types-requests
- types-setuptools
- uv - uv
- validate-pyproject - validate-pyproject
- id: mypy - id: mypy
name: mypy 3.13 name: mypy 3.12
exclude: ^cibuildwheel/resources/.*py$ exclude: ^cibuildwheel/resources/.*py$
args: ["--python-version=3.13"] args: ["--python-version=3.12"]
additional_dependencies: *mypy-dependencies additional_dependencies: *mypy-dependencies
- repo: https://github.com/shellcheck-py/shellcheck-py - repo: https://github.com/shellcheck-py/shellcheck-py
@@ -72,15 +69,15 @@ repos:
files: ^docs/changelog.md$ files: ^docs/changelog.md$
- repo: https://github.com/codespell-project/codespell - repo: https://github.com/codespell-project/codespell
rev: v2.4.1 rev: v2.3.0
hooks: hooks:
- id: codespell - id: codespell
args: ["-L", "sur,assertin,hep", "-w"] args: ["-L", "sur,assertin", "-w"]
exclude: ^docs/working-examples\.md$ # Autogenerated exclude: ^docs/working-examples\.md$ # Autogenerated
- repo: https://github.com/python-jsonschema/check-jsonschema - repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.33.0 rev: 0.29.2
hooks: hooks:
- id: check-dependabot - id: check-dependabot
- id: check-github-actions - id: check-github-actions
+3 -1
View File
@@ -10,4 +10,6 @@ build:
- asdf plugin add uv - asdf plugin add uv
- asdf install uv latest - asdf install uv latest
- asdf global uv latest - asdf global uv latest
- NO_COLOR=1 uv run --no-dev --group docs mkdocs build --strict --site-dir $READTHEDOCS_OUTPUT/html - uv venv
- uv pip install -e.[docs]
- NO_COLOR=1 .venv/bin/mkdocs build --strict --site-dir $READTHEDOCS_OUTPUT/html
+20 -30
View File
@@ -1,33 +1,28 @@
os: linux os: linux
dist: jammy dist: focal
language: python language: python
branches: branches:
only: only:
- main - main
# only build the main branch or PR that explicitely want to test with Travis CI
if: (type = "push") OR (commit_message =~ /travis-ci/)
jobs: jobs:
include: include:
- name: Linux | x86_64 + i686 | Python 3.12 - name: Linux | x86_64 + i686 | Python 3.9
python: 3.12 python: 3.9
services: docker services: docker
env: env: PYTHON=python
- PYTHON=python
- CIBW_ENABLE=all
- name: Linux | arm64 | Python 3.12 - name: Linux | arm64 | Python 3.9
python: 3.12 python: 3.9
services: docker services: docker
arch: arm64 arch: arm64-graviton2
env: group: edge
- PYTHON=python virt: vm
- CIBW_ENABLE=all env: PYTHON=python
- name: Linux | ppc64le | Python 3.12 - name: Linux | ppc64le | Python 3.9
python: 3.12 python: 3.9
services: docker services: docker
arch: ppc64le arch: ppc64le
allow_failure: True allow_failure: True
@@ -36,30 +31,25 @@ jobs:
# skip test_manylinuxXXXX_only, it uses too much disk space # skip test_manylinuxXXXX_only, it uses too much disk space
# c.f. https://travis-ci.community/t/running-out-of-disk-space-quota-when-using-docker-on-ppc64le/11634 # c.f. https://travis-ci.community/t/running-out-of-disk-space-quota-when-using-docker-on-ppc64le/11634
- PYTEST_ADDOPTS='-k "not test_manylinuxXXXX_only"' - PYTEST_ADDOPTS='-k "not test_manylinuxXXXX_only"'
- CIBW_ENABLE=all
- name: Windows | x86_64 | Python 3.12 - name: Windows | x86_64 | Python 3.9
os: windows os: windows
language: shell language: shell
before_install: before_install:
- choco upgrade python3 -y --version 3.12.8 --limit-output --params "/InstallDir:C:\\Python312" - choco upgrade python3 -y --version 3.9.13 --limit-output --params "/InstallDir:C:\\Python39"
env: env:
- PYTHON=C:\\Python312\\python - PYTHON=C:\\Python39\\python
- CIBW_ENABLE=all
- name: Linux | s390x | Python 3.12 - name: Linux | s390x | Python 3.9
python: 3.12 python: 3.9
services: docker services: docker
arch: s390x arch: s390x
allow_failure: True allow_failure: True
env: env: PYTHON=python
- PYTHON=python
- CIBW_ENABLE=all
install: install:
- if [ "${TRAVIS_OS_NAME}" == "linux" ]; then docker run --rm --privileged docker.io/tonistiigi/binfmt:latest --install all; fi - $PYTHON -m pip install -U pip
- $PYTHON -m pip install -U pip dependency-groups - $PYTHON -m pip install -e ".[dev]" pytest-custom-exit-code
- $PYTHON -m dependency_groups test | xargs $PYTHON -m pip install -e.
script: | script: |
# travis_wait disable the output while waiting # travis_wait disable the output while waiting
+6 -6
View File
@@ -1,11 +1,11 @@
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.8 | 3.9 | 3.10 | 3.11 | 3.12 |
|---------|----------------------------------|---------------------------------------------|----------------| |---------|----------------------------------|-----------|-----------|---------|--------------------------------------------------|
| Linux | Azure Pipelines / GitHub Actions | CircleCI¹ / Cirrus CI / GitLab¹ / Travis CI | GitHub Actions | | Linux | Azure Pipelines / GitHub Actions | Travis CI | Cirrus CI | | AppVeyor¹ / CircleCI¹ / GitHub Actions / GitLab¹ |
| macOS | Azure Pipelines | CircleCI¹ / Cirrus CI / GitLab¹ | GitHub Actions | | macOS | Azure Pipelines | | Cirrus CI | GitLab¹ | AppVeyor¹ /CircleCI¹ / GitHub Actions |
| Windows | Azure Pipelines | Cirrus CI / GitLab¹ / Travis CI | GitHub Actions | | Windows | Azure Pipelines | Travis CI | Cirrus CI | | AppVeyor¹ / GitHub Actions / GitLab¹ |
> ¹ Runs a reduced set of tests to reduce CI load > ¹ Runs a reduced set of tests to reduce CI load
Non-x86 architectures are covered on Travis CI using Python 3.12. Non-x86 architectures are covered on Travis CI using Python 3.9.
+1 -1
View File
@@ -1,6 +1,6 @@
This project is licensed under the 'BSD 2-clause license'. This project is licensed under the 'BSD 2-clause license'.
Copyright (c) 2017-2025, Joe Rickerby and contributors. All rights reserved. Copyright (c) 2017-2023, Joe Rickerby and contributors. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: modification, are permitted provided that the following conditions are met:
+81 -69
View File
@@ -4,7 +4,8 @@ cibuildwheel
[![PyPI](https://img.shields.io/pypi/v/cibuildwheel.svg)](https://pypi.python.org/pypi/cibuildwheel) [![PyPI](https://img.shields.io/pypi/v/cibuildwheel.svg)](https://pypi.python.org/pypi/cibuildwheel)
[![Documentation Status](https://readthedocs.org/projects/cibuildwheel/badge/?version=stable)](https://cibuildwheel.pypa.io/en/stable/?badge=stable) [![Documentation Status](https://readthedocs.org/projects/cibuildwheel/badge/?version=stable)](https://cibuildwheel.pypa.io/en/stable/?badge=stable)
[![Actions Status](https://github.com/pypa/cibuildwheel/workflows/Test/badge.svg)](https://github.com/pypa/cibuildwheel/actions) [![Actions Status](https://github.com/pypa/cibuildwheel/workflows/Test/badge.svg)](https://github.com/pypa/cibuildwheel/actions)
[![Travis Status](https://img.shields.io/travis/com/pypa/cibuildwheel/main?logo=travis)](https://travis-ci.com/github/pypa/cibuildwheel) [![Travis Status](https://img.shields.io/travis/com/pypa/cibuildwheel/main?logo=travis)](https://travis-ci.com/pypa/cibuildwheel)
[![Appveyor status](https://ci.appveyor.com/api/projects/status/gt3vwl88yt0y3hur/branch/main?svg=true)](https://ci.appveyor.com/project/joerick/cibuildwheel/branch/main)
[![CircleCI Status](https://img.shields.io/circleci/build/gh/pypa/cibuildwheel/main?logo=circleci)](https://circleci.com/gh/pypa/cibuildwheel) [![CircleCI Status](https://img.shields.io/circleci/build/gh/pypa/cibuildwheel/main?logo=circleci)](https://circleci.com/gh/pypa/cibuildwheel)
[![Azure Status](https://dev.azure.com/joerick0429/cibuildwheel/_apis/build/status/pypa.cibuildwheel?branchName=main)](https://dev.azure.com/joerick0429/cibuildwheel/_build/latest?definitionId=4&branchName=main) [![Azure Status](https://dev.azure.com/joerick0429/cibuildwheel/_apis/build/status/pypa.cibuildwheel?branchName=main)](https://dev.azure.com/joerick0429/cibuildwheel/_build/latest?definitionId=4&branchName=main)
@@ -15,37 +16,34 @@ cibuildwheel
Python wheels are great. Building them across **Mac, Linux, Windows**, on **multiple versions of Python**, is not. Python wheels are great. Building them across **Mac, Linux, Windows**, on **multiple versions of Python**, is not.
`cibuildwheel` is here to help. `cibuildwheel` runs on your CI server - currently it supports GitHub Actions, Azure Pipelines, Travis CI, CircleCI, and GitLab CI - and it builds and tests your wheels across all of your platforms. `cibuildwheel` is here to help. `cibuildwheel` runs on your CI server - currently it supports GitHub Actions, Azure Pipelines, Travis CI, AppVeyor, CircleCI, and GitLab CI - and it builds and tests your wheels across all of your platforms.
What does it do? 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: | | 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 | Pyodide |
|----------------|----|-----|-----|-----|-----|----|-----|----|-----|-----|-----|
| CPython 3.6 | ✅ | N/A | ✅ | ✅ | N/A | ✅ | ✅ | ✅ | ✅ | ✅ | N/A |
| CPython 3.7 | ✅ | N/A | ✅ | ✅ | N/A | ✅ | ✅ | ✅ | ✅ | ✅ | N/A |
| CPython 3.8 | ✅ | ✅ | ✅ | ✅ | N/A | ✅ | ✅ | ✅ | ✅ | ✅ | N/A |
| CPython 3.9 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | N/A |
| CPython 3.10 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | N/A |
| CPython 3.11 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | N/A |
| CPython 3.12 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | ✅⁴ |
| CPython 3.13³ | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | N/A |
| PyPy 3.7 v7.3 | ✅ | 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 |
| PyPy 3.9 v7.3 | ✅ | ✅ | ✅ | 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 |
| | 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 | iOS | Pyodide | <sup>¹ PyPy is only supported for manylinux wheels.</sup><br>
|----------------|----|-----|-----|-----|-----|----|-----|----|-----|-----|---|-----|-----|
| CPython 3.8 | ✅ | ✅ | ✅ | ✅ | N/A | ✅ | ✅ | ✅ | ✅ | ✅ | ✅⁵ | N/A | N/A |
| CPython 3.9 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | ✅⁵ | N/A | N/A |
| CPython 3.10 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | ✅⁵ | N/A | N/A |
| CPython 3.11 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | ✅⁵ | N/A | N/A |
| CPython 3.12 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | ✅⁵ | N/A | ✅⁴ |
| CPython 3.13³ | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | ✅⁵ | ✅ | N/A |
| CPython 3.14³ | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | ✅⁵ | ✅ | N/A |
| PyPy 3.8 v7.3 | ✅ | ✅ | ✅ | N/A | 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 |
| PyPy 3.10 v7.3 | ✅ | ✅ | ✅ | N/A | 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 |
| GraalPy 24.2 | ✅ | ✅ | ✅ | N/A | N/A | ✅¹ | N/A | ✅¹ | N/A | N/A | N/A | N/A | N/A |
<sup>¹ PyPy & GraalPy are only supported for manylinux wheels.</sup><br>
<sup>² Windows arm64 support is experimental.</sup><br> <sup>² Windows arm64 support is experimental.</sup><br>
<sup>³ Free-threaded mode requires opt-in using [`CIBW_ENABLE`](https://cibuildwheel.pypa.io/en/stable/options/#enable).</sup><br> <sup>³ CPython 3.13 is built by default using Python RCs, starting with cibuildwheel 2.20. Free-threaded mode will still require opt-in using [`CIBW_FREE_THREADED_SUPPORT`](https://cibuildwheel.pypa.io/en/stable/options/#free-threaded-support).</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>⁴ Experimental, not yet supported on PyPI, but can be used directly in web deployment. Use `--platform pyodide` to build.</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>
- Builds manylinux, musllinux, macOS 10.9+ (10.13+ for Python 3.12+), and Windows wheels for CPython, PyPy, and GraalPy - Builds manylinux, musllinux, macOS 10.9+, and Windows wheels for CPython and PyPy
- Works on GitHub Actions, Azure Pipelines, Travis CI, CircleCI, GitLab CI, and Cirrus CI - Works on GitHub Actions, Azure Pipelines, Travis CI, AppVeyor, CircleCI, GitLab CI, and Cirrus 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 and macOS through [auditwheel](https://github.com/pypa/auditwheel) and [delocate](https://github.com/matthew-brett/delocate)
- 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
@@ -56,18 +54,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 | iOS | | | Linux | macOS | Windows | Linux ARM | macOS ARM | Windows ARM |
|-----------------|-------|-------|---------|-----------|-----------|-------------|-----| |-----------------|-------|-------|---------|-----------|-----------|-------------|
| GitHub Actions | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅³ | | GitHub Actions | ✅ | ✅ | ✅ | ✅¹ | ✅ | ✅² |
| Azure Pipelines | ✅ | ✅ | ✅ | | ✅ | ✅² | ✅³ | | Azure Pipelines | ✅ | ✅ | ✅ | | ✅ | ✅² |
| Travis CI | ✅ | | ✅ | ✅ | | | | | Travis CI | ✅ | | ✅ | ✅ | | |
| CircleCI | ✅ | ✅ | | ✅ | ✅ | | ✅³ | | AppVeyor | ✅ | ✅ | | | ✅ | ✅² |
| Gitlab CI | ✅ | ✅ | | ✅¹ | ✅ | | ✅³ | | CircleCI | ✅ | ✅ | | ✅ | ✅ | |
| Cirrus CI | ✅ | ✅ | ✅ | ✅ | ✅ | | ✅³ | | Gitlab CI | ✅ | ✅ | ✅ | ✅¹ | ✅ | |
| Cirrus CI | ✅ | ✅ | ✅ | ✅ | ✅ | |
<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>¹ [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>² [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>
<sup>³ Requires a macOS runner; runs tests on the simulator for the runner's architecture.</sup>
<!--intro-end--> <!--intro-end-->
@@ -76,7 +74,6 @@ Example setup
To build manylinux, musllinux, macOS, and Windows wheels on GitHub Actions, you could use this `.github/workflows/wheels.yml`: To build manylinux, musllinux, macOS, and Windows wheels on GitHub Actions, you could use this `.github/workflows/wheels.yml`:
<!--generic-github-start-->
```yaml ```yaml
name: Build name: Build
@@ -88,7 +85,7 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-13, macos-latest] os: [ubuntu-latest, windows-latest, macos-13, macos-14]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -97,21 +94,19 @@ jobs:
- uses: actions/setup-python@v5 - uses: actions/setup-python@v5
- name: Install cibuildwheel - name: Install cibuildwheel
run: python -m pip install cibuildwheel==3.0.0b1 run: python -m pip install cibuildwheel==2.20.0
- name: Build wheels - name: Build wheels
run: python -m cibuildwheel --output-dir wheelhouse run: python -m cibuildwheel --output-dir wheelhouse
# to supply options, put them in 'env', like: # to supply options, put them in 'env', like:
# env: # env:
# CIBW_SOME_OPTION: value # CIBW_SOME_OPTION: value
# ...
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
with: with:
name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }} name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }}
path: ./wheelhouse/*.whl path: ./wheelhouse/*.whl
``` ```
<!--generic-github-end-->
For more information, including PyPI deployment, and the use of other CI services or the dedicated GitHub Action, check out the [documentation](https://cibuildwheel.pypa.io) and the [examples](https://github.com/pypa/cibuildwheel/tree/main/examples). For more information, including PyPI deployment, and the use of other CI services or the dedicated GitHub Action, check out the [documentation](https://cibuildwheel.pypa.io) and the [examples](https://github.com/pypa/cibuildwheel/tree/main/examples).
@@ -139,14 +134,12 @@ Options
| | [`CIBW_ENVIRONMENT_PASS_LINUX`](https://cibuildwheel.pypa.io/en/stable/options/#environment-pass) | Set environment variables on the host to pass-through to the container during the build. | | | [`CIBW_ENVIRONMENT_PASS_LINUX`](https://cibuildwheel.pypa.io/en/stable/options/#environment-pass) | Set environment variables on the host to pass-through to the container during the build. |
| | [`CIBW_BEFORE_ALL`](https://cibuildwheel.pypa.io/en/stable/options/#before-all) | Execute a shell command on the build system before any wheels are built. | | | [`CIBW_BEFORE_ALL`](https://cibuildwheel.pypa.io/en/stable/options/#before-all) | Execute a shell command on the build system before any wheels are built. |
| | [`CIBW_BEFORE_BUILD`](https://cibuildwheel.pypa.io/en/stable/options/#before-build) | Execute a shell command preparing each wheel's build | | | [`CIBW_BEFORE_BUILD`](https://cibuildwheel.pypa.io/en/stable/options/#before-build) | Execute a shell command preparing each wheel's build |
| | [`CIBW_XBUILD_TOOLS`](https://cibuildwheel.pypa.io/en/stable/options/#xbuild-tools) | Binaries on the path that should be included in an isolated cross-build environment. |
| | [`CIBW_REPAIR_WHEEL_COMMAND`](https://cibuildwheel.pypa.io/en/stable/options/#repair-wheel-command) | Execute a shell command to repair each built wheel | | | [`CIBW_REPAIR_WHEEL_COMMAND`](https://cibuildwheel.pypa.io/en/stable/options/#repair-wheel-command) | Execute a shell command to repair each built wheel |
| | [`CIBW_MANYLINUX_*_IMAGE`<br/>`CIBW_MUSLLINUX_*_IMAGE`](https://cibuildwheel.pypa.io/en/stable/options/#linux-image) | Specify alternative manylinux / musllinux Docker images | | | [`CIBW_MANYLINUX_*_IMAGE`<br/>`CIBW_MUSLLINUX_*_IMAGE`](https://cibuildwheel.pypa.io/en/stable/options/#linux-image) | Specify alternative manylinux / musllinux Docker images |
| | [`CIBW_CONTAINER_ENGINE`](https://cibuildwheel.pypa.io/en/stable/options/#container-engine) | Specify which container engine to use when building Linux wheels | | | [`CIBW_CONTAINER_ENGINE`](https://cibuildwheel.pypa.io/en/stable/options/#container-engine) | Specify which container engine to use when building Linux wheels |
| | [`CIBW_DEPENDENCY_VERSIONS`](https://cibuildwheel.pypa.io/en/stable/options/#dependency-versions) | Specify how cibuildwheel controls the versions of the tools it uses | | | [`CIBW_DEPENDENCY_VERSIONS`](https://cibuildwheel.pypa.io/en/stable/options/#dependency-versions) | Specify how cibuildwheel controls the versions of the tools it uses |
| **Testing** | [`CIBW_TEST_COMMAND`](https://cibuildwheel.pypa.io/en/stable/options/#test-command) | Execute a shell command to test each built wheel | | **Testing** | [`CIBW_TEST_COMMAND`](https://cibuildwheel.pypa.io/en/stable/options/#test-command) | Execute a shell command to test each built wheel |
| | [`CIBW_BEFORE_TEST`](https://cibuildwheel.pypa.io/en/stable/options/#before-test) | Execute a shell command before testing each wheel | | | [`CIBW_BEFORE_TEST`](https://cibuildwheel.pypa.io/en/stable/options/#before-test) | Execute a shell command before testing each wheel |
| | [`CIBW_TEST_SOURCES`](https://cibuildwheel.pypa.io/en/stable/options/#test-sources) | Files and folders from the source tree that are copied into an isolated tree before running the tests |
| | [`CIBW_TEST_REQUIRES`](https://cibuildwheel.pypa.io/en/stable/options/#test-requires) | Install Python dependencies before running the tests | | | [`CIBW_TEST_REQUIRES`](https://cibuildwheel.pypa.io/en/stable/options/#test-requires) | Install Python dependencies before running the tests |
| | [`CIBW_TEST_EXTRAS`](https://cibuildwheel.pypa.io/en/stable/options/#test-extras) | Install your wheel for testing using extras_require | | | [`CIBW_TEST_EXTRAS`](https://cibuildwheel.pypa.io/en/stable/options/#test-extras) | Install your wheel for testing using extras_require |
| | [`CIBW_TEST_SKIP`](https://cibuildwheel.pypa.io/en/stable/options/#test-skip) | Skip running tests on some builds | | | [`CIBW_TEST_SKIP`](https://cibuildwheel.pypa.io/en/stable/options/#test-skip) | Skip running tests on some builds |
@@ -172,8 +165,8 @@ Here are some repos that use cibuildwheel.
| [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. |
| [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][] | 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 |
| [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. |
| [MyPy][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | The compiled version of MyPy using MyPyC. |
| [Kivy][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Open source UI framework written in Python, running on Windows, Linux, macOS, Android and iOS | | [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
@@ -183,10 +176,11 @@ Here are some repos that use cibuildwheel.
[Tornado]: https://github.com/tornadoweb/tornado [Tornado]: https://github.com/tornadoweb/tornado
[NCNN]: https://github.com/Tencent/ncnn [NCNN]: https://github.com/Tencent/ncnn
[Matplotlib]: https://github.com/matplotlib/matplotlib [Matplotlib]: https://github.com/matplotlib/matplotlib
[MyPy]: https://github.com/mypyc/mypy_mypyc-wheels
[Prophet]: https://github.com/facebook/prophet [Prophet]: https://github.com/facebook/prophet
[MyPy]: https://github.com/mypyc/mypy_mypyc-wheels
[Kivy]: https://github.com/kivy/kivy [Kivy]: https://github.com/kivy/kivy
[appveyor icon]: docs/data/readme_icons/appveyor.svg
[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
@@ -217,47 +211,65 @@ Changelog
<!-- this section was generated by bin/update_readme_changelog.py -- do not edit manually --> <!-- this section was generated by bin/update_readme_changelog.py -- do not edit manually -->
### v3.0.0 ### v2.20.0
Not yet released, but available for testing - 🌟 CPython 3.13 wheels are now built by default - without the `CIBW_PRERELEASE_PYTHONS` flag. It's time to build and upload these wheels to PyPI! This release includes CPython 3.13.0rc1, which is guaranteed to be ABI compatible with the final release. Free-threading is still behind a flag/config option. (#1950)
- ✨ Provide a `CIBW_ALLOW_EMPTY` environment variable as an alternative to the command line flag. (#1937)
- 🐛 Don't use uv on PyPy3.8 on Windows, it stopped working starting in 0.2.25. Note that PyPy 3.8 is EoL. (#1868)
- 🛠 Set the `VSCMD_ARG_TGT_ARCH` variable based on target arch. (#1876)
- 🛠 Undo cleaner output on pytest 8-8.2 now that 8.3 is out. (#1943)
- 📚 Update examples to use Python 3.12 on host (cibuildwheel will require Python 3.11+ on the host machine starting in October 2024) (#1919)
#### v3.0.0b1
_19 May 2025_ ### v2.19.2
- 🌟 Adds the ability to [build wheels for iOS](https://cibuildwheel.pypa.io/en/latest/platforms/#ios)! Set the [`platform` option](https://cibuildwheel.pypa.io/en/latest/options/#platform) to `ios` on a Mac with the iOS toolchain to try it out! - 🐛 Update manylinux2014 pins to versions that support past-EoL CentOS 7 mirrors. (#1917)
- 🌟 Adds support for the GraalPy interpreter! Enable for your project using the [`enable` option](https://cibuildwheel.pypa.io/en/latest/options/#enable). (#1538) - 🐛 Support `--no-isolation` with `build[uv]` build-frontend. (#1889)
- ✨ Adds CPython 3.14 support, under the [`enable` option](https://cibuildwheel.pypa.io/en/latest/options/#enable) `cpython-prerelease`. This version of cibuildwheel uses 3.14.0b1. - 🛠 Provide attestations for releases at <https://github.com/pypa/cibuildwheel/attestations>. (#1916)
- 🛠 Provide CPython 3.13.0b3. (#1913)
- 🛠 Remove some workarounds now that pip 21.1 is available. (#1891, #1892)
- 📚 Remove nosetest from our docs. (#1821)
- 📚 Document the macOS ARM workaround for 3.8 on GHA. (#1871)
- 📚 GitLab CI + macOS is now a supported platform with an example. (#1911)
_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 the [test-sources option](https://cibuildwheel.pypa.io/en/latest/options/#test-sources). \[discussion about the test cwd change and how to use to come!\]
- ✨ Added `dependency-versions` inline syntax (#2123)
- 🛠 EOL manylinux options can no longer be specified by their shortname. Full OCI URL can still be used for these images, if you wish (#2316)
- 🛠 Build environments no longer have setuptools and wheel preinstalled. (#2329)
- ⚠️ PyPy wheels no longer built by default, due to a change to our options system. To continue building PyPy wheels, you'll now need to set the [`enable` option](https://cibuildwheel.pypa.io/en/latest/options/#enable) to `pypy` or `pypy-eol`.
- ⚠️ Dropped official support for Appveyor. If it was working for you before, it will probably continue to do so, but we can't be sure, because our CI doesn't run there anymore. (#2386)
- 📚 A reorganisation of the docs, and numerous updates (#2280)
### v2.23.3 ### v2.19.1
_26 April 2025_ - 🐛 Don't require setup-python on GHA for Pyodide (#1868)
- 🐛 Specify full python path for uv (fixes issue in 0.2.10 & 0.2.11) (#1881)
- 🛠 Update for pip 24.1b2 on CPython 3.13. (#1879)
- 🛠 Fix a warning in our schema generation script. (#1866)
- 🛠 Cleaner output on pytest 8-8.2. (#1865)
- 🛠 Dependency updates, including Python 3.13.3 (#2371)
### v2.23.2 ### v2.19.0
_24 March 2025_ See the [release post](https://iscinumpy.dev/post/cibuildwheel-2-19-0/) for more info on new features!
- 🐛 Workaround an issue with pyodide builds when running cibuildwheel with a Python that was installed via UV (#2328 via #2331) - 🌟 Add Pyodide platform. Set with `--platform pyodide` or `CIBW_PLATFORM: pyodide` on Linux with a host Python 3.12 to build WebAssembly wheels. Not accepted on PyPI currently, but usable directly in a website using Pyodide, for live docs, etc. (#1456, #1859)
- 🛠 Dependency updates, including a manylinux update that fixes an ['undefined symbol' error](https://github.com/pypa/manylinux/issues/1760) in gcc-toolset (#2334) - 🌟 Add `build[uv]` backend, which will take a pre-existing uv install (or install `cibuildwheel[uv]`) and use `uv` for all environment setup and installs on Python 3.8+. This is significantly faster in most cases. (#1856)
- - ✨ Add free-threaded macOS builds and update CPython to 3.13.0b2. (#1854)
- 🐛 Issue copying a wheel to a non-existent output dir fixed. (#1851, #1862)
- 🐛 Better determinism for the test environment seeding. (#1835)
- 🛠 `VIRTUAL_ENV` variable now set. (#1842)
- 🛠 Remove a pip<21.3 workaround. (#1842)
- 🛠 Error handling was refactored to use exceptions. (#1719)
- 🛠 Hardcoded paths in tests avoided. (#1834)
- 🛠 Single Python tests made more generic. (#1835)
- 🛠 Sped up our ci by splitting up emulation tests. (#1839)
### v2.23.1
_15 March 2025_
- ⚠️ Added warnings when the shorthand values `manylinux1`, `manylinux2010`, `manylinux_2_24`, and `musllinux_1_1` are used to specify the images in linux builds. The shorthand to these (unmaintainted) images will be removed in v3.0. If you want to keep using these images, explicitly opt-in using the full image URL, which can be found in [this file](https://github.com/pypa/cibuildwheel/blob/v2.23.1/cibuildwheel/resources/pinned_docker_images.cfg). (#2312) ### v2.18.1
- 🛠 Dependency updates, including a manylinux update which fixes an [issue with rustup](https://github.com/pypa/cibuildwheel/issues/2303). (#2315)
- 🌟 Add free-threaded Linux and Windows builds for 3.13. New identifiers `cp313t-*`, new option `CIBW_FREE_THREADED_SUPPORT`/`tool.cibuildwheel.free-threaded-support` required to opt-in. [See the docs](https://cibuildwheel.pypa.io/en/stable/options/#free-threaded-support) for more information. (#1831)
- ✨ The `container-engine` is now a build (non-global) option. (#1792)
- 🛠 The build backend for cibuildwheel is now hatchling. (#1297)
- 🛠 Significant improvements and modernization to our noxfile. (#1823)
- 🛠 Use pylint's new GitHub Actions reporter instead of a custom matcher. (#1823)
- 🛠 Unpin virtualenv updates for Python 3.7+ (#1830)
- 🐛 Fix running linux tests from Windows or macOS ARM. (#1788)
- 📚 Fix our documentation build. (#1821)
<!-- END bin/update_readme_changelog.py --> <!-- END bin/update_readme_changelog.py -->
+35
View File
@@ -0,0 +1,35 @@
environment:
matrix:
- APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu2204
APPVEYOR_JOB_NAME: "python312-x64-ubuntu"
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022
APPVEYOR_JOB_NAME: "python312-x64-vs2022"
- APPVEYOR_BUILD_WORKER_IMAGE: macos-sonoma
APPVEYOR_JOB_NAME: "python312-x64-macos"
stack: python 3.12
build: off
init:
- ps: |
$BRANCH = if ($env:APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH) { $env:APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH } else { $env:APPVEYOR_REPO_BRANCH }
if (-not ($BRANCH -eq 'main' -or $BRANCH.ToLower().StartsWith('appveyor-'))) {
$env:PYTEST_ADDOPTS = '-k "unit_test or test_0_basic" --suppress-no-test-exit-code'
}
install:
- python -m pip install -U pip
- python -m pip install -e ".[dev]" pytest-custom-exit-code
# the '-u' flag is required so the output is in the correct order.
# See https://github.com/pypa/cibuildwheel/pull/24 for more info.
test_script: python -u ./bin/run_tests.py
branches:
only:
- main
skip_commits:
files:
- docs/*
+17 -40
View File
@@ -5,57 +5,34 @@ pr:
- .pre-commit-config.yaml - .pre-commit-config.yaml
jobs: jobs:
- job: linux_311 - job: linux_38
timeoutInMinutes: 180
pool: {vmImage: 'Ubuntu-22.04'}
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '3.11'
- bash: |
docker run --rm --privileged docker.io/tonistiigi/binfmt:latest --install all
python -m pip install dependency-groups
python -m dependency_groups test | xargs python -m pip install -e.
if [ "$(Build.SourceBranch)" = "refs/heads/main" ]; then
echo "INFO: Exporting CIBW_ENABLE=all for main branch test run."
export CIBW_ENABLE=all
else
echo "INFO: CIBW_ENABLE not set for this branch ($(Build.SourceBranch))."
fi
python ./bin/run_tests.py
- job: macos_311
pool: {vmImage: 'macOS-13'}
timeoutInMinutes: 120 timeoutInMinutes: 120
pool: {vmImage: 'Ubuntu-20.04'}
steps: steps:
- task: UsePythonVersion@0 - task: UsePythonVersion@0
inputs: inputs:
versionSpec: '3.11' versionSpec: '3.8'
- bash: | - bash: |
python -m pip install dependency-groups python -m pip install -e ".[dev]"
python -m dependency_groups test | xargs python -m pip install -e.
if [ "$(Build.SourceBranch)" = "refs/heads/main" ]; then
echo "INFO: Exporting CIBW_ENABLE=all for main branch test run."
export CIBW_ENABLE=all
else
echo "INFO: CIBW_ENABLE not set for this branch ($(Build.SourceBranch))."
fi
python ./bin/run_tests.py python ./bin/run_tests.py
- job: windows_311 - job: macos_38
pool: {vmImage: 'macOS-12'}
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '3.8'
- bash: |
python -m pip install -e ".[dev]"
python ./bin/run_tests.py --num-processes 2
- job: windows_38
pool: {vmImage: 'windows-2019'} pool: {vmImage: 'windows-2019'}
timeoutInMinutes: 180 timeoutInMinutes: 180
steps: steps:
- task: UsePythonVersion@0 - task: UsePythonVersion@0
inputs: inputs:
versionSpec: '3.11' versionSpec: '3.8'
- bash: | - bash: |
python -m pip install dependency-groups python -m pip install -e ".[dev]"
python -m dependency_groups test | xargs python -m pip install -e.
if [ "$(Build.SourceBranch)" = "refs/heads/main" ]; then
echo "INFO: Exporting CIBW_ENABLE=all for main branch test run."
export CIBW_ENABLE=all
else
echo "INFO: CIBW_ENABLE not set for this branch ($(Build.SourceBranch))."
fi
python ./bin/run_tests.py python ./bin/run_tests.py
+10 -2
View File
@@ -5,16 +5,23 @@
# /// # ///
from __future__ import annotations
import glob
import os import os
import subprocess import subprocess
import sys import sys
import tomllib
import urllib.parse import urllib.parse
from pathlib import Path from pathlib import Path
import click import click
from packaging.version import InvalidVersion, Version from packaging.version import InvalidVersion, Version
if sys.version_info < (3, 11):
import tomli as tomllib
else:
import tomllib
config = [ config = [
# file path, version find/replace format # file path, version find/replace format
("pyproject.toml", 'version = "{}"'), ("pyproject.toml", 'version = "{}"'),
@@ -22,6 +29,7 @@ config = [
("cibuildwheel/__init__.py", '__version__ = "{}"'), ("cibuildwheel/__init__.py", '__version__ = "{}"'),
("docs/faq.md", "cibuildwheel=={}"), ("docs/faq.md", "cibuildwheel=={}"),
("docs/faq.md", "cibuildwheel@v{}"), ("docs/faq.md", "cibuildwheel@v{}"),
("docs/setup.md", "cibuildwheel=={}"),
("examples/*", "cibuildwheel=={}"), ("examples/*", "cibuildwheel=={}"),
("examples/*", "cibuildwheel@v{}"), ("examples/*", "cibuildwheel@v{}"),
] ]
@@ -82,7 +90,7 @@ def bump_version() -> None:
actions = [] actions = []
for path_pattern, version_pattern in config: for path_pattern, version_pattern in config:
paths = list(Path().glob(path_pattern)) paths = [Path(p) for p in glob.glob(path_pattern)]
if not paths: if not paths:
print(f"error: Pattern {path_pattern} didn't match any files") print(f"error: Pattern {path_pattern} didn't match any files")
+9 -66
View File
@@ -6,17 +6,12 @@
import argparse import argparse
import copy import copy
import functools
import json import json
import sys
from typing import Any from typing import Any
import yaml import yaml
make_parser = functools.partial(argparse.ArgumentParser, allow_abbrev=False) parser = argparse.ArgumentParser()
if sys.version_info >= (3, 14):
make_parser = functools.partial(make_parser, color=True, suggest_on_error=True)
parser = make_parser()
parser.add_argument("--schemastore", action="store_true", help="Generate schema_store version") parser.add_argument("--schemastore", action="store_true", help="Generate schema_store version")
args = parser.parse_args() args = parser.parse_args()
@@ -31,14 +26,6 @@ $defs:
- append - append
default: none default: none
description: How to inherit the parent's value. description: How to inherit the parent's value.
enable:
enum:
- cpython-freethreading
- cpython-prerelease
- pypy
- pypy-eol
- cpython-experimental-riscv64
description: A Python version or flavor to enable.
additionalProperties: false additionalProperties: false
description: cibuildwheel's settings. description: cibuildwheel's settings.
type: object type: object
@@ -111,31 +98,7 @@ properties:
dependency-versions: dependency-versions:
default: pinned default: pinned
description: Specify how cibuildwheel controls the versions of the tools it uses description: Specify how cibuildwheel controls the versions of the tools it uses
oneOf: type: string
- enum: [pinned, latest]
- type: string
description: Path to a file containing dependency versions, or inline package specifications, starting with "packages:"
not:
enum: [pinned, latest]
- type: object
additionalProperties: false
properties:
file:
type: string
- type: object
additionalProperties: false
properties:
packages:
type: array
items:
type: string
enable:
description: Enable or disable certain builds.
oneOf:
- $ref: "#/$defs/enable"
- type: array
items:
$ref: "#/$defs/enable"
environment: environment:
description: Set environment variables needed during the build. description: Set environment variables needed during the build.
type: string_table type: string_table
@@ -143,12 +106,13 @@ properties:
description: Set environment variables on the host to pass-through to the container description: Set environment variables on the host to pass-through to the container
during the build. during the build.
type: string_array type: string_array
free-threaded-support:
type: boolean
default: false
description: The project supports free-threaded builds of Python (PEP703)
manylinux-aarch64-image: manylinux-aarch64-image:
type: string type: string
description: Specify alternative manylinux / musllinux container images description: Specify alternative manylinux / musllinux container images
manylinux-armv7l-image:
type: string
description: Specify alternative manylinux / musllinux container images
manylinux-i686-image: manylinux-i686-image:
type: string type: string
description: Specify alternative manylinux / musllinux container images description: Specify alternative manylinux / musllinux container images
@@ -164,9 +128,6 @@ properties:
manylinux-pypy_x86_64-image: manylinux-pypy_x86_64-image:
type: string type: string
description: Specify alternative manylinux / musllinux container images description: Specify alternative manylinux / musllinux container images
manylinux-riscv64-image:
type: string
description: Specify alternative manylinux / musllinux container images
manylinux-s390x-image: manylinux-s390x-image:
type: string type: string
description: Specify alternative manylinux / musllinux container images description: Specify alternative manylinux / musllinux container images
@@ -176,30 +137,21 @@ properties:
musllinux-aarch64-image: musllinux-aarch64-image:
type: string type: string
description: Specify alternative manylinux / musllinux container images description: Specify alternative manylinux / musllinux container images
musllinux-armv7l-image:
type: string
description: Specify alternative manylinux / musllinux container images
musllinux-i686-image: musllinux-i686-image:
type: string type: string
description: Specify alternative manylinux / musllinux container images description: Specify alternative manylinux / musllinux container images
musllinux-ppc64le-image: musllinux-ppc64le-image:
type: string type: string
description: Specify alternative manylinux / musllinux container images description: Specify alternative manylinux / musllinux container images
musllinux-riscv64-image:
type: string
description: Specify alternative manylinux / musllinux container images
musllinux-s390x-image: musllinux-s390x-image:
type: string type: string
description: Specify alternative manylinux / musllinux container images description: Specify alternative manylinux / musllinux container images
musllinux-x86_64-image: musllinux-x86_64-image:
type: string type: string
description: Specify alternative manylinux / musllinux container images description: Specify alternative manylinux / musllinux container images
xbuild-tools:
description: Binaries on the path that should be included in an isolated cross-build environment
type: string_array
repair-wheel-command: repair-wheel-command:
description: Execute a shell command to repair each built wheel.
type: string_array type: string_array
description: Execute a shell command to repair each built wheel.
skip: skip:
description: Choose the Python versions to skip. description: Choose the Python versions to skip.
type: string_array type: string_array
@@ -209,12 +161,6 @@ properties:
test-extras: test-extras:
description: Install your wheel for testing using `extras_require` description: Install your wheel for testing using `extras_require`
type: string_array type: string_array
test-sources:
description: Test files that are required by the test environment
type: string_array
test-groups:
description: Install extra groups when testing
type: string_array
test-requires: test-requires:
description: Install Python dependencies before running the tests description: Install Python dependencies before running the tests
type: string_array type: string_array
@@ -289,7 +235,6 @@ items:
properties: properties:
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"}
before-test: {"$ref": "#/$defs/inherit"} before-test: {"$ref": "#/$defs/inherit"}
config-settings: {"$ref": "#/$defs/inherit"} config-settings: {"$ref": "#/$defs/inherit"}
container-engine: {"$ref": "#/$defs/inherit"} container-engine: {"$ref": "#/$defs/inherit"}
@@ -298,19 +243,18 @@ items:
repair-wheel-command: {"$ref": "#/$defs/inherit"} repair-wheel-command: {"$ref": "#/$defs/inherit"}
test-command: {"$ref": "#/$defs/inherit"} test-command: {"$ref": "#/$defs/inherit"}
test-extras: {"$ref": "#/$defs/inherit"} test-extras: {"$ref": "#/$defs/inherit"}
test-sources: {"$ref": "#/$defs/inherit"}
test-requires: {"$ref": "#/$defs/inherit"} test-requires: {"$ref": "#/$defs/inherit"}
""" """
) )
for key, value in schema["properties"].items(): for key, value in schema["properties"].items():
value["title"] = f"CIBW_{key.replace('-', '_').upper()}" value["title"] = f'CIBW_{key.replace("-", "_").upper()}'
non_global_options = {k: {"$ref": f"#/properties/{k}"} for k in schema["properties"]} non_global_options = {k: {"$ref": f"#/properties/{k}"} for k in schema["properties"]}
del non_global_options["build"] del non_global_options["build"]
del non_global_options["skip"] del non_global_options["skip"]
del non_global_options["test-skip"] del non_global_options["test-skip"]
del non_global_options["enable"] del non_global_options["free-threaded-support"]
overrides["items"]["properties"]["select"]["oneOf"] = string_array overrides["items"]["properties"]["select"]["oneOf"] = string_array
overrides["items"]["properties"] |= non_global_options.copy() overrides["items"]["properties"] |= non_global_options.copy()
@@ -339,7 +283,6 @@ oses = {
"windows": as_object(not_linux), "windows": as_object(not_linux),
"macos": as_object(not_linux), "macos": as_object(not_linux),
"pyodide": as_object(not_linux), "pyodide": as_object(not_linux),
"ios": as_object(not_linux),
} }
oses["linux"]["properties"]["repair-wheel-command"] = { oses["linux"]["properties"]["repair-wheel-command"] = {
+2
View File
@@ -11,6 +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 collections.abc import Iterable, Iterator
from pathlib import Path from pathlib import Path
+10 -11
View File
@@ -1,30 +1,29 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations
import os import os
import subprocess
import sys import sys
import textwrap import textwrap
import time import time
from pathlib import Path from pathlib import Path
from subprocess import run
import click import click
def shell(cmd: str, *, check: bool, **kwargs: object) -> subprocess.CompletedProcess[str]: def shell(cmd, *, check: bool, **kwargs):
return subprocess.run([cmd], shell=True, check=check, **kwargs) # type: ignore[call-overload, no-any-return] return run([cmd], shell=True, check=check, **kwargs)
def git_repo_has_changes() -> bool: def git_repo_has_changes():
unstaged_changes: bool = shell("git diff-index --quiet HEAD --", check=False).returncode != 0 unstaged_changes = shell("git diff-index --quiet HEAD --", check=False).returncode != 0
staged_changes: bool = ( staged_changes = shell("git diff-index --quiet --cached HEAD --", check=False).returncode != 0
shell("git diff-index --quiet --cached HEAD --", check=False).returncode != 0
)
return unstaged_changes or staged_changes return unstaged_changes or staged_changes
@click.command() @click.command()
def main() -> None: def main():
project_root = Path(__file__).parent / ".." project_root = Path(__file__).parent / ".."
os.chdir(project_root) os.chdir(project_root)
@@ -55,10 +54,10 @@ def main() -> None:
f""" f"""
Update the versions of our dependencies. Update the versions of our dependencies.
PR generated by `{Path(__file__).name}`. PR generated by `{os.path.basename(__file__)}`.
""" """
) )
subprocess.run( run(
[ [
"gh", "gh",
"pr", "pr",
+10 -9
View File
@@ -17,6 +17,8 @@ Suggested usage:
git diff git diff
""" """
from __future__ import annotations
import builtins import builtins
import functools import functools
import textwrap import textwrap
@@ -26,13 +28,14 @@ from collections.abc import Iterable, Mapping, Sequence
from datetime import datetime from datetime import datetime
from io import StringIO from io import StringIO
from pathlib import Path from pathlib import Path
from typing import Any, Self, TextIO from typing import Any, TextIO
import click import click
import yaml import yaml
from github import Github, GithubException from github import Github, GithubException
ICONS = ( ICONS = (
"appveyor",
"github", "github",
"azurepipelines", "azurepipelines",
"circleci", "circleci",
@@ -82,7 +85,7 @@ class Project:
name_len = len(self.name) + 4 name_len = len(self.name) + 4
self.__class__.NAME = max(self.__class__.NAME, name_len) self.__class__.NAME = max(self.__class__.NAME, name_len)
def __lt__(self, other: Self) -> bool: def __lt__(self, other: Project) -> bool:
if self.online: if self.online:
return self.num_stars < other.num_stars return self.num_stars < other.num_stars
else: else:
@@ -92,8 +95,8 @@ class Project:
def header(cls) -> str: def header(cls) -> str:
return textwrap.dedent( return textwrap.dedent(
f"""\ f"""\
| {"Name":{cls.NAME}} | CI | OS | Notes | | {'Name':{cls.NAME}} | CI | OS | Notes |
|{"":-^{cls.NAME + 2}}|----|----|:------|""" |{'':-^{cls.NAME+2 }}|----|----|:------|"""
) )
@property @property
@@ -169,9 +172,7 @@ def get_projects(
return sorted((Project(item, github) for item in config), reverse=online) return sorted((Project(item, github) for item in config), reverse=online)
def render_projects( def render_projects(projects: Sequence[Project], *, dest_path: Path, include_info: bool = True):
projects: Sequence[Project], *, dest_path: Path, include_info: bool = True
) -> str:
io = StringIO() io = StringIO()
print = functools.partial(builtins.print, file=io) print = functools.partial(builtins.print, file=io)
@@ -202,7 +203,7 @@ def insert_projects_table(
projects: Sequence[Project], projects: Sequence[Project],
input_filename: str, input_filename: str,
include_info: bool = True, include_info: bool = True,
) -> None: ):
text = file.read_text() text = file.read_text()
projects_table = render_projects(projects, include_info=include_info, dest_path=file) projects_table = render_projects(projects, include_info=include_info, dest_path=file)
@@ -216,7 +217,7 @@ def insert_projects_table(
generated_note = f"<!-- this section is generated by bin/projects.py. Don't edit it directly, instead, edit {input_filename} -->" generated_note = f"<!-- this section is generated by bin/projects.py. Don't edit it directly, instead, edit {input_filename} -->"
new_text = ( new_text = (
f"{text[: start + len(start_str)]}\n{generated_note}\n\n{projects_table}\n{text[end:]}" f"{text[:start + len(start_str)]}\n{generated_note}\n\n{projects_table}\n{text[end:]}"
) )
file.write_text(new_text) file.write_text(new_text)
+26 -18
View File
@@ -1,32 +1,32 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations
import os import os
import shutil import shutil
import subprocess
import sys import sys
import textwrap import textwrap
import time import time
import typing import typing
from glob import glob
from pathlib import Path from pathlib import Path
from subprocess import run
from urllib.parse import quote from urllib.parse import quote
import click import click
DIR = Path(__file__).parent.resolve()
def shell(cmd, *, check: bool, **kwargs):
return run([cmd], shell=True, check=check, **kwargs)
def shell(cmd: str, *, check: bool, **kwargs: object) -> subprocess.CompletedProcess[str]: def git_repo_has_changes():
return subprocess.run([cmd], shell=True, check=check, **kwargs) # type: ignore[call-overload, no-any-return]
def git_repo_has_changes() -> bool:
unstaged_changes = shell("git diff-index --quiet HEAD --", check=False).returncode != 0 unstaged_changes = shell("git diff-index --quiet HEAD --", check=False).returncode != 0
staged_changes = shell("git diff-index --quiet --cached HEAD --", check=False).returncode != 0 staged_changes = shell("git diff-index --quiet --cached HEAD --", check=False).returncode != 0
return unstaged_changes or staged_changes return unstaged_changes or staged_changes
def generate_basic_project(path: Path) -> None: def generate_basic_project(path):
sys.path.insert(0, "") sys.path.insert(0, "")
from test.test_projects.c import new_c_project from test.test_projects.c import new_c_project
@@ -41,6 +41,11 @@ class CIService(typing.NamedTuple):
services = [ services = [
CIService(
name="appveyor",
dst_config_path="appveyor.yml",
badge_md="[![Build status](https://ci.appveyor.com/api/projects/status/gt3vwl88yt0y3hur/branch/{branch}?svg=true)](https://ci.appveyor.com/project/joerick/cibuildwheel/branch/{branch})",
),
CIService( CIService(
name="azure-pipelines", name="azure-pipelines",
dst_config_path="azure-pipelines.yml", dst_config_path="azure-pipelines.yml",
@@ -74,8 +79,9 @@ services = [
] ]
def ci_service_for_config_file(config_file: Path) -> CIService: def ci_service_for_config_file(config_file):
filename = config_file.name filename = Path(config_file).name
try: try:
return next(s for s in services if filename.startswith(s.name)) return next(s for s in services if filename.startswith(s.name))
except StopIteration: except StopIteration:
@@ -92,16 +98,16 @@ def run_example_ci_configs(config_files=None):
""" """
if len(config_files) == 0: if len(config_files) == 0:
config_files = Path("examples").glob("*-minimal.yml") config_files = glob("examples/*-minimal.yml")
# check each CI service has at most 1 config file # check each CI service has at most 1 config file
configs_by_service = set() configs_by_service = {}
for config_file in config_files: for config_file in config_files:
service = ci_service_for_config_file(config_file) service = ci_service_for_config_file(config_file)
if service.name in configs_by_service: if service.name in configs_by_service:
msg = "You cannot specify more than one config per CI service" msg = "You cannot specify more than one config per CI service"
raise Exception(msg) raise Exception(msg)
configs_by_service.add(service.name) configs_by_service[service.name] = config_file
if git_repo_has_changes(): if git_repo_has_changes():
print("Your git repo has uncommitted changes. Commit or stash before continuing.") print("Your git repo has uncommitted changes. Commit or stash before continuing.")
@@ -122,22 +128,23 @@ def run_example_ci_configs(config_files=None):
for config_file in config_files: for config_file in config_files:
service = ci_service_for_config_file(config_file) service = ci_service_for_config_file(config_file)
src_config_file = Path(config_file)
dst_config_file = example_project / service.dst_config_path dst_config_file = example_project / service.dst_config_path
dst_config_file.parent.mkdir(parents=True, exist_ok=True) dst_config_file.parent.mkdir(parents=True, exist_ok=True)
shutil.copyfile(config_file, dst_config_file) shutil.copyfile(src_config_file, dst_config_file)
subprocess.run(["git", "add", example_project], check=True) run(["git", "add", example_project], check=True)
message = textwrap.dedent( message = textwrap.dedent(
f"""\ f"""\
Test example minimal configs Test example minimal configs
Testing files: {[str(f) for f in config_files]} Testing files: {config_files}
Generated from branch: {previous_branch} Generated from branch: {previous_branch}
Time: {timestamp} Time: {timestamp}
""" """
) )
subprocess.run(["git", "commit", "--no-verify", "--message", message], check=True) run(["git", "commit", "--no-verify", "--message", message], check=True)
shell(f"git subtree --prefix={example_project} push origin {branch_name}", check=True) shell(f"git subtree --prefix={example_project} push origin {branch_name}", check=True)
print("---") print("---")
@@ -167,5 +174,6 @@ def run_example_ci_configs(config_files=None):
if __name__ == "__main__": if __name__ == "__main__":
os.chdir(DIR) os.chdir(os.path.dirname(__file__))
os.chdir("..")
run_example_ci_configs(standalone_mode=True) run_example_ci_configs(standalone_mode=True)
+5 -43
View File
@@ -1,23 +1,16 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations
import argparse import argparse
import functools
import os import os
import subprocess import subprocess
import sys import sys
from pathlib import Path from pathlib import Path
if __name__ == "__main__": if __name__ == "__main__":
if sys.version_info < (3, 13): default_cpu_count = os.cpu_count() or 2
default_cpu_count = os.cpu_count() or 2 parser = argparse.ArgumentParser()
else:
default_cpu_count = os.process_cpu_count() or 2
make_parser = functools.partial(argparse.ArgumentParser, allow_abbrev=False)
if sys.version_info >= (3, 14):
make_parser = functools.partial(make_parser, color=True, suggest_on_error=True)
parser = make_parser()
parser.add_argument( parser.add_argument(
"--run-podman", action="store_true", default=False, help="run podman tests (linux only)" "--run-podman", action="store_true", default=False, help="run podman tests (linux only)"
) )
@@ -35,60 +28,29 @@ if __name__ == "__main__":
# unit tests # unit tests
unit_test_args = [sys.executable, "-m", "pytest", "unit_test"] unit_test_args = [sys.executable, "-m", "pytest", "unit_test"]
if sys.platform.startswith("linux") and os.environ.get("CIBW_PLATFORM", "linux") == "linux": if sys.platform.startswith("linux"):
# run the docker unit tests only on Linux # run the docker unit tests only on Linux
unit_test_args += ["--run-docker"] unit_test_args += ["--run-docker"]
if args.run_podman: if args.run_podman:
unit_test_args += ["--run-podman"] unit_test_args += ["--run-podman"]
print(
"\n\n================================== UNIT TESTS ==================================",
flush=True,
)
subprocess.run(unit_test_args, check=True) subprocess.run(unit_test_args, check=True)
# Run the serial integration tests without multiple processes # integration tests
serial_integration_test_args = [
sys.executable,
"-m",
"pytest",
"-m",
"serial",
"-x",
"--durations",
"0",
"--timeout=2400",
"test",
"-vv",
]
print(
"\n\n=========================== SERIAL INTEGRATION TESTS ===========================",
flush=True,
)
subprocess.run(serial_integration_test_args, check=True)
# Non-serial integration tests
integration_test_args = [ integration_test_args = [
sys.executable, sys.executable,
"-m", "-m",
"pytest", "pytest",
"-m",
"not serial",
f"--numprocesses={args.num_processes}", f"--numprocesses={args.num_processes}",
"-x", "-x",
"--durations", "--durations",
"0", "0",
"--timeout=2400", "--timeout=2400",
"test", "test",
"-vv",
] ]
if sys.platform.startswith("linux") and args.run_podman: if sys.platform.startswith("linux") and args.run_podman:
integration_test_args += ["--run-podman"] integration_test_args += ["--run-podman"]
print(
"\n\n========================= NON-SERIAL INTEGRATION TESTS =========================",
flush=True,
)
subprocess.run(integration_test_args, check=True) subprocess.run(integration_test_args, check=True)
+2 -5
View File
@@ -1,8 +1,8 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations
import argparse import argparse
import functools
import os import os
import subprocess import subprocess
import sys import sys
@@ -13,10 +13,7 @@ if __name__ == "__main__":
# move cwd to the project root # move cwd to the project root
os.chdir(Path(__file__).resolve().parents[1]) os.chdir(Path(__file__).resolve().parents[1])
make_parser = functools.partial(argparse.ArgumentParser, allow_abbrev=False) parser = argparse.ArgumentParser(description="Runs a sample build")
if sys.version_info >= (3, 14):
make_parser = functools.partial(make_parser, color=True, suggest_on_error=True)
parser = make_parser(description="Runs a sample build")
parser.add_argument("project_python_path", nargs="?", default="test.test_0_basic.basic_project") parser.add_argument("project_python_path", nargs="?", default="test.test_0_basic.basic_project")
options = parser.parse_args() options = parser.parse_args()
+48 -57
View File
@@ -1,11 +1,11 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations
import configparser import configparser
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
import requests import requests
from packaging.version import Version
DIR = Path(__file__).parent.resolve() DIR = Path(__file__).parent.resolve()
RESOURCES = DIR.parent / "cibuildwheel/resources" RESOURCES = DIR.parent / "cibuildwheel/resources"
@@ -14,45 +14,57 @@ RESOURCES = DIR.parent / "cibuildwheel/resources"
@dataclass(frozen=True) @dataclass(frozen=True)
class Image: class Image:
manylinux_version: str manylinux_version: str
platforms: list[str] platform: str
image_name: str image_name: str
tag: str | None = None # Set this to pin the image tag: str | None # Set this to pin the image
use_platform_suffix: bool = False
class PyPAImage(Image):
def __init__(self, manylinux_version: str, platforms: list[str], tag: str | None = None):
image_name = f"quay.io/pypa/{manylinux_version}"
super().__init__(manylinux_version, platforms, image_name, tag, True)
images = [ images = [
# manylinux1 images
Image("manylinux1", "x86_64", "quay.io/pypa/manylinux1_x86_64", None),
Image("manylinux1", "i686", "quay.io/pypa/manylinux1_i686", None),
# manylinux2010 images
Image("manylinux2010", "x86_64", "quay.io/pypa/manylinux2010_x86_64", None),
Image("manylinux2010", "i686", "quay.io/pypa/manylinux2010_i686", None),
Image("manylinux2010", "pypy_x86_64", "quay.io/pypa/manylinux2010_x86_64", None),
Image("manylinux2010", "pypy_i686", "quay.io/pypa/manylinux2010_i686", None),
# manylinux2014 images # manylinux2014 images
PyPAImage( Image("manylinux2014", "x86_64", "quay.io/pypa/manylinux2014_x86_64", None),
"manylinux2014", Image("manylinux2014", "i686", "quay.io/pypa/manylinux2014_i686", None),
[ Image("manylinux2014", "aarch64", "quay.io/pypa/manylinux2014_aarch64", None),
"x86_64", Image("manylinux2014", "ppc64le", "quay.io/pypa/manylinux2014_ppc64le", None),
"i686", Image("manylinux2014", "s390x", "quay.io/pypa/manylinux2014_s390x", None),
"aarch64", Image("manylinux2014", "pypy_x86_64", "quay.io/pypa/manylinux2014_x86_64", None),
"ppc64le", Image("manylinux2014", "pypy_i686", "quay.io/pypa/manylinux2014_i686", None),
"s390x", Image("manylinux2014", "pypy_aarch64", "quay.io/pypa/manylinux2014_aarch64", None),
"pypy_x86_64", # manylinux_2_24 images
"pypy_i686", Image("manylinux_2_24", "x86_64", "quay.io/pypa/manylinux_2_24_x86_64", None),
"pypy_aarch64", Image("manylinux_2_24", "i686", "quay.io/pypa/manylinux_2_24_i686", None),
], Image("manylinux_2_24", "aarch64", "quay.io/pypa/manylinux_2_24_aarch64", None),
), Image("manylinux_2_24", "ppc64le", "quay.io/pypa/manylinux_2_24_ppc64le", None),
Image("manylinux_2_24", "s390x", "quay.io/pypa/manylinux_2_24_s390x", None),
Image("manylinux_2_24", "pypy_x86_64", "quay.io/pypa/manylinux_2_24_x86_64", None),
Image("manylinux_2_24", "pypy_i686", "quay.io/pypa/manylinux_2_24_i686", None),
Image("manylinux_2_24", "pypy_aarch64", "quay.io/pypa/manylinux_2_24_aarch64", None),
# manylinux_2_28 images # manylinux_2_28 images
PyPAImage( Image("manylinux_2_28", "x86_64", "quay.io/pypa/manylinux_2_28_x86_64", None),
"manylinux_2_28", ["x86_64", "aarch64", "ppc64le", "s390x", "pypy_x86_64", "pypy_aarch64"] Image("manylinux_2_28", "aarch64", "quay.io/pypa/manylinux_2_28_aarch64", None),
), Image("manylinux_2_28", "ppc64le", "quay.io/pypa/manylinux_2_28_ppc64le", None),
# manylinux_2_31 images Image("manylinux_2_28", "s390x", "quay.io/pypa/manylinux_2_28_s390x", None),
PyPAImage("manylinux_2_31", ["armv7l"]), Image("manylinux_2_28", "pypy_x86_64", "quay.io/pypa/manylinux_2_28_x86_64", None),
# manylinux_2_34 images Image("manylinux_2_28", "pypy_aarch64", "quay.io/pypa/manylinux_2_28_aarch64", None),
PyPAImage( # musllinux_1_1 images
"manylinux_2_34", ["x86_64", "aarch64", "ppc64le", "s390x", "pypy_x86_64", "pypy_aarch64"] Image("musllinux_1_1", "x86_64", "quay.io/pypa/musllinux_1_1_x86_64", None),
), Image("musllinux_1_1", "i686", "quay.io/pypa/musllinux_1_1_i686", None),
Image("musllinux_1_1", "aarch64", "quay.io/pypa/musllinux_1_1_aarch64", None),
Image("musllinux_1_1", "ppc64le", "quay.io/pypa/musllinux_1_1_ppc64le", None),
Image("musllinux_1_1", "s390x", "quay.io/pypa/musllinux_1_1_s390x", None),
# musllinux_1_2 images # musllinux_1_2 images
PyPAImage("musllinux_1_2", ["x86_64", "i686", "aarch64", "ppc64le", "s390x", "armv7l"]), Image("musllinux_1_2", "x86_64", "quay.io/pypa/musllinux_1_2_x86_64", None),
Image("musllinux_1_2", "i686", "quay.io/pypa/musllinux_1_2_i686", None),
Image("musllinux_1_2", "aarch64", "quay.io/pypa/musllinux_1_2_aarch64", None),
Image("musllinux_1_2", "ppc64le", "quay.io/pypa/musllinux_1_2_ppc64le", None),
Image("musllinux_1_2", "s390x", "quay.io/pypa/musllinux_1_2_s390x", None),
] ]
config = configparser.ConfigParser() config = configparser.ConfigParser()
@@ -78,21 +90,6 @@ for image in images:
for (name, info) in tags_dict.items() for (name, info) in tags_dict.items()
if info["manifest_digest"] == latest_tag["manifest_digest"] if info["manifest_digest"] == latest_tag["manifest_digest"]
) )
elif image.image_name.startswith("ghcr.io/"):
repository = image.image_name[8:]
response = requests.get(
"https://ghcr.io/token", params={"scope": f"repository:{repository}:pull"}
)
response.raise_for_status()
token = response.json()["token"]
response = requests.get(
f"https://ghcr.io/v2/{repository}/tags/list",
headers={"Authorization": f"Bearer {token}"},
)
response.raise_for_status()
ghcr_tags = [(Version(tag), tag) for tag in response.json()["tags"] if tag != "latest"]
ghcr_tags.sort(reverse=True)
tag_name = ghcr_tags[0][1]
else: else:
response = requests.get(f"https://hub.docker.com/v2/repositories/{image.image_name}/tags") response = requests.get(f"https://hub.docker.com/v2/repositories/{image.image_name}/tags")
response.raise_for_status() response.raise_for_status()
@@ -108,16 +105,10 @@ for image in images:
) )
tag_name = pinned_tag["name"] tag_name = pinned_tag["name"]
for platform in image.platforms: if not config.has_section(image.platform):
if not config.has_section(platform): config[image.platform] = {}
config[platform] = {}
suffix = ""
if image.use_platform_suffix:
suffix = f"_{platform.removeprefix('pypy_')}"
config[platform][image.manylinux_version] = f"{image.image_name}{suffix}:{tag_name}"
if not config.has_section("riscv64"): config[image.platform][image.manylinux_version] = f"{image.image_name}:{tag_name}"
config["riscv64"] = {}
with open(RESOURCES / "pinned_docker_images.cfg", "w") as f: with open(RESOURCES / "pinned_docker_images.cfg", "w") as f:
config.write(f) config.write(f)
+4 -2
View File
@@ -1,5 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations
import subprocess import subprocess
import sys import sys
@@ -17,7 +18,7 @@ except ImportError:
) )
def main() -> None: def main():
subprocess.run(["mkdocs", "build"], check=True) subprocess.run(["mkdocs", "build"], check=True)
hti = Html2Image(custom_flags=["--force-device-scale-factor=2"]) hti = Html2Image(custom_flags=["--force-device-scale-factor=2"])
@@ -41,7 +42,8 @@ def main() -> None:
) )
dest_path = Path("docs/data/how-it-works.png") dest_path = Path("docs/data/how-it-works.png")
dest_path.unlink(missing_ok=True) if dest_path.exists():
dest_path.unlink()
Path(screenshot).rename(dest_path) Path(screenshot).rename(dest_path)
+3 -1
View File
@@ -1,9 +1,9 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations
import difflib import difflib
import logging import logging
import tomllib
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from typing import Final from typing import Final
@@ -16,6 +16,8 @@ from packaging.version import InvalidVersion, Version
from rich.logging import RichHandler from rich.logging import RichHandler
from rich.syntax import Syntax from rich.syntax import Syntax
from cibuildwheel._compat import tomllib
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
+11 -139
View File
@@ -1,15 +1,13 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations
import copy import copy
import difflib import difflib
import logging import logging
import operator
import re
import tomllib
from collections.abc import Mapping, MutableMapping from collections.abc import Mapping, MutableMapping
from pathlib import Path from pathlib import Path
from typing import Any, Final, Literal, TypedDict from typing import Any, Final, Literal, TypedDict, Union
import click import click
import requests import requests
@@ -19,6 +17,7 @@ from packaging.version import Version
from rich.logging import RichHandler from rich.logging import RichHandler
from rich.syntax import Syntax from rich.syntax import Syntax
from cibuildwheel._compat import tomllib
from cibuildwheel.extra import dump_python_configurations from cibuildwheel.extra import dump_python_configurations
log = logging.getLogger("cibw") log = logging.getLogger("cibw")
@@ -45,19 +44,13 @@ class ConfigWinPP(TypedDict):
url: str url: str
class ConfigWinGP(TypedDict): class ConfigMacOS(TypedDict):
identifier: str identifier: str
version: str version: str
url: str url: str
class ConfigApple(TypedDict): AnyConfig = Union[ConfigWinCP, ConfigWinPP, ConfigMacOS]
identifier: str
version: str
url: str
AnyConfig = ConfigWinCP | ConfigWinPP | ConfigWinGP | ConfigApple
# 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
@@ -113,72 +106,6 @@ class WindowsVersions:
) )
class GraalPyVersions:
def __init__(self) -> None:
response = requests.get("https://api.github.com/repos/oracle/graalpython/releases")
response.raise_for_status()
releases = response.json()
gp_version_re = re.compile(r"-(\d+\.\d+\.\d+)$")
cp_version_re = re.compile(r"Python (\d+\.\d+(?:\.\d+)?)")
for release in releases:
m = gp_version_re.search(release["tag_name"])
if m:
release["graalpy_version"] = Version(m.group(1))
m = cp_version_re.search(release["body"])
if m:
release["python_version"] = Version(m.group(1))
self.releases = [r for r in releases if "graalpy_version" in r and "python_version" in r]
def update_version(self, identifier: str, spec: Specifier) -> AnyConfig:
if "x86_64" in identifier or "amd64" in identifier:
arch = "x86_64"
elif "arm64" in identifier or "aarch64" in identifier:
arch = "aarch64"
else:
msg = f"{identifier} not supported yet on GraalPy"
raise RuntimeError(msg)
releases = [r for r in self.releases if spec.contains(r["python_version"])]
releases = sorted(releases, key=lambda r: r["graalpy_version"])
if not releases:
msg = f"GraalPy {arch} not found for {spec}!"
raise RuntimeError(msg)
release = releases[-1]
version = release["python_version"]
gpversion = release["graalpy_version"]
if "macosx" in identifier:
arch = "x86_64" if "x86_64" in identifier else "arm64"
config = ConfigApple
platform = "macos"
elif "win" in identifier:
arch = "aarch64" if "arm64" in identifier else "x86_64"
config = ConfigWinGP
platform = "windows"
else:
msg = "GraalPy provides downloads for macOS and Windows and is included for manylinux"
raise RuntimeError(msg)
arch = "amd64" if arch == "x86_64" else "aarch64"
ext = "zip" if "win" in identifier else "tar.gz"
(url,) = (
rf["browser_download_url"]
for rf in release["assets"]
if rf["name"].endswith(f"{platform}-{arch}.{ext}")
and rf["name"].startswith(f"graalpy-{gpversion.major}")
)
return config(
identifier=identifier,
version=f"{version.major}.{version.minor}",
url=url,
)
class PyPyVersions: class PyPyVersions:
def __init__(self, arch_str: ArchStr): def __init__(self, arch_str: ArchStr):
response = requests.get("https://downloads.python.org/pypy/versions.json") response = requests.get("https://downloads.python.org/pypy/versions.json")
@@ -206,7 +133,7 @@ class PyPyVersions:
def update_version_windows(self, spec: Specifier) -> ConfigWinCP: def update_version_windows(self, spec: Specifier) -> ConfigWinCP:
releases = [r for r in self.releases if spec.contains(r["python_version"])] releases = [r for r in self.releases if spec.contains(r["python_version"])]
releases = sorted(releases, key=operator.itemgetter("pypy_version")) releases = sorted(releases, key=lambda r: r["pypy_version"])
releases = [r for r in releases if self.get_arch_file(r)] releases = [r for r in releases if self.get_arch_file(r)]
if not releases: if not releases:
@@ -227,13 +154,13 @@ class PyPyVersions:
url=url, url=url,
) )
def update_version_macos(self, spec: Specifier) -> ConfigApple: def update_version_macos(self, spec: Specifier) -> ConfigMacOS:
if self.arch not in {"64", "ARM64"}: if self.arch not in {"64", "ARM64"}:
msg = f"'{self.arch}' arch not supported yet on macOS" msg = f"'{self.arch}' arch not supported yet on macOS"
raise RuntimeError(msg) raise RuntimeError(msg)
releases = [r for r in self.releases if spec.contains(r["python_version"])] releases = [r for r in self.releases if spec.contains(r["python_version"])]
releases = sorted(releases, key=operator.itemgetter("pypy_version")) releases = sorted(releases, key=lambda r: r["pypy_version"])
if not releases: if not releases:
msg = f"PyPy macOS {self.arch} not found for {spec}!" msg = f"PyPy macOS {self.arch} not found for {spec}!"
@@ -251,7 +178,7 @@ class PyPyVersions:
if "" in rf["platform"] == "darwin" and rf["arch"] == arch if "" in rf["platform"] == "darwin" and rf["arch"] == arch
) )
return ConfigApple( return ConfigMacOS(
identifier=identifier, identifier=identifier,
version=f"{version.major}.{version.minor}", version=f"{version.major}.{version.minor}",
url=url, url=url,
@@ -277,7 +204,7 @@ class CPythonVersions:
def update_version_macos( def update_version_macos(
self, identifier: str, version: Version, spec: Specifier self, identifier: str, version: Version, spec: Specifier
) -> ConfigApple | None: ) -> ConfigMacOS | None:
# see note above on Specifier.filter # see note above on Specifier.filter
unsorted_versions = spec.filter(self.versions_dict) unsorted_versions = spec.filter(self.versions_dict)
sorted_versions = sorted(unsorted_versions, reverse=True) sorted_versions = sorted(unsorted_versions, reverse=True)
@@ -296,7 +223,7 @@ class CPythonVersions:
urls = [rf["url"] for rf in file_info if file_ident in rf["url"]] urls = [rf["url"] for rf in file_info if file_ident in rf["url"]]
if urls: if urls:
return ConfigApple( return ConfigMacOS(
identifier=identifier, identifier=identifier,
version=f"{new_version.major}.{new_version.minor}", version=f"{new_version.major}.{new_version.minor}",
url=urls[0], url=urls[0],
@@ -305,48 +232,6 @@ class CPythonVersions:
return None return None
class CPythonIOSVersions:
def __init__(self) -> None:
response = requests.get(
"https://api.github.com/repos/beeware/Python-Apple-support/releases",
headers={
"Accept": "application/vnd.github+json",
"X-Github-Api-Version": "2022-11-28",
},
)
response.raise_for_status()
releases_info = response.json()
self.versions_dict: dict[Version, dict[int, str]] = {}
# Each release has a name like "3.13-b4"
for release in releases_info:
py_version, build = release["name"].split("-")
version = Version(py_version)
self.versions_dict.setdefault(version, {})
# There are several release assets associated with each release;
# The name of the asset will be something like
# "Python-3.11-iOS-support.b4.tar.gz". Store all builds that are
# "-iOS-support" builds, retaining the download URL.
for asset in release["assets"]:
filename, build, _, _ = asset["name"].rsplit(".", 3)
if filename.endswith("-iOS-support"):
self.versions_dict[version][int(build[1:])] = asset["browser_download_url"]
def update_version_ios(self, identifier: str, version: Version) -> ConfigApple | None:
# Return a config using the highest build number for the given version.
urls = [url for _, url in sorted(self.versions_dict.get(version, {}).items())]
if urls:
return ConfigApple(
identifier=identifier,
version=str(version),
url=urls[-1],
)
return None
# This is a universal interface to all the above Versions classes. Given an # This is a universal interface to all the above Versions classes. Given an
# identifier, it updates a config dict. # identifier, it updates a config dict.
@@ -365,10 +250,6 @@ class AllVersions:
self.macos_pypy = PyPyVersions("64") self.macos_pypy = PyPyVersions("64")
self.macos_pypy_arm64 = PyPyVersions("ARM64") self.macos_pypy_arm64 = PyPyVersions("ARM64")
self.ios_cpython = CPythonIOSVersions()
self.graalpy = GraalPyVersions()
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"])
@@ -386,8 +267,6 @@ class AllVersions:
config_update = self.macos_pypy.update_version_macos(spec) config_update = self.macos_pypy.update_version_macos(spec)
elif "macosx_arm64" in identifier: elif "macosx_arm64" in identifier:
config_update = self.macos_pypy_arm64.update_version_macos(spec) config_update = self.macos_pypy_arm64.update_version_macos(spec)
elif identifier.startswith("gp"):
config_update = self.graalpy.update_version(identifier, spec)
elif "t-win32" in identifier and identifier.startswith("cp"): elif "t-win32" in identifier and identifier.startswith("cp"):
config_update = self.windows_t_32.update_version_windows(spec) config_update = self.windows_t_32.update_version_windows(spec)
elif "win32" in identifier and identifier.startswith("cp"): elif "win32" in identifier and identifier.startswith("cp"):
@@ -399,14 +278,10 @@ class AllVersions:
config_update = self.windows_64.update_version_windows(spec) config_update = self.windows_64.update_version_windows(spec)
elif identifier.startswith("pp"): elif identifier.startswith("pp"):
config_update = self.windows_pypy_64.update_version_windows(spec) config_update = self.windows_pypy_64.update_version_windows(spec)
elif identifier.startswith("gp"):
config_update = self.graalpy.update_version(identifier, spec)
elif "t-win_arm64" in identifier and identifier.startswith("cp"): elif "t-win_arm64" in identifier and identifier.startswith("cp"):
config_update = self.windows_t_arm64.update_version_windows(spec) config_update = self.windows_t_arm64.update_version_windows(spec)
elif "win_arm64" in identifier and identifier.startswith("cp"): elif "win_arm64" in identifier and identifier.startswith("cp"):
config_update = self.windows_arm64.update_version_windows(spec) config_update = self.windows_arm64.update_version_windows(spec)
elif "ios" in identifier:
config_update = self.ios_cpython.update_version_ios(identifier, version)
assert config_update is not None, f"{identifier} not found!" assert config_update is not None, f"{identifier} not found!"
config.update(**config_update) config.update(**config_update)
@@ -442,9 +317,6 @@ def update_pythons(force: bool, level: str) -> None:
for config in configs["macos"]["python_configurations"]: for config in configs["macos"]["python_configurations"]:
all_versions.update_config(config) all_versions.update_config(config)
for config in configs["ios"]["python_configurations"]:
all_versions.update_config(config)
result_toml = dump_python_configurations(configs) result_toml = dump_python_configurations(configs)
rich.print() # spacer rich.print() # spacer
+2 -1
View File
@@ -1,5 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations
import re import re
import sys import sys
@@ -19,7 +20,7 @@ README_CHANGELOG_SECTION = re.compile(
) )
def main() -> None: def main():
changelog_text = CHANGELOG_FILE.read_text() changelog_text = CHANGELOG_FILE.read_text()
readme_text = README_FILE.read_text() readme_text = README_FILE.read_text()
+4 -2
View File
@@ -1,10 +1,10 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations
import difflib import difflib
import logging import logging
import subprocess import subprocess
import tomllib
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from typing import Final from typing import Final
@@ -15,6 +15,8 @@ from packaging.version import InvalidVersion, Version
from rich.logging import RichHandler from rich.logging import RichHandler
from rich.syntax import Syntax from rich.syntax import Syntax
from cibuildwheel._compat import tomllib
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
@@ -34,7 +36,7 @@ class VersionTuple:
version_string: str version_string: str
def git_ls_remote_versions(url: str) -> list[VersionTuple]: def git_ls_remote_versions(url) -> list[VersionTuple]:
versions: list[VersionTuple] = [] versions: list[VersionTuple] = []
tags = subprocess.run( tags = subprocess.run(
["git", "ls-remote", "--tags", url], check=True, text=True, capture_output=True ["git", "ls-remote", "--tags", url], check=True, text=True, capture_output=True
+3 -1
View File
@@ -1 +1,3 @@
__version__ = "3.0.0b1" from __future__ import annotations
__version__ = "2.20.0"
+79 -182
View File
@@ -1,32 +1,41 @@
from __future__ import annotations
import argparse import argparse
import contextlib
import dataclasses import dataclasses
import functools
import os import os
import shutil import shutil
import sys import sys
import tarfile import tarfile
import textwrap import textwrap
import time
import traceback import traceback
import typing import typing
from collections.abc import Generator, Iterable, Sequence from collections.abc import Iterable, Sequence, Set
from pathlib import Path from pathlib import Path
from tempfile import mkdtemp from tempfile import mkdtemp
from typing import Any, Literal, TextIO from typing import Protocol
import cibuildwheel import cibuildwheel
import cibuildwheel.linux
import cibuildwheel.macos
import cibuildwheel.pyodide
import cibuildwheel.util import cibuildwheel.util
import cibuildwheel.windows
from cibuildwheel import errors from cibuildwheel import errors
from cibuildwheel._compat.typing import assert_never
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.logger import log from cibuildwheel.logger import log
from cibuildwheel.options import CommandLineArguments, Options, compute_options from cibuildwheel.options import CommandLineArguments, Options, compute_options
from cibuildwheel.platforms import ALL_PLATFORM_MODULES, get_build_identifiers from cibuildwheel.typing import PLATFORMS, GenericPythonConfiguration, PlatformName
from cibuildwheel.selector import BuildSelector, EnableGroup, selector_matches from cibuildwheel.util import (
from cibuildwheel.typing import PLATFORMS, PlatformName CIBW_CACHE_PATH,
from cibuildwheel.util.file import CIBW_CACHE_PATH BuildSelector,
from cibuildwheel.util.helpers import strtobool CIProvider,
Unbuffered,
chdir,
detect_ci_provider,
fix_ansi_codes_for_github_actions,
strtobool,
)
@dataclasses.dataclass @dataclasses.dataclass
@@ -34,29 +43,6 @@ class GlobalOptions:
print_traceback_on_error: bool = True # decides what happens when errors are hit. print_traceback_on_error: bool = True # decides what happens when errors are hit.
@dataclasses.dataclass(frozen=True)
class FileReport:
name: str
size: str
# Taken from https://stackoverflow.com/a/107717
class Unbuffered:
def __init__(self, stream: TextIO) -> None:
self.stream = stream
def write(self, data: str) -> None:
self.stream.write(data)
self.stream.flush()
def writelines(self, data: Iterable[str]) -> None:
self.stream.writelines(data)
self.stream.flush()
def __getattr__(self, attr: str) -> Any:
return getattr(self.stream, attr)
def main() -> None: def main() -> None:
global_options = GlobalOptions() global_options = GlobalOptions()
try: try:
@@ -66,7 +52,7 @@ def main() -> None:
if log.step_active: if log.step_active:
log.step_end_with_error(message) log.step_end_with_error(message)
else: else:
log.error(message) print(f"cibuildwheel: {message}", file=sys.stderr)
if global_options.print_traceback_on_error: if global_options.print_traceback_on_error:
traceback.print_exc(file=sys.stderr) traceback.print_exc(file=sys.stderr)
@@ -80,10 +66,7 @@ def main_inner(global_options: GlobalOptions) -> None:
rather than exiting directly. rather than exiting directly.
""" """
make_parser = functools.partial(argparse.ArgumentParser, allow_abbrev=False) parser = argparse.ArgumentParser(
if sys.version_info >= (3, 14):
make_parser = functools.partial(make_parser, color=True, suggest_on_error=True)
parser = make_parser(
description="Build wheels for all the platforms.", description="Build wheels for all the platforms.",
epilog=""" epilog="""
Most options are supplied via environment variables or in Most options are supplied via environment variables or in
@@ -94,14 +77,13 @@ def main_inner(global_options: GlobalOptions) -> None:
parser.add_argument( parser.add_argument(
"--platform", "--platform",
choices=["auto", "linux", "macos", "windows", "pyodide", "ios"], choices=["auto", "linux", "macos", "windows", "pyodide"],
default=None, default=None,
help=""" help="""
Platform to build for. Use this option to override the auto-detected Platform to build for. Use this option to override the
platform. Specifying "macos" or "windows" only works on that auto-detected platform. Specifying "macos" or "windows" only works
operating system. "linux" works on any desktop OS, as long as on that operating system, but "linux" works on all three, as long
Docker/Podman is installed. "pyodide" only works on linux and macOS. as Docker/Podman is installed. Default: auto.
"ios" only work on macOS. Default: auto.
""", """,
) )
@@ -119,17 +101,6 @@ def main_inner(global_options: GlobalOptions) -> None:
""", """,
) )
enable_groups_str = ", ".join(g.value for g in EnableGroup)
parser.add_argument(
"--enable",
action="append",
default=[],
metavar="GROUP",
help=f"""
Enable an additional category of builds. Use multiple times to select multiple groups. Choices: {enable_groups_str}.
""",
)
parser.add_argument( parser.add_argument(
"--only", "--only",
default=None, default=None,
@@ -160,7 +131,7 @@ def main_inner(global_options: GlobalOptions) -> None:
parser.add_argument( parser.add_argument(
"package_dir", "package_dir",
metavar="PACKAGE", metavar="PACKAGE",
default=Path(), default=Path("."),
type=Path, type=Path,
nargs="?", nargs="?",
help=""" help="""
@@ -186,6 +157,12 @@ def main_inner(global_options: GlobalOptions) -> None:
help="Do not report an error code if the build does not match any wheels.", help="Do not report an error code if the build does not match any wheels.",
) )
parser.add_argument(
"--prerelease-pythons",
action="store_true",
help="Enable pre-release Python versions if available.",
)
parser.add_argument( parser.add_argument(
"--debug-traceback", "--debug-traceback",
action="store_true", action="store_true",
@@ -223,7 +200,7 @@ def main_inner(global_options: GlobalOptions) -> None:
# This is now the new package dir # This is now the new package dir
args.package_dir = project_dir.resolve() args.package_dir = project_dir.resolve()
with contextlib.chdir(project_dir): with chdir(project_dir):
build_in_directory(args) build_in_directory(args)
finally: finally:
# avoid https://github.com/python/cpython/issues/86962 by performing # avoid https://github.com/python/cpython/issues/86962 by performing
@@ -242,8 +219,6 @@ def _compute_platform_only(only: str) -> PlatformName:
return "windows" return "windows"
if "pyodide_" in only: if "pyodide_" in only:
return "pyodide" return "pyodide"
if "ios_" in only:
return "ios"
msg = f"Invalid --only='{only}', must be a build selector with a known platform" msg = f"Invalid --only='{only}', must be a build selector with a known platform"
raise errors.ConfigurationError(msg) raise errors.ConfigurationError(msg)
@@ -257,7 +232,7 @@ def _compute_platform_auto() -> PlatformName:
return "windows" return "windows"
else: else:
msg = ( msg = (
'Unable to detect platform from "sys.platform". cibuildwheel doesn\'t ' 'cibuildwheel: Unable to detect platform from "sys.platform". cibuildwheel doesn\'t '
"support building wheels for this platform. You might be able to build for a different " "support building wheels for this platform. You might be able to build for a different "
"platform using the --platform argument. Check --help output for more information." "platform using the --platform argument. Check --help output for more information."
) )
@@ -286,47 +261,35 @@ def _compute_platform(args: CommandLineArguments) -> PlatformName:
return _compute_platform_auto() return _compute_platform_auto()
@contextlib.contextmanager class PlatformModule(Protocol):
def print_new_wheels(msg: str, output_dir: Path) -> Generator[None, None, None]: # note that as per PEP544, the self argument is ignored when the protocol
""" # is applied to a module
Prints the new items in a directory upon exiting. The message to display def get_python_configurations(
can include {n} for number of wheels, {s} for total number of seconds, self, build_selector: BuildSelector, architectures: Set[Architecture]
and/or {m} for total number of minutes. Does not print anything if this ) -> Sequence[GenericPythonConfiguration]: ...
exits via exception.
"""
start_time = time.time() def build(self, options: Options, tmp_path: Path) -> None: ...
existing_contents = set(output_dir.iterdir())
yield
final_contents = set(output_dir.iterdir())
new_contents = [
FileReport(wheel.name, f"{(wheel.stat().st_size + 1023) // 1024:,d}")
for wheel in final_contents - existing_contents
]
if not new_contents: # pylint: disable-next=inconsistent-return-statements
return def get_platform_module(platform: PlatformName) -> PlatformModule:
if platform == "linux":
max_name_len = max(len(f.name) for f in new_contents) return cibuildwheel.linux
max_size_len = max(len(f.size) for f in new_contents) if platform == "windows":
n = len(new_contents) return cibuildwheel.windows
s = time.time() - start_time if platform == "macos":
m = s / 60 return cibuildwheel.macos
print( if platform == "pyodide":
msg.format(n=n, s=s, m=m), return cibuildwheel.pyodide
*sorted( assert_never(platform)
f" {f.name:<{max_name_len}s} {f.size:>{max_size_len}s} kB" for f in new_contents
),
sep="\n",
)
def build_in_directory(args: CommandLineArguments) -> None: def build_in_directory(args: CommandLineArguments) -> None:
platform: PlatformName = _compute_platform(args) platform: PlatformName = _compute_platform(args)
if platform == "pyodide" and sys.platform == "win32": if platform == "pyodide" and sys.platform == "win32":
msg = "Building for pyodide is not supported on Windows" msg = "cibuildwheel: Building for pyodide is not supported on Windows"
raise errors.ConfigurationError(msg) print(msg, file=sys.stderr)
sys.exit(2)
options = compute_options(platform=platform, command_line_arguments=args, env=os.environ) options = compute_options(platform=platform, command_line_arguments=args, env=os.environ)
@@ -338,7 +301,7 @@ def build_in_directory(args: CommandLineArguments) -> None:
msg = f"Could not find any of {{{names}}} at root of package" msg = f"Could not find any of {{{names}}} at root of package"
raise errors.ConfigurationError(msg) raise errors.ConfigurationError(msg)
platform_module = ALL_PLATFORM_MODULES[platform] platform_module = get_platform_module(platform)
identifiers = get_build_identifiers( identifiers = get_build_identifiers(
platform_module=platform_module, platform_module=platform_module,
build_selector=options.globals.build_selector, build_selector=options.globals.build_selector,
@@ -378,11 +341,14 @@ def build_in_directory(args: CommandLineArguments) -> None:
output_dir = options.globals.output_dir output_dir = options.globals.output_dir
output_dir.mkdir(parents=True, exist_ok=True) if not output_dir.exists():
output_dir.mkdir(parents=True)
tmp_path = Path(mkdtemp(prefix="cibw-run-")).resolve(strict=True) tmp_path = Path(mkdtemp(prefix="cibw-run-")).resolve(strict=True)
try: try:
with print_new_wheels("\n{n} wheels produced in {m:.0f} minutes:", output_dir): with cibuildwheel.util.print_new_wheels(
"\n{n} wheels produced in {m:.0f} minutes:", output_dir
):
platform_module.build(options, tmp_path) platform_module.build(options, tmp_path)
finally: finally:
# avoid https://github.com/python/cpython/issues/86962 by performing # avoid https://github.com/python/cpython/issues/86962 by performing
@@ -415,28 +381,28 @@ def print_preamble(platform: str, options: Options, identifiers: Sequence[str])
print() print()
print(f"Cache folder: {CIBW_CACHE_PATH}") print(f"Cache folder: {CIBW_CACHE_PATH}")
print()
warnings = detect_warnings(options=options, identifiers=identifiers) warnings = detect_warnings(options=options, identifiers=identifiers)
for warning in warnings: if warnings:
log.warning(warning) print("\nWarnings:")
for warning in warnings:
print(" " + warning)
print("Here we go!\n") print("\nHere we go!\n")
def get_build_identifiers(
platform_module: PlatformModule,
build_selector: BuildSelector,
architectures: Set[Architecture],
) -> list[str]:
python_configurations = platform_module.get_python_configurations(build_selector, architectures)
return [config.identifier for config in python_configurations]
def detect_warnings(*, options: Options, identifiers: Iterable[str]) -> list[str]: def detect_warnings(*, options: Options, identifiers: Iterable[str]) -> list[str]:
warnings = [] warnings = []
python_version_deprecation = ((3, 11), 3)
if sys.version_info[:2] < python_version_deprecation[0]:
python_version = ".".join(map(str, python_version_deprecation[0]))
msg = (
f"cibuildwheel {python_version_deprecation[1]} will require Python {python_version}+, "
"please upgrade the Python version used to run cibuildwheel. "
"This does not affect the versions you can target when building wheels. See: https://cibuildwheel.pypa.io/en/stable/#what-does-it-do"
)
warnings.append(msg)
# warn about deprecated {python} and {pip} # warn about deprecated {python} and {pip}
for option_name in ["test_command", "before_build"]: for option_name in ["test_command", "before_build"]:
option_values = [getattr(options.build_options(i), option_name) for i in identifiers] option_values = [getattr(options.build_options(i), option_name) for i in identifiers]
@@ -444,78 +410,9 @@ def detect_warnings(*, options: Options, identifiers: Iterable[str]) -> list[str
if any(o and ("{python}" in o or "{pip}" in o) for o in option_values): if any(o and ("{python}" in o or "{pip}" in o) for o in option_values):
# Reminder: in an f-string, double braces means literal single brace # Reminder: in an f-string, double braces means literal single brace
msg = ( msg = (
f"{option_name}: '{{python}}' and '{{pip}}' are no longer supported " f"{option_name}: '{{python}}' and '{{pip}}' are no longer needed, "
"and have been removed in cibuildwheel 3. Simply use 'python' or 'pip' instead." "and will be removed in a future release. Simply use 'python' or 'pip' instead."
) )
raise errors.ConfigurationError(msg)
build_selector = options.globals.build_selector
test_selector = options.globals.test_selector
all_valid_identifiers = [
config.identifier
for module in ALL_PLATFORM_MODULES.values()
for config in module.all_python_configurations()
]
enabled_selector = BuildSelector(
build_config="*", skip_config="", enable=options.globals.build_selector.enable
)
all_enabled_identifiers = [
identifier for identifier in all_valid_identifiers if enabled_selector(identifier)
]
warnings += check_for_invalid_selectors(
selector_name="build",
selector_value=build_selector.build_config,
all_valid_identifiers=all_valid_identifiers,
all_enabled_identifiers=all_enabled_identifiers,
)
warnings += check_for_invalid_selectors(
selector_name="skip",
selector_value=build_selector.skip_config,
all_valid_identifiers=all_valid_identifiers,
all_enabled_identifiers=all_enabled_identifiers,
)
warnings += check_for_invalid_selectors(
selector_name="test_skip",
selector_value=test_selector.skip_config,
all_valid_identifiers=all_valid_identifiers,
all_enabled_identifiers=all_enabled_identifiers,
)
return warnings
def check_for_invalid_selectors(
*,
selector_name: Literal["build", "skip", "test_skip"],
selector_value: str,
all_valid_identifiers: Sequence[str],
all_enabled_identifiers: Sequence[str],
) -> list[str]:
warnings = []
for selector in selector_value.split():
if not any(selector_matches(selector, i) for i in all_enabled_identifiers):
msg = f"Invalid {selector_name} selector: {selector!r}. "
error_type: type = errors.ConfigurationError
if any(selector_matches(selector, i) for i in all_valid_identifiers):
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:
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
if "p36" in selector or "p37" in selector:
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 selector_name == "build":
raise error_type(msg)
msg += "This selector will have no effect. "
warnings.append(msg) warnings.append(msg)
return warnings return warnings
+1
View File
@@ -0,0 +1 @@
from __future__ import annotations
+10
View File
@@ -0,0 +1,10 @@
from __future__ import annotations
import sys
if sys.version_info >= (3, 11):
from tomllib import load, loads
else:
from tomli import load, loads
__all__ = ["load", "loads"]
+14
View File
@@ -0,0 +1,14 @@
from __future__ import annotations
import sys
if sys.version_info < (3, 11):
from typing_extensions import NotRequired, Self, assert_never
else:
from typing import NotRequired, Self, assert_never
__all__ = (
"assert_never",
"NotRequired",
"Self",
)
+43 -85
View File
@@ -1,15 +1,14 @@
from __future__ import annotations
import functools
import platform as platform_module import platform as platform_module
import re import re
import shutil
import subprocess
import sys import sys
import typing
from collections.abc import Set from collections.abc import Set
from enum import StrEnum, auto from enum import Enum
from typing import Final, Literal from typing import Final, Literal
from cibuildwheel import errors from ._compat.typing import assert_never
from .typing import PlatformName from .typing import PlatformName
PRETTY_NAMES: Final[dict[PlatformName, str]] = { PRETTY_NAMES: Final[dict[PlatformName, str]] = {
@@ -17,7 +16,6 @@ PRETTY_NAMES: Final[dict[PlatformName, str]] = {
"macos": "macOS", "macos": "macOS",
"windows": "Windows", "windows": "Windows",
"pyodide": "Pyodide", "pyodide": "Pyodide",
"ios": "iOS",
} }
ARCH_SYNONYMS: Final[list[dict[PlatformName, str | None]]] = [ ARCH_SYNONYMS: Final[list[dict[PlatformName, str | None]]] = [
@@ -27,60 +25,46 @@ ARCH_SYNONYMS: Final[list[dict[PlatformName, str | None]]] = [
] ]
def _check_aarch32_el0() -> bool: @functools.total_ordering
"""Check if running armv7l natively on aarch64 is supported""" class Architecture(Enum):
if not sys.platform.startswith("linux"): value: str
return False
if platform_module.machine() != "aarch64":
return False
executable = shutil.which("linux32")
if executable is None:
return False
check = subprocess.run([executable, "uname", "-m"], check=False, capture_output=True, text=True)
return check.returncode == 0 and check.stdout.startswith("armv")
@typing.final
class Architecture(StrEnum):
# mac/linux archs # mac/linux archs
x86_64 = auto() x86_64 = "x86_64"
# linux archs # linux archs
i686 = auto() i686 = "i686"
aarch64 = auto() aarch64 = "aarch64"
ppc64le = auto() ppc64le = "ppc64le"
s390x = auto() s390x = "s390x"
armv7l = auto()
riscv64 = auto()
# mac archs # mac archs
universal2 = auto() universal2 = "universal2"
arm64 = auto() arm64 = "arm64"
# windows archs # windows archs
x86 = auto() x86 = "x86"
AMD64 = "AMD64" AMD64 = "AMD64"
ARM64 = "ARM64" ARM64 = "ARM64"
# WebAssembly # WebAssembly
wasm32 = auto() wasm32 = "wasm32"
# iOS "multiarch" architectures that include both # Allow this to be sorted
# the CPU architecture and the ABI. def __lt__(self, other: Architecture) -> bool:
arm64_iphoneos = auto() return self.value < other.value
arm64_iphonesimulator = auto()
x86_64_iphonesimulator = auto() def __str__(self) -> str:
return self.name
@staticmethod @staticmethod
def parse_config(config: str, platform: PlatformName) -> "set[Architecture]": 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):
if arch_str == "auto": if arch_str == "auto":
result |= Architecture.auto_archs(platform=platform) result |= Architecture.auto_archs(platform=platform)
elif arch_str == "native": elif arch_str == "native":
native_arch = Architecture.native_arch(platform=platform) result.add(Architecture(platform_module.machine()))
if native_arch:
result.add(native_arch)
elif arch_str == "all": elif arch_str == "all":
result |= Architecture.all_archs(platform=platform) result |= Architecture.all_archs(platform=platform)
elif arch_str == "auto64": elif arch_str == "auto64":
@@ -88,17 +72,13 @@ class Architecture(StrEnum):
elif arch_str == "auto32": elif arch_str == "auto32":
result |= Architecture.bitness_archs(platform=platform, bitness="32") result |= Architecture.bitness_archs(platform=platform, bitness="32")
else: else:
try: result.add(Architecture(arch_str))
result.add(Architecture(arch_str))
except ValueError as e:
msg = f"Invalid architecture '{arch_str}'"
raise errors.ConfigurationError(msg) from e
return result return result
@staticmethod @staticmethod
def native_arch(platform: PlatformName) -> "Architecture | None": def native_arch(platform: PlatformName) -> Architecture | None:
native_machine = platform_module.machine() if platform == "pyodide":
native_architecture = Architecture(native_machine) return Architecture.wasm32
# 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 = (
@@ -107,18 +87,8 @@ class Architecture(StrEnum):
else ("macos" if sys.platform.startswith("darwin") else "linux") else ("macos" if sys.platform.startswith("darwin") else "linux")
) )
if platform == "pyodide": native_machine = platform_module.machine()
return Architecture.wasm32 native_architecture = Architecture(native_machine)
elif platform == "ios":
# Can only build for iOS on macOS. The "native" architecture is the
# simulator for the macOS native platform.
if host_platform == "macos":
if native_architecture == Architecture.x86_64:
return Architecture.x86_64_iphonesimulator
else:
return Architecture.arm64_iphonesimulator
else:
return None
# we might need to rename the native arch to the machine we're running # we might need to rename the native arch to the machine we're running
# on, as the same arch can have different names on different platforms # on, as the same arch can have different names on different platforms
@@ -136,30 +106,23 @@ class Architecture(StrEnum):
return native_architecture return native_architecture
@staticmethod @staticmethod
def auto_archs(platform: PlatformName) -> "set[Architecture]": def auto_archs(platform: PlatformName) -> set[Architecture]:
native_arch = Architecture.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}
if platform == "linux": if platform == "linux" and Architecture.x86_64 in result:
if Architecture.x86_64 in result: # x86_64 machines can run i686 containers
# x86_64 machines can run i686 containers result.add(Architecture.i686)
result.add(Architecture.i686)
elif Architecture.aarch64 in result and _check_aarch32_el0():
result.add(Architecture.armv7l)
elif platform == "windows" and Architecture.AMD64 in result: if platform == "windows" and Architecture.AMD64 in result:
result.add(Architecture.x86) result.add(Architecture.x86)
elif platform == "ios" and native_arch == Architecture.arm64_iphonesimulator:
# Also build the device wheel if we're on ARM64.
result.add(Architecture.arm64_iphoneos)
return result return result
@staticmethod @staticmethod
def all_archs(platform: PlatformName) -> "set[Architecture]": def all_archs(platform: PlatformName) -> set[Architecture]:
all_archs_map = { all_archs_map = {
"linux": { "linux": {
Architecture.x86_64, Architecture.x86_64,
@@ -167,30 +130,25 @@ class Architecture(StrEnum):
Architecture.aarch64, Architecture.aarch64,
Architecture.ppc64le, Architecture.ppc64le,
Architecture.s390x, Architecture.s390x,
Architecture.armv7l,
Architecture.riscv64,
}, },
"macos": {Architecture.x86_64, Architecture.arm64, Architecture.universal2}, "macos": {Architecture.x86_64, Architecture.arm64, Architecture.universal2},
"windows": {Architecture.x86, Architecture.AMD64, Architecture.ARM64}, "windows": {Architecture.x86, Architecture.AMD64, Architecture.ARM64},
"pyodide": {Architecture.wasm32}, "pyodide": {Architecture.wasm32},
"ios": {
Architecture.x86_64_iphonesimulator,
Architecture.arm64_iphonesimulator,
Architecture.arm64_iphoneos,
},
} }
return all_archs_map[platform] return all_archs_map[platform]
@staticmethod @staticmethod
def bitness_archs(platform: PlatformName, bitness: Literal["64", "32"]) -> "set[Architecture]": # pylint: disable-next=inconsistent-return-statements
archs_32 = {Architecture.i686, Architecture.x86, Architecture.armv7l} def bitness_archs(platform: PlatformName, bitness: Literal["64", "32"]) -> set[Architecture]:
archs_32 = {Architecture.i686, Architecture.x86}
auto_archs = Architecture.auto_archs(platform) auto_archs = Architecture.auto_archs(platform)
if bitness == "64": if bitness == "64":
return auto_archs - archs_32 return auto_archs - archs_32
if bitness == "32": elif bitness == "32":
return auto_archs & archs_32 return auto_archs & archs_32
typing.assert_never(bitness) else:
assert_never(bitness)
def allowed_architectures_check( def allowed_architectures_check(
+5 -7
View File
@@ -1,16 +1,14 @@
from __future__ import annotations
import subprocess import subprocess
from collections.abc import ( from collections.abc import Iterable, Mapping, Sequence
Callable,
Iterable,
Mapping,
Sequence,
)
from dataclasses import dataclass from dataclasses import dataclass
from typing import Callable, Dict, List # noqa: TID251
import bashlex import bashlex
# a function that takes a command and the environment, and returns the result # a function that takes a command and the environment, and returns the result
EnvironmentExecutor = Callable[[list[str], dict[str, str]], str] 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:
-69
View File
@@ -1,69 +0,0 @@
import os
import re
from enum import Enum
from .util.helpers import strtobool
class CIProvider(Enum):
# official support
travis_ci = "travis"
circle_ci = "circle_ci"
azure_pipelines = "azure_pipelines"
github_actions = "github_actions"
gitlab = "gitlab"
cirrus_ci = "cirrus_ci"
# unofficial support
appveyor = "appveyor"
other = "other"
def detect_ci_provider() -> CIProvider | None:
if "TRAVIS" in os.environ:
return CIProvider.travis_ci
elif "APPVEYOR" in os.environ:
return CIProvider.appveyor
elif "CIRCLECI" in os.environ:
return CIProvider.circle_ci
elif "AZURE_HTTP_USER_AGENT" in os.environ:
return CIProvider.azure_pipelines
elif "GITHUB_ACTIONS" in os.environ:
return CIProvider.github_actions
elif "GITLAB_CI" in os.environ:
return CIProvider.gitlab
elif "CIRRUS_CI" in os.environ:
return CIProvider.cirrus_ci
elif strtobool(os.environ.get("CI", "false")):
return CIProvider.other
else:
return None
def fix_ansi_codes_for_github_actions(text: str) -> str:
"""
Github Actions forgets the current ANSI style on every new line. This
function repeats the current ANSI style on every new line.
"""
ansi_code_regex = re.compile(r"(\033\[[0-9;]*m)")
ansi_codes: list[str] = []
output = ""
for line in text.splitlines(keepends=True):
# add the current ANSI codes to the beginning of the line
output += "".join(ansi_codes) + line
# split the line at each ANSI code
parts = ansi_code_regex.split(line)
# if there are any ANSI codes, save them
if len(parts) > 1:
# iterate over the ANSI codes in this line
for code in parts[1::2]:
if code == "\033[0m":
# reset the list of ANSI codes when the clear code is found
ansi_codes = []
else:
ansi_codes.append(code)
return output
+2
View File
@@ -1,3 +1,5 @@
from __future__ import annotations
import dataclasses import dataclasses
from collections.abc import Mapping, Sequence from collections.abc import Mapping, Sequence
from typing import Any, Protocol from typing import Any, Protocol
+4 -31
View File
@@ -35,14 +35,11 @@ class NonPlatformWheelError(FatalError):
""" """
Build failed because a pure Python wheel was generated. Build failed because a pure Python wheel was generated.
If you intend to build a pure-Python wheel, you don't need If you intend to build a pure-Python wheel, you don't need cibuildwheel - use
cibuildwheel - use `pip wheel .`, `pipx run build --wheel`, `uv `pip wheel -w DEST_DIR .` instead.
build --wheel`, etc. instead. You only need cibuildwheel if you
have compiled (not Python) code in your wheels making them depend
on the platform.
If you expected a platform wheel, check your project configuration, If you expected a platform wheel, check your project configuration, or run
or run cibuildwheel with CIBW_BUILD_VERBOSITY=1 to view build logs. cibuildwheel with CIBW_BUILD_VERBOSITY=1 to view build logs.
""" """
) )
super().__init__(message) super().__init__(message)
@@ -61,27 +58,3 @@ class AlreadyBuiltWheelError(FatalError):
) )
super().__init__(message) super().__init__(message)
self.return_code = 6 self.return_code = 6
class OCIEngineTooOldError(FatalError):
def __init__(self, message: str) -> None:
super().__init__(message)
self.return_code = 7
class RepairStepProducedNoWheelError(FatalError):
def __init__(self) -> None:
message = textwrap.dedent(
"""
Build failed because the repair step completed successfully but
did not produce a wheel.
Your `repair-wheel-command` is expected to place the repaired
wheel in the {dest_dir} directory. See the documentation for
example configurations:
https://cibuildwheel.pypa.io/en/stable/options/#repair-wheel-command
"""
)
super().__init__(message)
self.return_code = 8
+2
View File
@@ -2,6 +2,8 @@
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
from collections.abc import Mapping, Sequence from collections.abc import Mapping, Sequence
from io import StringIO from io import StringIO
from typing import Protocol from typing import Protocol
-68
View File
@@ -1,68 +0,0 @@
import shlex
import typing
from collections.abc import Sequence
from dataclasses import dataclass
from typing import Literal, Self, get_args
from .logger import log
from .util.helpers import parse_key_value_string
BuildFrontendName = Literal["pip", "build", "build[uv]"]
@dataclass(frozen=True)
class BuildFrontendConfig:
name: BuildFrontendName
args: Sequence[str] = ()
@classmethod
def from_config_string(cls, config_string: str) -> Self:
config_dict = parse_key_value_string(config_string, ["name"], ["args"])
name = " ".join(config_dict["name"])
if name not in get_args(BuildFrontendName):
names = ", ".join(repr(n) for n in get_args(BuildFrontendName))
msg = f"Unrecognised build frontend {name!r}, must be one of {names}"
raise ValueError(msg)
name = typing.cast(BuildFrontendName, name)
args = config_dict.get("args") or []
return cls(name=name, args=args)
def options_summary(self) -> str | dict[str, str]:
if not self.args:
return self.name
else:
return {"name": self.name, "args": repr(self.args)}
def _get_verbosity_flags(level: int, frontend: BuildFrontendName) -> list[str]:
if level < 0:
if frontend == "pip":
return ["-" + -level * "q"]
msg = f"build_verbosity {level} is not supported for {frontend} frontend. Ignoring."
log.warning(msg)
if level > 0:
if frontend == "pip":
return ["-" + level * "v"]
if level > 1:
return ["-" + (level - 1) * "v"]
return []
def _split_config_settings(config_settings: str) -> list[str]:
config_settings_list = shlex.split(config_settings)
return [f"-C{setting}" for setting in config_settings_list]
def get_build_frontend_extra_flags(
build_frontend: BuildFrontendConfig, verbosity_level: int, config_settings: str
) -> list[str]:
return [
*_split_config_settings(config_settings),
*build_frontend.args,
*_get_verbosity_flags(verbosity_level, build_frontend.name),
]
@@ -1,35 +1,33 @@
import contextlib from __future__ import annotations
import subprocess import subprocess
import sys import sys
import textwrap import textwrap
from collections import OrderedDict
from collections.abc import Iterable, Iterator, Sequence, Set from collections.abc import Iterable, Iterator, Sequence, Set
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path, PurePath, PurePosixPath from pathlib import Path, PurePath, PurePosixPath
from typing import assert_never from typing import OrderedDict, Tuple
from .. import errors from packaging.version import Version
from ..architecture import Architecture
from ..frontend import BuildFrontendConfig, get_build_frontend_extra_flags
from ..logger import log
from ..oci_container import OCIContainer, OCIContainerEngineConfig, OCIPlatform
from ..options import BuildOptions, Options
from ..selector import BuildSelector
from ..typing import PathOrStr
from ..util import resources
from ..util.file import copy_test_sources
from ..util.helpers import prepare_command, unwrap
from ..util.packaging import find_compatible_wheel
ARCHITECTURE_OCI_PLATFORM_MAP = { from . import errors
Architecture.x86_64: OCIPlatform.AMD64, from ._compat.typing import assert_never
Architecture.i686: OCIPlatform.i386, from .architecture import Architecture
Architecture.aarch64: OCIPlatform.ARM64, from .logger import log
Architecture.ppc64le: OCIPlatform.PPC64LE, from .oci_container import OCIContainer, OCIContainerEngineConfig
Architecture.s390x: OCIPlatform.S390X, from .options import BuildOptions, Options
Architecture.armv7l: OCIPlatform.ARMV7, from .typing import PathOrStr
Architecture.riscv64: OCIPlatform.RISCV64, from .util import (
} BuildFrontendConfig,
BuildSelector,
find_compatible_wheel,
get_build_verbosity_extra_flags,
prepare_command,
read_python_configs,
split_config_settings,
test_fail_cwd_file,
unwrap,
)
@dataclass(frozen=True) @dataclass(frozen=True)
@@ -51,16 +49,13 @@ class BuildStep:
container_image: str container_image: str
def all_python_configurations() -> list[PythonConfiguration]:
config_dicts = resources.read_python_configs("linux")
return [PythonConfiguration(**item) for item in config_dicts]
def get_python_configurations( def get_python_configurations(
build_selector: BuildSelector, build_selector: BuildSelector,
architectures: Set[Architecture], architectures: Set[Architecture],
) -> list[PythonConfiguration]: ) -> list[PythonConfiguration]:
python_configurations = all_python_configurations() full_python_configs = read_python_configs("linux")
python_configurations = [PythonConfiguration(**item) for item in full_python_configs]
# return all configurations whose arch is in our `architectures` set, # return all configurations whose arch is in our `architectures` set,
# and match the build/skip rules # and match the build/skip rules
@@ -99,7 +94,7 @@ def get_build_steps(
Groups PythonConfigurations into BuildSteps. Each BuildStep represents a Groups PythonConfigurations into BuildSteps. Each BuildStep represents a
separate container instance. separate container instance.
""" """
steps = OrderedDict[tuple[str, str, str, OCIContainerEngineConfig], BuildStep]() steps = OrderedDict[Tuple[str, str, str, OCIContainerEngineConfig], BuildStep]()
for config in python_configurations: for config in python_configurations:
_, platform_tag = config.identifier.split("-", 1) _, platform_tag = config.identifier.split("-", 1)
@@ -129,33 +124,26 @@ def check_all_python_exist(
*, platform_configs: Iterable[PythonConfiguration], container: OCIContainer *, platform_configs: Iterable[PythonConfiguration], container: OCIContainer
) -> None: ) -> None:
exist = True exist = True
has_manylinux_interpreters = False has_manylinux_interpreters = True
messages = [] messages = []
with contextlib.suppress(subprocess.CalledProcessError): try:
# use capture_output to keep quiet # use capture_output to keep quiet
container.call(["manylinux-interpreters", "--help"], capture_output=True) container.call(["manylinux-interpreters", "--help"], capture_output=True)
has_manylinux_interpreters = True except subprocess.CalledProcessError:
has_manylinux_interpreters = False
for config in platform_configs: for config in platform_configs:
python_path = config.path / "bin" / "python" python_path = config.path / "bin" / "python"
if has_manylinux_interpreters: try:
try: if has_manylinux_interpreters:
container.call(["manylinux-interpreters", "ensure", config.path.name]) container.call(["manylinux-interpreters", "ensure", config.path.name])
except subprocess.CalledProcessError: container.call(["test", "-x", python_path])
messages.append( except subprocess.CalledProcessError:
f" 'manylinux-interpreters ensure {config.path.name}' needed to build '{config.identifier}' failed in container running image '{container.image}'." messages.append(
" Either the installation failed or this interpreter is not available in that image. Please check the logs." f" '{python_path}' executable doesn't exist in image '{container.image}' to build '{config.identifier}'."
) )
exist = False exist = False
else:
try:
container.call(["test", "-x", python_path])
except subprocess.CalledProcessError:
messages.append(
f" '{python_path}' executable doesn't exist in image '{container.image}' to build '{config.identifier}'."
)
exist = False
if not exist: if not exist:
message = "\n".join(messages) message = "\n".join(messages)
raise errors.FatalError(message) raise errors.FatalError(message)
@@ -168,7 +156,6 @@ def build_in_container(
container: OCIContainer, container: OCIContainer,
container_project_path: PurePath, container_project_path: PurePath,
container_package_dir: PurePath, container_package_dir: PurePath,
local_tmp_dir: Path,
) -> None: ) -> None:
container_output_dir = PurePosixPath("/output") container_output_dir = PurePosixPath("/output")
@@ -184,7 +171,7 @@ def build_in_container(
log.step("Running before_all...") log.step("Running before_all...")
env = container.get_environment() env = container.get_environment()
env["PATH"] = f"/opt/python/cp39-cp39/bin:{env['PATH']}" env["PATH"] = f'/opt/python/cp38-cp38/bin:{env["PATH"]}'
env["PIP_DISABLE_PIP_VERSION_CHECK"] = "1" env["PIP_DISABLE_PIP_VERSION_CHECK"] = "1"
env["PIP_ROOT_USER_ACTION"] = "ignore" env["PIP_ROOT_USER_ACTION"] = "ignore"
env = before_all_options.environment.as_dictionary( env = before_all_options.environment.as_dictionary(
@@ -202,31 +189,31 @@ def build_in_container(
for config in platform_configs: for config in platform_configs:
log.build_start(config.identifier) log.build_start(config.identifier)
local_identifier_tmp_dir = local_tmp_dir / config.identifier
build_options = options.build_options(config.identifier) build_options = options.build_options(config.identifier)
build_frontend = build_options.build_frontend or BuildFrontendConfig("build") build_frontend = build_options.build_frontend or BuildFrontendConfig("pip")
use_uv = build_frontend.name == "build[uv]" use_uv = build_frontend.name == "build[uv]" and Version(config.version) >= Version("3.8")
pip = ["uv", "pip"] if use_uv else ["pip"] pip = ["uv", "pip"] if use_uv else ["pip"]
log.step("Setting up build environment...")
dependency_constraint_flags: list[PathOrStr] = [] dependency_constraint_flags: list[PathOrStr] = []
local_constraints_file = build_options.dependency_constraints.get_for_python_version(
version=config.version, if build_options.dependency_constraints:
tmp_dir=local_identifier_tmp_dir, constraints_file = build_options.dependency_constraints.get_for_python_version(
) config.version
if local_constraints_file: )
container_constraints_file = PurePosixPath("/constraints.txt") container_constraints_file = PurePosixPath("/constraints.txt")
container.copy_into(local_constraints_file, container_constraints_file)
container.copy_into(constraints_file, container_constraints_file)
dependency_constraint_flags = ["-c", container_constraints_file] dependency_constraint_flags = ["-c", container_constraints_file]
log.step("Setting up build environment...")
env = container.get_environment() env = container.get_environment()
env["PIP_DISABLE_PIP_VERSION_CHECK"] = "1" env["PIP_DISABLE_PIP_VERSION_CHECK"] = "1"
env["PIP_ROOT_USER_ACTION"] = "ignore" env["PIP_ROOT_USER_ACTION"] = "ignore"
# put this config's python top of the list # put this config's python top of the list
python_bin = config.path / "bin" python_bin = config.path / "bin"
env["PATH"] = f"{python_bin}:{env['PATH']}" env["PATH"] = f'{python_bin}:{env["PATH"]}'
env = build_options.environment.as_dictionary(env, executor=container.environment_executor) env = build_options.environment.as_dictionary(env, executor=container.environment_executor)
@@ -271,11 +258,11 @@ def build_in_container(
container.call(["rm", "-rf", built_wheel_dir]) container.call(["rm", "-rf", built_wheel_dir])
container.call(["mkdir", "-p", built_wheel_dir]) container.call(["mkdir", "-p", built_wheel_dir])
extra_flags = get_build_frontend_extra_flags( extra_flags = split_config_settings(build_options.config_settings, build_frontend.name)
build_frontend, build_options.build_verbosity, build_options.config_settings extra_flags += build_frontend.args
)
if build_frontend.name == "pip": if build_frontend.name == "pip":
extra_flags += get_build_verbosity_extra_flags(build_options.build_verbosity)
container.call( container.call(
[ [
"python", "python",
@@ -290,6 +277,9 @@ def build_in_container(
env=env, env=env,
) )
elif build_frontend.name == "build" or build_frontend.name == "build[uv]": elif build_frontend.name == "build" or build_frontend.name == "build[uv]":
if not 0 <= build_options.build_verbosity < 2:
msg = f"build_verbosity {build_options.build_verbosity} is not supported for build frontend. Ignoring."
log.warning(msg)
if use_uv and "--no-isolation" not in extra_flags and "-n" not in extra_flags: if use_uv and "--no-isolation" not in extra_flags and "-n" not in extra_flags:
extra_flags += ["--installer=uv"] extra_flags += ["--installer=uv"]
container.call( container.call(
@@ -327,9 +317,6 @@ def build_in_container(
repaired_wheels = container.glob(repaired_wheel_dir, "*.whl") repaired_wheels = container.glob(repaired_wheel_dir, "*.whl")
if not repaired_wheels:
raise errors.RepairStepProducedNoWheelError()
for repaired_wheel in repaired_wheels: for repaired_wheel in repaired_wheels:
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)
@@ -350,12 +337,13 @@ def build_in_container(
venv_dir = testing_temp_dir / "venv" venv_dir = testing_temp_dir / "venv"
if use_uv: if use_uv:
container.call(["uv", "venv", venv_dir, "--python", python_bin / "python"], env=env) container.call(["uv", "venv", venv_dir], env=env)
else: else:
# Use embedded dependencies from virtualenv to ensure determinism # Use embedded dependencies from virtualenv to ensure determinism
venv_args = ["--no-periodic-update", "--pip=embed", "--no-setuptools"] venv_args = ["--no-periodic-update", "--pip=embed"]
if "38" in config.identifier: # In Python<3.12, setuptools & wheel are installed as well
venv_args.append("--no-wheel") if Version(config.version) < Version("3.12"):
venv_args.extend(("--setuptools=embed", "--wheel=embed"))
container.call(["python", "-m", "virtualenv", *venv_args, venv_dir], env=env) container.call(["python", "-m", "virtualenv", *venv_args, venv_dir], env=env)
virtualenv_env = env.copy() virtualenv_env = env.copy()
@@ -393,19 +381,9 @@ def build_in_container(
package=container_package_dir, package=container_package_dir,
wheel=wheel_to_test, wheel=wheel_to_test,
) )
test_cwd = testing_temp_dir / "test_cwd"
if build_options.test_sources: container.call(["mkdir", "-p", test_cwd])
test_cwd = testing_temp_dir / "test_cwd" container.copy_into(test_fail_cwd_file, test_cwd / "test_fail.py")
container.call(["mkdir", "-p", test_cwd])
copy_test_sources(
build_options.test_sources,
build_options.package_dir,
test_cwd,
copy_into=container.copy_into,
)
else:
# There are no test sources. Run the tests in the project directory.
test_cwd = PurePosixPath(container_project_path)
container.call(["sh", "-c", test_command_prepared], cwd=test_cwd, env=virtualenv_env) container.call(["sh", "-c", test_command_prepared], cwd=test_cwd, env=virtualenv_env)
@@ -428,7 +406,7 @@ def build_in_container(
log.step_end() log.step_end()
def build(options: Options, tmp_path: Path) -> None: def build(options: Options, tmp_path: Path) -> None: # noqa: ARG001
python_configurations = get_python_configurations( python_configurations = get_python_configurations(
options.globals.build_selector, options.globals.architectures options.globals.build_selector, options.globals.architectures
) )
@@ -453,12 +431,12 @@ def build(options: Options, tmp_path: Path) -> None:
except subprocess.CalledProcessError as error: except subprocess.CalledProcessError as error:
msg = unwrap( msg = unwrap(
f""" f"""
{build_step.container_engine.name} not found. An OCI exe like cibuildwheel: {build_step.container_engine.name} not found. An
Docker or Podman is required to run Linux builds. If you're OCI exe like Docker or Podman is required to run Linux builds.
building on Travis CI, add `services: [docker]` to your If you're building on Travis CI, add `services: [docker]` to
.travis.yml. If you're building on Circle CI in Linux, add a your .travis.yml. If you're building on Circle CI in Linux,
`setup_remote_docker` step to your .circleci/config.yml. If add a `setup_remote_docker` step to your .circleci/config.yml.
you're building on Cirrus CI, use `docker_builder` task. If you're building on Cirrus CI, use `docker_builder` task.
""" """
) )
raise errors.ConfigurationError(msg) from error raise errors.ConfigurationError(msg) from error
@@ -468,11 +446,10 @@ def build(options: Options, tmp_path: Path) -> None:
log.step(f"Starting container image {build_step.container_image}...") log.step(f"Starting container image {build_step.container_image}...")
print(f"info: This container will host the build for {', '.join(ids_to_build)}...") print(f"info: This container will host the build for {', '.join(ids_to_build)}...")
architecture = Architecture(build_step.platform_tag.split("_", 1)[1])
with OCIContainer( with OCIContainer(
image=build_step.container_image, image=build_step.container_image,
oci_platform=ARCHITECTURE_OCI_PLATFORM_MAP[architecture], enforce_32_bit=build_step.platform_tag.endswith("i686"),
cwd=container_project_path, cwd=container_project_path,
engine=build_step.container_engine, engine=build_step.container_engine,
) as container: ) as container:
@@ -482,7 +459,6 @@ def build(options: Options, tmp_path: Path) -> None:
container=container, container=container,
container_project_path=container_project_path, container_project_path=container_project_path,
container_package_dir=container_package_dir, container_package_dir=container_package_dir,
local_tmp_dir=tmp_path,
) )
except subprocess.CalledProcessError as error: except subprocess.CalledProcessError as error:
+38 -45
View File
@@ -1,13 +1,15 @@
from __future__ import annotations
import codecs import codecs
import os import os
import re import re
import sys import sys
import time import time
from typing import IO, AnyStr, Final from typing import IO, AnyStr, Final, Tuple
from .ci import CIProvider, detect_ci_provider from .util import CIProvider, detect_ci_provider
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]"),
@@ -21,15 +23,11 @@ PLATFORM_IDENTIFIER_DESCRIPTIONS: Final[dict[str, str]] = {
"manylinux_aarch64": "manylinux aarch64", "manylinux_aarch64": "manylinux aarch64",
"manylinux_ppc64le": "manylinux ppc64le", "manylinux_ppc64le": "manylinux ppc64le",
"manylinux_s390x": "manylinux s390x", "manylinux_s390x": "manylinux s390x",
"manylinux_armv7l": "manylinux armv7l",
"manylinux_riscv64": "manylinux riscv64",
"musllinux_x86_64": "musllinux x86_64", "musllinux_x86_64": "musllinux x86_64",
"musllinux_i686": "musllinux i686", "musllinux_i686": "musllinux i686",
"musllinux_aarch64": "musllinux aarch64", "musllinux_aarch64": "musllinux aarch64",
"musllinux_ppc64le": "musllinux ppc64le", "musllinux_ppc64le": "musllinux ppc64le",
"musllinux_s390x": "musllinux s390x", "musllinux_s390x": "manylinux s390x",
"musllinux_armv7l": "musllinux armv7l",
"musllinux_riscv64": "musllinux riscv64",
"win32": "Windows 32bit", "win32": "Windows 32bit",
"win_amd64": "Windows 64bit", "win_amd64": "Windows 64bit",
"win_arm64": "Windows on ARM 64bit", "win_arm64": "Windows on ARM 64bit",
@@ -37,38 +35,9 @@ PLATFORM_IDENTIFIER_DESCRIPTIONS: Final[dict[str, str]] = {
"macosx_universal2": "macOS Universal 2 - x86_64 and arm64", "macosx_universal2": "macOS Universal 2 - x86_64 and arm64",
"macosx_arm64": "macOS arm64 - Apple Silicon", "macosx_arm64": "macOS arm64 - Apple Silicon",
"pyodide_wasm32": "Pyodide", "pyodide_wasm32": "Pyodide",
"ios_arm64_iphoneos": "iOS Device (ARM64)",
"ios_arm64_iphonesimulator": "iOS Simulator (ARM64)",
"ios_x86_64_iphonesimulator": "iOS Simulator (x86_64)",
} }
class Colors:
def __init__(self, *, enabled: bool) -> None:
self.red = "\033[31m" if enabled else ""
self.green = "\033[32m" if enabled else ""
self.yellow = "\033[33m" if enabled else ""
self.blue = "\033[34m" if enabled else ""
self.cyan = "\033[36m" if enabled else ""
self.bright_red = "\033[91m" if enabled else ""
self.bright_green = "\033[92m" if enabled else ""
self.white = "\033[37m\033[97m" if enabled else ""
self.gray = "\033[38;5;244m" if enabled else ""
self.bg_grey = "\033[48;5;235m" if enabled else ""
self.bold = "\033[1m" if enabled else ""
self.faint = "\033[2m" if enabled else ""
self.end = "\033[0m" if enabled else ""
class Symbols:
def __init__(self, *, unicode: bool) -> None:
self.done = "" if unicode else "done"
self.error = "" if unicode else "failed"
class Logger: class Logger:
fold_mode: str fold_mode: str
colors_enabled: bool colors_enabled: bool
@@ -164,24 +133,24 @@ class Logger:
def notice(self, message: str) -> None: def notice(self, message: str) -> None:
if self.fold_mode == "github": if self.fold_mode == "github":
print(f"::notice::cibuildwheel: {message}\n", file=sys.stderr) print(f"::notice::{message}\n", file=sys.stderr)
else: else:
c = self.colors c = self.colors
print(f"cibuildwheel: {c.bold}note{c.end}: {message}\n", file=sys.stderr) print(f"{c.bold}Note{c.end}: {message}\n", file=sys.stderr)
def warning(self, message: str) -> None: def warning(self, message: str) -> None:
if self.fold_mode == "github": if self.fold_mode == "github":
print(f"::warning::cibuildwheel: {message}\n", file=sys.stderr) print(f"::warning::{message}\n", file=sys.stderr)
else: else:
c = self.colors c = self.colors
print(f"cibuildwheel: {c.yellow}warning{c.end}: {message}\n", file=sys.stderr) print(f"{c.yellow}Warning{c.end}: {message}\n", file=sys.stderr)
def error(self, error: BaseException | str) -> None: def error(self, error: BaseException | str) -> None:
if self.fold_mode == "github": if self.fold_mode == "github":
print(f"::error::cibuildwheel: {error}\n", file=sys.stderr) print(f"::error::{error}\n", file=sys.stderr)
else: else:
c = self.colors c = self.colors
print(f"cibuildwheel: {c.bright_red}error{c.end}: {error}\n", file=sys.stderr) print(f"{c.bright_red}Error{c.end}: {error}\n", file=sys.stderr)
@property @property
def step_active(self) -> bool: def step_active(self) -> bool:
@@ -243,8 +212,6 @@ def build_description_from_identifier(identifier: str) -> str:
build_description += "CPython" build_description += "CPython"
elif python_interpreter == "pp": elif python_interpreter == "pp":
build_description += "PyPy" build_description += "PyPy"
elif python_interpreter == "gp":
build_description += "GraalPy"
else: else:
msg = f"unknown python {python_interpreter!r}" msg = f"unknown python {python_interpreter!r}"
raise Exception(msg) raise Exception(msg)
@@ -260,6 +227,32 @@ def build_description_from_identifier(identifier: str) -> str:
return build_description return build_description
class Colors:
def __init__(self, *, enabled: bool) -> None:
self.red = "\033[31m" if enabled else ""
self.green = "\033[32m" if enabled else ""
self.yellow = "\033[33m" if enabled else ""
self.blue = "\033[34m" if enabled else ""
self.cyan = "\033[36m" if enabled else ""
self.bright_red = "\033[91m" if enabled else ""
self.bright_green = "\033[92m" if enabled else ""
self.white = "\033[37m\033[97m" if enabled else ""
self.gray = "\033[38;5;244m" if enabled else ""
self.bg_grey = "\033[48;5;235m" if enabled else ""
self.bold = "\033[1m" if enabled else ""
self.faint = "\033[2m" if enabled else ""
self.end = "\033[0m" if enabled else ""
class Symbols:
def __init__(self, *, unicode: bool) -> None:
self.done = "" if unicode else "done"
self.error = "" if unicode else "failed"
def file_supports_color(file_obj: IO[AnyStr]) -> bool: def file_supports_color(file_obj: IO[AnyStr]) -> bool:
""" """
Returns True if the running system's terminal supports color. Returns True if the running system's terminal supports color.
@@ -1,5 +1,6 @@
from __future__ import annotations
import functools import functools
import inspect
import os import os
import platform import platform
import re import re
@@ -7,36 +8,48 @@ import shutil
import subprocess import subprocess
import sys import sys
import typing import typing
from collections.abc import Set from collections.abc import Sequence, Set
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from typing import Literal, assert_never from typing import Literal, Tuple
from filelock import FileLock from filelock import FileLock
from packaging.version import Version from packaging.version import Version
from .. import errors from . import errors
from ..architecture import Architecture from ._compat.typing import assert_never
from ..ci import detect_ci_provider from .architecture import Architecture
from ..environment import ParsedEnvironment from .environment import ParsedEnvironment
from ..frontend import BuildFrontendConfig, BuildFrontendName, get_build_frontend_extra_flags from .logger import log
from ..logger import log from .options import Options
from ..options import Options from .typing import PathOrStr
from ..selector import BuildSelector from .util import (
from ..util import resources
from ..util.cmd import call, shell
from ..util.file import (
CIBW_CACHE_PATH, CIBW_CACHE_PATH,
copy_test_sources, BuildFrontendConfig,
BuildFrontendName,
BuildSelector,
call,
combine_constraints,
detect_ci_provider,
download, download,
find_compatible_wheel,
find_uv,
free_thread_enable_313,
get_build_verbosity_extra_flags,
get_pip_version,
install_certifi_script,
move_file, move_file,
prepare_command,
read_python_configs,
shell,
split_config_settings,
test_fail_cwd_file,
unwrap,
virtualenv,
) )
from ..util.helpers import prepare_command, unwrap
from ..util.packaging import combine_constraints, find_compatible_wheel, get_pip_version
from ..venv import constraint_flags, find_uv, virtualenv
@functools.cache @functools.lru_cache(maxsize=None)
def get_macos_version() -> tuple[int, int]: def get_macos_version() -> tuple[int, int]:
""" """
Returns the macOS major/minor version, as a tuple, e.g. (10, 15) or (11, 0) Returns the macOS major/minor version, as a tuple, e.g. (10, 15) or (11, 0)
@@ -60,10 +73,10 @@ def get_macos_version() -> tuple[int, int]:
capture_stdout=True, capture_stdout=True,
) )
version = tuple(map(int, version_str.split(".")[:2])) version = tuple(map(int, version_str.split(".")[:2]))
return typing.cast(tuple[int, int], version) return typing.cast(Tuple[int, int], version)
@functools.cache @functools.lru_cache(maxsize=None)
def get_test_macosx_deployment_target() -> str: def get_test_macosx_deployment_target() -> str:
version = get_macos_version() version = get_macos_version()
if version >= (11, 0): if version >= (11, 0):
@@ -83,15 +96,12 @@ class PythonConfiguration:
url: str url: str
def all_python_configurations() -> list[PythonConfiguration]:
config_dicts = resources.read_python_configs("macos")
return [PythonConfiguration(**item) for item in config_dicts]
def get_python_configurations( def get_python_configurations(
build_selector: BuildSelector, architectures: Set[Architecture] build_selector: BuildSelector, architectures: Set[Architecture]
) -> list[PythonConfiguration]: ) -> list[PythonConfiguration]:
python_configurations = all_python_configurations() full_python_configs = read_python_configs("macos")
python_configurations = [PythonConfiguration(**item) for item in full_python_configs]
# filter out configs that don't match any of the selected architectures # filter out configs that don't match any of the selected architectures
python_configurations = [ python_configurations = [
@@ -103,7 +113,7 @@ def get_python_configurations(
# skip builds as required by BUILD/SKIP # skip builds as required by BUILD/SKIP
python_configurations = [c for c in python_configurations if build_selector(c.identifier)] python_configurations = [c for c in python_configurations if build_selector(c.identifier)]
# filter-out some cross-compilation configs with PyPy and GraalPy: # filter-out some cross-compilation configs with PyPy:
# can't build arm64 on x86_64 # can't build arm64 on x86_64
# rosetta allows to build x86_64 on arm64 # rosetta allows to build x86_64 on arm64
if platform.machine() == "x86_64": if platform.machine() == "x86_64":
@@ -111,7 +121,7 @@ def get_python_configurations(
python_configurations = [ python_configurations = [
c c
for c in python_configurations for c in python_configurations
if not (c.identifier.startswith(("pp", "gp")) and c.identifier.endswith("arm64")) if not (c.identifier.startswith("pp") and c.identifier.endswith("arm64"))
] ]
removed_elements = python_configurations_before - set(python_configurations) removed_elements = python_configurations_before - set(python_configurations)
if removed_elements: if removed_elements:
@@ -119,7 +129,7 @@ def get_python_configurations(
log.quiet( log.quiet(
unwrap( unwrap(
f""" f"""
Note: {ids} {"was" if len(removed_elements) == 1 else "were"} Note: {ids} {'was' if len(removed_elements) == 1 else 'were'}
selected, but can't be built on x86_64 so will be skipped automatically. selected, but can't be built on x86_64 so will be skipped automatically.
""" """
) )
@@ -128,7 +138,7 @@ def get_python_configurations(
return python_configurations return python_configurations
def install_cpython(_tmp: Path, version: str, url: str, free_threading: bool) -> Path: def install_cpython(tmp: Path, version: str, url: str, free_threading: bool) -> 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"):
@@ -139,27 +149,21 @@ def install_cpython(_tmp: Path, version: str, url: str, free_threading: bool) ->
if detect_ci_provider() is None: if detect_ci_provider() is None:
# if running locally, we don't want to install CPython with sudo # if running locally, we don't want to install CPython with sudo
# let the user know & provide a link to the installer # let the user know & provide a link to the installer
msg = inspect.cleandoc( msg = (
f""" f"Error: CPython {version} is not installed.\n"
Error: CPython {version} is not installed. "cibuildwheel will not perform system-wide installs when running outside of CI.\n"
cibuildwheel will not perform system-wide installs when running outside of CI. f"To build locally, install CPython {version} on this machine, or, disable this version of Python using CIBW_SKIP=cp{version.replace('.', '')}-macosx_*\n"
To build locally, install CPython {version} on this machine, or, disable this f"\nDownload link: {url}"
version of Python using CIBW_SKIP=cp{version.replace(".", "")}-macosx_*
For portable builds, cibuildwheel needs the official builds from python.org.
Download link: {url}
"""
) )
raise errors.FatalError(msg) raise errors.FatalError(msg)
python_filename = url.split("/")[-1] pkg_path = tmp / "Python.pkg"
pkg_path = CIBW_CACHE_PATH / "cpython-installer" / python_filename # download the pkg
if not pkg_path.exists(): download(url, pkg_path)
download(url, pkg_path) # install
args = [] args = []
if version.startswith("3.13"): if version.startswith("3.13"):
# Python 3.13 is the first version to have a free-threading option # Python 3.13 is the first version to have a free-threading option
args += ["-applyChoiceChangesXML", str(resources.FREE_THREAD_ENABLE_313.resolve())] args += ["-applyChoiceChangesXML", str(free_thread_enable_313.resolve())]
elif version.startswith("3.14"):
args += ["-applyChoiceChangesXML", str(resources.FREE_THREAD_ENABLE_314.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()
@@ -167,13 +171,9 @@ def install_cpython(_tmp: Path, version: str, url: str, free_threading: bool) ->
if free_threading: if free_threading:
call(installation_path / f"bin/python{version}t", "-m", "ensurepip", env=env) call(installation_path / f"bin/python{version}t", "-m", "ensurepip", env=env)
call( call(installation_path / f"bin/python{version}t", install_certifi_script, env=env)
installation_path / f"bin/python{version}t",
resources.INSTALL_CERTIFI_SCRIPT,
env=env,
)
else: else:
call(installation_path / "bin/python3", resources.INSTALL_CERTIFI_SCRIPT, env=env) call(installation_path / "bin/python3", install_certifi_script, env=env)
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")
@@ -193,31 +193,20 @@ def install_pypy(tmp: Path, url: str) -> Path:
return installation_path / "bin" / "pypy3" return installation_path / "bin" / "pypy3"
def install_graalpy(tmp: Path, url: str) -> Path:
graalpy_archive = url.rsplit("/", 1)[-1]
extension = ".tar.gz"
assert graalpy_archive.endswith(extension)
installation_path = CIBW_CACHE_PATH / graalpy_archive[: -len(extension)]
with FileLock(str(installation_path) + ".lock"):
if not installation_path.exists():
downloaded_archive = tmp / graalpy_archive
download(url, downloaded_archive)
installation_path.mkdir(parents=True)
# GraalPy top-folder name is inconsistent with archive name
call("tar", "-C", installation_path, "--strip-components=1", "-xzf", downloaded_archive)
downloaded_archive.unlink()
return installation_path / "bin" / "graalpy"
def setup_python( def setup_python(
tmp: Path, tmp: Path,
python_configuration: PythonConfiguration, python_configuration: PythonConfiguration,
dependency_constraint: Path | None, dependency_constraint_flags: Sequence[PathOrStr],
environment: ParsedEnvironment, environment: ParsedEnvironment,
build_frontend: BuildFrontendName, build_frontend: BuildFrontendName,
) -> tuple[Path, dict[str, str]]: ) -> tuple[Path, dict[str, str]]:
if build_frontend == "build[uv]" and Version(python_configuration.version) < Version("3.8"):
build_frontend = "build"
uv_path = find_uv() uv_path = find_uv()
use_uv = build_frontend == "build[uv]" use_uv = build_frontend == "build[uv]" and Version(python_configuration.version) >= Version(
"3.8"
)
tmp.mkdir() tmp.mkdir()
implementation_id = python_configuration.identifier.split("-")[0] implementation_id = python_configuration.identifier.split("-")[0]
@@ -230,14 +219,12 @@ def setup_python(
elif implementation_id.startswith("pp"): elif implementation_id.startswith("pp"):
base_python = install_pypy(tmp, python_configuration.url) base_python = install_pypy(tmp, python_configuration.url)
elif implementation_id.startswith("gp"):
base_python = install_graalpy(tmp, python_configuration.url)
else: else:
msg = "Unknown Python implementation" msg = "Unknown Python implementation"
raise ValueError(msg) raise ValueError(msg)
assert base_python.exists(), ( assert (
f"{base_python.name} not found, has {list(base_python.parent.iterdir())}" base_python.exists()
) ), f"{base_python.name} not found, has {list(base_python.parent.iterdir())}"
log.step("Setting up build environment...") log.step("Setting up build environment...")
venv_path = tmp / "venv" venv_path = tmp / "venv"
@@ -245,7 +232,7 @@ def setup_python(
python_configuration.version, python_configuration.version,
base_python, base_python,
venv_path, venv_path,
dependency_constraint, dependency_constraint_flags,
use_uv=use_uv, use_uv=use_uv,
) )
venv_bin_path = venv_path / "bin" venv_bin_path = venv_path / "bin"
@@ -261,26 +248,45 @@ def setup_python(
# 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"
# upgrade pip to the version matching our constraints
# if necessary, reinstall it to ensure that it's available on PATH as 'pip'
if build_frontend == "build[uv]":
assert uv_path is not None
pip = [str(uv_path), "pip"]
else:
pip = ["python", "-m", "pip"]
if not use_uv:
call(
*pip,
"install",
"--upgrade",
"pip",
*dependency_constraint_flags,
env=env,
cwd=venv_path,
)
# Apply our environment after pip is ready # Apply our environment after pip is ready
env = environment.as_dictionary(prev_environment=env) env = environment.as_dictionary(prev_environment=env)
# check what Python version we're on
which_python = call("which", "python", env=env, capture_stdout=True).strip()
print(which_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."
raise errors.FatalError(msg)
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:
assert (venv_bin_path / "pip").exists() assert (venv_bin_path / "pip").exists()
which_pip = call("which", "pip", env=env, capture_stdout=True).strip() call("which", "pip", env=env)
print(which_pip)
if which_pip != str(venv_bin_path / "pip"):
msg = "pip available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert pip above it."
raise errors.FatalError(msg)
call("pip", "--version", env=env) call("pip", "--version", env=env)
which_pip = call("which", "pip", env=env, capture_stdout=True).strip()
if which_pip != str(venv_bin_path / "pip"):
msg = "cibuildwheel: pip available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert pip above it."
raise errors.FatalError(msg)
# check what Python version we're on
call("which", "python", env=env)
call("python", "--version", env=env)
which_python = call("which", "python", env=env, capture_stdout=True).strip()
if which_python != str(venv_bin_path / "python"):
msg = "cibuildwheel: 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)
config_is_arm64 = python_configuration.identifier.endswith("arm64") config_is_arm64 = python_configuration.identifier.endswith("arm64")
config_is_universal2 = python_configuration.identifier.endswith("universal2") config_is_universal2 = python_configuration.identifier.endswith("universal2")
@@ -288,10 +294,10 @@ def setup_python(
# Set MACOSX_DEPLOYMENT_TARGET, if the user didn't set it. # Set MACOSX_DEPLOYMENT_TARGET, if the user didn't set it.
# For arm64, the minimal deployment target is 11.0. # For arm64, the minimal deployment target is 11.0.
# On x86_64 (or universal2), use 10.9 as a default. # On x86_64 (or universal2), use 10.9 as a default.
# CPython 3.12.6+ needs 10.13. # CPython 3.13 needs 10.13.
if config_is_arm64: if config_is_arm64:
default_target = "11.0" default_target = "11.0"
elif Version(python_configuration.version) >= Version("3.12"): elif Version(python_configuration.version) >= Version("3.13"):
default_target = "10.13" default_target = "10.13"
elif python_configuration.identifier.startswith("pp") and Version( elif python_configuration.identifier.startswith("pp") and Version(
python_configuration.version python_configuration.version
@@ -308,19 +314,20 @@ def setup_python(
) )
env["MACOSX_DEPLOYMENT_TARGET"] = default_target env["MACOSX_DEPLOYMENT_TARGET"] = default_target
if config_is_arm64: if python_configuration.version not in {"3.6", "3.7"}:
# macOS 11 is the first OS with arm64 support, so the wheels if config_is_arm64:
# have that as a minimum. # macOS 11 is the first OS with arm64 support, so the wheels
env.setdefault("_PYTHON_HOST_PLATFORM", "macosx-11.0-arm64") # have that as a minimum.
env.setdefault("ARCHFLAGS", "-arch arm64") env.setdefault("_PYTHON_HOST_PLATFORM", "macosx-11.0-arm64")
elif config_is_universal2: env.setdefault("ARCHFLAGS", "-arch arm64")
env.setdefault("_PYTHON_HOST_PLATFORM", "macosx-10.9-universal2") elif config_is_universal2:
env.setdefault("ARCHFLAGS", "-arch arm64 -arch x86_64") env.setdefault("_PYTHON_HOST_PLATFORM", "macosx-10.9-universal2")
elif python_configuration.identifier.endswith("x86_64"): env.setdefault("ARCHFLAGS", "-arch arm64 -arch x86_64")
# even on the macos11.0 Python installer, on the x86_64 side it's elif python_configuration.identifier.endswith("x86_64"):
# compatible back to 10.9. # even on the macos11.0 Python installer, on the x86_64 side it's
env.setdefault("_PYTHON_HOST_PLATFORM", "macosx-10.9-x86_64") # compatible back to 10.9.
env.setdefault("ARCHFLAGS", "-arch x86_64") env.setdefault("_PYTHON_HOST_PLATFORM", "macosx-10.9-x86_64")
env.setdefault("ARCHFLAGS", "-arch x86_64")
building_arm64 = config_is_arm64 or config_is_universal2 building_arm64 = config_is_arm64 or config_is_universal2
if building_arm64 and get_macos_version() < (10, 16) and "SDKROOT" not in env: if building_arm64 and get_macos_version() < (10, 16) and "SDKROOT" not in env:
@@ -351,7 +358,7 @@ def setup_python(
"install", "install",
"--upgrade", "--upgrade",
"delocate", "delocate",
*constraint_flags(dependency_constraint), *dependency_constraint_flags,
env=env, env=env,
) )
elif build_frontend == "build": elif build_frontend == "build":
@@ -361,7 +368,7 @@ def setup_python(
"--upgrade", "--upgrade",
"delocate", "delocate",
"build[virtualenv]", "build[virtualenv]",
*constraint_flags(dependency_constraint), *dependency_constraint_flags,
env=env, env=env,
) )
elif build_frontend == "build[uv]": elif build_frontend == "build[uv]":
@@ -373,7 +380,7 @@ def setup_python(
"--upgrade", "--upgrade",
"delocate", "delocate",
"build[virtualenv, uv]", "build[virtualenv, uv]",
*constraint_flags(dependency_constraint), *dependency_constraint_flags,
env=env, env=env,
) )
else: else:
@@ -407,8 +414,10 @@ 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 or BuildFrontendConfig("build") build_frontend = build_options.build_frontend or BuildFrontendConfig("pip")
use_uv = build_frontend.name == "build[uv]" use_uv = build_frontend.name == "build[uv]" and Version(config.version) >= Version(
"3.8"
)
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"
@@ -424,18 +433,22 @@ def build(options: Options, tmp_path: Path) -> None:
config_is_arm64 = config.identifier.endswith("arm64") config_is_arm64 = config.identifier.endswith("arm64")
config_is_universal2 = config.identifier.endswith("universal2") config_is_universal2 = config.identifier.endswith("universal2")
constraints_path = build_options.dependency_constraints.get_for_python_version( dependency_constraint_flags: Sequence[PathOrStr] = []
version=config.version, tmp_dir=identifier_tmp_dir if build_options.dependency_constraints:
) dependency_constraint_flags = [
"-c",
build_options.dependency_constraints.get_for_python_version(config.version),
]
base_python, env = setup_python( base_python, env = setup_python(
identifier_tmp_dir / "build", identifier_tmp_dir / "build",
config, config,
constraints_path, dependency_constraint_flags,
build_options.environment, build_options.environment,
build_frontend.name, build_frontend.name,
) )
pip_version = None if use_uv else get_pip_version(env) if not use_uv:
pip_version = get_pip_version(env)
compatible_wheel = find_compatible_wheel(built_wheels, config.identifier) compatible_wheel = find_compatible_wheel(built_wheels, config.identifier)
if compatible_wheel: if compatible_wheel:
@@ -455,19 +468,24 @@ def build(options: Options, tmp_path: Path) -> None:
log.step("Building wheel...") log.step("Building wheel...")
built_wheel_dir.mkdir() built_wheel_dir.mkdir()
extra_flags = get_build_frontend_extra_flags( extra_flags = split_config_settings(
build_frontend, build_options.build_verbosity, build_options.config_settings build_options.config_settings, build_frontend.name
) )
extra_flags += build_frontend.args
build_env = env.copy() build_env = env.copy()
if pip_version is not None: if not use_uv:
build_env["VIRTUALENV_PIP"] = pip_version build_env["VIRTUALENV_PIP"] = pip_version
if constraints_path: if build_options.dependency_constraints:
constraint_path = build_options.dependency_constraints.get_for_python_version(
config.version
)
combine_constraints( combine_constraints(
build_env, constraints_path, identifier_tmp_dir if use_uv else None build_env, constraint_path, identifier_tmp_dir if use_uv else None
) )
if build_frontend.name == "pip": if build_frontend.name == "pip":
extra_flags += get_build_verbosity_extra_flags(build_options.build_verbosity)
# Path.resolve() is needed. Without it pip wheel may try to fetch package from pypi.org # Path.resolve() is needed. Without it pip wheel may try to fetch package from pypi.org
# see https://github.com/pypa/cibuildwheel/pull/369 # see https://github.com/pypa/cibuildwheel/pull/369
call( call(
@@ -482,6 +500,9 @@ def build(options: Options, tmp_path: Path) -> None:
env=build_env, env=build_env,
) )
elif build_frontend.name == "build" or build_frontend.name == "build[uv]": elif build_frontend.name == "build" or build_frontend.name == "build[uv]":
if not 0 <= build_options.build_verbosity < 2:
msg = f"build_verbosity {build_options.build_verbosity} is not supported for build frontend. Ignoring."
log.warning(msg)
if use_uv and "--no-isolation" not in extra_flags and "-n" not in extra_flags: if use_uv and "--no-isolation" not in extra_flags and "-n" not in extra_flags:
extra_flags.append("--installer=uv") extra_flags.append("--installer=uv")
call( call(
@@ -524,10 +545,7 @@ def build(options: Options, tmp_path: Path) -> None:
else: else:
shutil.move(str(built_wheel), repaired_wheel_dir) shutil.move(str(built_wheel), repaired_wheel_dir)
try: repaired_wheel = next(repaired_wheel_dir.glob("*.whl"))
repaired_wheel = next(repaired_wheel_dir.glob("*.whl"))
except StopIteration:
raise errors.RepairStepProducedNoWheelError() from None
if repaired_wheel.name in {wheel.name for wheel in built_wheels}: if repaired_wheel.name in {wheel.name for wheel in built_wheels}:
raise errors.AlreadyBuiltWheelError(repaired_wheel.name) raise errors.AlreadyBuiltWheelError(repaired_wheel.name)
@@ -614,6 +632,13 @@ def build(options: Options, tmp_path: Path) -> None:
else f"Testing wheel on {testing_arch}..." else f"Testing wheel on {testing_arch}..."
) )
# set up a virtual environment to install and test from, to make sure
# there are no dependencies that were pulled in at build time.
if not use_uv:
call("pip", "install", "virtualenv", *dependency_constraint_flags, env=env)
venv_dir = identifier_tmp_dir / f"venv-test-{testing_arch}"
arch_prefix = [] arch_prefix = []
uv_arch_args = [] uv_arch_args = []
if testing_arch != machine_arch: if testing_arch != machine_arch:
@@ -629,24 +654,27 @@ def build(options: Options, tmp_path: Path) -> None:
call_with_arch = functools.partial(call, *arch_prefix) call_with_arch = functools.partial(call, *arch_prefix)
shell_with_arch = functools.partial(call, *arch_prefix, "/bin/sh", "-c") shell_with_arch = functools.partial(call, *arch_prefix, "/bin/sh", "-c")
# set up a virtual environment to install and test from, to make sure
# there are no dependencies that were pulled in at build time.
venv_dir = identifier_tmp_dir / f"venv-test-{testing_arch}"
virtualenv_env = virtualenv(
config.version,
base_python,
venv_dir,
None,
use_uv=use_uv,
env=env,
pip_version=pip_version,
)
if use_uv: if use_uv:
pip_install = functools.partial(call, *pip, "install", *uv_arch_args) pip_install = functools.partial(call, *pip, "install", *uv_arch_args)
call("uv", "venv", venv_dir, f"--python={base_python}", env=env)
else: else:
pip_install = functools.partial(call_with_arch, *pip, "install") pip_install = functools.partial(call_with_arch, *pip, "install")
# Use pip version from the initial env to ensure determinism
venv_args = ["--no-periodic-update", f"--pip={pip_version}"]
# In Python<3.12, setuptools & wheel are installed as well, use virtualenv embedded ones
if Version(config.version) < Version("3.12"):
venv_args.extend(("--setuptools=embed", "--wheel=embed"))
call_with_arch("python", "-m", "virtualenv", *venv_args, venv_dir, env=env)
virtualenv_env = env.copy()
virtualenv_env["MACOSX_DEPLOYMENT_TARGET"] = get_test_macosx_deployment_target() virtualenv_env["MACOSX_DEPLOYMENT_TARGET"] = get_test_macosx_deployment_target()
virtualenv_env["PATH"] = os.pathsep.join(
[
str(venv_dir / "bin"),
virtualenv_env["PATH"],
]
)
virtualenv_env["VIRTUAL_ENV"] = str(venv_dir)
# check that we are using the Python from the virtual environment # check that we are using the Python from the virtual environment
call_with_arch("which", "python", env=virtualenv_env) call_with_arch("which", "python", env=virtualenv_env)
@@ -694,25 +722,14 @@ def build(options: Options, tmp_path: Path) -> None:
# and not the repo code) # and not the repo code)
test_command_prepared = prepare_command( test_command_prepared = prepare_command(
build_options.test_command, build_options.test_command,
project=Path.cwd(), project=Path(".").resolve(),
package=build_options.package_dir.resolve(), package=build_options.package_dir.resolve(),
wheel=repaired_wheel, wheel=repaired_wheel,
) )
if build_options.test_sources: test_cwd = identifier_tmp_dir / "test_cwd"
test_cwd = identifier_tmp_dir / "test_cwd" test_cwd.mkdir(exist_ok=True)
# only create test_cwd if it doesn't already exist - it (test_cwd / "test_fail.py").write_text(test_fail_cwd_file.read_text())
# may have been created during a previous `testing_arch`
if not test_cwd.exists():
test_cwd.mkdir()
copy_test_sources(
build_options.test_sources,
build_options.package_dir,
test_cwd,
)
else:
# There are no test sources. Run the tests in the project directory.
test_cwd = Path.cwd()
shell_with_arch(test_command_prepared, cwd=test_cwd, env=virtualenv_env) shell_with_arch(test_command_prepared, cwd=test_cwd, env=virtualenv_env)
@@ -722,7 +739,7 @@ def build(options: Options, tmp_path: Path) -> None:
moved_wheel = move_file(repaired_wheel, output_wheel) moved_wheel = move_file(repaired_wheel, output_wheel)
if moved_wheel != output_wheel.resolve(): if moved_wheel != output_wheel.resolve():
log.warning( log.warning(
f"{repaired_wheel} was moved to {moved_wheel} instead of {output_wheel}" "{repaired_wheel} was moved to {moved_wheel} instead of {output_wheel}"
) )
built_wheels.append(output_wheel) built_wheels.append(output_wheel)
+59 -141
View File
@@ -1,3 +1,5 @@
from __future__ import annotations
import io import io
import json import json
import os import os
@@ -6,45 +8,35 @@ import shlex
import shutil import shutil
import subprocess import subprocess
import sys import sys
import textwrap
import typing import typing
import uuid import uuid
from collections.abc import Mapping, Sequence from collections.abc import Mapping, Sequence
from dataclasses import dataclass, field from dataclasses import dataclass, field
from enum import Enum
from pathlib import Path, PurePath, PurePosixPath from pathlib import Path, PurePath, PurePosixPath
from types import TracebackType from types import TracebackType
from typing import IO, Literal, Self, assert_never from typing import IO, Dict, Literal
from .ci import CIProvider, detect_ci_provider from ._compat.typing import Self
from .errors import OCIEngineTooOldError from .typing import PathOrStr, PopenBytes
from .logger import log from .util import (
from .typing import PathOrStr CIProvider,
from .util.cmd import call call,
from .util.helpers import FlexibleVersion, parse_key_value_string, strtobool detect_ci_provider,
parse_key_value_string,
strtobool,
)
ContainerEngineName = Literal["docker", "podman"] ContainerEngineName = Literal["docker", "podman"]
# Order of the enum matters for tests. 386 shall appear before amd64.
class OCIPlatform(Enum):
i386 = "linux/386"
AMD64 = "linux/amd64"
ARMV7 = "linux/arm/v7"
ARM64 = "linux/arm64"
PPC64LE = "linux/ppc64le"
RISCV64 = "linux/riscv64"
S390X = "linux/s390x"
@dataclass(frozen=True) @dataclass(frozen=True)
class OCIContainerEngineConfig: class OCIContainerEngineConfig:
name: ContainerEngineName name: ContainerEngineName
create_args: tuple[str, ...] = field(default_factory=tuple) create_args: tuple[str, ...] = field(default_factory=tuple)
disable_host_mount: bool = False disable_host_mount: bool = False
@classmethod @staticmethod
def from_config_string(cls, config_string: str) -> Self: def from_config_string(config_string: str) -> OCIContainerEngineConfig:
config_dict = parse_key_value_string( config_dict = parse_key_value_string(
config_string, config_string,
["name"], ["name"],
@@ -64,17 +56,10 @@ class OCIContainerEngineConfig:
disable_host_mount = ( disable_host_mount = (
strtobool(disable_host_mount_options[-1]) if disable_host_mount_options else False strtobool(disable_host_mount_options[-1]) if disable_host_mount_options else False
) )
if "--platform" in create_args or any(arg.startswith("--platform=") for arg in create_args):
msg = "Using '--platform' in 'container-engine::create_args' is deprecated. It will be ignored."
log.warning(msg)
if "--platform" in create_args:
index = create_args.index("--platform")
create_args.pop(index)
create_args.pop(index)
else:
create_args = [arg for arg in create_args if not arg.startswith("--platform=")]
return cls(name=name, create_args=tuple(create_args), disable_host_mount=disable_host_mount) return OCIContainerEngineConfig(
name=name, create_args=tuple(create_args), disable_host_mount=disable_host_mount
)
def options_summary(self) -> str | dict[str, str]: def options_summary(self) -> str | dict[str, str]:
if not self.create_args: if not self.create_args:
@@ -90,52 +75,6 @@ class OCIContainerEngineConfig:
DEFAULT_ENGINE = OCIContainerEngineConfig("docker") DEFAULT_ENGINE = OCIContainerEngineConfig("docker")
def _check_engine_version(engine: OCIContainerEngineConfig) -> None:
try:
version_string = call(engine.name, "version", "-f", "{{json .}}", capture_stdout=True)
version_info = json.loads(version_string.strip())
if engine.name == "docker":
client_api_version = FlexibleVersion(version_info["Client"]["ApiVersion"])
server_api_version = FlexibleVersion(version_info["Server"]["ApiVersion"])
# --platform support was introduced in 1.32 as experimental, 1.41 removed the experimental flag
version = min(client_api_version, server_api_version)
minimum_version = FlexibleVersion("1.41")
minimum_version_str = "20.10.0" # docker version
error_msg = textwrap.dedent(
f"""
Build failed because {engine.name} is too old.
cibuildwheel requires {engine.name}>={minimum_version_str} running API version {minimum_version}.
The API version found by cibuildwheel is {version}.
"""
)
elif engine.name == "podman":
# podman uses the same version string for "Version" & "ApiVersion"
client_version = FlexibleVersion(version_info["Client"]["Version"])
if "Server" in version_info:
server_version = FlexibleVersion(version_info["Server"]["Version"])
else:
server_version = client_version
# --platform support was introduced in v3
version = min(client_version, server_version)
minimum_version = FlexibleVersion("3")
error_msg = textwrap.dedent(
f"""
Build failed because {engine.name} is too old.
cibuildwheel requires {engine.name}>={minimum_version}.
The version found by cibuildwheel is {version}.
"""
)
else:
assert_never(engine.name)
if version < minimum_version:
raise OCIEngineTooOldError(error_msg) from None
except (subprocess.CalledProcessError, KeyError, ValueError) as e:
msg = f"Build failed because {engine.name} is too old or is not working properly."
raise OCIEngineTooOldError(msg) from e
class OCIContainer: class OCIContainer:
""" """
An object that represents a running OCI (e.g. Docker) container. An object that represents a running OCI (e.g. Docker) container.
@@ -159,9 +98,9 @@ class OCIContainer:
... print(self.debug_info()) ... print(self.debug_info())
""" """
UTILITY_PYTHON = "/opt/python/cp39-cp39/bin/python" UTILITY_PYTHON = "/opt/python/cp38-cp38/bin/python"
process: subprocess.Popen[bytes] process: PopenBytes
bash_stdin: IO[bytes] bash_stdin: IO[bytes]
bash_stdout: IO[bytes] bash_stdout: IO[bytes]
@@ -169,7 +108,7 @@ class OCIContainer:
self, self,
*, *,
image: str, image: str,
oci_platform: OCIPlatform, enforce_32_bit: bool = False,
cwd: PathOrStr | None = None, cwd: PathOrStr | None = None,
engine: OCIContainerEngineConfig = DEFAULT_ENGINE, engine: OCIContainerEngineConfig = DEFAULT_ENGINE,
): ):
@@ -178,48 +117,14 @@ class OCIContainer:
raise ValueError(msg) raise ValueError(msg)
self.image = image self.image = image
self.oci_platform = oci_platform self.enforce_32_bit = enforce_32_bit
self.cwd = cwd self.cwd = cwd
self.name: str | None = None self.name: str | None = None
self.engine = engine self.engine = engine
self.host_tar_format = ""
if sys.platform.startswith("darwin"):
self.host_tar_format = "--format gnutar"
def _get_platform_args(self, *, oci_platform: OCIPlatform | None = None) -> tuple[str, str]:
if oci_platform is None:
oci_platform = self.oci_platform
# we need '--pull=always' otherwise some images with the wrong platform get reused (e.g. 386 image for amd64)
# c.f. https://github.com/moby/moby/issues/48197#issuecomment-2282802313
pull = "always"
try:
image_platform = call(
self.engine.name,
"image",
"inspect",
self.image,
"--format",
(
"{{.Os}}/{{.Architecture}}/{{.Variant}}"
if len(oci_platform.value.split("/")) == 3
else "{{.Os}}/{{.Architecture}}"
),
capture_stdout=True,
).strip()
if image_platform == oci_platform.value:
# in case the correct image is already present, don't pull
# this allows to run local only images
pull = "never"
except subprocess.CalledProcessError:
pass
return f"--platform={oci_platform.value}", f"--pull={pull}"
def __enter__(self) -> Self: def __enter__(self) -> Self:
self.name = f"cibuildwheel-{uuid.uuid4()}" self.name = f"cibuildwheel-{uuid.uuid4()}"
_check_engine_version(self.engine)
# work-around for Travis-CI PPC64le Docker runs since 2021: # work-around for Travis-CI PPC64le Docker runs since 2021:
# this avoids network splits # this avoids network splits
# https://github.com/pypa/cibuildwheel/issues/904 # https://github.com/pypa/cibuildwheel/issues/904
@@ -228,30 +133,15 @@ class OCIContainer:
if detect_ci_provider() == CIProvider.travis_ci and platform.machine() == "ppc64le": if detect_ci_provider() == CIProvider.travis_ci and platform.machine() == "ppc64le":
network_args = ["--network=host"] network_args = ["--network=host"]
platform_args = self._get_platform_args()
simulate_32_bit = False simulate_32_bit = False
if self.oci_platform in {OCIPlatform.i386, OCIPlatform.ARMV7}: if self.enforce_32_bit:
# If the architecture running the image is already the right one # If the architecture running the image is already the right one
# or the image entrypoint takes care of enforcing this, then we don't need to # or the image entrypoint takes care of enforcing this, then we don't need to
# simulate this # simulate this
run_cmd = [self.engine.name, "run", "--rm"] container_machine = call(
ctr_cmd = ["uname", "-m"] self.engine.name, "run", "--rm", self.image, "uname", "-m", capture_stdout=True
try: ).strip()
container_machine = call( simulate_32_bit = container_machine != "i686"
*run_cmd, *platform_args, self.image, *ctr_cmd, capture_stdout=True
).strip()
except subprocess.CalledProcessError:
if self.oci_platform == OCIPlatform.i386:
# The image might have been built with amd64 architecture
# Let's try that
platform_args = self._get_platform_args(oci_platform=OCIPlatform.AMD64)
container_machine = call(
*run_cmd, *platform_args, self.image, *ctr_cmd, capture_stdout=True
).strip()
else:
raise
simulate_32_bit = container_machine not in {"i686", "armv7l", "armv8l"}
shell_args = ["linux32", "/bin/bash"] if simulate_32_bit else ["/bin/bash"] shell_args = ["linux32", "/bin/bash"] if simulate_32_bit else ["/bin/bash"]
@@ -265,7 +155,6 @@ class OCIContainer:
"--interactive", "--interactive",
*(["--volume=/:/host"] if not self.engine.disable_host_mount else []), *(["--volume=/:/host"] if not self.engine.disable_host_mount else []),
*network_args, *network_args,
*platform_args,
*self.engine.create_args, *self.engine.create_args,
self.image, self.image,
*shell_args, *shell_args,
@@ -332,10 +221,15 @@ class OCIContainer:
self.name = None self.name = None
def copy_into(self, from_path: Path, to_path: PurePath) -> None: def copy_into(self, from_path: Path, to_path: PurePath) -> None:
# `docker cp` causes 'no space left on device' error when
# a container is running and the host filesystem is
# mounted. https://github.com/moby/moby/issues/38995
# Use `docker exec` instead.
if from_path.is_dir(): if from_path.is_dir():
self.call(["mkdir", "-p", to_path]) self.call(["mkdir", "-p", to_path])
subprocess.run( subprocess.run(
f"tar -c {self.host_tar_format} -f - . | {self.engine.name} exec -i {self.name} tar --no-same-owner -xC {shell_quote(to_path)} -f -", f"tar cf - . | {self.engine.name} exec -i {self.name} tar --no-same-owner -xC {shell_quote(to_path)} -f -",
shell=True, shell=True,
check=True, check=True,
cwd=from_path, cwd=from_path,
@@ -356,7 +250,8 @@ class OCIContainer:
) as exec_process: ) as exec_process:
assert exec_process.stdin assert exec_process.stdin
with open(from_path, "rb") as from_file: with open(from_path, "rb") as from_file:
shutil.copyfileobj(from_file, exec_process.stdin) # Bug in mypy, https://github.com/python/mypy/issues/15031
shutil.copyfileobj(from_file, exec_process.stdin) # type: ignore[misc]
exec_process.stdin.close() exec_process.stdin.close()
exec_process.wait() exec_process.wait()
@@ -369,7 +264,30 @@ class OCIContainer:
def copy_out(self, from_path: PurePath, to_path: Path) -> None: def copy_out(self, from_path: PurePath, to_path: Path) -> None:
# note: we assume from_path is a dir # note: we assume from_path is a dir
to_path.mkdir(parents=True, exist_ok=True) to_path.mkdir(parents=True, exist_ok=True)
call(self.engine.name, "cp", f"{self.name}:{from_path}/.", to_path)
if self.engine.name == "podman":
subprocess.run(
[
self.engine.name,
"cp",
f"{self.name}:{from_path}/.",
str(to_path),
],
check=True,
cwd=to_path,
)
elif self.engine.name == "docker":
# There is a bug in docker that prevents a simple 'cp' invocation
# from working https://github.com/moby/moby/issues/38995
command = f"{self.engine.name} exec -i {self.name} tar -cC {shell_quote(from_path)} -f - . | tar -xf -"
subprocess.run(
command,
shell=True,
check=True,
cwd=to_path,
)
else:
raise KeyError(self.engine.name)
def glob(self, path: PurePosixPath, pattern: str) -> list[PurePosixPath]: def glob(self, path: PurePosixPath, pattern: str) -> list[PurePosixPath]:
glob_pattern = path.joinpath(pattern) glob_pattern = path.joinpath(pattern)
@@ -480,7 +398,7 @@ class OCIContainer:
capture_output=True, capture_output=True,
) )
) )
return typing.cast(dict[str, str], env) return typing.cast(Dict[str, str], env)
def environment_executor(self, command: Sequence[str], environment: dict[str, str]) -> str: def environment_executor(self, command: Sequence[str], environment: dict[str, str]) -> str:
# used as an EnvironmentExecutor to evaluate commands and capture output # used as an EnvironmentExecutor to evaluate commands and capture output
+119 -175
View File
@@ -1,3 +1,5 @@
from __future__ import annotations
import collections import collections
import configparser import configparser
import contextlib import contextlib
@@ -7,47 +9,33 @@ import enum
import functools import functools
import shlex import shlex
import textwrap import textwrap
import tomllib from collections.abc import Generator, Iterable, Set
from collections.abc import Callable, Generator, Iterable, Mapping, Sequence, Set
from pathlib import Path from pathlib import Path
from typing import Any, Final, Literal, Self, assert_never from typing import Any, Literal, Mapping, Sequence, Union # noqa: TID251
from packaging.specifiers import SpecifierSet from packaging.specifiers import SpecifierSet
from . import errors from . import errors
from ._compat import tomllib
from ._compat.typing import assert_never
from .architecture import Architecture from .architecture import Architecture
from .environment import EnvironmentParseError, ParsedEnvironment, parse_environment from .environment import EnvironmentParseError, ParsedEnvironment, parse_environment
from .frontend import BuildFrontendConfig
from .logger import log from .logger import log
from .oci_container import OCIContainerEngineConfig from .oci_container import OCIContainerEngineConfig
from .projectfiles import get_requires_python_str, resolve_dependency_groups from .projectfiles import get_requires_python_str
from .selector import BuildSelector, EnableGroup, TestSelector, selector_matches
from .typing import PLATFORMS, PlatformName from .typing import PLATFORMS, PlatformName
from .util import resources from .util import (
from .util.helpers import format_safe, strtobool, unwrap MANYLINUX_ARCHS,
from .util.packaging import DependencyConstraints MUSLLINUX_ARCHS,
BuildFrontendConfig,
MANYLINUX_ARCHS: Final[tuple[str, ...]] = ( BuildSelector,
"x86_64", DependencyConstraints,
"i686", TestSelector,
"pypy_x86_64", format_safe,
"aarch64", resources_dir,
"ppc64le", selector_matches,
"s390x", strtobool,
"armv7l", unwrap,
"riscv64",
"pypy_aarch64",
"pypy_i686",
)
MUSLLINUX_ARCHS: Final[tuple[str, ...]] = (
"x86_64",
"i686",
"aarch64",
"ppc64le",
"s390x",
"armv7l",
"riscv64",
) )
@@ -61,22 +49,22 @@ class CommandLineArguments:
package_dir: Path package_dir: Path
print_build_identifiers: bool print_build_identifiers: bool
allow_empty: bool allow_empty: bool
prerelease_pythons: bool
debug_traceback: bool debug_traceback: bool
enable: list[str]
@classmethod @staticmethod
def defaults(cls) -> Self: def defaults() -> CommandLineArguments:
return cls( return CommandLineArguments(
platform="auto", platform="auto",
allow_empty=False, allow_empty=False,
archs=None, archs=None,
only=None, only=None,
config_file="", config_file="",
output_dir=Path("wheelhouse"), output_dir=Path("wheelhouse"),
package_dir=Path(), package_dir=Path("."),
prerelease_pythons=False,
print_build_identifiers=False, print_build_identifiers=False,
debug_traceback=False, debug_traceback=False,
enable=[],
) )
@@ -96,17 +84,14 @@ class BuildOptions:
environment: ParsedEnvironment environment: ParsedEnvironment
before_all: str before_all: str
before_build: str | None before_build: str | None
xbuild_tools: list[str] | None
repair_command: str repair_command: str
manylinux_images: dict[str, str] | None manylinux_images: dict[str, str] | None
musllinux_images: dict[str, str] | None musllinux_images: dict[str, str] | None
dependency_constraints: DependencyConstraints dependency_constraints: DependencyConstraints | None
test_command: str | None test_command: str | None
before_test: str | None before_test: str | None
test_sources: list[str]
test_requires: list[str] test_requires: list[str]
test_extras: str test_extras: str
test_groups: list[str]
build_verbosity: int build_verbosity: int
build_frontend: BuildFrontendConfig | None build_frontend: BuildFrontendConfig | None
config_settings: str config_settings: str
@@ -133,16 +118,10 @@ class BuildOptions:
return self.globals.architectures return self.globals.architectures
SettingLeaf = str | int | bool SettingLeaf = Union[str, int, bool]
SettingList = Sequence[SettingLeaf] SettingList = Sequence[SettingLeaf]
SettingTable = Mapping[str, SettingLeaf | SettingList] SettingTable = Mapping[str, Union[SettingLeaf, SettingList]]
SettingValue = SettingTable | SettingList | SettingLeaf SettingValue = Union[SettingTable, SettingList, SettingLeaf]
class InheritRule(enum.Enum):
NONE = enum.auto()
APPEND = enum.auto()
PREPEND = enum.auto()
@dataclasses.dataclass(frozen=True) @dataclasses.dataclass(frozen=True)
@@ -189,12 +168,11 @@ class ListFormat(OptionFormat):
A format that joins lists with a separator. A format that joins lists with a separator.
""" """
def __init__(self, sep: str, quote: Callable[[str], str] | None = None) -> None: def __init__(self, sep: str) -> None:
self.sep = sep self.sep = sep
self.quote = quote or (lambda s: s)
def format_list(self, value: SettingList) -> str: def format_list(self, value: SettingList) -> str:
return self.sep.join(self.quote(str(v)) for v in value) return self.sep.join(str(v) for v in value)
def merge_values(self, before: str, after: str) -> str: def merge_values(self, before: str, after: str) -> str:
return f"{before}{self.sep}{after}" return f"{before}{self.sep}{after}"
@@ -223,7 +201,8 @@ class ShlexTableFormat(OptionFormat):
if isinstance(v, str): if isinstance(v, str):
assignments.append((k, v)) assignments.append((k, v))
elif isinstance(v, Sequence): elif isinstance(v, Sequence):
assignments.extend((k, str(inner_v)) for inner_v in v) for inner_v in v:
assignments.append((k, str(inner_v)))
else: else:
assignments.append((k, str(v))) assignments.append((k, str(v)))
@@ -278,6 +257,12 @@ class EnvironmentFormat(OptionFormat):
return f"{before} {after}" return f"{before} {after}"
class InheritRule(enum.Enum):
NONE = enum.auto()
APPEND = enum.auto()
PREPEND = enum.auto()
def _resolve_cascade( def _resolve_cascade(
*pairs: tuple[SettingValue | None, InheritRule], *pairs: tuple[SettingValue | None, InheritRule],
ignore_empty: bool = False, ignore_empty: bool = False,
@@ -322,6 +307,7 @@ def _resolve_cascade(
return result return result
# pylint: disable-next=inconsistent-return-statements
def _apply_inherit_rule( def _apply_inherit_rule(
before: str | None, after: str, rule: InheritRule, option_format: OptionFormat | None before: str | None, after: str, rule: InheritRule, option_format: OptionFormat | None
) -> str: ) -> str:
@@ -343,10 +329,10 @@ def _apply_inherit_rule(
if rule == InheritRule.APPEND: if rule == InheritRule.APPEND:
return option_format.merge_values(before, after) return option_format.merge_values(before, after)
if rule == InheritRule.PREPEND: elif rule == InheritRule.PREPEND:
return option_format.merge_values(after, before) return option_format.merge_values(after, before)
else:
assert_never(rule) assert_never(rule)
def _stringify_setting( def _stringify_setting(
@@ -371,7 +357,7 @@ def _stringify_setting(
msg = f"Error converting {setting!r} to a string: this setting doesn't accept a list" msg = f"Error converting {setting!r} to a string: this setting doesn't accept a list"
raise OptionsReaderError(msg) from None raise OptionsReaderError(msg) from None
if isinstance(setting, bool | int): if isinstance(setting, (bool, int)):
return str(setting) return str(setting)
return setting return setting
@@ -407,7 +393,8 @@ class OptionsReader:
self.disallow = disallow or {} self.disallow = disallow or {}
# Open defaults.toml, loading both global and platform sections # Open defaults.toml, loading both global and platform sections
self.default_options, self.default_platform_options = self._load_file(resources.DEFAULTS) defaults_path = resources_dir / "defaults.toml"
self.default_options, self.default_platform_options = self._load_file(defaults_path)
# Load the project config file # Load the project config file
config_options: dict[str, Any] = {} config_options: dict[str, Any] = {}
@@ -525,7 +512,6 @@ class OptionsReader:
env_plat: bool = True, env_plat: bool = True,
option_format: OptionFormat | None = None, option_format: OptionFormat | None = None,
ignore_empty: bool = False, ignore_empty: bool = False,
env_rule: InheritRule = InheritRule.NONE,
) -> str: ) -> str:
""" """
Get and return the value for the named option from environment, Get and return the value for the named option from environment,
@@ -557,48 +543,33 @@ class OptionsReader:
(o.options.get(name), o.inherit.get(name, InheritRule.NONE)) (o.options.get(name), o.inherit.get(name, InheritRule.NONE))
for o in self.active_config_overrides for o in self.active_config_overrides
], ],
(self.env.get(envvar), env_rule), (self.env.get(envvar), InheritRule.NONE),
(self.env.get(plat_envvar) if env_plat else None, env_rule), (self.env.get(plat_envvar) if env_plat else None, InheritRule.NONE),
ignore_empty=ignore_empty, ignore_empty=ignore_empty,
option_format=option_format, option_format=option_format,
) )
class Options: class Options:
pyproject_toml: dict[str, Any] | None
def __init__( def __init__(
self, self,
platform: PlatformName, platform: PlatformName,
command_line_arguments: CommandLineArguments, command_line_arguments: CommandLineArguments,
env: Mapping[str, str], env: Mapping[str, str],
defaults: bool = False, read_config_file: bool = True,
): ):
self.platform = platform self.platform = platform
self.command_line_arguments = command_line_arguments self.command_line_arguments = command_line_arguments
self.env = env self.env = env
self._defaults = defaults
self._image_warnings = set[str]()
self.reader = OptionsReader( self.reader = OptionsReader(
None if defaults else self.config_file_path, self.config_file_path if read_config_file else None,
platform=platform, platform=platform,
env=env, env=env,
disallow=DISALLOWED_OPTIONS, disallow=DISALLOWED_OPTIONS,
) )
self.package_dir = Path(command_line_arguments.package_dir) @property
try:
with self.package_dir.joinpath("pyproject.toml").open("rb") as f:
self.pyproject_toml = tomllib.load(f)
except FileNotFoundError:
self.pyproject_toml = None
# cache the build options method so repeated calls don't need to
# resolve the options again
self.build_options = functools.cache(self._compute_build_options)
@functools.cached_property
def config_file_path(self) -> Path | None: def config_file_path(self) -> Path | None:
args = self.command_line_arguments args = self.command_line_arguments
@@ -614,9 +585,10 @@ class Options:
@functools.cached_property @functools.cached_property
def package_requires_python_str(self) -> str | None: def package_requires_python_str(self) -> str | None:
return get_requires_python_str(self.package_dir, self.pyproject_toml) args = self.command_line_arguments
return get_requires_python_str(Path(args.package_dir))
@functools.cached_property @property
def globals(self) -> GlobalOptions: def globals(self) -> GlobalOptions:
args = self.command_line_arguments args = self.command_line_arguments
package_dir = args.package_dir package_dir = args.package_dir
@@ -628,19 +600,15 @@ class Options:
skip_config = self.reader.get("skip", env_plat=False, option_format=ListFormat(sep=" ")) skip_config = self.reader.get("skip", env_plat=False, option_format=ListFormat(sep=" "))
test_skip = self.reader.get("test-skip", env_plat=False, option_format=ListFormat(sep=" ")) test_skip = self.reader.get("test-skip", env_plat=False, option_format=ListFormat(sep=" "))
free_threaded_support = strtobool(
self.reader.get("free-threaded-support", env_plat=False, ignore_empty=True)
)
allow_empty = args.allow_empty or strtobool(self.env.get("CIBW_ALLOW_EMPTY", "0")) allow_empty = args.allow_empty or strtobool(self.env.get("CIBW_ALLOW_EMPTY", "0"))
enable_groups = self.reader.get( prerelease_pythons = args.prerelease_pythons or strtobool(
"enable", env_plat=False, option_format=ListFormat(sep=" "), env_rule=InheritRule.APPEND self.env.get("CIBW_PRERELEASE_PYTHONS", "0")
) )
try:
enable = {
*EnableGroup.parse_option_value(enable_groups),
*EnableGroup.parse_option_value(" ".join(args.enable)),
}
except ValueError as e:
msg = f"Failed to parse enable group. {e}. Valid group names are: {', '.join(g.value for g in EnableGroup)}"
raise errors.ConfigurationError(msg) from e
# This is not supported in tool.cibuildwheel, as it comes from a standard location. # This is not supported in tool.cibuildwheel, as it comes from a standard location.
# Passing this in as an environment variable will override pyproject.toml, setup.cfg, or setup.py # Passing this in as an environment variable will override pyproject.toml, setup.cfg, or setup.py
@@ -657,13 +625,15 @@ class Options:
build_config = args.only build_config = args.only
skip_config = "" skip_config = ""
architectures = Architecture.all_archs(self.platform) architectures = Architecture.all_archs(self.platform)
enable = set(EnableGroup) prerelease_pythons = True
free_threaded_support = True
build_selector = BuildSelector( build_selector = BuildSelector(
build_config=build_config, build_config=build_config,
skip_config=skip_config, skip_config=skip_config,
requires_python=requires_python, requires_python=requires_python,
enable=frozenset(enable), prerelease_pythons=prerelease_pythons,
free_threaded_support=free_threaded_support,
) )
test_selector = TestSelector(skip_config=test_skip) test_selector = TestSelector(skip_config=test_skip)
@@ -676,33 +646,9 @@ class Options:
allow_empty=allow_empty, allow_empty=allow_empty,
) )
def _check_pinned_image(self, value: str, pinned_images: Mapping[str, str]) -> None: def build_options(self, identifier: str | None) -> BuildOptions:
error_set = {"manylinux1", "manylinux2010", "manylinux_2_24", "musllinux_1_1"}
warning_set: set[str] = set()
if value in error_set:
msg = (
f"cibuildwheel 3.x does not support the image {value!r}. Either upgrade to a "
"supported image or continue using the image by pinning it directly with"
" its full OCI registry '<name>{:<tag>|@<digest>}'."
)
raise errors.DeprecationError(msg)
if value in warning_set and value not in self._image_warnings:
self._image_warnings.add(value)
msg = (
f"Deprecated image {value!r}. This value will not work"
" in a future version of cibuildwheel. Either upgrade to a supported"
" image or continue using the deprecated image by pinning directly"
f" to {pinned_images[value]!r}."
)
log.warning(msg)
def _compute_build_options(self, identifier: str | None) -> BuildOptions:
""" """
Compute BuildOptions for a single run configuration. Normally accessed Compute BuildOptions for a single run configuration.
through the `build_options` method, which is the same but the result
is cached.
""" """
with self.reader.identifier(identifier): with self.reader.identifier(identifier):
@@ -720,34 +666,13 @@ class Options:
"config-settings", option_format=ShlexTableFormat(sep=" ", pair_sep="=") "config-settings", option_format=ShlexTableFormat(sep=" ", pair_sep="=")
) )
dependency_versions = self.reader.get("dependency-versions")
test_command = self.reader.get("test-command", option_format=ListFormat(sep=" && ")) test_command = self.reader.get("test-command", option_format=ListFormat(sep=" && "))
before_test = self.reader.get("before-test", option_format=ListFormat(sep=" && ")) before_test = self.reader.get("before-test", option_format=ListFormat(sep=" && "))
xbuild_tools: list[str] | None = shlex.split(
self.reader.get(
"xbuild-tools", option_format=ListFormat(sep=" ", quote=shlex.quote)
)
)
# ["\u0000"] is a sentinel value used as a default, because TOML
# doesn't have an explicit NULL value. If xbuild-tools is set to the
# sentinel, it indicates that the user hasn't defined xbuild-tools
# *at all* (not even an `xbuild-tools = []` definition).
if xbuild_tools == ["\u0000"]:
xbuild_tools = None
test_sources = shlex.split(
self.reader.get(
"test-sources", option_format=ListFormat(sep=" ", quote=shlex.quote)
)
)
test_requires = self.reader.get( test_requires = self.reader.get(
"test-requires", option_format=ListFormat(sep=" ") "test-requires", option_format=ListFormat(sep=" ")
).split() ).split()
test_extras = self.reader.get("test-extras", option_format=ListFormat(sep=",")) test_extras = self.reader.get("test-extras", option_format=ListFormat(sep=","))
test_groups_str = self.reader.get("test-groups", option_format=ListFormat(sep=" "))
test_groups = [x for x in test_groups_str.split() if x]
test_requirements_from_groups = resolve_dependency_groups(
self.pyproject_toml, *test_groups
)
build_verbosity_str = self.reader.get("build-verbosity") build_verbosity_str = self.reader.get("build-verbosity")
build_frontend_str = self.reader.get( build_frontend_str = self.reader.get(
@@ -777,18 +702,15 @@ class Options:
with contextlib.suppress(KeyError): with contextlib.suppress(KeyError):
environment.add(env_var_name, self.env[env_var_name], prepend=True) environment.add(env_var_name, self.env[env_var_name], prepend=True)
dependency_versions_str = self.reader.get( if dependency_versions == "pinned":
"dependency-versions", dependency_constraints: None | (
env_plat=True, DependencyConstraints
option_format=ShlexTableFormat(sep="; ", pair_sep=":", allow_merge=False), ) = DependencyConstraints.with_defaults()
) elif dependency_versions == "latest":
try: dependency_constraints = None
dependency_constraints = DependencyConstraints.from_config_string( else:
dependency_versions_str dependency_versions_path = Path(dependency_versions)
) dependency_constraints = DependencyConstraints(dependency_versions_path)
except (ValueError, OSError) as e:
msg = f"Failed to parse dependency versions. {e}"
raise errors.ConfigurationError(msg) from e
if test_extras: if test_extras:
test_extras = f"[{test_extras}]" test_extras = f"[{test_extras}]"
@@ -807,26 +729,33 @@ class Options:
for build_platform in MANYLINUX_ARCHS: for build_platform in MANYLINUX_ARCHS:
pinned_images = all_pinned_container_images[build_platform] pinned_images = all_pinned_container_images[build_platform]
config_value = self.reader.get( config_value = self.reader.get(
f"manylinux-{build_platform}-image", ignore_empty=True f"manylinux-{build_platform}-image", ignore_empty=True
) )
self._check_pinned_image(config_value, pinned_images)
if config_value in pinned_images: if not config_value:
# default to manylinux2014
image = pinned_images["manylinux2014"]
elif config_value in pinned_images:
image = pinned_images[config_value] image = pinned_images[config_value]
else: else:
image = config_value image = config_value
manylinux_images[build_platform] = image manylinux_images[build_platform] = image
for build_platform in MUSLLINUX_ARCHS: for build_platform in MUSLLINUX_ARCHS:
pinned_images = all_pinned_container_images[build_platform] pinned_images = all_pinned_container_images[build_platform]
config_value = self.reader.get(
f"musllinux-{build_platform}-image", ignore_empty=True config_value = self.reader.get(f"musllinux-{build_platform}-image")
)
self._check_pinned_image(config_value, pinned_images) if not config_value:
if config_value in pinned_images: image = pinned_images["musllinux_1_2"]
elif config_value in pinned_images:
image = pinned_images[config_value] image = pinned_images[config_value]
else: else:
image = config_value image = config_value
musllinux_images[build_platform] = image musllinux_images[build_platform] = image
container_engine_str = self.reader.get( container_engine_str = self.reader.get(
@@ -843,15 +772,12 @@ class Options:
return BuildOptions( return BuildOptions(
globals=self.globals, globals=self.globals,
test_command=test_command, test_command=test_command,
test_sources=test_sources, test_requires=test_requires,
test_requires=[*test_requires, *test_requirements_from_groups],
test_extras=test_extras, test_extras=test_extras,
test_groups=test_groups,
before_test=before_test, before_test=before_test,
before_build=before_build, before_build=before_build,
before_all=before_all, before_all=before_all,
build_verbosity=build_verbosity, build_verbosity=build_verbosity,
xbuild_tools=xbuild_tools,
repair_command=repair_command, repair_command=repair_command,
environment=environment, environment=environment,
dependency_constraints=dependency_constraints, dependency_constraints=dependency_constraints,
@@ -877,13 +803,21 @@ class Options:
) )
) )
def check_for_deprecated_options(self) -> None:
build_selector = self.globals.build_selector
test_selector = self.globals.test_selector
deprecated_selectors("CIBW_BUILD", build_selector.build_config, error=True)
deprecated_selectors("CIBW_SKIP", build_selector.skip_config)
deprecated_selectors("CIBW_TEST_SKIP", test_selector.skip_config)
@functools.cached_property @functools.cached_property
def defaults(self) -> Self: def defaults(self) -> Options:
return self.__class__( return Options(
platform=self.platform, platform=self.platform,
command_line_arguments=CommandLineArguments.defaults(), command_line_arguments=CommandLineArguments.defaults(),
env={}, env={},
defaults=True, read_config_file=False,
) )
def summary(self, identifiers: Iterable[str]) -> str: def summary(self, identifiers: Iterable[str]) -> str:
@@ -962,15 +896,13 @@ class Options:
return result return result
@staticmethod def indent_if_multiline(self, value: str, indent: str) -> str:
def indent_if_multiline(value: str, indent: str) -> str:
if "\n" in value: if "\n" in value:
return "\n" + textwrap.indent(value.strip(), indent) return "\n" + textwrap.indent(value.strip(), indent)
else: else:
return value return value
@staticmethod def option_summary_value(self, option_value: Any) -> str:
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()
@@ -991,10 +923,12 @@ def compute_options(
command_line_arguments: CommandLineArguments, command_line_arguments: CommandLineArguments,
env: Mapping[str, str], env: Mapping[str, str],
) -> Options: ) -> Options:
return Options(platform=platform, command_line_arguments=command_line_arguments, env=env) options = Options(platform=platform, command_line_arguments=command_line_arguments, env=env)
options.check_for_deprecated_options()
return options
@functools.cache @functools.lru_cache(maxsize=None)
def _get_pinned_container_images() -> Mapping[str, Mapping[str, str]]: def _get_pinned_container_images() -> Mapping[str, Mapping[str, str]]:
""" """
This looks like a dict of dicts, e.g. This looks like a dict of dicts, e.g.
@@ -1003,6 +937,16 @@ def _get_pinned_container_images() -> Mapping[str, Mapping[str, str]]:
'pypy_x86_64': {'manylinux2010': '...' } 'pypy_x86_64': {'manylinux2010': '...' }
... } ... }
""" """
pinned_images_file = resources_dir / "pinned_docker_images.cfg"
all_pinned_images = configparser.ConfigParser() all_pinned_images = configparser.ConfigParser()
all_pinned_images.read(resources.PINNED_DOCKER_IMAGES) all_pinned_images.read(pinned_images_file)
return all_pinned_images return all_pinned_images
def deprecated_selectors(name: str, selector: str, *, error: bool = False) -> None:
if "p2" in selector or "p35" in selector:
msg = f"cibuildwheel 2.x no longer supports Python < 3.6. Please use the 1.x series or update {name}"
if error:
raise errors.DeprecationError(msg)
log.warning(msg)
-41
View File
@@ -1,41 +0,0 @@
from __future__ import annotations
from collections.abc import Sequence
from pathlib import Path
from typing import Final, Protocol
from cibuildwheel.architecture import Architecture
from cibuildwheel.options import Options
from cibuildwheel.platforms import ios, linux, macos, pyodide, windows
from cibuildwheel.selector import BuildSelector
from cibuildwheel.typing import GenericPythonConfiguration, PlatformName
class PlatformModule(Protocol):
# note that as per PEP544, the self argument is ignored when the protocol
# is applied to a module
def all_python_configurations(self) -> Sequence[GenericPythonConfiguration]: ...
def get_python_configurations(
self, build_selector: BuildSelector, architectures: set[Architecture]
) -> Sequence[GenericPythonConfiguration]: ...
def build(self, options: Options, tmp_path: Path) -> None: ...
ALL_PLATFORM_MODULES: Final[dict[PlatformName, PlatformModule]] = {
"linux": linux,
"windows": windows,
"macos": macos,
"pyodide": pyodide,
"ios": ios,
}
def get_build_identifiers(
platform_module: PlatformModule,
build_selector: BuildSelector,
architectures: set[Architecture],
) -> list[str]:
python_configurations = platform_module.get_python_configurations(build_selector, architectures)
return [config.identifier for config in python_configurations]
-669
View File
@@ -1,669 +0,0 @@
from __future__ import annotations
import os
import shlex
import shutil
import subprocess
import sys
import textwrap
from collections.abc import Sequence, Set
from dataclasses import dataclass
from pathlib import Path
from typing import assert_never
from filelock import FileLock
from .. import errors
from ..architecture import Architecture
from ..environment import ParsedEnvironment
from ..frontend import (
BuildFrontendConfig,
BuildFrontendName,
get_build_frontend_extra_flags,
)
from ..logger import log
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 ..util.helpers import prepare_command, unwrap_preserving_paragraphs
from ..util.packaging import (
combine_constraints,
find_compatible_wheel,
get_pip_version,
)
from ..venv import constraint_flags, virtualenv
from .macos import install_cpython as install_build_cpython
@dataclass(frozen=True)
class PythonConfiguration:
version: str
identifier: str
url: str
build_url: str
@property
def sdk(self) -> str:
return self.multiarch.rsplit("-", 1)[1]
@property
def arch(self) -> str:
return self.multiarch.rsplit("-", 1)[0]
@property
def multiarch(self) -> str:
# The multiarch identifier, as reported by `sys.implementation._multiarch`
return "-".join(self.identifier.split("-ios_")[1].rsplit("_", 1))
@property
def is_simulator(self) -> bool:
return self.identifier.endswith("_iphonesimulator")
@property
def xcframework_slice(self) -> str:
"XCframeworks include binaries for multiple ABIs; which ABI section should be used?"
return "ios-arm64_x86_64-simulator" if self.is_simulator else "ios-arm64"
def all_python_configurations() -> list[PythonConfiguration]:
# iOS builds are always cross builds; we need to install a macOS Python as
# well. Rather than duplicate the location of the URL of macOS installers,
# load the macos configurations, determine the macOS configuration that
# matches the platform we're building, and embed that URL in the parsed iOS
# configuration.
macos_python_configs = resources.read_python_configs("macos")
def build_url(config_dict: dict[str, str]) -> str:
# The iOS identifier will be something like cp313-ios_arm64_iphoneos.
# Drop the iphoneos suffix, then replace ios with macosx to yield
# cp313-macosx_arm64, which will be a macOS build identifier.
modified_ios_identifier = config_dict["identifier"].rsplit("_", 1)[0]
macos_identifier = modified_ios_identifier.replace("ios", "macosx")
matching = [
config for config in macos_python_configs if config["identifier"] == macos_identifier
]
return matching[0]["url"]
# Load the platform configuration
full_python_configs = resources.read_python_configs("ios")
# Build the configurations, annotating with macOS URL details.
return [
PythonConfiguration(
**item,
build_url=build_url(item),
)
for item in full_python_configs
]
def get_python_configurations(
build_selector: BuildSelector,
architectures: Set[Architecture],
) -> list[PythonConfiguration]:
python_configurations = all_python_configurations()
# Filter out configs that don't match any of the selected architectures
python_configurations = [
c
for c in python_configurations
if any(c.identifier.endswith(f"-ios_{a.value}") for a in architectures)
]
# Skip builds as required by BUILD/SKIP
python_configurations = [c for c in python_configurations if build_selector(c.identifier)]
return python_configurations
def install_target_cpython(tmp: Path, config: PythonConfiguration, free_threading: bool) -> Path:
if free_threading:
msg = "Free threading builds aren't available for iOS (yet)"
raise errors.FatalError(msg)
# Install an iOS build of CPython
ios_python_tar_gz = config.url.rsplit("/", 1)[-1]
extension = ".tar.gz"
assert ios_python_tar_gz.endswith(extension)
installation_path = CIBW_CACHE_PATH / ios_python_tar_gz[: -len(extension)]
with FileLock(str(installation_path) + ".lock"):
if not installation_path.exists():
downloaded_tar_gz = tmp / ios_python_tar_gz
download(config.url, downloaded_tar_gz)
installation_path.mkdir(parents=True, exist_ok=True)
call("tar", "-C", installation_path, "-xf", downloaded_tar_gz)
downloaded_tar_gz.unlink()
return installation_path
def cross_virtualenv(
*,
py_version: str,
target_python: Path,
multiarch: str,
build_python: Path,
venv_path: Path,
dependency_constraint: Path | None,
xbuild_tools: Sequence[str] | None,
) -> dict[str, str]:
"""Create a cross-compilation virtual environment.
In a cross-compilation environment, the *target* is the platform where the
code will ultimately run, and the *build* is the platform where you're
running the compilation. When building iOS wheels, iOS is the target machine
and macOS is the build machine. The terminology around these machines varies
between build tools (configure uses "host" and "build"; cmake uses "target" and
"build host").
A cross-compilation virtualenv is an environment that is based on the
*build* python (so that binaries can execute); but it modifies the
environment at startup so that any request for platform details (such as
`sys.platform` or `sysconfig.get_platform()`) return details of the target
platform. It also applies a loader patch so that any virtualenv created by
the cross-compilation environment will also be a cross-compilation
environment.
:param py_version: The Python version (major.minor) in use
:param target_python: The path to the python binary for the target platform
:param multiarch: The multiarch tag for the target platform (i.e., the value
of `sys.implementation._multiarch`)
:param build_python: The path to the python binary for the build platform
:param venv_path: The path where the cross virtual environment should be
created.
:param dependency_constraint: A path to a constraint file that should be
used when constraining dependencies in the environment.
:param xbuild_tools: A list of executable names (without paths) that are
on the path, but must be preserved in the cross environment.
"""
# Create an initial macOS virtual environment
env = virtualenv(
py_version,
build_python,
venv_path,
dependency_constraint,
use_uv=False,
)
# Convert the macOS virtual environment into an iOS virtual environment
# using the cross-platform conversion script in the iOS distribution.
# target_python is the path to the Python binary;
# determine the root of the XCframework slice that is being used.
slice_path = target_python.parent.parent
call(
"python",
str(slice_path / f"platform-config/{multiarch}/make_cross_venv.py"),
str(venv_path),
env=env,
cwd=venv_path,
)
# When running on macOS, it's easy for the build environment to leak into
# the target environment, especially when building for ARM64 (because the
# build architecture is the same as the target architecture). The primary
# culprit for this is Homebrew libraries leaking in as dependencies for iOS
# libraries.
#
# To prevent problems, set the PATH to isolate the build environment from
# sources that could introduce incompatible binaries.
#
# However, there may be some tools on the path that are needed for the
# build. Find their location on the path, and link the underlying binaries
# (fully resolving symlinks) to a "safe" location that will *only* contain
# those tools. This avoids needing to add *all* of Homebrew to the path just
# to get access to (for example) cmake for build purposes. A value of None
# means the user hasn't provided a list of xbuild tools.
xbuild_tools_path = venv_path / "cibw_xbuild_tools"
xbuild_tools_path.mkdir()
if xbuild_tools is None:
log.warning(
textwrap.dedent(
"""
Your project configuration does not define any cross-build tools.
iOS builds use an isolated build environment; if your build process requires any
third-party tools (such as cmake, ninja, or rustc), you must explicitly declare
that those tools are required using xbuild-tools/CIBW_XBUILD_TOOLS. This will
likely manifest as a "somebuildtool: command not found" error.
If the build succeeds, you can silence this warning by setting adding
`xbuild-tools = []` to your pyproject.toml configuration, or exporting
CIBW_XBUILD_TOOLS as an empty string into your environment.
"""
)
)
else:
for tool in xbuild_tools:
tool_path = shutil.which(tool)
if tool_path is None:
msg = f"Could not find a {tool!r} executable on the path."
raise errors.FatalError(msg)
# Link the binary into the safe tools directory
original = Path(tool_path).resolve()
print(f"{tool!r} will be included in the cross-build environment (using {original})")
(xbuild_tools_path / tool).symlink_to(original)
env["PATH"] = os.pathsep.join(
[
# The target python's binary directory
str(target_python.parent),
# The cross-platform environment's binary directory
str(venv_path / "bin"),
# The directory of cross-build tools
str(xbuild_tools_path),
# The bare minimum Apple system paths.
"/usr/bin",
"/bin",
"/usr/sbin",
"/sbin",
"/Library/Apple/usr/bin",
]
)
# Also unset DYLD_LIBRARY_PATH to ensure that no macOS libraries will be
# found and linked.
env.pop("DYLD_LIBRARY_PATH", None)
return env
def setup_python(
tmp: Path,
*,
python_configuration: PythonConfiguration,
dependency_constraint: Path | None,
environment: ParsedEnvironment,
build_frontend: BuildFrontendName,
xbuild_tools: Sequence[str] | None,
) -> tuple[Path, dict[str, str]]:
if build_frontend == "build[uv]":
msg = "uv doesn't support iOS"
raise errors.FatalError(msg)
# An iOS environment requires 2 python installs - one for the build machine
# (macOS), and one for the target (iOS). We'll only ever interact with the
# *target* python, but the build Python needs to exist to act as the base
# for a cross venv.
tmp.mkdir()
implementation_id = python_configuration.identifier.split("-")[0]
log.step(f"Installing Build Python {implementation_id}...")
if implementation_id.startswith("cp"):
free_threading = "t-ios" in python_configuration.identifier
build_python = install_build_cpython(
tmp,
python_configuration.version,
python_configuration.build_url,
free_threading,
)
else:
msg = f"Unknown Python implementation: {implementation_id}"
raise errors.FatalError(msg)
assert build_python.exists(), (
f"{build_python.name} not found, has {list(build_python.parent.iterdir())}"
)
log.step(f"Installing Target Python {implementation_id}...")
target_install_path = install_target_cpython(tmp, python_configuration, free_threading)
target_python = (
target_install_path
/ "Python.xcframework"
/ python_configuration.xcframework_slice
/ "bin"
/ f"python{python_configuration.version}"
)
assert target_python.exists(), (
f"{target_python.name} not found, has {list(target_install_path.iterdir())}"
)
log.step("Creating cross build environment...")
venv_path = tmp / "venv"
env = cross_virtualenv(
py_version=python_configuration.version,
target_python=target_python,
multiarch=python_configuration.multiarch,
build_python=build_python,
venv_path=venv_path,
dependency_constraint=dependency_constraint,
xbuild_tools=xbuild_tools,
)
venv_bin_path = venv_path / "bin"
assert venv_bin_path.exists()
# We version pip ourselves, so we don't care about pip version checking
env["PIP_DISABLE_PIP_VERSION_CHECK"] = "1"
# upgrade pip to the version matching our constraints
# if necessary, reinstall it to ensure that it's available on PATH as 'pip'
pip = ["python", "-m", "pip"]
call(
*pip,
"install",
"--upgrade",
"pip",
*constraint_flags(dependency_constraint),
env=env,
cwd=venv_path,
)
# Apply our environment after pip is ready
env = environment.as_dictionary(prev_environment=env)
# Check what Python version we're on
which_python = call("which", "python", env=env, capture_stdout=True).strip()
print(which_python)
if which_python != str(venv_bin_path / "python"):
msg = (
"cibuildwheel: 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)
call("python", "--version", env=env)
# Check what pip version we're on
assert (venv_bin_path / "pip").exists()
which_pip = call("which", "pip", env=env, capture_stdout=True).strip()
print(which_pip)
if which_pip != str(venv_bin_path / "pip"):
msg = (
"cibuildwheel: pip available on PATH doesn't match our installed instance. "
"If you have modified PATH, ensure that you don't overwrite cibuildwheel's "
"entry or insert pip above it."
)
raise errors.FatalError(msg)
call("pip", "--version", env=env)
# Ensure that IPHONEOS_DEPLOYMENT_TARGET is set in the environment
env.setdefault("IPHONEOS_DEPLOYMENT_TARGET", "13.0")
log.step("Installing build tools...")
if build_frontend == "pip":
# No additional build tools required
pass
elif build_frontend == "build":
call(
"pip",
"install",
"--upgrade",
"build[virtualenv]",
*constraint_flags(dependency_constraint),
env=env,
)
else:
assert_never(build_frontend)
return target_install_path, env
def build(options: Options, tmp_path: Path) -> None:
if sys.platform != "darwin":
msg = "iOS binaries can only be built on macOS"
raise errors.FatalError(msg)
python_configurations = get_python_configurations(
build_selector=options.globals.build_selector,
architectures=options.globals.architectures,
)
if not python_configurations:
return
try:
before_all_options_identifier = python_configurations[0].identifier
before_all_options = options.build_options(before_all_options_identifier)
if before_all_options.before_all:
log.step("Running before_all...")
env = before_all_options.environment.as_dictionary(prev_environment=os.environ)
env.setdefault("IPHONEOS_DEPLOYMENT_TARGET", "13.0")
before_all_prepared = prepare_command(
before_all_options.before_all,
project=".",
package=before_all_options.package_dir,
)
shell(before_all_prepared, env=env)
built_wheels: list[Path] = []
for config in python_configurations:
build_options = options.build_options(config.identifier)
build_frontend = build_options.build_frontend or BuildFrontendConfig("build")
# uv doesn't support iOS
if build_frontend.name == "build[uv]":
msg = "uv doesn't support iOS"
raise errors.FatalError(msg)
log.build_start(config.identifier)
identifier_tmp_dir = tmp_path / config.identifier
identifier_tmp_dir.mkdir()
built_wheel_dir = identifier_tmp_dir / "built_wheel"
constraints_path = build_options.dependency_constraints.get_for_python_version(
version=config.version, tmp_dir=identifier_tmp_dir
)
target_install_path, env = setup_python(
identifier_tmp_dir / "build",
python_configuration=config,
dependency_constraint=constraints_path,
environment=build_options.environment,
build_frontend=build_frontend.name,
xbuild_tools=build_options.xbuild_tools,
)
pip_version = get_pip_version(env)
compatible_wheel = find_compatible_wheel(built_wheels, config.identifier)
if compatible_wheel:
log.step_end()
print(
f"\nFound previously built wheel {compatible_wheel.name} "
f"that is compatible with {config.identifier}. "
"Skipping build step..."
)
test_wheel = compatible_wheel
else:
if build_options.before_build:
log.step("Running before_build...")
before_build_prepared = prepare_command(
build_options.before_build,
project=".",
package=build_options.package_dir,
)
shell(before_build_prepared, env=env)
log.step("Building wheel...")
built_wheel_dir.mkdir()
extra_flags = get_build_frontend_extra_flags(
build_frontend, build_options.build_verbosity, build_options.config_settings
)
build_env = env.copy()
build_env["VIRTUALENV_PIP"] = pip_version
if constraints_path:
combine_constraints(build_env, constraints_path, None)
if build_frontend.name == "pip":
# Path.resolve() is needed. Without it pip wheel may try to
# fetch package from pypi.org. See
# https://github.com/pypa/cibuildwheel/pull/369
call(
"python",
"-m",
"pip",
"wheel",
build_options.package_dir.resolve(),
f"--wheel-dir={built_wheel_dir}",
"--no-deps",
*extra_flags,
env=build_env,
)
elif build_frontend.name == "build":
call(
"python",
"-m",
"build",
build_options.package_dir,
"--wheel",
f"--outdir={built_wheel_dir}",
*extra_flags,
env=build_env,
)
else:
assert_never(build_frontend)
test_wheel = built_wheel = next(built_wheel_dir.glob("*.whl"))
if built_wheel.name.endswith("none-any.whl"):
raise errors.NonPlatformWheelError()
log.step_end()
if build_options.test_command and build_options.test_selector(config.identifier):
if not config.is_simulator:
log.step("Skipping tests on non-simulator SDK")
elif config.arch != os.uname().machine:
log.step("Skipping tests on non-native simulator architecture")
else:
if build_options.before_test:
before_test_prepared = prepare_command(
build_options.before_test,
project=".",
package=build_options.package_dir,
)
shell(before_test_prepared, env=env)
log.step("Setting up test harness...")
# Clone the testbed project into the build directory
testbed_path = identifier_tmp_dir / "testbed"
call(
"python",
target_install_path / "testbed",
"clone",
testbed_path,
env=build_env,
)
if not build_options.test_sources:
# iOS requires an explicit test-sources, as the project directory
# isn't visible on the simulator.
msg = "Testing on iOS requires a definition of test-sources."
raise errors.FatalError(msg)
# Copy the test sources to the testbed app
copy_test_sources(
build_options.test_sources,
build_options.package_dir,
testbed_path / "iOSTestbed" / "app",
)
log.step("Installing test requirements...")
# Install the compiled wheel (with any test extras), plus
# the test requirements. Use the --platform tag to force
# the installation of iOS wheels; this requires the use of
# --only-binary=:all:
ios_version = build_env["IPHONEOS_DEPLOYMENT_TARGET"]
platform_tag = f"ios_{ios_version.replace('.', '_')}_{config.arch}_{config.sdk}"
call(
"python",
"-m",
"pip",
"install",
"--only-binary=:all:",
"--platform",
platform_tag,
"--target",
testbed_path / "iOSTestbed" / "app_packages",
f"{test_wheel}{build_options.test_extras}",
*build_options.test_requires,
env=build_env,
)
log.step("Running test suite...")
test_command_parts = shlex.split(build_options.test_command)
if test_command_parts[0:2] != ["python", "-m"]:
first_part = test_command_parts[0]
if first_part == "pytest":
# pytest works exactly the same as a module, so we
# can just run it as a module.
log.warning(
unwrap_preserving_paragraphs(f"""
iOS tests configured with a test command which doesn't start
with 'python -m'. iOS tests must execute python modules - other
entrypoints are not supported.
cibuildwheel will try to execute it as if it started with
'python -m'. If this works, all you need to do is add that to
your test command.
Test command: {build_options.test_command!r}
""")
)
else:
msg = unwrap_preserving_paragraphs(
f"""
iOS tests configured with a test command which doesn't start
with 'python -m'. iOS tests must execute python modules - other
entrypoints are not supported.
Test command: {build_options.test_command!r}
"""
)
raise errors.FatalError(msg)
else:
# the testbed run command actually doesn't want the
# python -m prefix - it's implicit, so we remove it
# here.
test_command_parts = test_command_parts[2:]
try:
call(
"python",
testbed_path,
"run",
*(["--verbose"] if build_options.build_verbosity > 0 else []),
"--",
*test_command_parts,
env=build_env,
)
failed = False
except subprocess.CalledProcessError:
failed = True
log.step_end(success=not failed)
if failed:
log.error(f"Test suite failed on {config.identifier}")
sys.exit(1)
# We're all done here; move it to output (overwrite existing)
if compatible_wheel is None:
output_wheel = build_options.output_dir.joinpath(built_wheel.name)
moved_wheel = move_file(built_wheel, output_wheel)
if moved_wheel != output_wheel.resolve():
log.warning(
f"{built_wheel} was moved to {moved_wheel} instead of {output_wheel}"
)
built_wheels.append(output_wheel)
# Clean up
shutil.rmtree(identifier_tmp_dir)
log.build_end()
except subprocess.CalledProcessError as error:
msg = f"Command {error.cmd} failed with code {error.returncode}. {error.stdout or ''}"
raise errors.FatalError(msg) from error
+9 -28
View File
@@ -1,10 +1,11 @@
from __future__ import annotations
import ast import ast
import configparser import configparser
import contextlib import contextlib
from pathlib import Path from pathlib import Path
from typing import Any
import dependency_groups from ._compat import tomllib
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:
@@ -83,12 +84,15 @@ def setup_py_python_requires(content: str) -> str | None:
return None return None
def get_requires_python_str(package_dir: Path, pyproject_toml: dict[str, Any] | None) -> str | None: def get_requires_python_str(package_dir: Path) -> str | None:
"""Return the python requires string from the most canonical source available, or None""" """Return the python requires string from the most canonical source available, or None"""
# Read in from pyproject.toml:project.requires-python # Read in from pyproject.toml:project.requires-python
with contextlib.suppress(KeyError, IndexError, TypeError): with contextlib.suppress(FileNotFoundError):
return str((pyproject_toml or {})["project"]["requires-python"]) with (package_dir / "pyproject.toml").open("rb") as f1:
info = tomllib.load(f1)
with contextlib.suppress(KeyError, IndexError, TypeError):
return str(info["project"]["requires-python"])
# Read in from setup.cfg:options.python_requires # Read in from setup.cfg:options.python_requires
config = configparser.ConfigParser() config = configparser.ConfigParser()
@@ -102,26 +106,3 @@ def get_requires_python_str(package_dir: Path, pyproject_toml: dict[str, Any] |
return setup_py_python_requires(f2.read()) return setup_py_python_requires(f2.read())
return None return None
def resolve_dependency_groups(
pyproject_toml: dict[str, Any] | None, *groups: str
) -> tuple[str, ...]:
"""
Get the packages in dependency-groups for a package.
"""
if not groups:
return ()
if pyproject_toml is None:
msg = f"Didn't find a pyproject.toml, so can't read [dependency-groups] {groups!r} from it!"
raise FileNotFoundError(msg)
try:
dependency_groups_toml = pyproject_toml["dependency-groups"]
except KeyError:
msg = f"Didn't find [dependency-groups] in pyproject.toml, which is needed to resolve {groups!r}."
raise KeyError(msg) from None
return dependency_groups.resolve(dependency_groups_toml, *groups)
@@ -1,38 +1,39 @@
import functools from __future__ import annotations
import os import os
import shutil import shutil
import sys import sys
import tomllib from collections.abc import Sequence, Set
from collections.abc import Set
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from tempfile import TemporaryDirectory
from typing import Final
from filelock import FileLock from filelock import FileLock
from .. import errors from . import errors
from ..architecture import Architecture from .architecture import Architecture
from ..environment import ParsedEnvironment from .environment import ParsedEnvironment
from ..frontend import BuildFrontendConfig, get_build_frontend_extra_flags from .logger import log
from ..logger import log from .options import Options
from ..options import Options from .typing import PathOrStr
from ..selector import BuildSelector from .util import (
from ..util import resources
from ..util.cmd import call, shell
from ..util.file import (
CIBW_CACHE_PATH, CIBW_CACHE_PATH,
copy_test_sources, BuildFrontendConfig,
BuildSelector,
call,
combine_constraints,
download, download,
extract_tar, ensure_node,
extract_zip, extract_zip,
find_compatible_wheel,
get_pip_version,
move_file, move_file,
prepare_command,
read_python_configs,
shell,
split_config_settings,
test_fail_cwd_file,
virtualenv,
) )
from ..util.helpers import prepare_command
from ..util.packaging import combine_constraints, find_compatible_wheel, get_pip_version
from ..venv import constraint_flags, virtualenv
IS_WIN: Final[bool] = sys.platform.startswith("win")
@dataclass(frozen=True) @dataclass(frozen=True)
@@ -40,42 +41,10 @@ class PythonConfiguration:
version: str version: str
identifier: str identifier: str
pyodide_version: str pyodide_version: str
pyodide_build_version: str
emscripten_version: str emscripten_version: str
node_version: str node_version: str
@functools.cache
def ensure_node(major_version: str) -> Path:
with resources.NODEJS.open("rb") as f:
loaded_file = tomllib.load(f)
version = str(loaded_file[major_version])
base_url = str(loaded_file["url"])
ext = "zip" if IS_WIN else "tar.xz"
platform = "win" if IS_WIN else ("darwin" if sys.platform.startswith("darwin") else "linux")
linux_arch = Architecture.native_arch("linux")
assert linux_arch is not None
arch = {"x86_64": "x64", "i686": "x86", "aarch64": "arm64"}.get(
linux_arch.value, linux_arch.value
)
name = f"node-{version}-{platform}-{arch}"
path = CIBW_CACHE_PATH / name
with FileLock(str(path) + ".lock"):
if not path.exists():
url = f"{base_url}{version}/{name}.{ext}"
with TemporaryDirectory() as tmp_path:
archive = Path(tmp_path) / f"{name}.{ext}"
download(url, archive)
if ext == "zip":
extract_zip(archive, path.parent)
else:
extract_tar(archive, path.parent)
assert path.exists()
if not IS_WIN:
return path / "bin"
return path
def install_emscripten(tmp: Path, version: str) -> Path: def install_emscripten(tmp: Path, version: str) -> Path:
# We don't need to match the emsdk version to the version we install, but # We don't need to match the emsdk version to the version we install, but
# we do for stability # we do for stability
@@ -96,15 +65,10 @@ def install_emscripten(tmp: Path, version: str) -> Path:
return emcc_path return emcc_path
def install_xbuildenv(env: dict[str, str], pyodide_build_version: str, pyodide_version: str) -> str: def install_xbuildenv(env: dict[str, str], pyodide_version: str) -> str:
"""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
# not guaranteed to match the versions of Pyodide or be in sync with them. Hence, we shall
# specify the pyodide-build version in the root path, which will set up the xbuildenv for
# the requested Pyodide version.
pyodide_root = ( pyodide_root = (
CIBW_CACHE_PATH CIBW_CACHE_PATH
/ f".pyodide-xbuildenv-{pyodide_build_version}/{pyodide_version}/xbuildenv/pyodide-root" / f".pyodide-xbuildenv-{pyodide_version}/{pyodide_version}/xbuildenv/pyodide-root"
) )
with FileLock(CIBW_CACHE_PATH / "xbuildenv.lock"): with FileLock(CIBW_CACHE_PATH / "xbuildenv.lock"):
if pyodide_root.exists(): if pyodide_root.exists():
@@ -144,14 +108,14 @@ def get_base_python(identifier: str) -> Path:
def setup_python( def setup_python(
tmp: Path, tmp: Path,
python_configuration: PythonConfiguration, python_configuration: PythonConfiguration,
constraints_path: Path | None, dependency_constraint_flags: Sequence[PathOrStr],
environment: ParsedEnvironment, environment: ParsedEnvironment,
) -> dict[str, str]: ) -> dict[str, str]:
base_python = get_base_python(python_configuration.identifier) base_python = get_base_python(python_configuration.identifier)
log.step("Setting up build environment...") log.step("Setting up build environment...")
venv_path = tmp / "venv" venv_path = tmp / "venv"
env = virtualenv(python_configuration.version, base_python, venv_path, None, use_uv=False) env = virtualenv(python_configuration.version, base_python, venv_path, [], use_uv=False)
venv_bin_path = venv_path / "bin" venv_bin_path = venv_path / "bin"
assert venv_bin_path.exists() assert venv_bin_path.exists()
env["PIP_DISABLE_PIP_VERSION_CHECK"] = "1" env["PIP_DISABLE_PIP_VERSION_CHECK"] = "1"
@@ -165,29 +129,29 @@ def setup_python(
"install", "install",
"--upgrade", "--upgrade",
"pip", "pip",
*constraint_flags(constraints_path), *dependency_constraint_flags,
env=env, env=env,
cwd=venv_path, cwd=venv_path,
) )
env = environment.as_dictionary(prev_environment=env) env = environment.as_dictionary(prev_environment=env)
# check what Python version we're on
which_python = call("which", "python", env=env, capture_stdout=True).strip()
print(which_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."
raise errors.FatalError(msg)
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()
call("which", "pip", env=env)
call("pip", "--version", env=env)
which_pip = call("which", "pip", env=env, capture_stdout=True).strip() which_pip = call("which", "pip", env=env, capture_stdout=True).strip()
print(which_pip)
if which_pip != str(venv_bin_path / "pip"): if which_pip != str(venv_bin_path / "pip"):
msg = "pip 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 pip above it." msg = "pip 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 pip above it."
raise errors.FatalError(msg) raise errors.FatalError(msg)
call("pip", "--version", env=env)
# check what Python version we're on
call("which", "python", env=env)
call("python", "--version", env=env)
which_python = call("which", "python", env=env, capture_stdout=True).strip()
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."
raise errors.FatalError(msg)
log.step("Installing build tools...") log.step("Installing build tools...")
call( call(
@@ -197,33 +161,30 @@ def setup_python(
"auditwheel-emscripten", "auditwheel-emscripten",
"build[virtualenv]", "build[virtualenv]",
"pyodide-build", "pyodide-build",
*constraint_flags(constraints_path), *dependency_constraint_flags,
env=env, env=env,
) )
log.step(f"Installing Emscripten version: {python_configuration.emscripten_version} ...") log.step("Installing emscripten...")
emcc_path = install_emscripten(tmp, python_configuration.emscripten_version) emcc_path = install_emscripten(tmp, python_configuration.emscripten_version)
env["PATH"] = os.pathsep.join([str(emcc_path.parent), env["PATH"]]) env["PATH"] = os.pathsep.join([str(emcc_path.parent), env["PATH"]])
log.step(f"Installing Pyodide xbuildenv version: {python_configuration.pyodide_version} ...") log.step("Installing Pyodide xbuildenv...")
env["PYODIDE_ROOT"] = install_xbuildenv( env["PYODIDE_ROOT"] = install_xbuildenv(env, python_configuration.pyodide_version)
env, python_configuration.pyodide_build_version, python_configuration.pyodide_version
)
return env return env
def all_python_configurations() -> list[PythonConfiguration]:
full_python_configs = resources.read_python_configs("pyodide")
return [PythonConfiguration(**item) for item in full_python_configs]
def get_python_configurations( def get_python_configurations(
build_selector: BuildSelector, build_selector: BuildSelector,
architectures: Set[Architecture], # noqa: ARG001 architectures: Set[Architecture], # noqa: ARG001
) -> list[PythonConfiguration]: ) -> list[PythonConfiguration]:
return [c for c in all_python_configurations() if build_selector(c.identifier)] full_python_configs = read_python_configs("pyodide")
python_configurations = [PythonConfiguration(**item) for item in full_python_configs]
python_configurations = [c for c in python_configurations if build_selector(c.identifier)]
return python_configurations
def build(options: Options, tmp_path: Path) -> None: def build(options: Options, tmp_path: Path) -> None:
@@ -265,14 +226,17 @@ def build(options: Options, tmp_path: Path) -> None:
built_wheel_dir.mkdir() built_wheel_dir.mkdir()
repaired_wheel_dir.mkdir() repaired_wheel_dir.mkdir()
constraints_path = build_options.dependency_constraints.get_for_python_version( dependency_constraint_flags: Sequence[PathOrStr] = []
version=config.version, variant="pyodide", tmp_dir=identifier_tmp_dir if build_options.dependency_constraints:
) constraints_path = build_options.dependency_constraints.get_for_python_version(
config.version, variant="pyodide"
)
dependency_constraint_flags = ["-c", constraints_path]
env = setup_python( env = setup_python(
identifier_tmp_dir / "build", identifier_tmp_dir / "build",
config, config,
constraints_path, dependency_constraint_flags,
build_options.environment, build_options.environment,
) )
pip_version = get_pip_version(env) pip_version = get_pip_version(env)
@@ -287,8 +251,8 @@ def build(options: Options, tmp_path: Path) -> None:
# directory. # directory.
oldmounts = "" oldmounts = ""
extra_mounts = [str(identifier_tmp_dir)] extra_mounts = [str(identifier_tmp_dir)]
if Path.cwd().is_relative_to("/tmp"): if str(Path(".").resolve()).startswith("/tmp"):
extra_mounts.append(str(Path.cwd())) extra_mounts.append(str(Path(".").resolve()))
if "_PYODIDE_EXTRA_MOUNTS" in env: if "_PYODIDE_EXTRA_MOUNTS" in env:
oldmounts = env["_PYODIDE_EXTRA_MOUNTS"] + ":" oldmounts = env["_PYODIDE_EXTRA_MOUNTS"] + ":"
@@ -311,12 +275,15 @@ def build(options: Options, tmp_path: Path) -> None:
log.step("Building wheel...") log.step("Building wheel...")
extra_flags = get_build_frontend_extra_flags( extra_flags = split_config_settings(build_options.config_settings, "build")
build_frontend, build_options.build_verbosity, build_options.config_settings extra_flags += build_frontend.args
)
if not 0 <= build_options.build_verbosity < 2:
msg = f"build_verbosity {build_options.build_verbosity} is not supported for build frontend. Ignoring."
log.warning(msg)
build_env = env.copy() build_env = env.copy()
if constraints_path: if build_options.dependency_constraints:
combine_constraints(build_env, constraints_path, identifier_tmp_dir) combine_constraints(build_env, constraints_path, identifier_tmp_dir)
build_env["VIRTUALENV_PIP"] = pip_version build_env["VIRTUALENV_PIP"] = pip_version
call( call(
@@ -408,21 +375,13 @@ def build(options: Options, tmp_path: Path) -> None:
# and not the repo code) # and not the repo code)
test_command_prepared = prepare_command( test_command_prepared = prepare_command(
build_options.test_command, build_options.test_command,
project=Path.cwd(), project=Path(".").resolve(),
package=build_options.package_dir.resolve(), package=build_options.package_dir.resolve(),
) )
if build_options.test_sources: test_cwd = identifier_tmp_dir / "test_cwd"
test_cwd = identifier_tmp_dir / "test_cwd" test_cwd.mkdir(exist_ok=True)
test_cwd.mkdir(exist_ok=True) (test_cwd / "test_fail.py").write_text(test_fail_cwd_file.read_text())
copy_test_sources(
build_options.test_sources,
build_options.package_dir,
test_cwd,
)
else:
# There are no test sources. Run the tests in the project directory.
test_cwd = Path.cwd()
shell(test_command_prepared, cwd=test_cwd, env=virtualenv_env) shell(test_command_prepared, cwd=test_cwd, env=virtualenv_env)
@@ -432,7 +391,7 @@ def build(options: Options, tmp_path: Path) -> None:
moved_wheel = move_file(repaired_wheel, output_wheel) moved_wheel = move_file(repaired_wheel, output_wheel)
if moved_wheel != output_wheel.resolve(): if moved_wheel != output_wheel.resolve():
log.warning( log.warning(
f"{repaired_wheel} was moved to {moved_wheel} instead of {output_wheel}" "{repaired_wheel} was moved to {moved_wheel} instead of {output_wheel}"
) )
built_wheels.append(output_wheel) built_wheels.append(output_wheel)
+53 -108
View File
@@ -1,5 +1,7 @@
[linux] [linux]
python_configurations = [ python_configurations = [
{ identifier = "cp36-manylinux_x86_64", version = "3.6", path_str = "/opt/python/cp36-cp36m" },
{ identifier = "cp37-manylinux_x86_64", version = "3.7", path_str = "/opt/python/cp37-cp37m" },
{ identifier = "cp38-manylinux_x86_64", version = "3.8", path_str = "/opt/python/cp38-cp38" }, { 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" },
@@ -7,8 +9,8 @@ python_configurations = [
{ 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 = "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 = "cp36-manylinux_i686", version = "3.6", path_str = "/opt/python/cp36-cp36m" },
{ identifier = "cp314t-manylinux_x86_64", version = "3.14", path_str = "/opt/python/cp314-cp314t" }, { identifier = "cp37-manylinux_i686", version = "3.7", path_str = "/opt/python/cp37-cp37m" },
{ identifier = "cp38-manylinux_i686", version = "3.8", path_str = "/opt/python/cp38-cp38" }, { identifier = "cp38-manylinux_i686", version = "3.8", path_str = "/opt/python/cp38-cp38" },
{ 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" },
@@ -16,13 +18,12 @@ python_configurations = [
{ 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 = "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 = "pp37-manylinux_x86_64", version = "3.7", path_str = "/opt/python/pp37-pypy37_pp73" },
{ identifier = "cp314t-manylinux_i686", version = "3.14", path_str = "/opt/python/cp314-cp314t" },
{ identifier = "pp38-manylinux_x86_64", version = "3.8", path_str = "/opt/python/pp38-pypy38_pp73" }, { identifier = "pp38-manylinux_x86_64", version = "3.8", path_str = "/opt/python/pp38-pypy38_pp73" },
{ 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 = "cp36-manylinux_aarch64", version = "3.6", path_str = "/opt/python/cp36-cp36m" },
{ identifier = "gp242-manylinux_x86_64", version = "3.11", path_str = "/opt/python/graalpy311-graalpy242_311_native" }, { identifier = "cp37-manylinux_aarch64", version = "3.7", path_str = "/opt/python/cp37-cp37m" },
{ identifier = "cp38-manylinux_aarch64", version = "3.8", path_str = "/opt/python/cp38-cp38" }, { 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" },
@@ -30,8 +31,8 @@ python_configurations = [
{ 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 = "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 = "cp36-manylinux_ppc64le", version = "3.6", path_str = "/opt/python/cp36-cp36m" },
{ identifier = "cp314t-manylinux_aarch64", version = "3.14", path_str = "/opt/python/cp314-cp314t" }, { identifier = "cp37-manylinux_ppc64le", version = "3.7", path_str = "/opt/python/cp37-cp37m" },
{ identifier = "cp38-manylinux_ppc64le", version = "3.8", path_str = "/opt/python/cp38-cp38" }, { identifier = "cp38-manylinux_ppc64le", version = "3.8", path_str = "/opt/python/cp38-cp38" },
{ 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" },
@@ -39,8 +40,8 @@ python_configurations = [
{ 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 = "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 = "cp36-manylinux_s390x", version = "3.6", path_str = "/opt/python/cp36-cp36m" },
{ identifier = "cp314t-manylinux_ppc64le", version = "3.14", path_str = "/opt/python/cp314-cp314t" }, { identifier = "cp37-manylinux_s390x", version = "3.7", path_str = "/opt/python/cp37-cp37m" },
{ identifier = "cp38-manylinux_s390x", version = "3.8", path_str = "/opt/python/cp38-cp38" }, { identifier = "cp38-manylinux_s390x", version = "3.8", path_str = "/opt/python/cp38-cp38" },
{ 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" },
@@ -48,35 +49,16 @@ python_configurations = [
{ 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 = "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 = "pp37-manylinux_aarch64", version = "3.7", path_str = "/opt/python/pp37-pypy37_pp73" },
{ identifier = "cp314t-manylinux_s390x", version = "3.14", path_str = "/opt/python/cp314-cp314t" },
{ identifier = "cp38-manylinux_armv7l", version = "3.8", path_str = "/opt/python/cp38-cp38" },
{ 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 = "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 = "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 = "cp314t-manylinux_armv7l", version = "3.14", path_str = "/opt/python/cp314-cp314t" },
{ identifier = "cp38-manylinux_riscv64", version = "3.8", path_str = "/opt/python/cp38-cp38" },
{ 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 = "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 = "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 = "cp314t-manylinux_riscv64", version = "3.14", path_str = "/opt/python/cp314-cp314t" },
{ identifier = "pp38-manylinux_aarch64", version = "3.8", path_str = "/opt/python/pp38-pypy38_pp73" }, { identifier = "pp38-manylinux_aarch64", version = "3.8", path_str = "/opt/python/pp38-pypy38_pp73" },
{ 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 = "pp37-manylinux_i686", version = "3.7", path_str = "/opt/python/pp37-pypy37_pp73" },
{ identifier = "gp242-manylinux_aarch64", version = "3.11", path_str = "/opt/python/graalpy311-graalpy242_311_native" },
{ identifier = "pp38-manylinux_i686", version = "3.8", path_str = "/opt/python/pp38-pypy38_pp73" }, { 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 = "cp36-musllinux_x86_64", version = "3.6", path_str = "/opt/python/cp36-cp36m" },
{ identifier = "cp37-musllinux_x86_64", version = "3.7", path_str = "/opt/python/cp37-cp37m" },
{ identifier = "cp38-musllinux_x86_64", version = "3.8", path_str = "/opt/python/cp38-cp38" }, { 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" },
@@ -84,8 +66,8 @@ python_configurations = [
{ 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 = "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 = "cp36-musllinux_i686", version = "3.6", path_str = "/opt/python/cp36-cp36m" },
{ identifier = "cp314t-musllinux_x86_64", version = "3.14", path_str = "/opt/python/cp314-cp314t" }, { identifier = "cp37-musllinux_i686", version = "3.7", path_str = "/opt/python/cp37-cp37m" },
{ identifier = "cp38-musllinux_i686", version = "3.8", path_str = "/opt/python/cp38-cp38" }, { identifier = "cp38-musllinux_i686", version = "3.8", path_str = "/opt/python/cp38-cp38" },
{ 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" },
@@ -93,8 +75,8 @@ python_configurations = [
{ 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 = "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 = "cp36-musllinux_aarch64", version = "3.6", path_str = "/opt/python/cp36-cp36m" },
{ identifier = "cp314t-musllinux_i686", version = "3.14", path_str = "/opt/python/cp314-cp314t" }, { identifier = "cp37-musllinux_aarch64", version = "3.7", path_str = "/opt/python/cp37-cp37m" },
{ identifier = "cp38-musllinux_aarch64", version = "3.8", path_str = "/opt/python/cp38-cp38" }, { identifier = "cp38-musllinux_aarch64", version = "3.8", path_str = "/opt/python/cp38-cp38" },
{ 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" },
@@ -102,8 +84,8 @@ python_configurations = [
{ 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 = "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 = "cp36-musllinux_ppc64le", version = "3.6", path_str = "/opt/python/cp36-cp36m" },
{ identifier = "cp314t-musllinux_aarch64", version = "3.14", path_str = "/opt/python/cp314-cp314t" }, { identifier = "cp37-musllinux_ppc64le", version = "3.7", path_str = "/opt/python/cp37-cp37m" },
{ identifier = "cp38-musllinux_ppc64le", version = "3.8", path_str = "/opt/python/cp38-cp38" }, { identifier = "cp38-musllinux_ppc64le", version = "3.8", path_str = "/opt/python/cp38-cp38" },
{ 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" },
@@ -111,8 +93,8 @@ python_configurations = [
{ 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 = "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 = "cp36-musllinux_s390x", version = "3.6", path_str = "/opt/python/cp36-cp36m" },
{ identifier = "cp314t-musllinux_ppc64le", version = "3.14", path_str = "/opt/python/cp314-cp314t" }, { identifier = "cp37-musllinux_s390x", version = "3.7", path_str = "/opt/python/cp37-cp37m" },
{ identifier = "cp38-musllinux_s390x", version = "3.8", path_str = "/opt/python/cp38-cp38" }, { identifier = "cp38-musllinux_s390x", version = "3.8", path_str = "/opt/python/cp38-cp38" },
{ 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" },
@@ -120,30 +102,12 @@ python_configurations = [
{ 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 = "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 = "cp314t-musllinux_s390x", version = "3.14", path_str = "/opt/python/cp314-cp314t" },
{ identifier = "cp38-musllinux_armv7l", version = "3.8", path_str = "/opt/python/cp38-cp38" },
{ 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 = "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 = "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 = "cp314t-musllinux_armv7l", version = "3.14", path_str = "/opt/python/cp314-cp314t" },
{ identifier = "cp38-musllinux_riscv64", version = "3.8", path_str = "/opt/python/cp38-cp38" },
{ identifier = "cp39-musllinux_ricv64", version = "3.9", path_str = "/opt/python/cp39-cp39" },
{ 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 = "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 = "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 = "cp314t-musllinux_riscv64", version = "3.14", path_str = "/opt/python/cp314-cp314t" },
] ]
[macos] [macos]
python_configurations = [ python_configurations = [
{ identifier = "cp36-macosx_x86_64", version = "3.6", url = "https://www.python.org/ftp/python/3.6.8/python-3.6.8-macosx10.9.pkg" },
{ identifier = "cp37-macosx_x86_64", version = "3.7", url = "https://www.python.org/ftp/python/3.7.9/python-3.7.9-macosx10.9.pkg" },
{ 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 = "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 = "cp38-macosx_arm64", version = "3.8", url = "https://www.python.org/ftp/python/3.8.10/python-3.8.10-macosx10.9.pkg" }, { 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 = "cp38-macosx_universal2", version = "3.8", url = "https://www.python.org/ftp/python/3.8.10/python-3.8.10-macosx10.9.pkg" }, { identifier = "cp38-macosx_universal2", version = "3.8", url = "https://www.python.org/ftp/python/3.8.10/python-3.8.10-macosx10.9.pkg" },
@@ -156,35 +120,30 @@ python_configurations = [
{ identifier = "cp311-macosx_x86_64", version = "3.11", url = "https://www.python.org/ftp/python/3.11.9/python-3.11.9-macos11.pkg" }, { identifier = "cp311-macosx_x86_64", version = "3.11", url = "https://www.python.org/ftp/python/3.11.9/python-3.11.9-macos11.pkg" },
{ identifier = "cp311-macosx_arm64", version = "3.11", url = "https://www.python.org/ftp/python/3.11.9/python-3.11.9-macos11.pkg" }, { identifier = "cp311-macosx_arm64", version = "3.11", url = "https://www.python.org/ftp/python/3.11.9/python-3.11.9-macos11.pkg" },
{ identifier = "cp311-macosx_universal2", version = "3.11", url = "https://www.python.org/ftp/python/3.11.9/python-3.11.9-macos11.pkg" }, { identifier = "cp311-macosx_universal2", version = "3.11", url = "https://www.python.org/ftp/python/3.11.9/python-3.11.9-macos11.pkg" },
{ identifier = "cp312-macosx_x86_64", version = "3.12", url = "https://www.python.org/ftp/python/3.12.10/python-3.12.10-macos11.pkg" }, { identifier = "cp312-macosx_x86_64", version = "3.12", url = "https://www.python.org/ftp/python/3.12.5/python-3.12.5-macos11.pkg" },
{ identifier = "cp312-macosx_arm64", version = "3.12", url = "https://www.python.org/ftp/python/3.12.10/python-3.12.10-macos11.pkg" }, { identifier = "cp312-macosx_arm64", version = "3.12", url = "https://www.python.org/ftp/python/3.12.5/python-3.12.5-macos11.pkg" },
{ identifier = "cp312-macosx_universal2", version = "3.12", url = "https://www.python.org/ftp/python/3.12.10/python-3.12.10-macos11.pkg" }, { identifier = "cp312-macosx_universal2", version = "3.12", url = "https://www.python.org/ftp/python/3.12.5/python-3.12.5-macos11.pkg" },
{ identifier = "cp313-macosx_x86_64", version = "3.13", url = "https://www.python.org/ftp/python/3.13.3/python-3.13.3-macos11.pkg" }, { identifier = "cp313-macosx_x86_64", version = "3.13", url = "https://www.python.org/ftp/python/3.13.0/python-3.13.0rc1-macos11.pkg" },
{ identifier = "cp313-macosx_arm64", version = "3.13", url = "https://www.python.org/ftp/python/3.13.3/python-3.13.3-macos11.pkg" }, { identifier = "cp313-macosx_arm64", version = "3.13", url = "https://www.python.org/ftp/python/3.13.0/python-3.13.0rc1-macos11.pkg" },
{ identifier = "cp313-macosx_universal2", version = "3.13", url = "https://www.python.org/ftp/python/3.13.3/python-3.13.3-macos11.pkg" }, { identifier = "cp313-macosx_universal2", version = "3.13", url = "https://www.python.org/ftp/python/3.13.0/python-3.13.0rc1-macos11.pkg" },
{ identifier = "cp313t-macosx_x86_64", version = "3.13", url = "https://www.python.org/ftp/python/3.13.3/python-3.13.3-macos11.pkg" }, { identifier = "cp313t-macosx_x86_64", version = "3.13", url = "https://www.python.org/ftp/python/3.13.0/python-3.13.0rc1-macos11.pkg" },
{ identifier = "cp313t-macosx_arm64", version = "3.13", url = "https://www.python.org/ftp/python/3.13.3/python-3.13.3-macos11.pkg" }, { identifier = "cp313t-macosx_arm64", version = "3.13", url = "https://www.python.org/ftp/python/3.13.0/python-3.13.0rc1-macos11.pkg" },
{ identifier = "cp313t-macosx_universal2", version = "3.13", url = "https://www.python.org/ftp/python/3.13.3/python-3.13.3-macos11.pkg" }, { identifier = "cp313t-macosx_universal2", version = "3.13", url = "https://www.python.org/ftp/python/3.13.0/python-3.13.0rc1-macos11.pkg" },
{ identifier = "cp314-macosx_x86_64", version = "3.14", url = "https://www.python.org/ftp/python/3.14.0/python-3.14.0b1-macos11.pkg" }, { identifier = "pp37-macosx_x86_64", version = "3.7", url = "https://downloads.python.org/pypy/pypy3.7-v7.3.9-osx64.tar.bz2" },
{ identifier = "cp314-macosx_arm64", version = "3.14", url = "https://www.python.org/ftp/python/3.14.0/python-3.14.0b1-macos11.pkg" },
{ identifier = "cp314-macosx_universal2", version = "3.14", url = "https://www.python.org/ftp/python/3.14.0/python-3.14.0b1-macos11.pkg" },
{ identifier = "cp314t-macosx_x86_64", version = "3.14", url = "https://www.python.org/ftp/python/3.14.0/python-3.14.0b1-macos11.pkg" },
{ identifier = "cp314t-macosx_arm64", version = "3.14", url = "https://www.python.org/ftp/python/3.14.0/python-3.14.0b1-macos11.pkg" },
{ identifier = "cp314t-macosx_universal2", version = "3.14", url = "https://www.python.org/ftp/python/3.14.0/python-3.14.0b1-macos11.pkg" },
{ 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 = "pp38-macosx_x86_64", version = "3.8", url = "https://downloads.python.org/pypy/pypy3.8-v7.3.11-macos_x86_64.tar.bz2" },
{ identifier = "pp38-macosx_arm64", version = "3.8", url = "https://downloads.python.org/pypy/pypy3.8-v7.3.11-macos_arm64.tar.bz2" }, { identifier = "pp38-macosx_arm64", version = "3.8", url = "https://downloads.python.org/pypy/pypy3.8-v7.3.11-macos_arm64.tar.bz2" },
{ identifier = "pp39-macosx_x86_64", version = "3.9", url = "https://downloads.python.org/pypy/pypy3.9-v7.3.16-macos_x86_64.tar.bz2" }, { identifier = "pp39-macosx_x86_64", version = "3.9", url = "https://downloads.python.org/pypy/pypy3.9-v7.3.16-macos_x86_64.tar.bz2" },
{ identifier = "pp39-macosx_arm64", version = "3.9", url = "https://downloads.python.org/pypy/pypy3.9-v7.3.16-macos_arm64.tar.bz2" }, { identifier = "pp39-macosx_arm64", version = "3.9", url = "https://downloads.python.org/pypy/pypy3.9-v7.3.16-macos_arm64.tar.bz2" },
{ identifier = "pp310-macosx_x86_64", version = "3.10", url = "https://downloads.python.org/pypy/pypy3.10-v7.3.19-macos_x86_64.tar.bz2" }, { identifier = "pp310-macosx_x86_64", version = "3.10", url = "https://downloads.python.org/pypy/pypy3.10-v7.3.17-macos_x86_64.tar.bz2" },
{ identifier = "pp310-macosx_arm64", version = "3.10", url = "https://downloads.python.org/pypy/pypy3.10-v7.3.19-macos_arm64.tar.bz2" }, { identifier = "pp310-macosx_arm64", version = "3.10", url = "https://downloads.python.org/pypy/pypy3.10-v7.3.17-macos_arm64.tar.bz2" },
{ identifier = "pp311-macosx_x86_64", version = "3.11", url = "https://downloads.python.org/pypy/pypy3.11-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.19-macos_arm64.tar.bz2" },
{ identifier = "gp242-macosx_x86_64", version = "3.11", url = "https://github.com/oracle/graalpython/releases/download/graal-24.2.1/graalpy-24.2.1-macos-amd64.tar.gz" },
{ identifier = "gp242-macosx_arm64", version = "3.11", url = "https://github.com/oracle/graalpython/releases/download/graal-24.2.1/graalpy-24.2.1-macos-aarch64.tar.gz" },
] ]
[windows] [windows]
python_configurations = [ python_configurations = [
{ identifier = "cp36-win32", version = "3.6.8", arch = "32" },
{ identifier = "cp36-win_amd64", version = "3.6.8", arch = "64" },
{ identifier = "cp37-win32", version = "3.7.9", arch = "32" },
{ identifier = "cp37-win_amd64", version = "3.7.9", arch = "64" },
{ identifier = "cp38-win32", version = "3.8.10", arch = "32" }, { identifier = "cp38-win32", version = "3.8.10", arch = "32" },
{ identifier = "cp38-win_amd64", version = "3.8.10", arch = "64" }, { identifier = "cp38-win_amd64", version = "3.8.10", arch = "64" },
{ identifier = "cp39-win32", version = "3.9.13", arch = "32" }, { identifier = "cp39-win32", version = "3.9.13", arch = "32" },
@@ -193,39 +152,25 @@ python_configurations = [
{ identifier = "cp310-win_amd64", version = "3.10.11", arch = "64" }, { identifier = "cp310-win_amd64", version = "3.10.11", arch = "64" },
{ identifier = "cp311-win32", version = "3.11.9", arch = "32" }, { identifier = "cp311-win32", version = "3.11.9", arch = "32" },
{ identifier = "cp311-win_amd64", version = "3.11.9", arch = "64" }, { identifier = "cp311-win_amd64", version = "3.11.9", arch = "64" },
{ identifier = "cp312-win32", version = "3.12.10", arch = "32" }, { identifier = "cp312-win32", version = "3.12.5", arch = "32" },
{ identifier = "cp312-win_amd64", version = "3.12.10", arch = "64" }, { identifier = "cp312-win_amd64", version = "3.12.5", arch = "64" },
{ identifier = "cp313-win32", version = "3.13.3", arch = "32" }, { identifier = "cp313-win32", version = "3.13.0-rc1", arch = "32" },
{ identifier = "cp313t-win32", version = "3.13.3", arch = "32" }, { identifier = "cp313t-win32", version = "3.13.0-rc1", arch = "32" },
{ identifier = "cp313-win_amd64", version = "3.13.3", arch = "64" }, { identifier = "cp313-win_amd64", version = "3.13.0-rc1", arch = "64" },
{ identifier = "cp313t-win_amd64", version = "3.13.3", arch = "64" }, { identifier = "cp313t-win_amd64", version = "3.13.0-rc1", arch = "64" },
{ identifier = "cp314-win32", version = "3.14.0-b1", arch = "32" },
{ identifier = "cp314t-win32", version = "3.14.0-b1", arch = "32" },
{ identifier = "cp314-win_amd64", version = "3.14.0-b1", arch = "64" },
{ identifier = "cp314t-win_amd64", version = "3.14.0-b1", arch = "64" },
{ identifier = "cp39-win_arm64", version = "3.9.10", arch = "ARM64" }, { identifier = "cp39-win_arm64", version = "3.9.10", arch = "ARM64" },
{ identifier = "cp310-win_arm64", version = "3.10.11", arch = "ARM64" }, { identifier = "cp310-win_arm64", version = "3.10.11", arch = "ARM64" },
{ identifier = "cp311-win_arm64", version = "3.11.9", arch = "ARM64" }, { identifier = "cp311-win_arm64", version = "3.11.9", arch = "ARM64" },
{ identifier = "cp312-win_arm64", version = "3.12.10", arch = "ARM64" }, { identifier = "cp312-win_arm64", version = "3.12.5", arch = "ARM64" },
{ identifier = "cp313-win_arm64", version = "3.13.3", arch = "ARM64" }, { identifier = "cp313-win_arm64", version = "3.13.0-rc1", arch = "ARM64" },
{ identifier = "cp313t-win_arm64", version = "3.13.3", arch = "ARM64" }, { identifier = "cp313t-win_arm64", version = "3.13.0-rc1", arch = "ARM64" },
{ identifier = "cp314-win_arm64", version = "3.14.0-b1", arch = "ARM64" }, { identifier = "pp37-win_amd64", version = "3.7", arch = "64", url = "https://downloads.python.org/pypy/pypy3.7-v7.3.9-win64.zip" },
{ identifier = "cp314t-win_arm64", version = "3.14.0-b1", arch = "ARM64" },
{ identifier = "pp38-win_amd64", version = "3.8", arch = "64", url = "https://downloads.python.org/pypy/pypy3.8-v7.3.11-win64.zip" }, { identifier = "pp38-win_amd64", version = "3.8", arch = "64", url = "https://downloads.python.org/pypy/pypy3.8-v7.3.11-win64.zip" },
{ identifier = "pp39-win_amd64", version = "3.9", arch = "64", url = "https://downloads.python.org/pypy/pypy3.9-v7.3.16-win64.zip" }, { identifier = "pp39-win_amd64", version = "3.9", arch = "64", url = "https://downloads.python.org/pypy/pypy3.9-v7.3.16-win64.zip" },
{ identifier = "pp310-win_amd64", version = "3.10", arch = "64", url = "https://downloads.python.org/pypy/pypy3.10-v7.3.19-win64.zip" }, { identifier = "pp310-win_amd64", version = "3.10", arch = "64", url = "https://downloads.python.org/pypy/pypy3.10-v7.3.17-win64.zip" },
{ identifier = "pp311-win_amd64", version = "3.11", arch = "64", url = "https://downloads.python.org/pypy/pypy3.11-v7.3.19-win64.zip" },
{ identifier = "gp242-win_amd64", version = "3.11", arch = "64", url = "https://github.com/oracle/graalpython/releases/download/graal-24.2.1/graalpy-24.2.1-windows-amd64.zip" },
] ]
[pyodide] [pyodide]
python_configurations = [ python_configurations = [
{ identifier = "cp312-pyodide_wasm32", version = "3.12", pyodide_version = "0.27.0", pyodide_build_version = "0.29.2", emscripten_version = "3.1.58", node_version = "v20" }, { identifier = "cp312-pyodide_wasm32", version = "3.12.1", pyodide_version = "0.26.1", emscripten_version = "3.1.58", node_version = "v20" },
]
[ios]
python_configurations = [
{ identifier = "cp313-ios_arm64_iphoneos", version = "3.13", url = "https://github.com/beeware/Python-Apple-support/releases/download/3.13-b6/Python-3.13-iOS-support.b6.tar.gz" },
{ identifier = "cp313-ios_x86_64_iphonesimulator", version = "3.13", url = "https://github.com/beeware/Python-Apple-support/releases/download/3.13-b6/Python-3.13-iOS-support.b6.tar.gz" },
{ identifier = "cp313-ios_arm64_iphonesimulator", version = "3.13", url = "https://github.com/beeware/Python-Apple-support/releases/download/3.13-b6/Python-3.13-iOS-support.b6.tar.gz" },
] ]
+8 -259
View File
@@ -10,17 +10,7 @@
], ],
"default": "none", "default": "none",
"description": "How to inherit the parent's value." "description": "How to inherit the parent's value."
}, }
"enable": {
"enum": [
"cpython-freethreading",
"cpython-prerelease",
"pypy",
"pypy-eol",
"cpython-experimental-riscv64"
]
},
"description": "A Python version or flavor to enable."
}, },
"additionalProperties": false, "additionalProperties": false,
"description": "cibuildwheel's settings.", "description": "cibuildwheel's settings.",
@@ -235,62 +225,9 @@
"dependency-versions": { "dependency-versions": {
"default": "pinned", "default": "pinned",
"description": "Specify how cibuildwheel controls the versions of the tools it uses", "description": "Specify how cibuildwheel controls the versions of the tools it uses",
"oneOf": [ "type": "string",
{
"enum": [
"pinned",
"latest"
]
},
{
"type": "string",
"description": "Path to a file containing dependency versions, or inline package specifications, starting with \"packages:\"",
"not": {
"enum": [
"pinned",
"latest"
]
}
},
{
"type": "object",
"additionalProperties": false,
"properties": {
"file": {
"type": "string"
}
}
},
{
"type": "object",
"additionalProperties": false,
"properties": {
"packages": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
],
"title": "CIBW_DEPENDENCY_VERSIONS" "title": "CIBW_DEPENDENCY_VERSIONS"
}, },
"enable": {
"description": "Enable or disable certain builds.",
"oneOf": [
{
"$ref": "#/$defs/enable"
},
{
"type": "array",
"items": {
"$ref": "#/$defs/enable"
}
}
],
"title": "CIBW_ENABLE"
},
"environment": { "environment": {
"description": "Set environment variables needed during the build.", "description": "Set environment variables needed during the build.",
"oneOf": [ "oneOf": [
@@ -324,16 +261,17 @@
], ],
"title": "CIBW_ENVIRONMENT_PASS" "title": "CIBW_ENVIRONMENT_PASS"
}, },
"free-threaded-support": {
"type": "boolean",
"default": false,
"description": "The project supports free-threaded builds of Python (PEP703)",
"title": "CIBW_FREE_THREADED_SUPPORT"
},
"manylinux-aarch64-image": { "manylinux-aarch64-image": {
"type": "string", "type": "string",
"description": "Specify alternative manylinux / musllinux container images", "description": "Specify alternative manylinux / musllinux container images",
"title": "CIBW_MANYLINUX_AARCH64_IMAGE" "title": "CIBW_MANYLINUX_AARCH64_IMAGE"
}, },
"manylinux-armv7l-image": {
"type": "string",
"description": "Specify alternative manylinux / musllinux container images",
"title": "CIBW_MANYLINUX_ARMV7L_IMAGE"
},
"manylinux-i686-image": { "manylinux-i686-image": {
"type": "string", "type": "string",
"description": "Specify alternative manylinux / musllinux container images", "description": "Specify alternative manylinux / musllinux container images",
@@ -359,11 +297,6 @@
"description": "Specify alternative manylinux / musllinux container images", "description": "Specify alternative manylinux / musllinux container images",
"title": "CIBW_MANYLINUX_PYPY_X86_64_IMAGE" "title": "CIBW_MANYLINUX_PYPY_X86_64_IMAGE"
}, },
"manylinux-riscv64-image": {
"type": "string",
"description": "Specify alternative manylinux / musllinux container images",
"title": "CIBW_MANYLINUX_RISCV64_IMAGE"
},
"manylinux-s390x-image": { "manylinux-s390x-image": {
"type": "string", "type": "string",
"description": "Specify alternative manylinux / musllinux container images", "description": "Specify alternative manylinux / musllinux container images",
@@ -379,11 +312,6 @@
"description": "Specify alternative manylinux / musllinux container images", "description": "Specify alternative manylinux / musllinux container images",
"title": "CIBW_MUSLLINUX_AARCH64_IMAGE" "title": "CIBW_MUSLLINUX_AARCH64_IMAGE"
}, },
"musllinux-armv7l-image": {
"type": "string",
"description": "Specify alternative manylinux / musllinux container images",
"title": "CIBW_MUSLLINUX_ARMV7L_IMAGE"
},
"musllinux-i686-image": { "musllinux-i686-image": {
"type": "string", "type": "string",
"description": "Specify alternative manylinux / musllinux container images", "description": "Specify alternative manylinux / musllinux container images",
@@ -394,11 +322,6 @@
"description": "Specify alternative manylinux / musllinux container images", "description": "Specify alternative manylinux / musllinux container images",
"title": "CIBW_MUSLLINUX_PPC64LE_IMAGE" "title": "CIBW_MUSLLINUX_PPC64LE_IMAGE"
}, },
"musllinux-riscv64-image": {
"type": "string",
"description": "Specify alternative manylinux / musllinux container images",
"title": "CIBW_MUSLLINUX_RISCV64_IMAGE"
},
"musllinux-s390x-image": { "musllinux-s390x-image": {
"type": "string", "type": "string",
"description": "Specify alternative manylinux / musllinux container images", "description": "Specify alternative manylinux / musllinux container images",
@@ -409,21 +332,6 @@
"description": "Specify alternative manylinux / musllinux container images", "description": "Specify alternative manylinux / musllinux container images",
"title": "CIBW_MUSLLINUX_X86_64_IMAGE" "title": "CIBW_MUSLLINUX_X86_64_IMAGE"
}, },
"xbuild-tools": {
"description": "Binaries on the path that should be included in an isolated cross-build environment",
"oneOf": [
{
"type": "string"
},
{
"type": "array",
"items": {
"type": "string"
}
}
],
"title": "CIBW_XBUILD_TOOLS"
},
"repair-wheel-command": { "repair-wheel-command": {
"description": "Execute a shell command to repair each built wheel.", "description": "Execute a shell command to repair each built wheel.",
"oneOf": [ "oneOf": [
@@ -484,36 +392,6 @@
], ],
"title": "CIBW_TEST_EXTRAS" "title": "CIBW_TEST_EXTRAS"
}, },
"test-sources": {
"description": "Test files that are required by the test environment",
"oneOf": [
{
"type": "string"
},
{
"type": "array",
"items": {
"type": "string"
}
}
],
"title": "CIBW_TEST_SOURCES"
},
"test-groups": {
"description": "Install extra groups when testing",
"oneOf": [
{
"type": "string"
},
{
"type": "array",
"items": {
"type": "string"
}
}
],
"title": "CIBW_TEST_GROUPS"
},
"test-requires": { "test-requires": {
"description": "Install Python dependencies before running the tests", "description": "Install Python dependencies before running the tests",
"oneOf": [ "oneOf": [
@@ -578,9 +456,6 @@
"before-build": { "before-build": {
"$ref": "#/$defs/inherit" "$ref": "#/$defs/inherit"
}, },
"xbuild-tools": {
"$ref": "#/$defs/inherit"
},
"before-test": { "before-test": {
"$ref": "#/$defs/inherit" "$ref": "#/$defs/inherit"
}, },
@@ -605,9 +480,6 @@
"test-extras": { "test-extras": {
"$ref": "#/$defs/inherit" "$ref": "#/$defs/inherit"
}, },
"test-sources": {
"$ref": "#/$defs/inherit"
},
"test-requires": { "test-requires": {
"$ref": "#/$defs/inherit" "$ref": "#/$defs/inherit"
} }
@@ -646,9 +518,6 @@
"manylinux-aarch64-image": { "manylinux-aarch64-image": {
"$ref": "#/properties/manylinux-aarch64-image" "$ref": "#/properties/manylinux-aarch64-image"
}, },
"manylinux-armv7l-image": {
"$ref": "#/properties/manylinux-armv7l-image"
},
"manylinux-i686-image": { "manylinux-i686-image": {
"$ref": "#/properties/manylinux-i686-image" "$ref": "#/properties/manylinux-i686-image"
}, },
@@ -664,9 +533,6 @@
"manylinux-pypy_x86_64-image": { "manylinux-pypy_x86_64-image": {
"$ref": "#/properties/manylinux-pypy_x86_64-image" "$ref": "#/properties/manylinux-pypy_x86_64-image"
}, },
"manylinux-riscv64-image": {
"$ref": "#/properties/manylinux-riscv64-image"
},
"manylinux-s390x-image": { "manylinux-s390x-image": {
"$ref": "#/properties/manylinux-s390x-image" "$ref": "#/properties/manylinux-s390x-image"
}, },
@@ -676,27 +542,18 @@
"musllinux-aarch64-image": { "musllinux-aarch64-image": {
"$ref": "#/properties/musllinux-aarch64-image" "$ref": "#/properties/musllinux-aarch64-image"
}, },
"musllinux-armv7l-image": {
"$ref": "#/properties/musllinux-armv7l-image"
},
"musllinux-i686-image": { "musllinux-i686-image": {
"$ref": "#/properties/musllinux-i686-image" "$ref": "#/properties/musllinux-i686-image"
}, },
"musllinux-ppc64le-image": { "musllinux-ppc64le-image": {
"$ref": "#/properties/musllinux-ppc64le-image" "$ref": "#/properties/musllinux-ppc64le-image"
}, },
"musllinux-riscv64-image": {
"$ref": "#/properties/musllinux-riscv64-image"
},
"musllinux-s390x-image": { "musllinux-s390x-image": {
"$ref": "#/properties/musllinux-s390x-image" "$ref": "#/properties/musllinux-s390x-image"
}, },
"musllinux-x86_64-image": { "musllinux-x86_64-image": {
"$ref": "#/properties/musllinux-x86_64-image" "$ref": "#/properties/musllinux-x86_64-image"
}, },
"xbuild-tools": {
"$ref": "#/properties/xbuild-tools"
},
"repair-wheel-command": { "repair-wheel-command": {
"$ref": "#/properties/repair-wheel-command" "$ref": "#/properties/repair-wheel-command"
}, },
@@ -706,12 +563,6 @@
"test-extras": { "test-extras": {
"$ref": "#/properties/test-extras" "$ref": "#/properties/test-extras"
}, },
"test-sources": {
"$ref": "#/properties/test-sources"
},
"test-groups": {
"$ref": "#/properties/test-groups"
},
"test-requires": { "test-requires": {
"$ref": "#/properties/test-requires" "$ref": "#/properties/test-requires"
} }
@@ -755,9 +606,6 @@
"manylinux-aarch64-image": { "manylinux-aarch64-image": {
"$ref": "#/properties/manylinux-aarch64-image" "$ref": "#/properties/manylinux-aarch64-image"
}, },
"manylinux-armv7l-image": {
"$ref": "#/properties/manylinux-armv7l-image"
},
"manylinux-i686-image": { "manylinux-i686-image": {
"$ref": "#/properties/manylinux-i686-image" "$ref": "#/properties/manylinux-i686-image"
}, },
@@ -773,9 +621,6 @@
"manylinux-pypy_x86_64-image": { "manylinux-pypy_x86_64-image": {
"$ref": "#/properties/manylinux-pypy_x86_64-image" "$ref": "#/properties/manylinux-pypy_x86_64-image"
}, },
"manylinux-riscv64-image": {
"$ref": "#/properties/manylinux-riscv64-image"
},
"manylinux-s390x-image": { "manylinux-s390x-image": {
"$ref": "#/properties/manylinux-s390x-image" "$ref": "#/properties/manylinux-s390x-image"
}, },
@@ -785,27 +630,18 @@
"musllinux-aarch64-image": { "musllinux-aarch64-image": {
"$ref": "#/properties/musllinux-aarch64-image" "$ref": "#/properties/musllinux-aarch64-image"
}, },
"musllinux-armv7l-image": {
"$ref": "#/properties/musllinux-armv7l-image"
},
"musllinux-i686-image": { "musllinux-i686-image": {
"$ref": "#/properties/musllinux-i686-image" "$ref": "#/properties/musllinux-i686-image"
}, },
"musllinux-ppc64le-image": { "musllinux-ppc64le-image": {
"$ref": "#/properties/musllinux-ppc64le-image" "$ref": "#/properties/musllinux-ppc64le-image"
}, },
"musllinux-riscv64-image": {
"$ref": "#/properties/musllinux-riscv64-image"
},
"musllinux-s390x-image": { "musllinux-s390x-image": {
"$ref": "#/properties/musllinux-s390x-image" "$ref": "#/properties/musllinux-s390x-image"
}, },
"musllinux-x86_64-image": { "musllinux-x86_64-image": {
"$ref": "#/properties/musllinux-x86_64-image" "$ref": "#/properties/musllinux-x86_64-image"
}, },
"xbuild-tools": {
"$ref": "#/properties/xbuild-tools"
},
"repair-wheel-command": { "repair-wheel-command": {
"description": "Execute a shell command to repair each built wheel.", "description": "Execute a shell command to repair each built wheel.",
"oneOf": [ "oneOf": [
@@ -828,12 +664,6 @@
"test-extras": { "test-extras": {
"$ref": "#/properties/test-extras" "$ref": "#/properties/test-extras"
}, },
"test-sources": {
"$ref": "#/properties/test-sources"
},
"test-groups": {
"$ref": "#/properties/test-groups"
},
"test-requires": { "test-requires": {
"$ref": "#/properties/test-requires" "$ref": "#/properties/test-requires"
} }
@@ -870,9 +700,6 @@
"environment": { "environment": {
"$ref": "#/properties/environment" "$ref": "#/properties/environment"
}, },
"xbuild-tools": {
"$ref": "#/properties/xbuild-tools"
},
"repair-wheel-command": { "repair-wheel-command": {
"$ref": "#/properties/repair-wheel-command" "$ref": "#/properties/repair-wheel-command"
}, },
@@ -882,12 +709,6 @@
"test-extras": { "test-extras": {
"$ref": "#/properties/test-extras" "$ref": "#/properties/test-extras"
}, },
"test-sources": {
"$ref": "#/properties/test-sources"
},
"test-groups": {
"$ref": "#/properties/test-groups"
},
"test-requires": { "test-requires": {
"$ref": "#/properties/test-requires" "$ref": "#/properties/test-requires"
} }
@@ -924,9 +745,6 @@
"environment": { "environment": {
"$ref": "#/properties/environment" "$ref": "#/properties/environment"
}, },
"xbuild-tools": {
"$ref": "#/properties/xbuild-tools"
},
"repair-wheel-command": { "repair-wheel-command": {
"description": "Execute a shell command to repair each built wheel.", "description": "Execute a shell command to repair each built wheel.",
"oneOf": [ "oneOf": [
@@ -949,12 +767,6 @@
"test-extras": { "test-extras": {
"$ref": "#/properties/test-extras" "$ref": "#/properties/test-extras"
}, },
"test-sources": {
"$ref": "#/properties/test-sources"
},
"test-groups": {
"$ref": "#/properties/test-groups"
},
"test-requires": { "test-requires": {
"$ref": "#/properties/test-requires" "$ref": "#/properties/test-requires"
} }
@@ -991,9 +803,6 @@
"environment": { "environment": {
"$ref": "#/properties/environment" "$ref": "#/properties/environment"
}, },
"xbuild-tools": {
"$ref": "#/properties/xbuild-tools"
},
"repair-wheel-command": { "repair-wheel-command": {
"$ref": "#/properties/repair-wheel-command" "$ref": "#/properties/repair-wheel-command"
}, },
@@ -1003,66 +812,6 @@
"test-extras": { "test-extras": {
"$ref": "#/properties/test-extras" "$ref": "#/properties/test-extras"
}, },
"test-sources": {
"$ref": "#/properties/test-sources"
},
"test-groups": {
"$ref": "#/properties/test-groups"
},
"test-requires": {
"$ref": "#/properties/test-requires"
}
}
},
"ios": {
"type": "object",
"additionalProperties": false,
"properties": {
"archs": {
"$ref": "#/properties/archs"
},
"before-all": {
"$ref": "#/properties/before-all"
},
"before-build": {
"$ref": "#/properties/before-build"
},
"before-test": {
"$ref": "#/properties/before-test"
},
"build-frontend": {
"$ref": "#/properties/build-frontend"
},
"build-verbosity": {
"$ref": "#/properties/build-verbosity"
},
"config-settings": {
"$ref": "#/properties/config-settings"
},
"dependency-versions": {
"$ref": "#/properties/dependency-versions"
},
"environment": {
"$ref": "#/properties/environment"
},
"xbuild-tools": {
"$ref": "#/properties/xbuild-tools"
},
"repair-wheel-command": {
"$ref": "#/properties/repair-wheel-command"
},
"test-command": {
"$ref": "#/properties/test-command"
},
"test-extras": {
"$ref": "#/properties/test-extras"
},
"test-sources": {
"$ref": "#/properties/test-sources"
},
"test-groups": {
"$ref": "#/properties/test-groups"
},
"test-requires": { "test-requires": {
"$ref": "#/properties/test-requires" "$ref": "#/properties/test-requires"
} }
@@ -2,116 +2,122 @@
# nox -s update_constraints # nox -s update_constraints
annotated-types==0.7.0 annotated-types==0.7.0
# via pydantic # via pydantic
anyio==4.9.0 anyio==4.4.0
# via httpx # via httpx
auditwheel-emscripten==0.0.16 auditwheel-emscripten==0.0.16
# via pyodide-build # via pyodide-build
build==1.2.2.post1 build==1.2.1
# via # via
# -r .nox/update_constraints/tmp/constraints-pyodide.in # -r .nox/update_constraints/tmp/constraints-pyodide.in
# pyodide-build # pyodide-build
certifi==2025.4.26 certifi==2024.8.30
# via # via
# httpcore # httpcore
# httpx # httpx
# requests # requests
charset-normalizer==3.4.2 charset-normalizer==3.3.2
# via requests # via requests
click==8.1.8 click==8.1.7
# via # via typer
# -r .nox/update_constraints/tmp/constraints-pyodide.in cloudpickle==3.0.0
# typer # via loky
cmake==4.0.2 cmake==3.30.2
# via pyodide-build # via pyodide-build
distlib==0.3.9 distlib==0.3.8
# via virtualenv # via virtualenv
filelock==3.18.0 filelock==3.15.4
# via virtualenv # via virtualenv
h11==0.16.0 h11==0.14.0
# via httpcore # via httpcore
httpcore==1.0.9 httpcore==1.0.5
# via httpx # via httpx
httpx==0.28.1 httpx==0.27.2
# via unearth # via unearth
idna==3.10 idna==3.8
# via # via
# anyio # anyio
# httpx # httpx
# requests # requests
leb128==1.0.8 leb128==1.0.8
# via auditwheel-emscripten # via auditwheel-emscripten
loky==3.4.1
# via pyodide-build
markdown-it-py==3.0.0 markdown-it-py==3.0.0
# via rich # via rich
mdurl==0.1.2 mdurl==0.1.2
# via markdown-it-py # via markdown-it-py
packaging==25.0 packaging==24.1
# via # via
# auditwheel-emscripten # auditwheel-emscripten
# build # build
# pyodide-build # pyodide-build
# unearth # unearth
pip==25.1.1 pip==24.2
# via -r .nox/update_constraints/tmp/constraints-pyodide.in # via -r .nox/update_constraints/tmp/constraints-pyodide.in
platformdirs==4.3.8 platformdirs==4.2.2
# via virtualenv # via virtualenv
pydantic==2.11.4 pydantic==2.8.2
# via # via
# pyodide-build # pyodide-build
# pyodide-lock # pyodide-lock
pydantic-core==2.33.2 pydantic-core==2.20.1
# via pydantic # via pydantic
pygments==2.19.1 pygments==2.18.0
# via rich # via rich
pyodide-build==0.29.2 pyodide-build==0.26.1
# via -r .nox/update_constraints/tmp/constraints-pyodide.in # via -r .nox/update_constraints/tmp/constraints-pyodide.in
pyodide-cli==0.2.4 pyodide-cli==0.2.4
# via # via
# auditwheel-emscripten # auditwheel-emscripten
# pyodide-build # pyodide-build
pyodide-lock==0.1.0a7 pyodide-lock==0.1.0a6
# via pyodide-build # via pyodide-build
pyproject-hooks==1.2.0 pyproject-hooks==1.1.0
# via build # via build
pyyaml==6.0.2
# via pyodide-build
requests==2.32.3 requests==2.32.3
# via pyodide-build # via pyodide-build
resolvelib==1.1.0 resolvelib==1.0.1
# via pyodide-build # via pyodide-build
rich==14.0.0 rich==13.8.0
# via # via
# pyodide-build # pyodide-build
# pyodide-cli # pyodide-cli
# typer # typer
ruamel-yaml==0.18.10 ruamel-yaml==0.18.6
# via pyodide-build # via pyodide-build
ruamel-yaml-clib==0.2.12 ruamel-yaml-clib==0.2.8
# via ruamel-yaml # via ruamel-yaml
shellingham==1.5.4 shellingham==1.5.4
# via typer # via typer
sniffio==1.3.1 sniffio==1.3.1
# via anyio # via
typer==0.15.4 # anyio
# httpx
typer==0.12.5
# via # via
# auditwheel-emscripten # auditwheel-emscripten
# pyodide-build # pyodide-build
# pyodide-cli # pyodide-cli
typing-extensions==4.13.2 types-requests==2.32.0.20240712
# via pyodide-build
typing-extensions==4.12.2
# via # via
# anyio
# pydantic # pydantic
# pydantic-core # pydantic-core
# typer # typer
# typing-inspection unearth==0.17.2
typing-inspection==0.4.0
# via pydantic
unearth==0.17.5
# via pyodide-build # via pyodide-build
urllib3==2.4.0 urllib3==2.2.2
# via requests # via
virtualenv==20.31.2 # requests
# types-requests
virtualenv==20.26.3
# via # via
# build # build
# pyodide-build # pyodide-build
wheel==0.45.1 wheel==0.44.0
# via # via
# auditwheel-emscripten # auditwheel-emscripten
# pyodide-build # pyodide-build
@@ -2,33 +2,33 @@
# nox -s update_constraints # nox -s update_constraints
altgraph==0.17.4 altgraph==0.17.4
# via macholib # via macholib
build==1.2.2.post1 build==1.2.1
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
delocate==0.13.0 delocate==0.12.0
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
distlib==0.3.9 distlib==0.3.8
# via virtualenv # via virtualenv
filelock==3.18.0 filelock==3.15.4
# via virtualenv # via virtualenv
importlib-metadata==8.7.0 importlib-metadata==8.4.0
# via build # via build
macholib==1.16.3 macholib==1.16.3
# via delocate # via delocate
packaging==25.0 packaging==24.1
# via # via
# build # build
# delocate # delocate
pip==25.1.1 pip==24.2
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
platformdirs==4.3.8 platformdirs==4.2.2
# via virtualenv # via virtualenv
pyproject-hooks==1.2.0 pyproject-hooks==1.1.0
# via build # via build
tomli==2.2.1 tomli==2.0.1
# via build # via build
typing-extensions==4.13.2 typing-extensions==4.12.2
# via delocate # via delocate
virtualenv==20.31.2 virtualenv==20.26.3
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
zipp==3.21.0 zipp==3.20.1
# via importlib-metadata # via importlib-metadata
@@ -2,27 +2,27 @@
# nox -s update_constraints # nox -s update_constraints
altgraph==0.17.4 altgraph==0.17.4
# via macholib # via macholib
build==1.2.2.post1 build==1.2.1
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
delocate==0.13.0 delocate==0.12.0
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
distlib==0.3.9 distlib==0.3.8
# via virtualenv # via virtualenv
filelock==3.18.0 filelock==3.15.4
# via virtualenv # via virtualenv
macholib==1.16.3 macholib==1.16.3
# via delocate # via delocate
packaging==25.0 packaging==24.1
# via # via
# build # build
# delocate # delocate
pip==25.1.1 pip==24.2
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
platformdirs==4.3.8 platformdirs==4.2.2
# via virtualenv # via virtualenv
pyproject-hooks==1.2.0 pyproject-hooks==1.1.0
# via build # via build
typing-extensions==4.13.2 typing-extensions==4.12.2
# via delocate # via delocate
virtualenv==20.31.2 virtualenv==20.26.3
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
@@ -2,27 +2,27 @@
# nox -s update_constraints # nox -s update_constraints
altgraph==0.17.4 altgraph==0.17.4
# via macholib # via macholib
build==1.2.2.post1 build==1.2.1
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
delocate==0.13.0 delocate==0.12.0
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
distlib==0.3.9 distlib==0.3.8
# via virtualenv # via virtualenv
filelock==3.18.0 filelock==3.15.4
# via virtualenv # via virtualenv
macholib==1.16.3 macholib==1.16.3
# via delocate # via delocate
packaging==25.0 packaging==24.1
# via # via
# build # build
# delocate # delocate
pip==25.1.1 pip==24.2
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
platformdirs==4.3.8 platformdirs==4.2.2
# via virtualenv # via virtualenv
pyproject-hooks==1.2.0 pyproject-hooks==1.1.0
# via build # via build
typing-extensions==4.13.2 typing-extensions==4.12.2
# via delocate # via delocate
virtualenv==20.31.2 virtualenv==20.26.3
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
@@ -2,27 +2,27 @@
# nox -s update_constraints # nox -s update_constraints
altgraph==0.17.4 altgraph==0.17.4
# via macholib # via macholib
build==1.2.2.post1 build==1.2.1
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
delocate==0.13.0 delocate==0.12.0
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
distlib==0.3.9 distlib==0.3.8
# via virtualenv # via virtualenv
filelock==3.18.0 filelock==3.15.4
# via virtualenv # via virtualenv
macholib==1.16.3 macholib==1.16.3
# via delocate # via delocate
packaging==25.0 packaging==24.1
# via # via
# build # build
# delocate # delocate
pip==25.1.1 pip==24.2
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
platformdirs==4.3.8 platformdirs==4.2.2
# via virtualenv # via virtualenv
pyproject-hooks==1.2.0 pyproject-hooks==1.1.0
# via build # via build
typing-extensions==4.13.2 typing-extensions==4.12.2
# via delocate # via delocate
virtualenv==20.31.2 virtualenv==20.26.3
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
@@ -0,0 +1,52 @@
#
# This file is autogenerated by pip-compile with python 3.6
# To update, run:
#
# nox -s update_constraints-3.6
#
build==0.9.0
# via -r cibuildwheel/resources/constraints.in
delocate==0.10.2
# via -r cibuildwheel/resources/constraints.in
distlib==0.3.8
# via virtualenv
filelock==3.4.1
# via virtualenv
importlib-metadata==4.8.3
# via
# build
# pep517
# virtualenv
importlib-resources==5.4.0
# via virtualenv
packaging==21.3
# via build
pep517==0.13.1
# via build
platformdirs==2.4.0
# via virtualenv
pyparsing==3.1.1
# via packaging
tomli==1.2.3
# via
# build
# pep517
typing-extensions==4.1.1
# via
# delocate
# importlib-metadata
virtualenv==20.17.1
# via -r cibuildwheel/resources/constraints.in
wheel==0.37.1
# via delocate
zipp==3.6.0
# via
# importlib-metadata
# importlib-resources
# pep517
# The following packages are considered to be unsafe in a requirements file:
pip==21.3.1
# via -r cibuildwheel/resources/constraints.in
setuptools==59.6.0
# via -r cibuildwheel/resources/constraints.in
@@ -2,27 +2,38 @@
# nox -s update_constraints # nox -s update_constraints
altgraph==0.17.4 altgraph==0.17.4
# via macholib # via macholib
build==1.2.2.post1 build==1.1.1
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
delocate==0.13.0 delocate==0.12.0
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
distlib==0.3.9 distlib==0.3.8
# via virtualenv # via virtualenv
filelock==3.18.0 filelock==3.12.2
# via virtualenv # via virtualenv
importlib-metadata==6.7.0
# via
# build
# virtualenv
macholib==1.16.3 macholib==1.16.3
# via delocate # via delocate
packaging==25.0 packaging==24.0
# via # via
# build # build
# delocate # delocate
pip==25.1.1 pip==24.0
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
platformdirs==4.3.8 platformdirs==4.0.0
# via virtualenv # via virtualenv
pyproject-hooks==1.2.0 pyproject-hooks==1.1.0
# via build # via build
typing-extensions==4.13.2 tomli==2.0.1
# via delocate # via build
virtualenv==20.31.2 typing-extensions==4.7.1
# via
# delocate
# importlib-metadata
# platformdirs
virtualenv==20.26.3
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
zipp==3.15.0
# via importlib-metadata
+12 -12
View File
@@ -2,33 +2,33 @@
# nox -s update_constraints # nox -s update_constraints
altgraph==0.17.4 altgraph==0.17.4
# via macholib # via macholib
build==1.2.2.post1 build==1.2.1
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
delocate==0.12.0 delocate==0.12.0
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
distlib==0.3.9 distlib==0.3.8
# via virtualenv # via virtualenv
filelock==3.16.1 filelock==3.15.4
# via virtualenv # via virtualenv
importlib-metadata==8.5.0 importlib-metadata==8.4.0
# via build # via build
macholib==1.16.3 macholib==1.16.3
# via delocate # via delocate
packaging==25.0 packaging==24.1
# via # via
# build # build
# delocate # delocate
pip==25.0.1 pip==24.2
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
platformdirs==4.3.6 platformdirs==4.2.2
# via virtualenv # via virtualenv
pyproject-hooks==1.2.0 pyproject-hooks==1.1.0
# via build # via build
tomli==2.2.1 tomli==2.0.1
# via build # via build
typing-extensions==4.13.2 typing-extensions==4.12.2
# via delocate # via delocate
virtualenv==20.31.2 virtualenv==20.26.3
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
zipp==3.20.2 zipp==3.20.1
# via importlib-metadata # via importlib-metadata
+13 -13
View File
@@ -2,33 +2,33 @@
# nox -s update_constraints # nox -s update_constraints
altgraph==0.17.4 altgraph==0.17.4
# via macholib # via macholib
build==1.2.2.post1 build==1.2.1
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
delocate==0.13.0 delocate==0.12.0
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
distlib==0.3.9 distlib==0.3.8
# via virtualenv # via virtualenv
filelock==3.18.0 filelock==3.15.4
# via virtualenv # via virtualenv
importlib-metadata==8.7.0 importlib-metadata==8.4.0
# via build # via build
macholib==1.16.3 macholib==1.16.3
# via delocate # via delocate
packaging==25.0 packaging==24.1
# via # via
# build # build
# delocate # delocate
pip==25.1.1 pip==24.2
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
platformdirs==4.3.8 platformdirs==4.2.2
# via virtualenv # via virtualenv
pyproject-hooks==1.2.0 pyproject-hooks==1.1.0
# via build # via build
tomli==2.2.1 tomli==2.0.1
# via build # via build
typing-extensions==4.13.2 typing-extensions==4.12.2
# via delocate # via delocate
virtualenv==20.31.2 virtualenv==20.26.3
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
zipp==3.21.0 zipp==3.20.1
# via importlib-metadata # via importlib-metadata
+10 -10
View File
@@ -2,27 +2,27 @@
# nox -s update_constraints # nox -s update_constraints
altgraph==0.17.4 altgraph==0.17.4
# via macholib # via macholib
build==1.2.2.post1 build==1.2.1
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
delocate==0.13.0 delocate==0.12.0
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
distlib==0.3.9 distlib==0.3.8
# via virtualenv # via virtualenv
filelock==3.18.0 filelock==3.15.4
# via virtualenv # via virtualenv
macholib==1.16.3 macholib==1.16.3
# via delocate # via delocate
packaging==25.0 packaging==24.1
# via # via
# build # build
# delocate # delocate
pip==25.1.1 pip==24.2
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
platformdirs==4.3.8 platformdirs==4.2.2
# via virtualenv # via virtualenv
pyproject-hooks==1.2.0 pyproject-hooks==1.1.0
# via build # via build
typing-extensions==4.13.2 typing-extensions==4.12.2
# via delocate # via delocate
virtualenv==20.31.2 virtualenv==20.26.3
# via -r cibuildwheel/resources/constraints.in # via -r cibuildwheel/resources/constraints.in
+7 -17
View File
@@ -2,7 +2,7 @@
build = "*" build = "*"
skip = "" skip = ""
test-skip = "" test-skip = ""
enable = [] free-threaded-support = false
archs = ["auto"] archs = ["auto"]
build-frontend = "default" build-frontend = "default"
@@ -14,37 +14,29 @@ build-verbosity = 0
before-all = "" before-all = ""
before-build = "" before-build = ""
# TOML doesn't support explicit NULLs; use ["\u0000"] as a sentinel value.
xbuild-tools = ["\u0000"]
repair-wheel-command = "" repair-wheel-command = ""
test-command = "" test-command = ""
before-test = "" before-test = ""
test-sources = []
test-requires = [] test-requires = []
test-extras = [] test-extras = []
test-groups = []
container-engine = "docker" container-engine = "docker"
manylinux-x86_64-image = "manylinux_2_28" manylinux-x86_64-image = "manylinux2014"
manylinux-i686-image = "manylinux2014" manylinux-i686-image = "manylinux2014"
manylinux-aarch64-image = "manylinux_2_28" manylinux-aarch64-image = "manylinux2014"
manylinux-ppc64le-image = "manylinux_2_28" manylinux-ppc64le-image = "manylinux2014"
manylinux-s390x-image = "manylinux_2_28" manylinux-s390x-image = "manylinux2014"
manylinux-armv7l-image = "manylinux_2_31" manylinux-pypy_x86_64-image = "manylinux2014"
manylinux-riscv64-image = "ghcr.io/pypa/cibuildwheel/no_default_image:please_use_override"
manylinux-pypy_x86_64-image = "manylinux_2_28"
manylinux-pypy_i686-image = "manylinux2014" manylinux-pypy_i686-image = "manylinux2014"
manylinux-pypy_aarch64-image = "manylinux_2_28" manylinux-pypy_aarch64-image = "manylinux2014"
musllinux-x86_64-image = "musllinux_1_2" musllinux-x86_64-image = "musllinux_1_2"
musllinux-i686-image = "musllinux_1_2" musllinux-i686-image = "musllinux_1_2"
musllinux-aarch64-image = "musllinux_1_2" musllinux-aarch64-image = "musllinux_1_2"
musllinux-ppc64le-image = "musllinux_1_2" musllinux-ppc64le-image = "musllinux_1_2"
musllinux-s390x-image = "musllinux_1_2" musllinux-s390x-image = "musllinux_1_2"
musllinux-armv7l-image = "musllinux_1_2"
musllinux-riscv64-image = "ghcr.io/pypa/cibuildwheel/no_default_image:please_use_override"
[tool.cibuildwheel.linux] [tool.cibuildwheel.linux]
@@ -55,6 +47,4 @@ repair-wheel-command = "delocate-wheel --require-archs {delocate_archs} -w {dest
[tool.cibuildwheel.windows] [tool.cibuildwheel.windows]
[tool.cibuildwheel.ios]
[tool.cibuildwheel.pyodide] [tool.cibuildwheel.pyodide]
@@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
<dict>
<key>attributeSetting</key>
<integer>1</integer>
<key>choiceAttribute</key>
<string>selected</string>
<key>choiceIdentifier</key>
<string>org.python.Python.PythonTFramework-3.14</string>
</dict>
</array>
</plist>
+1 -1
View File
@@ -26,7 +26,7 @@ STAT_0o775 = (
) )
def main() -> None: def main():
openssl_dir, openssl_cafile = os.path.split(ssl.get_default_verify_paths().openssl_cafile) openssl_dir, openssl_cafile = os.path.split(ssl.get_default_verify_paths().openssl_cafile)
print(" -- pip install --upgrade certifi") print(" -- pip install --upgrade certifi")
subprocess.check_call( subprocess.check_call(
+1 -2
View File
@@ -1,3 +1,2 @@
url = "https://nodejs.org/dist/" url = "https://nodejs.org/dist/"
v22 = "v22.15.1" v20 = "v20.17.0"
v20 = "v20.19.2"
+44 -37
View File
@@ -1,47 +1,54 @@
[x86_64] [x86_64]
manylinux2014 = quay.io/pypa/manylinux2014_x86_64:2025.05.16-1 manylinux1 = quay.io/pypa/manylinux1_x86_64:2024-04-29-76807b8
manylinux_2_28 = quay.io/pypa/manylinux_2_28_x86_64:2025.05.16-1 manylinux2010 = quay.io/pypa/manylinux2010_x86_64:2022-08-05-4535177
manylinux_2_34 = quay.io/pypa/manylinux_2_34_x86_64:2025.05.16-1 manylinux2014 = quay.io/pypa/manylinux2014_x86_64:2024.09.01-2
musllinux_1_2 = quay.io/pypa/musllinux_1_2_x86_64:2025.05.16-1 manylinux_2_24 = quay.io/pypa/manylinux_2_24_x86_64:2022-12-26-0d38463
manylinux_2_28 = quay.io/pypa/manylinux_2_28_x86_64:2024.09.01-2
musllinux_1_1 = quay.io/pypa/musllinux_1_1_x86_64:2024.09.01-2
musllinux_1_2 = quay.io/pypa/musllinux_1_2_x86_64:2024.09.01-2
[i686] [i686]
manylinux2014 = quay.io/pypa/manylinux2014_i686:2025.05.16-1 manylinux1 = quay.io/pypa/manylinux1_i686:2024-04-29-76807b8
musllinux_1_2 = quay.io/pypa/musllinux_1_2_i686:2025.05.16-1 manylinux2010 = quay.io/pypa/manylinux2010_i686:2022-08-05-4535177
manylinux2014 = quay.io/pypa/manylinux2014_i686:2024.09.01-2
[aarch64] manylinux_2_24 = quay.io/pypa/manylinux_2_24_i686:2022-12-26-0d38463
manylinux2014 = quay.io/pypa/manylinux2014_aarch64:2025.05.16-1 musllinux_1_1 = quay.io/pypa/musllinux_1_1_i686:2024.09.01-2
manylinux_2_28 = quay.io/pypa/manylinux_2_28_aarch64:2025.05.16-1 musllinux_1_2 = quay.io/pypa/musllinux_1_2_i686:2024.09.01-2
manylinux_2_34 = quay.io/pypa/manylinux_2_34_aarch64:2025.05.16-1
musllinux_1_2 = quay.io/pypa/musllinux_1_2_aarch64:2025.05.16-1
[ppc64le]
manylinux2014 = quay.io/pypa/manylinux2014_ppc64le:2025.05.16-1
manylinux_2_28 = quay.io/pypa/manylinux_2_28_ppc64le:2025.05.16-1
manylinux_2_34 = quay.io/pypa/manylinux_2_34_ppc64le:2025.05.16-1
musllinux_1_2 = quay.io/pypa/musllinux_1_2_ppc64le:2025.05.16-1
[s390x]
manylinux2014 = quay.io/pypa/manylinux2014_s390x:2025.05.16-1
manylinux_2_28 = quay.io/pypa/manylinux_2_28_s390x:2025.05.16-1
manylinux_2_34 = quay.io/pypa/manylinux_2_34_s390x:2025.05.16-1
musllinux_1_2 = quay.io/pypa/musllinux_1_2_s390x:2025.05.16-1
[pypy_x86_64] [pypy_x86_64]
manylinux2014 = quay.io/pypa/manylinux2014_x86_64:2025.05.16-1 manylinux2010 = quay.io/pypa/manylinux2010_x86_64:2022-08-05-4535177
manylinux_2_28 = quay.io/pypa/manylinux_2_28_x86_64:2025.05.16-1 manylinux2014 = quay.io/pypa/manylinux2014_x86_64:2024.09.01-2
manylinux_2_34 = quay.io/pypa/manylinux_2_34_x86_64:2025.05.16-1 manylinux_2_24 = quay.io/pypa/manylinux_2_24_x86_64:2022-12-26-0d38463
manylinux_2_28 = quay.io/pypa/manylinux_2_28_x86_64:2024.09.01-2
[pypy_i686] [pypy_i686]
manylinux2014 = quay.io/pypa/manylinux2014_i686:2025.05.16-1 manylinux2010 = quay.io/pypa/manylinux2010_i686:2022-08-05-4535177
manylinux2014 = quay.io/pypa/manylinux2014_i686:2024.09.01-2
manylinux_2_24 = quay.io/pypa/manylinux_2_24_i686:2022-12-26-0d38463
[aarch64]
manylinux2014 = quay.io/pypa/manylinux2014_aarch64:2024.09.01-2
manylinux_2_24 = quay.io/pypa/manylinux_2_24_aarch64:2022-12-26-0d38463
manylinux_2_28 = quay.io/pypa/manylinux_2_28_aarch64:2024.09.01-2
musllinux_1_1 = quay.io/pypa/musllinux_1_1_aarch64:2024.09.01-2
musllinux_1_2 = quay.io/pypa/musllinux_1_2_aarch64:2024.09.01-2
[ppc64le]
manylinux2014 = quay.io/pypa/manylinux2014_ppc64le:2024.09.01-2
manylinux_2_24 = quay.io/pypa/manylinux_2_24_ppc64le:2022-12-26-0d38463
manylinux_2_28 = quay.io/pypa/manylinux_2_28_ppc64le:2024.09.01-2
musllinux_1_1 = quay.io/pypa/musllinux_1_1_ppc64le:2024.09.01-2
musllinux_1_2 = quay.io/pypa/musllinux_1_2_ppc64le:2024.09.01-2
[s390x]
manylinux2014 = quay.io/pypa/manylinux2014_s390x:2024.09.01-2
manylinux_2_24 = quay.io/pypa/manylinux_2_24_s390x:2022-12-26-0d38463
manylinux_2_28 = quay.io/pypa/manylinux_2_28_s390x:2024.09.01-2
musllinux_1_1 = quay.io/pypa/musllinux_1_1_s390x:2024.09.01-2
musllinux_1_2 = quay.io/pypa/musllinux_1_2_s390x:2024.09.01-2
[pypy_aarch64] [pypy_aarch64]
manylinux2014 = quay.io/pypa/manylinux2014_aarch64:2025.05.16-1 manylinux2014 = quay.io/pypa/manylinux2014_aarch64:2024.09.01-2
manylinux_2_28 = quay.io/pypa/manylinux_2_28_aarch64:2025.05.16-1 manylinux_2_24 = quay.io/pypa/manylinux_2_24_aarch64:2022-12-26-0d38463
manylinux_2_34 = quay.io/pypa/manylinux_2_34_aarch64:2025.05.16-1 manylinux_2_28 = quay.io/pypa/manylinux_2_28_aarch64:2024.09.01-2
[armv7l]
manylinux_2_31 = quay.io/pypa/manylinux_2_31_armv7l:2025.05.16-1
musllinux_1_2 = quay.io/pypa/musllinux_1_2_armv7l:2025.05.16-1
[riscv64]
@@ -0,0 +1,17 @@
# this file is copied to the testing cwd, to raise the below error message if
# pytest/unittest is run from there.
import unittest
class TestStringMethods(unittest.TestCase):
def test_fail(self):
self.fail(
"cibuildwheel executes tests from a different working directory to "
"your project. This ensures only your wheel is imported, preventing "
"Python from accessing files that haven't been packaged into the "
"wheel. Please specify a path to your tests when invoking pytest "
"using the {project} placeholder, e.g. `pytest {project}` or "
"`pytest {project}/tests`. cibuildwheel will replace {project} with "
"the path to your project."
)
+2 -1
View File
@@ -1 +1,2 @@
default = { version = "20.31.2", url = "https://github.com/pypa/get-virtualenv/blob/20.31.2/public/virtualenv.pyz?raw=true" } py36 = { version = "20.21.1", url = "https://github.com/pypa/get-virtualenv/blob/20.21.1/public/virtualenv.pyz?raw=true" }
default = { version = "20.26.3", url = "https://github.com/pypa/get-virtualenv/blob/20.26.3/public/virtualenv.pyz?raw=true" }
+5 -2
View File
@@ -1,12 +1,15 @@
from __future__ import annotations
import json import json
from pathlib import Path
from typing import Any from typing import Any
from .util import resources DIR = Path(__file__).parent.resolve()
def get_schema(tool_name: str = "cibuildwheel") -> dict[str, Any]: def get_schema(tool_name: str = "cibuildwheel") -> dict[str, Any]:
"Get the stored complete schema for cibuildwheel settings." "Get the stored complete schema for cibuildwheel settings."
assert tool_name == "cibuildwheel", "Only cibuildwheel is supported." assert tool_name == "cibuildwheel", "Only cibuildwheel is supported."
with resources.CIBUILDWHEEL_SCHEMA.open(encoding="utf-8") as f: with DIR.joinpath("resources/cibuildwheel.schema.json").open(encoding="utf-8") as f:
return json.load(f) # type: ignore[no-any-return] return json.load(f) # type: ignore[no-any-return]
-129
View File
@@ -1,129 +0,0 @@
import itertools
from dataclasses import dataclass
from enum import StrEnum
from fnmatch import fnmatch
from typing import Any
import bracex
from packaging.specifiers import SpecifierSet
from packaging.version import Version
def selector_matches(patterns: str, string: str) -> bool:
"""
Returns True if `string` is matched by any of the wildcard patterns in
`patterns`.
Matching is according to fnmatch, but with shell-like curly brace
expansion. For example, 'cp{36,37}-*' would match either of 'cp36-*' or
'cp37-*'.
"""
patterns_list = patterns.split()
expanded_patterns = itertools.chain.from_iterable(bracex.expand(p) for p in patterns_list)
return any(fnmatch(string, pat) for pat in expanded_patterns)
class EnableGroup(StrEnum):
"""
Groups of build selectors that are not enabled by default.
"""
CPythonFreeThreading = "cpython-freethreading"
CPythonPrerelease = "cpython-prerelease"
PyPy = "pypy"
PyPyEoL = "pypy-eol"
CPythonExperimentalRiscV64 = "cpython-experimental-riscv64"
GraalPy = "graalpy"
@classmethod
def all_groups(cls) -> frozenset["EnableGroup"]:
return frozenset(cls)
@classmethod
def parse_option_value(cls, value: str) -> frozenset["EnableGroup"]:
"""
Parses a string of space-separated values into a set of EnableGroup
members. The string may contain group names or "all".
"""
result = set()
for group in value.strip().split():
if group == "all":
return cls.all_groups()
try:
result.add(cls(group))
except ValueError:
msg = f"Unknown enable group: {group}"
raise ValueError(msg) from None
return frozenset(result)
@dataclass(frozen=True, kw_only=True)
class BuildSelector:
"""
This class holds a set of build/skip patterns. You call an instance with a
build identifier, and it returns True if that identifier should be
included. Only call this on valid identifiers, ones that have at least 2
numeric digits before the first dash.
"""
build_config: str
skip_config: str
requires_python: SpecifierSet | None = None
enable: frozenset[EnableGroup] = frozenset()
def __call__(self, build_id: str) -> bool:
# Filter build selectors by python_requires if set
if self.requires_python is not None:
py_ver_str = build_id.split("-")[0]
py_ver_str = py_ver_str.removesuffix("t")
major = int(py_ver_str[2])
minor = int(py_ver_str[3:])
version = Version(f"{major}.{minor}.99")
if not self.requires_python.contains(version):
return False
# filter out groups that are not enabled
if EnableGroup.CPythonFreeThreading not in self.enable and fnmatch(build_id, "cp3??t-*"):
return False
if EnableGroup.CPythonPrerelease not in self.enable and fnmatch(build_id, "cp314*"):
return False
if EnableGroup.PyPy not in self.enable and fnmatch(build_id, "pp31*"):
return False
if EnableGroup.PyPyEoL not in self.enable and fnmatch(build_id, "pp3?-*"):
return False
if EnableGroup.CPythonExperimentalRiscV64 not in self.enable and fnmatch(
build_id, "*_riscv64"
):
return False
if EnableGroup.GraalPy not in self.enable and fnmatch(build_id, "gp*"):
return False
should_build = selector_matches(self.build_config, build_id)
should_skip = selector_matches(self.skip_config, build_id)
return should_build and not should_skip
def options_summary(self) -> Any:
return {
"build_config": self.build_config,
"skip_config": self.skip_config,
"requires_python": str(self.requires_python),
"enable": sorted(group.value for group in self.enable),
}
@dataclass(frozen=True)
class TestSelector:
"""
A build selector that can only skip tests according to a skip pattern.
"""
skip_config: str
def __call__(self, build_id: str) -> bool:
should_skip = selector_matches(self.skip_config, build_id)
return not should_skip
def options_summary(self) -> Any:
return {"skip_config": self.skip_config}
+14 -4
View File
@@ -1,19 +1,29 @@
from __future__ import annotations
import os import os
import subprocess
import typing import typing
from typing import Final, Literal, Protocol from typing import Final, Literal, Protocol, Union
__all__ = ( __all__ = (
"PLATFORMS", "PLATFORMS",
"PathOrStr", "PathOrStr",
"PlatformName", "PlatformName",
"PLATFORMS",
"PopenBytes",
) )
PathOrStr = str | os.PathLike[str] if typing.TYPE_CHECKING:
PopenBytes = subprocess.Popen[bytes]
PathOrStr = Union[str, os.PathLike[str]]
else:
PopenBytes = subprocess.Popen
PathOrStr = Union[str, "os.PathLike[str]"]
PlatformName = Literal["linux", "macos", "windows", "pyodide", "ios"] PlatformName = Literal["linux", "macos", "windows", "pyodide"]
PLATFORMS: Final[frozenset[PlatformName]] = frozenset(typing.get_args(PlatformName)) PLATFORMS: Final[set[PlatformName]] = {"linux", "macos", "windows", "pyodide"}
class GenericPythonConfiguration(Protocol): class GenericPythonConfiguration(Protocol):
+901
View File
@@ -0,0 +1,901 @@
from __future__ import annotations
import contextlib
import fnmatch
import itertools
import os
import re
import shlex
import shutil
import ssl
import subprocess
import sys
import tarfile
import textwrap
import time
import typing
import urllib.request
from collections import defaultdict
from collections.abc import Generator, Iterable, Mapping, MutableMapping, Sequence
from dataclasses import dataclass
from enum import Enum
from functools import lru_cache
from pathlib import Path, PurePath
from tempfile import TemporaryDirectory
from time import sleep
from typing import Any, ClassVar, Final, Literal, TextIO, TypeVar
from zipfile import ZipFile
import bracex
import certifi
from filelock import FileLock
from packaging.requirements import InvalidRequirement, Requirement
from packaging.specifiers import SpecifierSet
from packaging.utils import parse_wheel_filename
from packaging.version import Version
from platformdirs import user_cache_path
from ._compat import tomllib
from .architecture import Architecture
from .typing import PathOrStr, PlatformName
__all__ = [
"MANYLINUX_ARCHS",
"call",
"chdir",
"combine_constraints",
"find_compatible_wheel",
"find_uv",
"format_safe",
"get_build_verbosity_extra_flags",
"prepare_command",
"read_python_configs",
"resources_dir",
"selector_matches",
"shell",
"split_config_settings",
"strtobool",
]
resources_dir: Final[Path] = Path(__file__).parent / "resources"
install_certifi_script: Final[Path] = resources_dir / "install_certifi.py"
free_thread_enable_313: Final[Path] = resources_dir / "free-threaded-enable-313.xml"
test_fail_cwd_file: Final[Path] = resources_dir / "testing_temp_dir_file.py"
MANYLINUX_ARCHS: Final[tuple[str, ...]] = (
"x86_64",
"i686",
"pypy_x86_64",
"aarch64",
"ppc64le",
"s390x",
"pypy_aarch64",
"pypy_i686",
)
MUSLLINUX_ARCHS: Final[tuple[str, ...]] = (
"x86_64",
"i686",
"aarch64",
"ppc64le",
"s390x",
)
DEFAULT_CIBW_CACHE_PATH: Final[Path] = user_cache_path(appname="cibuildwheel", appauthor="pypa")
CIBW_CACHE_PATH: Final[Path] = Path(
os.environ.get("CIBW_CACHE_PATH", DEFAULT_CIBW_CACHE_PATH)
).resolve()
IS_WIN: Final[bool] = sys.platform.startswith("win")
@typing.overload
def call(
*args: PathOrStr,
env: Mapping[str, str] | None = None,
cwd: PathOrStr | None = None,
capture_stdout: Literal[False] = ...,
) -> None: ...
@typing.overload
def call(
*args: PathOrStr,
env: Mapping[str, str] | None = None,
cwd: PathOrStr | None = None,
capture_stdout: Literal[True],
) -> str: ...
def call(
*args: PathOrStr,
env: Mapping[str, str] | None = None,
cwd: PathOrStr | None = None,
capture_stdout: bool = False,
) -> str | None:
"""
Run subprocess.run, but print the commands first. Takes the commands as
*args. Uses shell=True on Windows due to a bug. Also converts to
Paths to strings, due to Windows behavior at least on older Pythons.
https://bugs.python.org/issue8557
"""
args_ = [str(arg) for arg in args]
# print the command executing for the logs
print("+ " + " ".join(shlex.quote(a) for a in args_))
kwargs: dict[str, Any] = {}
if capture_stdout:
kwargs["universal_newlines"] = True
kwargs["stdout"] = subprocess.PIPE
result = subprocess.run(args_, check=True, shell=IS_WIN, env=env, cwd=cwd, **kwargs)
if not capture_stdout:
return None
return typing.cast(str, result.stdout)
def shell(
*commands: str, env: Mapping[str, str] | None = None, cwd: PathOrStr | None = None
) -> None:
command = " ".join(commands)
print(f"+ {command}")
subprocess.run(command, env=env, cwd=cwd, shell=True, check=True)
def format_safe(template: str, **kwargs: str | os.PathLike[str]) -> str:
"""
Works similarly to `template.format(**kwargs)`, except that unmatched
fields in `template` are passed through untouched.
>>> format_safe('{a} {b}', a='123')
'123 {b}'
>>> format_safe('{a} {b[4]:3f}', a='123')
'123 {b[4]:3f}'
To avoid variable expansion, precede with a single backslash e.g.
>>> format_safe('\\{a} {b}', a='123')
'{a} {b}'
"""
result = template
for key, value in kwargs.items():
find_pattern = re.compile(
rf"""
(?<!\#) # don't match if preceded by a hash
{{ # literal open curly bracket
{re.escape(key)} # the field name
}} # literal close curly bracket
""",
re.VERBOSE,
)
result = re.sub(
pattern=find_pattern,
repl=str(value).replace("\\", r"\\"),
string=result,
)
# transform escaped sequences into their literal equivalents
result = result.replace(f"#{{{key}}}", f"{{{key}}}")
return result
def prepare_command(command: str, **kwargs: PathOrStr) -> str:
"""
Preprocesses a command by expanding variables like {python}.
For example, used in the test_command option to specify the path to the
project's root. Unmatched syntax will mostly be allowed through.
"""
return format_safe(command, python="python", pip="pip", **kwargs)
def get_build_verbosity_extra_flags(level: int) -> list[str]:
if level > 0:
return ["-" + level * "v"]
elif level < 0:
return ["-" + -level * "q"]
else:
return []
def split_config_settings(
config_settings: str, frontend: Literal["pip", "build", "build[uv]"]
) -> list[str]:
config_settings_list = shlex.split(config_settings)
s = "s" if frontend == "pip" else ""
return [f"--config-setting{s}={setting}" for setting in config_settings_list]
def read_python_configs(config: PlatformName) -> list[dict[str, str]]:
input_file = resources_dir / "build-platforms.toml"
with input_file.open("rb") as f:
loaded_file = tomllib.load(f)
results: list[dict[str, str]] = list(loaded_file[config]["python_configurations"])
return results
def selector_matches(patterns: str, string: str) -> bool:
"""
Returns True if `string` is matched by any of the wildcard patterns in
`patterns`.
Matching is according to fnmatch, but with shell-like curly brace
expansion. For example, 'cp{36,37}-*' would match either of 'cp36-*' or
'cp37-*'.
"""
patterns_list = patterns.split()
expanded_patterns = itertools.chain.from_iterable(bracex.expand(p) for p in patterns_list)
return any(fnmatch.fnmatch(string, pat) for pat in expanded_patterns)
# Once we require Python 3.10+, we can add kw_only=True
@dataclass(frozen=True)
class BuildSelector:
"""
This class holds a set of build/skip patterns. You call an instance with a
build identifier, and it returns True if that identifier should be
included. Only call this on valid identifiers, ones that have at least 2
numeric digits before the first dash.
"""
build_config: str
skip_config: str
requires_python: SpecifierSet | None = None
# a pattern that skips prerelease versions, when include_prereleases is False.
PRERELEASE_SKIP: ClassVar[str] = ""
prerelease_pythons: bool = False
free_threaded_support: bool = False
def __call__(self, build_id: str) -> bool:
# Filter build selectors by python_requires if set
if self.requires_python is not None:
py_ver_str = build_id.split("-")[0]
if py_ver_str.endswith("t"):
py_ver_str = py_ver_str[:-1]
major = int(py_ver_str[2])
minor = int(py_ver_str[3:])
version = Version(f"{major}.{minor}.99")
if not self.requires_python.contains(version):
return False
# filter out the prerelease pythons if self.prerelease_pythons is False
if not self.prerelease_pythons and selector_matches(self.PRERELEASE_SKIP, build_id):
return False
# filter out free threaded pythons if self.free_threaded_support is False
if not self.free_threaded_support and selector_matches("*t-*", build_id):
return False
should_build = selector_matches(self.build_config, build_id)
should_skip = selector_matches(self.skip_config, build_id)
return should_build and not should_skip
def options_summary(self) -> Any:
return {
"build_config": self.build_config,
"skip_config": self.skip_config,
"requires_python": str(self.requires_python),
"prerelease_pythons": self.prerelease_pythons,
"free_threaded_support": self.free_threaded_support,
}
@dataclass(frozen=True)
class TestSelector:
"""
A build selector that can only skip tests according to a skip pattern.
"""
skip_config: str
def __call__(self, build_id: str) -> bool:
should_skip = selector_matches(self.skip_config, build_id)
return not should_skip
def options_summary(self) -> Any:
return {"skip_config": self.skip_config}
# Taken from https://stackoverflow.com/a/107717
class Unbuffered:
def __init__(self, stream: TextIO) -> None:
self.stream = stream
def write(self, data: str) -> None:
self.stream.write(data)
self.stream.flush()
def writelines(self, data: Iterable[str]) -> None:
self.stream.writelines(data)
self.stream.flush()
def __getattr__(self, attr: str) -> Any:
return getattr(self.stream, attr)
def download(url: str, dest: Path) -> None:
print(f"+ Download {url} to {dest}")
dest_dir = dest.parent
if not dest_dir.exists():
dest_dir.mkdir(parents=True)
# we've had issues when relying on the host OS' CA certificates on Windows,
# so we use certifi (this sounds odd but requests also does this by default)
cafile = os.environ.get("SSL_CERT_FILE", certifi.where())
context = ssl.create_default_context(cafile=cafile)
repeat_num = 3
for i in range(repeat_num):
try:
with urllib.request.urlopen(url, context=context) as response:
dest.write_bytes(response.read())
return
except OSError:
if i == repeat_num - 1:
raise
sleep(3)
def extract_zip(zip_src: Path, dest: Path) -> None:
with ZipFile(zip_src) as zip_:
for zinfo in zip_.filelist:
zip_.extract(zinfo, dest)
# Set permissions to the same values as they were set in the archive
# We have to do this manually due to
# https://github.com/python/cpython/issues/59999
# But some files in the zipfile seem to have external_attr with 0
# permissions. In that case just use the default value???
permissions = (zinfo.external_attr >> 16) & 0o777
if permissions != 0:
dest.joinpath(zinfo.filename).chmod(permissions)
def extract_tar(tar_src: Path, dest: Path) -> None:
with tarfile.open(tar_src) as tar_:
tar_.extraction_filter = getattr(tarfile, "tar_filter", (lambda member, _: member))
tar_.extractall(dest)
def move_file(src_file: Path, dst_file: Path) -> Path:
"""Moves a file safely while avoiding potential semantic confusion:
1. `dst_file` must point to the target filename, not a directory
2. `dst_file` will be overwritten if it already exists
3. any missing parent directories will be created
Returns the fully resolved Path of the resulting file.
Raises:
NotADirectoryError: If any part of the intermediate path to `dst_file` is an existing file
IsADirectoryError: If `dst_file` points directly to an existing directory
"""
src_file = src_file.resolve(strict=True)
dst_file = dst_file.resolve()
if dst_file.is_dir():
msg = "dst_file must be a valid target filename, not an existing directory."
raise IsADirectoryError(msg)
dst_file.unlink(missing_ok=True)
dst_file.parent.mkdir(parents=True, exist_ok=True)
# using shutil.move() as Path.rename() is not guaranteed to work across filesystem boundaries
# explicit str() needed for Python 3.8
resulting_file = shutil.move(str(src_file), str(dst_file))
return Path(resulting_file).resolve(strict=True)
class DependencyConstraints:
def __init__(self, base_file_path: Path):
assert base_file_path.exists()
self.base_file_path = base_file_path.resolve()
@staticmethod
def with_defaults() -> DependencyConstraints:
return DependencyConstraints(base_file_path=resources_dir / "constraints.txt")
def get_for_python_version(
self, version: str, *, variant: Literal["python", "pyodide"] = "python"
) -> Path:
version_parts = version.split(".")
# try to find a version-specific dependency file e.g. if
# ./constraints.txt is the base, look for ./constraints-python36.txt
specific_stem = self.base_file_path.stem + f"-{variant}{version_parts[0]}{version_parts[1]}"
specific_name = specific_stem + self.base_file_path.suffix
specific_file_path = self.base_file_path.with_name(specific_name)
if specific_file_path.exists():
return specific_file_path
else:
return self.base_file_path
def __repr__(self) -> str:
return f"{self.__class__.__name__}({self.base_file_path!r})"
def __eq__(self, o: object) -> bool:
if not isinstance(o, DependencyConstraints):
return False
return self.base_file_path == o.base_file_path
def options_summary(self) -> Any:
if self == DependencyConstraints.with_defaults():
return "pinned"
else:
return self.base_file_path.name
BuildFrontendName = Literal["pip", "build", "build[uv]"]
@dataclass(frozen=True)
class BuildFrontendConfig:
name: BuildFrontendName
args: Sequence[str] = ()
@staticmethod
def from_config_string(config_string: str) -> BuildFrontendConfig:
config_dict = parse_key_value_string(config_string, ["name"], ["args"])
name = " ".join(config_dict["name"])
if name not in {"pip", "build", "build[uv]"}:
msg = f"Unrecognised build frontend {name!r}, only 'pip', 'build', and 'build[uv]' are supported"
raise ValueError(msg)
name = typing.cast(BuildFrontendName, name)
args = config_dict.get("args") or []
return BuildFrontendConfig(name=name, args=args)
def options_summary(self) -> str | dict[str, str]:
if not self.args:
return self.name
else:
return {"name": self.name, "args": repr(self.args)}
def strtobool(val: str) -> bool:
return val.lower() in {"y", "yes", "t", "true", "on", "1"}
class CIProvider(Enum):
travis_ci = "travis"
appveyor = "appveyor"
circle_ci = "circle_ci"
azure_pipelines = "azure_pipelines"
github_actions = "github_actions"
gitlab = "gitlab"
cirrus_ci = "cirrus_ci"
other = "other"
def detect_ci_provider() -> CIProvider | None:
if "TRAVIS" in os.environ:
return CIProvider.travis_ci
elif "APPVEYOR" in os.environ:
return CIProvider.appveyor
elif "CIRCLECI" in os.environ:
return CIProvider.circle_ci
elif "AZURE_HTTP_USER_AGENT" in os.environ:
return CIProvider.azure_pipelines
elif "GITHUB_ACTIONS" in os.environ:
return CIProvider.github_actions
elif "GITLAB_CI" in os.environ:
return CIProvider.gitlab
elif "CIRRUS_CI" in os.environ:
return CIProvider.cirrus_ci
elif strtobool(os.environ.get("CI", "false")):
return CIProvider.other
else:
return None
def unwrap(text: str) -> str:
"""
Unwraps multi-line text to a single line
"""
# remove initial line indent
text = textwrap.dedent(text)
# remove leading/trailing whitespace
text = text.strip()
# remove consecutive whitespace
return re.sub(r"\s+", " ", text)
@dataclass(frozen=True)
class FileReport:
name: str
size: str
@contextlib.contextmanager
def print_new_wheels(msg: str, output_dir: Path) -> Generator[None, None, None]:
"""
Prints the new items in a directory upon exiting. The message to display
can include {n} for number of wheels, {s} for total number of seconds,
and/or {m} for total number of minutes. Does not print anything if this
exits via exception.
"""
start_time = time.time()
existing_contents = set(output_dir.iterdir())
yield
final_contents = set(output_dir.iterdir())
new_contents = [
FileReport(wheel.name, f"{(wheel.stat().st_size + 1023) // 1024:,d}")
for wheel in final_contents - existing_contents
]
if not new_contents:
return
max_name_len = max(len(f.name) for f in new_contents)
max_size_len = max(len(f.size) for f in new_contents)
n = len(new_contents)
s = time.time() - start_time
m = s / 60
print(
msg.format(n=n, s=s, m=m),
*sorted(
f" {f.name:<{max_name_len}s} {f.size:>{max_size_len}s} kB" for f in new_contents
),
sep="\n",
)
def get_pip_version(env: Mapping[str, str]) -> str:
versions_output_text = call(
"python", "-m", "pip", "freeze", "--all", capture_stdout=True, env=env
)
(pip_version,) = (
version[5:]
for version in versions_output_text.strip().splitlines()
if version.startswith("pip==")
)
return pip_version
@lru_cache(maxsize=None)
def ensure_node(major_version: str) -> Path:
input_file = resources_dir / "nodejs.toml"
with input_file.open("rb") as f:
loaded_file = tomllib.load(f)
version = str(loaded_file[major_version])
base_url = str(loaded_file["url"])
ext = "zip" if IS_WIN else "tar.xz"
platform = "win" if IS_WIN else ("darwin" if sys.platform.startswith("darwin") else "linux")
linux_arch = Architecture.native_arch("linux")
assert linux_arch is not None
arch = {"x86_64": "x64", "i686": "x86", "aarch64": "arm64"}.get(
linux_arch.value, linux_arch.value
)
name = f"node-{version}-{platform}-{arch}"
path = CIBW_CACHE_PATH / name
with FileLock(str(path) + ".lock"):
if not path.exists():
url = f"{base_url}{version}/{name}.{ext}"
with TemporaryDirectory() as tmp_path:
archive = Path(tmp_path) / f"{name}.{ext}"
download(url, archive)
if ext == "zip":
extract_zip(archive, path.parent)
else:
extract_tar(archive, path.parent)
assert path.exists()
if not IS_WIN:
return path / "bin"
return path
@lru_cache(maxsize=None)
def _ensure_virtualenv(version: str) -> Path:
version_parts = version.split(".")
key = f"py{version_parts[0]}{version_parts[1]}"
input_file = resources_dir / "virtualenv.toml"
with input_file.open("rb") as f:
loaded_file = tomllib.load(f)
configuration = loaded_file.get(key, loaded_file["default"])
version = str(configuration["version"])
url = str(configuration["url"])
path = CIBW_CACHE_PATH / f"virtualenv-{version}.pyz"
with FileLock(str(path) + ".lock"):
if not path.exists():
download(url, path)
return path
def _parse_constraints_for_virtualenv(
seed_packages: list[str],
dependency_constraint_flags: Sequence[PathOrStr],
) -> dict[str, str]:
"""
Parses the constraints file referenced by `dependency_constraint_flags` and returns a dict where
the key is the package name, and the value is the constraint version.
If a package version cannot be found, its value is "embed" meaning that virtualenv will install
its bundled version, already available locally.
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
{macos|windows}.setup_python function.
"""
assert len(dependency_constraint_flags) in {0, 2}
# only seed pip if other seed packages do not appear in a constraint file
constraints_dict = {"pip": "embed"}
if len(dependency_constraint_flags) == 2:
assert dependency_constraint_flags[0] == "-c"
constraint_path = Path(dependency_constraint_flags[1])
assert constraint_path.exists()
with constraint_path.open(encoding="utf-8") as constraint_file:
for line_ in constraint_file:
line = line_.strip()
if not line:
continue
if line.startswith("#"):
continue
try:
requirement = Requirement(line)
package = requirement.name
if (
package not in seed_packages
or requirement.url is not None
or requirement.marker is not None
or len(requirement.extras) != 0
or len(requirement.specifier) != 1
):
continue
specifier = next(iter(requirement.specifier))
if specifier.operator != "==":
continue
constraints_dict[package] = specifier.version
except InvalidRequirement:
continue
return constraints_dict
def virtualenv(
version: str,
python: Path,
venv_path: Path,
dependency_constraint_flags: Sequence[PathOrStr],
*,
use_uv: bool,
) -> dict[str, str]:
"""
Create a virtual environment. If `use_uv` is True,
dependency_constraint_flags are ignored since nothing is installed in the
venv. Otherwise, pip is installed, and setuptools + wheel if Python < 3.12.
"""
assert python.exists()
if use_uv:
call("uv", "venv", venv_path, "--python", python)
else:
virtualenv_app = _ensure_virtualenv(version)
allowed_seed_packages = ["pip", "setuptools", "wheel"]
constraints = _parse_constraints_for_virtualenv(
allowed_seed_packages, dependency_constraint_flags
)
additional_flags: list[str] = []
for package in allowed_seed_packages:
if package in constraints:
additional_flags.append(f"--{package}={constraints[package]}")
else:
additional_flags.append(f"--no-{package}")
# Using symlinks to pre-installed seed packages is really the fastest way to get a virtual
# environment. The initial cost is a bit higher but reusing is much faster.
# Windows does not always allow symlinks so just disabling for now.
# Requires pip>=19.3 so disabling for "embed" because this means we don't know what's the
# version of pip that will end-up installed.
# c.f. https://virtualenv.pypa.io/en/latest/cli_interface.html#section-seeder
if (
not IS_WIN
and constraints["pip"] != "embed"
and Version(constraints["pip"]) >= Version("19.3")
):
additional_flags.append("--symlink-app-data")
call(
sys.executable,
"-sS", # just the stdlib, https://github.com/pypa/virtualenv/issues/2133#issuecomment-1003710125
virtualenv_app,
"--activators=",
"--no-periodic-update",
*additional_flags,
"--python",
python,
venv_path,
)
paths = [str(venv_path), str(venv_path / "Scripts")] if IS_WIN else [str(venv_path / "bin")]
env = os.environ.copy()
env["PATH"] = os.pathsep.join([*paths, env["PATH"]])
env["VIRTUAL_ENV"] = str(venv_path)
return env
T = TypeVar("T", bound=PurePath)
def find_compatible_wheel(wheels: Sequence[T], identifier: str) -> T | None:
"""
Finds a wheel with an abi3 or a none ABI tag in `wheels` compatible with the Python interpreter
specified by `identifier` that is previously built.
"""
interpreter, platform = identifier.split("-")
free_threaded = interpreter.endswith("t")
if free_threaded:
interpreter = interpreter[:-1]
for wheel in wheels:
_, _, _, tags = parse_wheel_filename(wheel.name)
for tag in tags:
if tag.abi == "abi3" and not free_threaded:
# ABI3 wheels must start with cp3 for impl and tag
if not (interpreter.startswith("cp3") and tag.interpreter.startswith("cp3")):
continue
elif tag.abi == "none":
# CPythonless wheels must include py3 tag
if tag.interpreter[:3] != "py3":
continue
else:
# Other types of wheels are not detected, this is looking for previously built wheels.
continue
if tag.interpreter != "py3" and int(tag.interpreter[3:]) > int(interpreter[3:]):
# If a minor version number is given, it has to be lower than the current one.
continue
if platform.startswith(("manylinux", "musllinux", "macosx")):
# Linux, macOS require the beginning and ending match (macos/manylinux version doesn't need to)
os_, arch = platform.split("_", 1)
if not tag.platform.startswith(os_):
continue
if not tag.platform.endswith(f"_{arch}"):
continue
else:
# Windows should exactly match
if tag.platform != platform:
continue
# If all the filters above pass, then the wheel is a previously built compatible wheel.
return wheel
return None
# Can be replaced by contextlib.chdir in Python 3.11
@contextlib.contextmanager
def chdir(new_path: Path | str) -> Generator[None, None, None]:
"""Non thread-safe context manager to change the current working directory."""
cwd = os.getcwd()
try:
os.chdir(new_path)
yield
finally:
os.chdir(cwd)
def fix_ansi_codes_for_github_actions(text: str) -> str:
"""
Github Actions forgets the current ANSI style on every new line. This
function repeats the current ANSI style on every new line.
"""
ansi_code_regex = re.compile(r"(\033\[[0-9;]*m)")
ansi_codes: list[str] = []
output = ""
for line in text.splitlines(keepends=True):
# add the current ANSI codes to the beginning of the line
output += "".join(ansi_codes) + line
# split the line at each ANSI code
parts = ansi_code_regex.split(line)
# if there are any ANSI codes, save them
if len(parts) > 1:
# iterate over the ANSI codes in this line
for code in parts[1::2]:
if code == "\033[0m":
# reset the list of ANSI codes when the clear code is found
ansi_codes = []
else:
ansi_codes.append(code)
return output
def parse_key_value_string(
key_value_string: str,
positional_arg_names: Sequence[str] | None = None,
kw_arg_names: Sequence[str] | None = None,
) -> dict[str, list[str]]:
"""
Parses a string like "docker; create_args: --some-option=value another-option"
"""
if positional_arg_names is None:
positional_arg_names = []
if kw_arg_names is None:
kw_arg_names = []
all_field_names = [*positional_arg_names, *kw_arg_names]
shlexer = shlex.shlex(key_value_string, posix=True, punctuation_chars=";")
shlexer.commenters = ""
shlexer.whitespace_split = True
parts = list(shlexer)
# parts now looks like
# ['docker', ';', 'create_args:', '--some-option=value', 'another-option']
# split by semicolon
fields = [list(group) for k, group in itertools.groupby(parts, lambda x: x == ";") if not k]
result: defaultdict[str, list[str]] = defaultdict(list)
for field_i, field in enumerate(fields):
# check to see if the option name is specified
field_name, sep, first_value = field[0].partition(":")
if sep:
if field_name not in all_field_names:
msg = f"Failed to parse {key_value_string!r}. Unknown field name {field_name!r}"
raise ValueError(msg)
values = ([first_value] if first_value else []) + field[1:]
else:
try:
field_name = positional_arg_names[field_i]
except IndexError:
msg = f"Failed to parse {key_value_string!r}. Too many positional arguments - expected a maximum of {len(positional_arg_names)}"
raise ValueError(msg) from None
values = field
result[field_name] += values
return dict(result)
def find_uv() -> Path | None:
# Prefer uv in our environment
with contextlib.suppress(ImportError, FileNotFoundError):
# pylint: disable-next=import-outside-toplevel
from uv import find_uv_bin
return Path(find_uv_bin())
uv_on_path = shutil.which("uv")
return Path(uv_on_path) if uv_on_path else None
def combine_constraints(
env: MutableMapping[str, str], /, constraints_path: Path, tmp_dir: Path | None
) -> None:
"""
This will workaround a bug in pip<=21.1.1 or uv<=0.2.0 if a tmp_dir is given.
If set to None, this will use the modern URI method.
"""
if tmp_dir:
if " " in str(constraints_path):
assert " " not in str(tmp_dir)
tmp_file = tmp_dir / "constraints.txt"
tmp_file.write_bytes(constraints_path.read_bytes())
constraints_path = tmp_file
our_constraints = str(constraints_path)
else:
our_constraints = (
constraints_path.as_uri() if " " in str(constraints_path) else str(constraints_path)
)
user_constraints = env.get("PIP_CONSTRAINT")
env["UV_CONSTRAINT"] = env["PIP_CONSTRAINT"] = " ".join(
c for c in [our_constraints, user_constraints] if c
)
View File
-83
View File
@@ -1,83 +0,0 @@
import os
import shlex
import shutil
import subprocess
import sys
import typing
from collections.abc import Mapping
from typing import Final, Literal
from ..errors import FatalError
from ..typing import PathOrStr
_IS_WIN: Final[bool] = sys.platform.startswith("win")
@typing.overload
def call(
*args: PathOrStr,
env: Mapping[str, str] | None = None,
cwd: PathOrStr | None = None,
capture_stdout: Literal[False] = ...,
) -> None: ...
@typing.overload
def call(
*args: PathOrStr,
env: Mapping[str, str] | None = None,
cwd: PathOrStr | None = None,
capture_stdout: Literal[True],
) -> str: ...
def call(
*args: PathOrStr,
env: Mapping[str, str] | None = None,
cwd: PathOrStr | None = None,
capture_stdout: bool = False,
) -> str | None:
"""
Run subprocess.run, but print the commands first. Takes the commands as
*args. Uses shell=True on Windows due to a bug. Also converts to
Paths to strings, due to Windows behavior at least on older Pythons.
https://bugs.python.org/issue8557
"""
args_ = [str(arg) for arg in args]
# print the command executing for the logs
print("+ " + " ".join(shlex.quote(a) for a in args_))
# workaround platform behaviour differences outlined
# in https://github.com/python/cpython/issues/52803
path_env = env if env is not None else os.environ
path = path_env.get("PATH", None)
executable = shutil.which(args_[0], path=path)
if executable is None:
msg = f"Couldn't find {args_[0]!r} in PATH {path!r}"
raise FatalError(msg)
args_[0] = executable
try:
result = subprocess.run(
args_,
check=True,
shell=_IS_WIN,
env=env,
cwd=cwd,
capture_output=capture_stdout,
text=capture_stdout,
)
except subprocess.CalledProcessError as e:
if capture_stdout:
sys.stderr.write(e.stderr)
raise
if not capture_stdout:
return None
sys.stderr.write(result.stderr)
return typing.cast(str, result.stdout)
def shell(
*commands: str, env: Mapping[str, str] | None = None, cwd: PathOrStr | None = None
) -> None:
command = " ".join(commands)
print(f"+ {command}")
subprocess.run(command, env=env, cwd=cwd, shell=True, check=True)
-134
View File
@@ -1,134 +0,0 @@
import os
import shutil
import ssl
import tarfile
import time
import urllib.request
from collections.abc import Callable
from pathlib import Path, PurePath
from typing import Final
from zipfile import ZipFile
import certifi
from platformdirs import user_cache_path
from ..errors import FatalError
DEFAULT_CIBW_CACHE_PATH: Final[Path] = user_cache_path(appname="cibuildwheel", appauthor="pypa")
CIBW_CACHE_PATH: Final[Path] = Path(
os.environ.get("CIBW_CACHE_PATH", DEFAULT_CIBW_CACHE_PATH)
).resolve()
def download(url: str, dest: Path) -> None:
print(f"+ Download {url} to {dest}")
dest_dir = dest.parent
dest_dir.mkdir(parents=True, exist_ok=True)
# we've had issues when relying on the host OS' CA certificates on Windows,
# so we use certifi (this sounds odd but requests also does this by default)
cafile = os.environ.get("SSL_CERT_FILE", certifi.where())
context = ssl.create_default_context(cafile=cafile)
repeat_num = 3
for i in range(repeat_num):
try:
with urllib.request.urlopen(url, context=context) as response:
dest.write_bytes(response.read())
return
except OSError:
if i == repeat_num - 1:
raise
time.sleep(3)
def extract_zip(zip_src: Path, dest: Path) -> None:
"""Extracts a zip and correctly sets permissions on extracted files.
Notes:
- sets permissions to the same values as they were set in the archive
- files with no clear permissions in `external_attr` will be extracted with default values
"""
with ZipFile(zip_src) as zip_:
for zinfo in zip_.filelist:
zip_.extract(zinfo, dest)
# Set permissions to the same values as they were set in the archive
# We have to do this manually due to https://github.com/python/cpython/issues/59999
permissions = (zinfo.external_attr >> 16) & 0o777
if permissions != 0:
dest.joinpath(zinfo.filename).chmod(permissions)
def extract_tar(tar_src: Path, dest: Path) -> None:
"""Extracts a tar file using the stdlib 'tar' filter.
See: https://docs.python.org/3/library/tarfile.html#tarfile.tar_filter for filter details
"""
with tarfile.open(tar_src) as tar_:
tar_.extraction_filter = getattr(tarfile, "tar_filter", (lambda member, _: member))
tar_.extractall(dest)
def move_file(src_file: Path, dst_file: Path) -> Path:
"""Moves a file safely while avoiding potential semantic confusion:
1. `dst_file` must point to the target filename, not a directory
2. `dst_file` will be overwritten if it already exists
3. any missing parent directories will be created
Returns the fully resolved Path of the resulting file.
Raises:
NotADirectoryError: If any part of the intermediate path to `dst_file` is an existing file
IsADirectoryError: If `dst_file` points directly to an existing directory
"""
src_file = src_file.resolve(strict=True)
dst_file = dst_file.resolve()
if dst_file.is_dir():
msg = "dst_file must be a valid target filename, not an existing directory."
raise IsADirectoryError(msg)
dst_file.unlink(missing_ok=True)
dst_file.parent.mkdir(parents=True, exist_ok=True)
# using shutil.move() as Path.rename() is not guaranteed to work across filesystem boundaries
# explicit str() needed for Python 3.8
resulting_file = shutil.move(str(src_file), str(dst_file))
return Path(resulting_file).resolve(strict=True)
def copy_into_local(src: Path, dst: PurePath) -> None:
"""Copy a path from src to dst, regardless of whether it's a file or a directory."""
# Ensure the target folder location exists
Path(dst.parent).mkdir(exist_ok=True, parents=True)
if src.is_dir():
shutil.copytree(src, dst)
else:
shutil.copy(src, dst)
def copy_test_sources(
test_sources: list[str],
package_dir: Path,
test_dir: PurePath,
copy_into: Callable[[Path, PurePath], None] = copy_into_local,
) -> None:
"""Copy the list of test sources from the package to the test directory.
:param test_sources: A list of test paths, relative to the package_dir.
:param package_dir: The root of the package directory.
:param test_dir: The folder where test sources should be placed.
:param copy_info: The copy function to use. By default, does a local
filesystem copy; but an OCIContainer.copy_info method (or equivalent)
can be provided.
"""
for test_path in test_sources:
source = package_dir.resolve() / test_path
if not source.exists():
msg = f"Test source {test_path} does not exist."
raise FatalError(msg)
copy_into(source, test_dir / test_path)
-189
View File
@@ -1,189 +0,0 @@
import itertools
import os
import re
import shlex
import textwrap
from collections import defaultdict
from collections.abc import Sequence
from functools import total_ordering
from ..typing import PathOrStr
def format_safe(template: str, **kwargs: str | os.PathLike[str]) -> str:
"""
Works similarly to `template.format(**kwargs)`, except that unmatched
fields in `template` are passed through untouched.
>>> format_safe('{a} {b}', a='123')
'123 {b}'
>>> format_safe('{a} {b[4]:3f}', a='123')
'123 {b[4]:3f}'
To avoid variable expansion, precede with a single backslash e.g.
>>> format_safe('\\{a} {b}', a='123')
'{a} {b}'
"""
result = template
for key, value in kwargs.items():
find_pattern = re.compile(
rf"""
(?<!\#) # don't match if preceded by a hash
{{ # literal open curly bracket
{re.escape(key)} # the field name
}} # literal close curly bracket
""",
re.VERBOSE,
)
result = re.sub(
pattern=find_pattern,
repl=str(value).replace("\\", r"\\"),
string=result,
)
# transform escaped sequences into their literal equivalents
result = result.replace(f"#{{{key}}}", f"{{{key}}}")
return result
def prepare_command(command: str, **kwargs: PathOrStr) -> str:
"""
Preprocesses a command by expanding variables like {project}.
For example, used in the test_command option to specify the path to the
project's root. Unmatched syntax will mostly be allowed through.
"""
return format_safe(command, **kwargs)
def strtobool(val: str) -> bool:
return val.lower() in {"y", "yes", "t", "true", "on", "1"}
def unwrap(text: str) -> str:
"""
Unwraps multi-line text to a single line
"""
# remove initial line indent
text = textwrap.dedent(text)
# remove leading/trailing whitespace
text = text.strip()
# remove consecutive whitespace
return re.sub(r"\s+", " ", text)
def unwrap_preserving_paragraphs(text: str) -> str:
"""
Unwraps multi-line text to a single line, but preserves paragraphs
"""
# remove initial line indent
text = textwrap.dedent(text)
# remove leading/trailing whitespace
text = text.strip()
paragraphs = text.split("\n\n")
# remove consecutive whitespace
paragraphs = [re.sub(r"\s+", " ", paragraph) for paragraph in paragraphs]
return "\n\n".join(paragraphs)
def parse_key_value_string(
key_value_string: str,
positional_arg_names: Sequence[str] | None = None,
kw_arg_names: Sequence[str] | None = None,
) -> dict[str, list[str]]:
"""
Parses a string like "docker; create_args: --some-option=value another-option"
"""
if positional_arg_names is None:
positional_arg_names = []
if kw_arg_names is None:
kw_arg_names = []
all_field_names = [*positional_arg_names, *kw_arg_names]
shlexer = shlex.shlex(key_value_string, posix=True, punctuation_chars=";")
shlexer.commenters = ""
shlexer.whitespace_split = True
parts = list(shlexer)
# parts now looks like
# ['docker', ';', 'create_args:', '--some-option=value', 'another-option']
# split by semicolon
fields = [list(group) for k, group in itertools.groupby(parts, lambda x: x == ";") if not k]
result: defaultdict[str, list[str]] = defaultdict(list)
for field_i, field in enumerate(fields):
# check to see if the option name is specified
field_name, sep, first_value = field[0].partition(":")
if sep:
if field_name not in all_field_names:
msg = f"Failed to parse {key_value_string!r}. Unknown field name {field_name!r}"
raise ValueError(msg)
values = ([first_value] if first_value else []) + field[1:]
else:
try:
field_name = positional_arg_names[field_i]
except IndexError:
msg = f"Failed to parse {key_value_string!r}. Too many positional arguments - expected a maximum of {len(positional_arg_names)}"
raise ValueError(msg) from None
values = field
result[field_name] += values
return dict(result)
@total_ordering
class FlexibleVersion:
version_str: str
version_parts: tuple[int, ...]
suffix: str
def __init__(self, version_str: str) -> None:
self.version_str = version_str
# Split into numeric parts and the optional suffix
match = re.match(r"^[v]?(\d+(\.\d+)*)(.*)$", version_str)
if not match:
msg = f"Invalid version string: {version_str}"
raise ValueError(msg)
version_part, _, suffix = match.groups()
# Convert numeric version part into a tuple of integers
self.version_parts = tuple(map(int, version_part.split(".")))
self.suffix = suffix.strip() if suffix else ""
# Normalize by removing trailing zeros
self.version_parts = self._remove_trailing_zeros(self.version_parts)
@staticmethod
def _remove_trailing_zeros(parts: tuple[int, ...]) -> tuple[int, ...]:
# Remove trailing zeros for accurate comparisons
# without this, "3.0" would be considered greater than "3"
while parts and parts[-1] == 0:
parts = parts[:-1]
return parts
def __eq__(self, other: object) -> bool:
if not isinstance(other, FlexibleVersion):
raise NotImplementedError()
return (self.version_parts, self.suffix) == (other.version_parts, other.suffix)
def __lt__(self, other: object) -> bool:
if not isinstance(other, FlexibleVersion):
raise NotImplementedError()
return (self.version_parts, self.suffix) < (other.version_parts, other.suffix)
def __repr__(self) -> str:
return f"FlexibleVersion('{self.version_str}')"
def __str__(self) -> str:
return self.version_str
-203
View File
@@ -1,203 +0,0 @@
import shlex
from collections.abc import Mapping, MutableMapping, Sequence
from dataclasses import dataclass, field
from pathlib import Path, PurePath
from typing import Any, Literal, Self, TypeVar
from packaging.utils import parse_wheel_filename
from . import resources
from .cmd import call
from .helpers import parse_key_value_string, unwrap
@dataclass()
class DependencyConstraints:
base_file_path: Path | None = None
packages: list[str] = field(default_factory=list)
def __post_init__(self) -> None:
if self.packages and self.base_file_path is not None:
msg = "Cannot specify both a file and packages in the dependency constraints"
raise ValueError(msg)
if self.base_file_path is not None:
if not self.base_file_path.exists():
msg = f"Dependency constraints file not found: {self.base_file_path}"
raise FileNotFoundError(msg)
self.base_file_path = self.base_file_path.resolve()
@classmethod
def pinned(cls) -> Self:
return cls(base_file_path=resources.CONSTRAINTS)
@classmethod
def latest(cls) -> Self:
return cls()
@classmethod
def from_config_string(cls, config_string: str) -> Self:
if config_string == "pinned":
return cls.pinned()
if config_string == "latest" or not config_string:
return cls.latest()
if config_string.startswith(("file:", "packages:")):
# we only do the table-style parsing if it looks like a table,
# because this option used to be only a file path. We don't want
# to break existing configurations, whose file paths might include
# special characters like ':' or ' ', which would require quoting
# if they were to be passed as a parse_key_value_string positional
# argument.
return cls.from_table_style_config_string(config_string)
return cls(base_file_path=Path(config_string))
@classmethod
def from_table_style_config_string(cls, config_string: str) -> Self:
config_dict = parse_key_value_string(config_string, kw_arg_names=["file", "packages"])
files = config_dict.get("file")
packages = config_dict.get("packages") or []
if files and packages:
msg = "Cannot specify both a file and packages in dependency-versions"
raise ValueError(msg)
if files:
if len(files) > 1:
msg = unwrap("""
Only one file can be specified in dependency-versions.
If you intended to pass only one, perhaps you need to quote the path?
""")
raise ValueError(msg)
return cls(base_file_path=Path(files[0]))
return cls(packages=packages)
def get_for_python_version(
self, *, version: str, variant: Literal["python", "pyodide"] = "python", tmp_dir: Path
) -> Path | None:
if self.packages:
constraint_file = tmp_dir / "constraints.txt"
constraint_file.write_text("\n".join(self.packages))
return constraint_file
if self.base_file_path is not None:
version_parts = version.split(".")
# try to find a version-specific dependency file e.g. if
# ./constraints.txt is the base, look for ./constraints-python36.txt
specific_stem = (
self.base_file_path.stem + f"-{variant}{version_parts[0]}{version_parts[1]}"
)
specific_name = specific_stem + self.base_file_path.suffix
specific_file_path = self.base_file_path.with_name(specific_name)
if specific_file_path.exists():
return specific_file_path
else:
return self.base_file_path
return None
def options_summary(self) -> Any:
if self == DependencyConstraints.pinned():
return "pinned"
elif self.packages:
return {"packages": " ".join(shlex.quote(p) for p in self.packages)}
elif self.base_file_path is not None:
return self.base_file_path.name
else:
return "latest"
def get_pip_version(env: Mapping[str, str]) -> str:
versions_output_text = call(
"python", "-m", "pip", "freeze", "--all", capture_stdout=True, env=env
)
(pip_version,) = (
version[5:]
for version in versions_output_text.strip().splitlines()
if version.startswith("pip==")
)
return pip_version
T = TypeVar("T", bound=PurePath)
def find_compatible_wheel(wheels: Sequence[T], identifier: str) -> T | None:
"""
Finds a wheel with an abi3 or a none ABI tag in `wheels` compatible with the Python interpreter
specified by `identifier` that is previously built.
"""
interpreter, platform = identifier.split("-", 1)
free_threaded = interpreter.endswith("t")
if free_threaded:
interpreter = interpreter[:-1]
for wheel in wheels:
_, _, _, tags = parse_wheel_filename(wheel.name)
for tag in tags:
if tag.abi == "abi3" and not free_threaded:
# ABI3 wheels must start with cp3 for impl and tag
if not (interpreter.startswith("cp3") and tag.interpreter.startswith("cp3")):
continue
elif tag.abi == "none":
# CPythonless wheels must include py3 tag
if tag.interpreter[:3] != "py3":
continue
else:
# Other types of wheels are not detected, this is looking for previously built wheels.
continue
if tag.interpreter != "py3" and int(tag.interpreter[3:]) > int(interpreter[3:]):
# If a minor version number is given, it has to be lower than the current one.
continue
if platform.startswith(("manylinux", "musllinux", "macosx", "ios")):
# Linux, macOS, and iOS require the beginning and ending match
# (macos/manylinux/iOS version number doesn't need to match)
os_, arch = platform.split("_", 1)
if not tag.platform.startswith(os_):
continue
if not tag.platform.endswith(f"_{arch}"):
continue
else:
# Windows should exactly match
if tag.platform != platform:
continue
# If all the filters above pass, then the wheel is a previously built compatible wheel.
return wheel
return None
def combine_constraints(
env: MutableMapping[str, str], /, constraints_path: Path, tmp_dir: Path | None
) -> None:
"""
This will workaround a bug in pip<=21.1.1 or uv<=0.2.0 if a tmp_dir is given.
If set to None, this will use the modern URI method.
"""
if tmp_dir:
if " " in str(constraints_path):
assert " " not in str(tmp_dir)
tmp_file = tmp_dir / "constraints.txt"
tmp_file.write_bytes(constraints_path.read_bytes())
constraints_path = tmp_file
our_constraints = str(constraints_path)
else:
our_constraints = (
constraints_path.as_uri() if " " in str(constraints_path) else str(constraints_path)
)
user_constraints = env.get("PIP_CONSTRAINT")
env["UV_CONSTRAINT"] = env["PIP_CONSTRAINT"] = " ".join(
c for c in [our_constraints, user_constraints] if c
)
-27
View File
@@ -1,27 +0,0 @@
import functools
import tomllib
from pathlib import Path
from typing import Final
from ..typing import PlatformName
PATH: Final[Path] = Path(__file__).parent.parent / "resources"
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"
NODEJS: Final[Path] = PATH / "nodejs.toml"
DEFAULTS: Final[Path] = PATH / "defaults.toml"
PINNED_DOCKER_IMAGES: Final[Path] = PATH / "pinned_docker_images.cfg"
BUILD_PLATFORMS: Final[Path] = PATH / "build-platforms.toml"
CONSTRAINTS: Final[Path] = PATH / "constraints.txt"
VIRTUALENV: Final[Path] = PATH / "virtualenv.toml"
CIBUILDWHEEL_SCHEMA: Final[Path] = PATH / "cibuildwheel.schema.json"
# this value is cached because it's used a lot in unit tests
@functools.cache
def read_python_configs(config: PlatformName) -> list[dict[str, str]]:
with BUILD_PLATFORMS.open("rb") as f:
loaded_file = tomllib.load(f)
results: list[dict[str, str]] = list(loaded_file[config]["python_configurations"])
return results
-166
View File
@@ -1,166 +0,0 @@
import contextlib
import functools
import os
import shutil
import sys
import tomllib
from collections.abc import Sequence
from pathlib import Path
from typing import Final
from filelock import FileLock
from packaging.requirements import InvalidRequirement, Requirement
from packaging.version import Version
from .util import resources
from .util.cmd import call
from .util.file import CIBW_CACHE_PATH, download
_IS_WIN: Final[bool] = sys.platform.startswith("win")
@functools.cache
def _ensure_virtualenv(version: str) -> tuple[Path, Version]:
version_parts = version.split(".")
key = f"py{version_parts[0]}{version_parts[1]}"
with resources.VIRTUALENV.open("rb") as f:
loaded_file = tomllib.load(f)
configuration = loaded_file.get(key, loaded_file["default"])
version = str(configuration["version"])
url = str(configuration["url"])
path = CIBW_CACHE_PATH / f"virtualenv-{version}.pyz"
with FileLock(str(path) + ".lock"):
if not path.exists():
download(url, path)
return (path, Version(version))
def constraint_flags(
dependency_constraint: Path | None,
) -> Sequence[str]:
"""
Returns the flags to pass to pip for the given dependency constraint.
"""
return ["-c", dependency_constraint.as_uri()] if dependency_constraint else []
def _parse_pip_constraint_for_virtualenv(
constraint_path: Path | None,
) -> str:
"""
Parses the constraints file referenced by `dependency_constraint_flags` and returns a dict where
the key is the package name, and the value is the constraint version.
If a package version cannot be found, its value is "embed" meaning that virtualenv will install
its bundled version, already available locally.
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
{macos|windows}.setup_python function.
"""
if constraint_path:
assert constraint_path.exists()
with constraint_path.open(encoding="utf-8") as constraint_file:
for line_ in constraint_file:
line = line_.strip()
if not line:
continue
if line.startswith("#"):
continue
try:
requirement = Requirement(line)
package = requirement.name
if (
package != "pip"
or requirement.url is not None
or requirement.marker is not None
or len(requirement.extras) != 0
or len(requirement.specifier) != 1
):
continue
specifier = next(iter(requirement.specifier))
if specifier.operator != "==":
continue
return specifier.version
except InvalidRequirement:
continue
return "embed"
def virtualenv(
version: str,
python: Path,
venv_path: Path,
dependency_constraint: Path | None,
*,
use_uv: bool,
env: dict[str, str] | None = None,
pip_version: str | None = None,
) -> dict[str, str]:
"""
Create a virtual environment. If `use_uv` is True,
dependency_constraint_flags are ignored since nothing is installed in the
venv. Otherwise, pip is installed.
"""
# virtualenv may fail if this is a symlink.
python = python.resolve()
assert python.exists()
if use_uv:
call("uv", "venv", venv_path, "--python", python)
else:
virtualenv_app, virtualenv_version = _ensure_virtualenv(version)
if pip_version is None:
pip_version = _parse_pip_constraint_for_virtualenv(dependency_constraint)
additional_flags = [f"--pip={pip_version}", "--no-setuptools"]
if virtualenv_version < Version("20.31") or Version(version) < Version("3.9"):
additional_flags.append("--no-wheel")
# Using symlinks to pre-installed seed packages is really the fastest way to get a virtual
# environment. The initial cost is a bit higher but reusing is much faster.
# Windows does not always allow symlinks so just disabling for now.
# Requires pip>=19.3 so disabling for "embed" because this means we don't know what's the
# version of pip that will end-up installed.
# c.f. https://virtualenv.pypa.io/en/latest/cli_interface.html#section-seeder
if not _IS_WIN and pip_version != "embed" and Version(pip_version) >= Version("19.3"):
additional_flags.append("--symlink-app-data")
call(
sys.executable,
"-sS", # just the stdlib, https://github.com/pypa/virtualenv/issues/2133#issuecomment-1003710125
virtualenv_app,
"--activators=",
"--no-periodic-update",
*additional_flags,
"--python",
python,
venv_path,
)
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":
call(
"pip",
"install",
"--upgrade",
"pip",
*constraint_flags(dependency_constraint),
env=venv_env,
cwd=venv_path,
)
return venv_env
def find_uv() -> Path | None:
# Prefer uv in our environment
with contextlib.suppress(ImportError, FileNotFoundError):
# pylint: disable-next=import-outside-toplevel
from uv import find_uv_bin
return Path(find_uv_bin())
uv_on_path = shutil.which("uv")
return Path(uv_on_path) if uv_on_path else None
@@ -1,29 +1,47 @@
from __future__ import annotations
import os import os
import platform as platform_module import platform as platform_module
import shutil import shutil
import subprocess import subprocess
import textwrap import textwrap
from collections.abc import MutableMapping, Set from collections.abc import MutableMapping, Sequence, Set
from dataclasses import dataclass from dataclasses import dataclass
from functools import cache from functools import lru_cache
from pathlib import Path from pathlib import Path
from typing import assert_never
from filelock import FileLock from filelock import FileLock
from packaging.version import Version
from .. import errors from . import errors
from ..architecture import Architecture from ._compat.typing import assert_never
from ..environment import ParsedEnvironment from .architecture import Architecture
from ..frontend import BuildFrontendConfig, BuildFrontendName, get_build_frontend_extra_flags from .environment import ParsedEnvironment
from ..logger import log from .logger import log
from ..options import Options from .options import Options
from ..selector import BuildSelector from .typing import PathOrStr
from ..util import resources from .util import (
from ..util.cmd import call, shell CIBW_CACHE_PATH,
from ..util.file import CIBW_CACHE_PATH, copy_test_sources, download, extract_zip, move_file BuildFrontendConfig,
from ..util.helpers import prepare_command, unwrap BuildFrontendName,
from ..util.packaging import combine_constraints, find_compatible_wheel, get_pip_version BuildSelector,
from ..venv import constraint_flags, find_uv, virtualenv call,
combine_constraints,
download,
extract_zip,
find_compatible_wheel,
find_uv,
get_build_verbosity_extra_flags,
get_pip_version,
move_file,
prepare_command,
read_python_configs,
shell,
split_config_settings,
test_fail_cwd_file,
unwrap,
virtualenv,
)
def get_nuget_args( def get_nuget_args(
@@ -58,16 +76,13 @@ class PythonConfiguration:
url: str | None = None url: str | None = None
def all_python_configurations() -> list[PythonConfiguration]:
config_dicts = resources.read_python_configs("windows")
return [PythonConfiguration(**item) for item in config_dicts]
def get_python_configurations( def get_python_configurations(
build_selector: BuildSelector, build_selector: BuildSelector,
architectures: Set[Architecture], architectures: Set[Architecture],
) -> list[PythonConfiguration]: ) -> list[PythonConfiguration]:
python_configurations = all_python_configurations() full_python_configs = read_python_configs("windows")
python_configurations = [PythonConfiguration(**item) for item in full_python_configs]
map_arch = {"32": Architecture.x86, "64": Architecture.AMD64, "ARM64": Architecture.ARM64} map_arch = {"32": Architecture.x86, "64": Architecture.AMD64, "ARM64": Architecture.ARM64}
@@ -81,7 +96,7 @@ def get_python_configurations(
return python_configurations return python_configurations
@cache @lru_cache(maxsize=None)
def _ensure_nuget() -> Path: def _ensure_nuget() -> Path:
nuget = CIBW_CACHE_PATH / "nuget.exe" nuget = CIBW_CACHE_PATH / "nuget.exe"
with FileLock(str(nuget) + ".lock"): with FileLock(str(nuget) + ".lock"):
@@ -123,20 +138,6 @@ def install_pypy(tmp: Path, arch: str, url: str) -> Path:
return installation_path / "python.exe" return installation_path / "python.exe"
def install_graalpy(tmp: Path, url: str) -> Path:
zip_filename = url.rsplit("/", 1)[-1]
extension = ".zip"
assert zip_filename.endswith(extension)
installation_path = CIBW_CACHE_PATH / zip_filename[: -len(extension)]
with FileLock(str(installation_path) + ".lock"):
if not installation_path.exists():
graalpy_zip = tmp / zip_filename
download(url, graalpy_zip)
# Extract to the parent directory because the zip file still contains a directory
extract_zip(graalpy_zip, installation_path.parent)
return installation_path / "bin" / "graalpy.exe"
def setup_setuptools_cross_compile( def setup_setuptools_cross_compile(
tmp: Path, tmp: Path,
python_configuration: PythonConfiguration, python_configuration: PythonConfiguration,
@@ -224,15 +225,10 @@ 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,
dependency_constraint: Path | None, dependency_constraint_flags: Sequence[PathOrStr],
environment: ParsedEnvironment, environment: ParsedEnvironment,
build_frontend: BuildFrontendName, build_frontend: BuildFrontendName,
) -> tuple[Path, dict[str, str]]: ) -> tuple[Path, dict[str, str]]:
@@ -253,17 +249,16 @@ def setup_python(
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(tmp, python_configuration.arch, python_configuration.url) base_python = install_pypy(tmp, python_configuration.arch, python_configuration.url)
elif implementation_id.startswith("gp"):
base_python = install_graalpy(tmp, python_configuration.url or "")
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): use_uv = (
build_frontend = "build" build_frontend == "build[uv]"
and Version(python_configuration.version) >= Version("3.8")
use_uv = build_frontend == "build[uv]" and not python_configuration.identifier.startswith("pp38-")
)
uv_path = find_uv() uv_path = find_uv()
log.step("Setting up build environment...") log.step("Setting up build environment...")
@@ -272,7 +267,7 @@ def setup_python(
python_configuration.version, python_configuration.version,
base_python, base_python,
venv_path, venv_path,
dependency_constraint, dependency_constraint_flags,
use_uv=use_uv, use_uv=use_uv,
) )
@@ -281,26 +276,41 @@ def setup_python(
env["PYTHON_ARCH"] = python_configuration.arch env["PYTHON_ARCH"] = python_configuration.arch
env["PIP_DISABLE_PIP_VERSION_CHECK"] = "1" env["PIP_DISABLE_PIP_VERSION_CHECK"] = "1"
# upgrade pip to the version matching our constraints
# if necessary, reinstall it to ensure that it's available on PATH as 'pip.exe'
if not use_uv:
call(
"python",
"-m",
"pip",
"install",
"--upgrade",
"pip",
*dependency_constraint_flags,
env=env,
cwd=venv_path,
)
# 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)
# check what Python version we're on # check what Python version we're on
call("where", "python", env=env)
call("python", "--version", env=env)
call("python", "-c", "\"import struct; print(struct.calcsize('P') * 8)\"", env=env)
where_python = call("where", "python", env=env, capture_stdout=True).splitlines()[0].strip() where_python = call("where", "python", env=env, capture_stdout=True).splitlines()[0].strip()
print(where_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", "--version", 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
if not use_uv: if not use_uv:
assert (venv_path / "Scripts" / "pip.exe").exists() assert (venv_path / "Scripts" / "pip.exe").exists()
where_pip = call("where", "pip", env=env, capture_stdout=True).splitlines()[0].strip() where_pip = call("where", "pip", env=env, capture_stdout=True).splitlines()[0].strip()
print(where_pip)
if where_pip.strip() != str(venv_path / "Scripts" / "pip.exe"): if where_pip.strip() != str(venv_path / "Scripts" / "pip.exe"):
msg = "pip available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert pip above it." msg = "pip available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert pip above it."
raise errors.FatalError(msg) raise errors.FatalError(msg)
call("pip", "--version", env=env) call("pip", "--version", env=env)
log.step("Installing build tools...") log.step("Installing build tools...")
@@ -310,7 +320,7 @@ def setup_python(
"install", "install",
"--upgrade", "--upgrade",
"build[virtualenv]", "build[virtualenv]",
*constraint_flags(dependency_constraint), *dependency_constraint_flags,
env=env, env=env,
) )
elif build_frontend == "build[uv]": elif build_frontend == "build[uv]":
@@ -321,7 +331,7 @@ def setup_python(
"install", "install",
"--upgrade", "--upgrade",
"build[virtualenv]", "build[virtualenv]",
*constraint_flags(dependency_constraint), *dependency_constraint_flags,
env=env, env=env,
) )
@@ -330,49 +340,6 @@ def setup_python(
setup_setuptools_cross_compile(tmp, python_configuration, python_libs_base, env) setup_setuptools_cross_compile(tmp, python_configuration, python_libs_base, env)
setup_rust_cross_compile(tmp, python_configuration, python_libs_base, env) setup_rust_cross_compile(tmp, python_configuration, python_libs_base, env)
if implementation_id.startswith("gp"):
# GraalPy fails to discover compilers, setup the relevant environment
# variables. Adapted from
# https://github.com/microsoft/vswhere/wiki/Start-Developer-Command-Prompt
# Remove when https://github.com/oracle/graalpython/issues/492 is fixed.
vcpath = subprocess.check_output(
[
Path(os.environ["PROGRAMFILES(X86)"])
/ "Microsoft Visual Studio"
/ "Installer"
/ "vswhere.exe",
"-products",
"*",
"-latest",
"-property",
"installationPath",
],
text=True,
).strip()
log.notice(f"Discovering Visual Studio for GraalPy at {vcpath}")
env.update(
dict(
[
envvar.strip().split("=", 1)
for envvar in subprocess.check_output(
[
f"{vcpath}\\Common7\\Tools\\vsdevcmd.bat",
"-no_logo",
"-arch=amd64",
"-host_arch=amd64",
"&&",
"set",
],
shell=True,
text=True,
env=env,
)
.strip()
.split("\n")
]
)
)
return base_python, env return base_python, env
@@ -400,9 +367,12 @@ 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 or BuildFrontendConfig("build") build_frontend = build_options.build_frontend or BuildFrontendConfig("pip")
use_uv = (
use_uv = build_frontend.name == "build[uv]" and can_use_uv(config) build_frontend.name == "build[uv]"
and Version(config.version) >= Version("3.8")
and not config.identifier.startswith("pp38-")
)
log.build_start(config.identifier) log.build_start(config.identifier)
identifier_tmp_dir = tmp_path / config.identifier identifier_tmp_dir = tmp_path / config.identifier
@@ -410,20 +380,23 @@ def build(options: Options, tmp_path: Path) -> None:
built_wheel_dir = identifier_tmp_dir / "built_wheel" built_wheel_dir = identifier_tmp_dir / "built_wheel"
repaired_wheel_dir = identifier_tmp_dir / "repaired_wheel" repaired_wheel_dir = identifier_tmp_dir / "repaired_wheel"
constraints_path = build_options.dependency_constraints.get_for_python_version( dependency_constraint_flags: Sequence[PathOrStr] = []
version=config.version, if build_options.dependency_constraints:
tmp_dir=identifier_tmp_dir, dependency_constraint_flags = [
) "-c",
build_options.dependency_constraints.get_for_python_version(config.version),
]
# install Python # install Python
base_python, env = setup_python( base_python, env = setup_python(
identifier_tmp_dir / "build", identifier_tmp_dir / "build",
config, config,
constraints_path, dependency_constraint_flags,
build_options.environment, build_options.environment,
build_frontend.name, build_frontend.name,
) )
pip_version = None if use_uv else get_pip_version(env) if not use_uv:
pip_version = get_pip_version(env)
compatible_wheel = find_compatible_wheel(built_wheels, config.identifier) compatible_wheel = find_compatible_wheel(built_wheels, config.identifier)
if compatible_wheel: if compatible_wheel:
@@ -446,34 +419,23 @@ def build(options: Options, tmp_path: Path) -> None:
log.step("Building wheel...") log.step("Building wheel...")
built_wheel_dir.mkdir() built_wheel_dir.mkdir()
extra_flags = get_build_frontend_extra_flags( extra_flags = split_config_settings(
build_frontend, build_options.build_verbosity, build_options.config_settings build_options.config_settings, build_frontend.name
) )
extra_flags += build_frontend.args
if (
config.identifier.startswith("gp")
and build_frontend.name == "build"
and "--no-isolation" not in extra_flags
and "-n" not in extra_flags
):
# GraalPy fails to discover its standard library when a venv is created
# from a virtualenv seeded executable. See
# https://github.com/oracle/graalpython/issues/491 and remove this once
# fixed upstream.
log.notice(
"Disabling build isolation to workaround GraalPy bug. If the build fails, consider using pip or build[uv] as build frontend."
)
shell("graalpy -m pip install setuptools wheel", env=env)
extra_flags = [*extra_flags, "-n"]
build_env = env.copy() build_env = env.copy()
if pip_version is not None: if not use_uv:
build_env["VIRTUALENV_PIP"] = pip_version build_env["VIRTUALENV_PIP"] = pip_version
if constraints_path: if build_options.dependency_constraints:
constraints_path = build_options.dependency_constraints.get_for_python_version(
config.version
)
combine_constraints(build_env, constraints_path, identifier_tmp_dir) combine_constraints(build_env, constraints_path, identifier_tmp_dir)
if build_frontend.name == "pip": if build_frontend.name == "pip":
extra_flags += get_build_verbosity_extra_flags(build_options.build_verbosity)
# Path.resolve() is needed. Without it pip wheel may try to fetch package from pypi.org # Path.resolve() is needed. Without it pip wheel may try to fetch package from pypi.org
# see https://github.com/pypa/cibuildwheel/pull/369 # see https://github.com/pypa/cibuildwheel/pull/369
call( call(
@@ -488,9 +450,11 @@ def build(options: Options, tmp_path: Path) -> None:
env=build_env, env=build_env,
) )
elif build_frontend.name == "build" or build_frontend.name == "build[uv]": elif build_frontend.name == "build" or build_frontend.name == "build[uv]":
if not 0 <= build_options.build_verbosity < 2:
msg = f"build_verbosity {build_options.build_verbosity} is not supported for build frontend. Ignoring."
log.warning(msg)
if use_uv and "--no-isolation" not in extra_flags and "-n" not in extra_flags: if use_uv and "--no-isolation" not in extra_flags and "-n" not in extra_flags:
extra_flags.append("--installer=uv") extra_flags.append("--installer=uv")
call( call(
"python", "python",
"-m", "-m",
@@ -523,10 +487,7 @@ def build(options: Options, tmp_path: Path) -> None:
else: else:
shutil.move(str(built_wheel), repaired_wheel_dir) shutil.move(str(built_wheel), repaired_wheel_dir)
try: repaired_wheel = next(repaired_wheel_dir.glob("*.whl"))
repaired_wheel = next(repaired_wheel_dir.glob("*.whl"))
except StopIteration:
raise errors.RepairStepProducedNoWheelError() from None
if repaired_wheel.name in {wheel.name for wheel in built_wheels}: if repaired_wheel.name in {wheel.name for wheel in built_wheels}:
raise errors.AlreadyBuiltWheelError(repaired_wheel.name) raise errors.AlreadyBuiltWheelError(repaired_wheel.name)
@@ -547,16 +508,29 @@ def build(options: Options, tmp_path: Path) -> None:
log.step("Testing wheel...") log.step("Testing wheel...")
# set up a virtual environment to install and test from, to make sure # set up a virtual environment to install and test from, to make sure
# there are no dependencies that were pulled in at build time. # there are no dependencies that were pulled in at build time.
if not use_uv:
call("pip", "install", "virtualenv", *dependency_constraint_flags, env=env)
venv_dir = identifier_tmp_dir / "venv-test" venv_dir = identifier_tmp_dir / "venv-test"
virtualenv_env = virtualenv(
config.version, if use_uv:
base_python, call("uv", "venv", venv_dir, f"--python={base_python}", env=env)
venv_dir, else:
None, # Use pip version from the initial env to ensure determinism
use_uv=use_uv, venv_args = ["--no-periodic-update", f"--pip={pip_version}"]
env=env, # In Python<3.12, setuptools & wheel are installed as well, use virtualenv embedded ones
pip_version=pip_version, if Version(config.version) < Version("3.12"):
venv_args.extend(("--setuptools=embed", "--wheel=embed"))
call("python", "-m", "virtualenv", *venv_args, venv_dir, env=env)
virtualenv_env = env.copy()
virtualenv_env["PATH"] = os.pathsep.join(
[
str(venv_dir / "Scripts"),
virtualenv_env["PATH"],
]
) )
virtualenv_env["VIRTUAL_ENV"] = str(venv_dir)
# check that we are using the Python from the virtual environment # check that we are using the Python from the virtual environment
call("where", "python", env=virtualenv_env) call("where", "python", env=virtualenv_env)
@@ -588,21 +562,13 @@ def build(options: Options, tmp_path: Path) -> None:
# and not the repo code) # and not the repo code)
test_command_prepared = prepare_command( test_command_prepared = prepare_command(
build_options.test_command, build_options.test_command,
project=Path.cwd(), project=Path(".").resolve(),
package=options.globals.package_dir.resolve(), package=options.globals.package_dir.resolve(),
wheel=repaired_wheel, wheel=repaired_wheel,
) )
if build_options.test_sources: test_cwd = identifier_tmp_dir / "test_cwd"
test_cwd = identifier_tmp_dir / "test_cwd" test_cwd.mkdir()
test_cwd.mkdir() (test_cwd / "test_fail.py").write_text(test_fail_cwd_file.read_text())
copy_test_sources(
build_options.test_sources,
build_options.package_dir,
test_cwd,
)
else:
# There are no test sources. Run the tests in the project directory.
test_cwd = Path.cwd()
shell(test_command_prepared, cwd=test_cwd, env=virtualenv_env) shell(test_command_prepared, cwd=test_cwd, env=virtualenv_env)
@@ -612,7 +578,7 @@ def build(options: Options, tmp_path: Path) -> None:
moved_wheel = move_file(repaired_wheel, output_wheel) moved_wheel = move_file(repaired_wheel, output_wheel)
if moved_wheel != output_wheel.resolve(): if moved_wheel != output_wheel.resolve():
log.warning( log.warning(
f"{repaired_wheel} was moved to {moved_wheel} instead of {output_wheel}" "{repaired_wheel} was moved to {moved_wheel} instead of {output_wheel}"
) )
built_wheels.append(output_wheel) built_wheels.append(output_wheel)
-119
View File
@@ -4,119 +4,8 @@ title: Changelog
# Changelog # Changelog
### v3.0.0
Not yet released, but available for testing
#### v3.0.0b1
_19 May 2025_
- 🌟 Adds the ability to [build wheels for iOS](https://cibuildwheel.pypa.io/en/latest/platforms/#ios)! Set the [`platform` option](https://cibuildwheel.pypa.io/en/latest/options/#platform) to `ios` on a Mac with the iOS toolchain to try it out!
- 🌟 Adds support for the GraalPy interpreter! Enable for your project using the [`enable` option](https://cibuildwheel.pypa.io/en/latest/options/#enable). (#1538)
- ✨ Adds CPython 3.14 support, under the [`enable` option](https://cibuildwheel.pypa.io/en/latest/options/#enable) `cpython-prerelease`. This version of cibuildwheel uses 3.14.0b1.
_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 the [test-sources option](https://cibuildwheel.pypa.io/en/latest/options/#test-sources). \[discussion about the test cwd change and how to use to come!\]
- ✨ Added `dependency-versions` inline syntax (#2123)
- 🛠 EOL manylinux options can no longer be specified by their shortname. Full OCI URL can still be used for these images, if you wish (#2316)
- 🛠 Build environments no longer have setuptools and wheel preinstalled. (#2329)
- ⚠️ PyPy wheels no longer built by default, due to a change to our options system. To continue building PyPy wheels, you'll now need to set the [`enable` option](https://cibuildwheel.pypa.io/en/latest/options/#enable) to `pypy` or `pypy-eol`.
- ⚠️ Dropped official support for Appveyor. If it was working for you before, it will probably continue to do so, but we can't be sure, because our CI doesn't run there anymore. (#2386)
- 📚 A reorganisation of the docs, and numerous updates (#2280)
### v2.23.3
_26 April 2025_
- 🛠 Dependency updates, including Python 3.13.3 (#2371)
### v2.23.2
_24 March 2025_
- 🐛 Workaround an issue with pyodide builds when running cibuildwheel with a Python that was installed via UV (#2328 via #2331)
- 🛠 Dependency updates, including a manylinux update that fixes an ['undefined symbol' error](https://github.com/pypa/manylinux/issues/1760) in gcc-toolset (#2334)
-
### v2.23.1
_15 March 2025_
- ⚠️ Added warnings when the shorthand values `manylinux1`, `manylinux2010`, `manylinux_2_24`, and `musllinux_1_1` are used to specify the images in linux builds. The shorthand to these (unmaintainted) images will be removed in v3.0. If you want to keep using these images, explicitly opt-in using the full image URL, which can be found in [this file](https://github.com/pypa/cibuildwheel/blob/v2.23.1/cibuildwheel/resources/pinned_docker_images.cfg). (#2312)
- 🛠 Dependency updates, including a manylinux update which fixes an [issue with rustup](https://github.com/pypa/cibuildwheel/issues/2303). (#2315)
### v2.23.0
_1 March 2025_
- ✨ Adds official support for the new GitHub Actions Arm runners. In fact these worked out-of-the-box, now we include them in our tests and example configs. (#2135 via #2281)
- ✨ Adds support for building PyPy 3.11 wheels (#2268 via #2281)
- 🛠 Adopts the beta pypa/manylinux image for armv7l builds (#2269 via #2281)
- 🛠 Dependency updates, including Pyodide 0.27 (#2117 and #2281)
### v2.22.0
_23 November 2024_
- 🌟 Added a new `CIBW_ENABLE`/`enable` feature that replaces `CIBW_FREETHREADED_SUPPORT`/`free-threaded-support` and `CIBW_PRERELEASE_PYTHONS` with a system that supports both. In cibuildwheel 3, this will also include a PyPy setting and the deprecated options will be removed. (#2048)
- 🌟 [Dependency groups](https://peps.python.org/pep-0735/) are now supported for tests. Use `CIBW_TEST_GROUPS`/`test-groups` to specify groups in `[dependency-groups]` for testing. (#2063)
- 🌟 Support for the experimental Ubuntu-based ARMv7l manylinux image (#2052)
- ✨ Show a warning when cibuildwheel is run from Python 3.10 or older; cibuildwheel 3.0 will require Python 3.11 or newer as host (#2050)
- 🐛 Fix issue with stderr interfering with checking the docker version (#2074)
- 🛠 Python 3.9 is now used in `CIBW_BEFORE_ALL`/`before-all` on linux, replacing 3.8, which is now EoL (#2043)
- 🛠 Error messages for producing a pure-Python wheel are slightly more informative (#2044)
- 🛠 Better error when `uname -m` fails on ARM (#2049)
- 🛠 Better error when repair fails and docs for abi3audit on Windows (#2058)
- 🛠 Better error when `manylinux-interpreters ensure` fails (#2066)
- 🛠 Update Pyodide to 0.26.4, and adapt to the unbundled pyodide-build (now 0.29) (#2090)
- 🛠 Now cibuildwheel uses dependency-groups for development dependencies (#2064, #2085)
- 📚 Docs updates and tidy ups (#2061, #2067, #2072)
### v2.21.3
_9 October 2024_
- 🛠 Update CPython 3.13 to 3.13.0 final release (#2032)
- 📚 Docs updates and tidy ups (#2035)
### v2.21.2
_2 October 2024_
- ✨ Adds support for building 32-bit armv7l wheels on musllinux. On a Linux system with emulation set up, set [CIBW_ARCHS](https://cibuildwheel.pypa.io/en/stable/options/#archs) to `armv7l` on Linux to try it out if you're interested! (#2017)
- 🐛 Fix Linux Podman builds on some systems (#2016)
- ✨ Adds official support for running on Python 3.13 (#2026)
- 🛠 Update CPython 3.13 to 3.13.0rc3 (#2029)
Note: the default [manylinux image](https://cibuildwheel.pypa.io/en/stable/options/#linux-image) is **scheduled to change** from `manylinux2014` to `manylinux_2_28` in a cibuildwheel release on or after **6th May 2025** - you can set the value now to avoid getting upgraded if you want. (#1992)
### v2.21.1
_16 September 2024_
- 🐛 Fix a bug in the Linux build, where files copied to the container would have invalid ownership permissions (#2007)
- 🐛 Fix a bug on Windows where cibuildwheel would call upon `uv` to install dependencies for versions of CPython that it does not support (#2005)
- 🐛 Fix a bug where `uv 0.4.10` would not use the right Python when testing on Linux. (#2008)
- 🛠 Bump our documentation pins, fixes an issue with a missing package (#2011)
### v2.21.0
_13 September 2024_
- ⚠️ Update CPython 3.12 to 3.12.6, which changes the macOS minimum deployment target on CPython 3.12 from macOS 10.9 to macOS 10.13 (#1998)
- 🛠 Changes the behaviour when inheriting `config-settings` in TOML overrides - rather than extending each key, which is rarely useful, individual keys will override previously set values. (#1803)
- 🛠 Update CPython 3.13 to 3.13.0rc2 (#1998)
- ✨ Adds support for multiarch OCI images (#1961)
- 🐛 Fixes some bugs building Linux wheels on macOS. (#1961)
- ⚠️ Changes the minimum version of Docker/Podman to Docker API version 1.43, Podman API version 3. The only mainstream runner this should affect is Travis Graviton2 runners - if so you can [upgrade your version of Docker](https://github.com/pypa/cibuildwheel/pull/1961#issuecomment-2304060019). (#1961)
### v2.20.0 ### v2.20.0
_4 August 2024_
- 🌟 CPython 3.13 wheels are now built by default - without the `CIBW_PRERELEASE_PYTHONS` flag. It's time to build and upload these wheels to PyPI! This release includes CPython 3.13.0rc1, which is guaranteed to be ABI compatible with the final release. Free-threading is still behind a flag/config option. (#1950) - 🌟 CPython 3.13 wheels are now built by default - without the `CIBW_PRERELEASE_PYTHONS` flag. It's time to build and upload these wheels to PyPI! This release includes CPython 3.13.0rc1, which is guaranteed to be ABI compatible with the final release. Free-threading is still behind a flag/config option. (#1950)
- ✨ Provide a `CIBW_ALLOW_EMPTY` environment variable as an alternative to the command line flag. (#1937) - ✨ Provide a `CIBW_ALLOW_EMPTY` environment variable as an alternative to the command line flag. (#1937)
- 🐛 Don't use uv on PyPy3.8 on Windows, it stopped working starting in 0.2.25. Note that PyPy 3.8 is EoL. (#1868) - 🐛 Don't use uv on PyPy3.8 on Windows, it stopped working starting in 0.2.25. Note that PyPy 3.8 is EoL. (#1868)
@@ -127,8 +16,6 @@ _4 August 2024_
### v2.19.2 ### v2.19.2
_2 July 2024_
- 🐛 Update manylinux2014 pins to versions that support past-EoL CentOS 7 mirrors. (#1917) - 🐛 Update manylinux2014 pins to versions that support past-EoL CentOS 7 mirrors. (#1917)
- 🐛 Support `--no-isolation` with `build[uv]` build-frontend. (#1889) - 🐛 Support `--no-isolation` with `build[uv]` build-frontend. (#1889)
- 🛠 Provide attestations for releases at <https://github.com/pypa/cibuildwheel/attestations>. (#1916) - 🛠 Provide attestations for releases at <https://github.com/pypa/cibuildwheel/attestations>. (#1916)
@@ -141,8 +28,6 @@ _2 July 2024_
### v2.19.1 ### v2.19.1
_13 June 2024_
- 🐛 Don't require setup-python on GHA for Pyodide (#1868) - 🐛 Don't require setup-python on GHA for Pyodide (#1868)
- 🐛 Specify full python path for uv (fixes issue in 0.2.10 & 0.2.11) (#1881) - 🐛 Specify full python path for uv (fixes issue in 0.2.10 & 0.2.11) (#1881)
- 🛠 Update for pip 24.1b2 on CPython 3.13. (#1879) - 🛠 Update for pip 24.1b2 on CPython 3.13. (#1879)
@@ -152,8 +37,6 @@ _13 June 2024_
### v2.19.0 ### v2.19.0
_10 June 2024_
See the [release post](https://iscinumpy.dev/post/cibuildwheel-2-19-0/) for more info on new features! See the [release post](https://iscinumpy.dev/post/cibuildwheel-2-19-0/) for more info on new features!
- 🌟 Add Pyodide platform. Set with `--platform pyodide` or `CIBW_PLATFORM: pyodide` on Linux with a host Python 3.12 to build WebAssembly wheels. Not accepted on PyPI currently, but usable directly in a website using Pyodide, for live docs, etc. (#1456, #1859) - 🌟 Add Pyodide platform. Set with `--platform pyodide` or `CIBW_PLATFORM: pyodide` on Linux with a host Python 3.12 to build WebAssembly wheels. Not accepted on PyPI currently, but usable directly in a website using Pyodide, for live docs, etc. (#1456, #1859)
@@ -172,8 +55,6 @@ See the [release post](https://iscinumpy.dev/post/cibuildwheel-2-19-0/) for more
### v2.18.1 ### v2.18.1
_20 May 2024_
- 🌟 Add free-threaded Linux and Windows builds for 3.13. New identifiers `cp313t-*`, new option `CIBW_FREE_THREADED_SUPPORT`/`tool.cibuildwheel.free-threaded-support` required to opt-in. [See the docs](https://cibuildwheel.pypa.io/en/stable/options/#free-threaded-support) for more information. (#1831) - 🌟 Add free-threaded Linux and Windows builds for 3.13. New identifiers `cp313t-*`, new option `CIBW_FREE_THREADED_SUPPORT`/`tool.cibuildwheel.free-threaded-support` required to opt-in. [See the docs](https://cibuildwheel.pypa.io/en/stable/options/#free-threaded-support) for more information. (#1831)
- ✨ The `container-engine` is now a build (non-global) option. (#1792) - ✨ The `container-engine` is now a build (non-global) option. (#1792)
- 🛠 The build backend for cibuildwheel is now hatchling. (#1297) - 🛠 The build backend for cibuildwheel is now hatchling. (#1297)
-175
View File
@@ -1,175 +0,0 @@
---
title: Configuring a CI service
---
## Configuring a CI service
cibuildwheel works on many popular CI services. Others may work, but it will depend on the software installed on the CI machine/image. See the [platforms page](platforms.md) for details.
### GitHub Actions [linux/mac/windows] {: #github-actions}
To build Linux, macOS, and Windows wheels using GitHub Actions, create a `.github/workflows/build_wheels.yml` file in your repo.
!!! tab "Action"
For GitHub Actions, `cibuildwheel` provides an action you can use. This is
concise and enables easier auto updating via GitHub's Dependabot; see
[Automatic updates](faq.md#automatic-updates).
> .github/workflows/build_wheels.yml
```yaml
{% include "../examples/github-minimal.yml" %}
```
Use `env:` to pass [build options](options.md) and `with:` to set
`package-dir: .`, `output-dir: wheelhouse` and `config-file: ''`
locations (those values are the defaults).
!!! tab "pipx"
The GitHub Actions runners have pipx installed, so you can easily build in
just one line. This is internally how the action works; the main benefit of
the action form is easy updates via GitHub's Dependabot.
> .github/workflows/build_wheels.yml
```yaml
{% include "../examples/github-pipx.yml" %}
```
!!! tab "Generic"
This is the most generic form using setup-python and pip; it looks the most
like the other CI examples. If you want to avoid having setup that takes
advantage of GitHub Actions features or pipx being preinstalled, this might
appeal to you.
> .github/workflows/build_wheels.yml
{%
include-markdown "../README.md"
start="<!--generic-github-start-->"
end="<!--generic-github-end-->"
%}
Commit this file, and push to GitHub - either to your default branch, or to a PR branch. The build should start automatically.
For more info on this file, check out the [docs](https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions).
[`examples/github-deploy.yml`](https://github.com/pypa/cibuildwheel/blob/main/examples/github-deploy.yml) extends this minimal example to include iOS and Pyodide builds, and a demonstration of how to automatically upload the built wheels to PyPI.
### Azure Pipelines [linux/mac/windows] {: #azure-pipelines}
To build Linux, Mac, and Windows wheels on Azure Pipelines, create a `azure-pipelines.yml` file in your repo.
> azure-pipelines.yml
```yaml
{% include "../examples/azure-pipelines-minimal.yml" %}
```
Commit this file, enable building of your repo on Azure Pipelines, and push.
Wheels will be stored for you and available through the Pipelines interface. For more info on this file, check out the [docs](https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema).
### Travis CI [linux/windows] {: #travis-ci}
To build Linux and Windows wheels on Travis CI, create a `.travis.yml` file in your repo.
> .travis.yml
```yaml
{% include "../examples/travis-ci-minimal.yml" %}
```
Commit this file, enable building of your repo on Travis CI, and push.
Then setup a deployment method by following the [Travis CI deployment docs](https://docs.travis-ci.com/user/deployment/), or see [Delivering to PyPI](deliver-to-pypi.md). For more info on `.travis.yml`, check out the [docs](https://docs.travis-ci.com/).
[`examples/travis-ci-deploy.yml`](https://github.com/pypa/cibuildwheel/blob/main/examples/travis-ci-deploy.yml) extends this minimal example with a demonstration of how to automatically upload the built wheels to PyPI.
### CircleCI [linux/mac] {: #circleci}
To build Linux and Mac wheels on CircleCI, create a `.circleci/config.yml` file in your repo,
> .circleci/config.yml
```yaml
{% include "../examples/circleci-minimal.yml" %}
```
Commit this file, enable building of your repo on CircleCI, and push.
!!! note
CircleCI doesn't enable free macOS containers for open source by default, but you can ask for access. See [here](https://circleci.com/docs/2.0/oss/#overview) for more information.
CircleCI will store the built wheels for you - you can access them from the project console. Check out the CircleCI [docs](https://circleci.com/docs/2.0/configuration-reference/#section=configuration) for more info on this config file.
### Gitlab CI [linux] {: #gitlab-ci}
To build Linux wheels on Gitlab CI, create a `.gitlab-ci.yml` file in your repo,
> .gitlab-ci.yml
```yaml
{% include "../examples/gitlab-minimal.yml" %}
```
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.
### 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).
### Other CI services
#### AppVeyor {: #appveyor}
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).
## 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).
<script>
document.addEventListener('DOMContentLoaded', function() {
$('.toctree-l2>a, .rst-content h3').each(function(i, el) {
var text = $(el).text()
var match = text.match(/(.*) \[([a-z/]+)\]/);
if (match) {
var iconHTML = $.map(match[2].split('/'), function(ident) {
switch (ident) {
case 'linux':
return '<i class="fa fa-linux" aria-hidden="true"></i>'
case 'windows':
return '<i class="fa fa-windows" aria-hidden="true"></i>'
case 'mac':
return '<i class="fa fa-apple" aria-hidden="true"></i>'
}
}).join(' ');
$(el).append(
$('<div>')
.append(iconHTML)
.css({float: 'right'})
)
$(el).contents()
.filter(function(){ return this.nodeType == 3; }).first()
.replaceWith(match[1]);
}
});
});
</script>
-238
View File
@@ -1,238 +0,0 @@
# Configuration methods
cibuildwheel can either be configured using environment variables, or from
config file such as `pyproject.toml`.
This page describes how to set options. For a full list of available options, see the [options reference](options.md).
## Environment variables {: #environment-variables}
Environment variables can be set in your CI config. For example, to configure
cibuildwheel to run tests, add the following YAML to your CI config file:
!!! tab "GitHub Actions"
> .github/workflows/*.yml ([docs](https://help.github.com/en/actions/configuring-and-managing-workflows/using-environment-variables)) (can be global, in job, or in step)
```yaml
env:
CIBW_TEST_REQUIRES: pytest
CIBW_TEST_COMMAND: "pytest ./tests"
```
!!! tab "Azure Pipelines"
> azure-pipelines.yml ([docs](https://docs.microsoft.com/en-us/azure/devops/pipelines/process/variables))
```yaml
variables:
CIBW_TEST_REQUIRES: pytest
CIBW_TEST_COMMAND: "pytest ./tests"
```
!!! tab "Travis CI"
> .travis.yml ([docs](https://docs.travis-ci.com/user/environment-variables/))
```yaml
env:
global:
- CIBW_TEST_REQUIRES=pytest
- CIBW_TEST_COMMAND="pytest ./tests"
```
!!! tab "AppVeyor"
> appveyor.yml ([docs](https://www.appveyor.com/docs/build-configuration/#environment-variables))
```yaml
environment:
global:
CIBW_TEST_REQUIRES: pytest
CIBW_TEST_COMMAND: "pytest {project}\\tests"
```
!!! tab "CircleCI"
> .circleci/config.yml ([docs](https://circleci.com/docs/2.0/configuration-reference/#environment))
```yaml
jobs:
job_name:
environment:
CIBW_TEST_REQUIRES: pytest
CIBW_TEST_COMMAND: "pytest ./tests"
```
!!! tab "Gitlab CI"
> .gitlab-ci.yml ([docs](https://docs.gitlab.com/ee/ci/variables/README.html#create-a-custom-variable-in-gitlab-ciyml))
```yaml
linux:
variables:
CIBW_TEST_REQUIRES: pytest
CIBW_TEST_COMMAND: "pytest ./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 ./tests"
```
## Configuration file {: #configuration-file}
You can configure cibuildwheel with a config file, such as `pyproject.toml`.
Options have the same names as the environment variable overrides, but are
placed in `[tool.cibuildwheel]` and are lower case, with dashes, following
common [TOML][https://toml.io] practice. Anything placed in subsections `linux`, `windows`,
`macos`, or `pyodide` will only affect those platforms. Lists can be used
instead of strings for items that are naturally a list. Multiline strings also
work just like in the environment variables. Environment variables will take
precedence if defined.
The example above using environment variables could have been written like this:
```toml
[tool.cibuildwheel]
test-requires = "pytest"
test-command = "pytest ./tests"
```
The complete set of defaults for the current version of cibuildwheel are shown below:
```toml
{% include "../cibuildwheel/resources/defaults.toml" %}
```
!!! tip
Static configuration works across all CI systems, and can be used locally if
you run `cibuildwheel --platform linux`. This is preferred, but environment
variables are better if you need to change per-matrix element
(`CIBW_BUILD` is often in this category, for example), or if you cannot or do
not want to change a `pyproject.toml` file. You can specify a different file to
use with `--config-file` on the command line, as well.
## Configuration overrides {: #overrides }
One feature specific to the configuration files is the ability to override
settings based on selectors. To use, add a ``tool.cibuildwheel.overrides``
array, and specify a ``select`` string. Then any options you set will only
apply to items that match that selector. These are applied in order, with later
matches overriding earlier ones if multiple selectors match. Environment
variables always override static configuration.
A few of the options below have special handling in overrides. A different
`before-all` will trigger a new container to launch on Linux, and cannot be
overridden on macOS or Windows. Overriding the image on linux will also
trigger new containers, one per image. Some commands are not supported;
`output-dir`, build/skip/test_skip selectors, and architectures cannot be
overridden.
You can specify a table of overrides in `inherit={}`, any list or table in this
list will inherit from previous overrides or the main configuration. The valid
options are `"none"` (the default), `"append"`, and `"prepend"`.
#### Examples:
```toml
[tool.cibuildwheel.linux]
before-all = "yum install mylib"
test-command = "echo 'installed'"
[[tool.cibuildwheel.overrides]]
select = "*-musllinux*"
before-all = "apk add mylib"
```
This example will override the before-all command on musllinux only, but will
still run the test-command. Note the double brackets, this is an array in TOML,
which means it can be given multiple times.
```toml
[tool.cibuildwheel]
# Normal options, etc.
manylinux-x86_64-image = "manylinux_2_34"
[[tool.cibuildwheel.overrides]]
select = "cp38-*"
manylinux-x86_64-image = "manylinux2014"
[[tool.cibuildwheel.overrides]]
select = "cp3{9,10}-*"
manylinux-x86_64-image = "manylinux_2_28"
```
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
(like 3.10).
```toml
[tool.cibuildwheel]
environment = {FOO="BAR", "HAM"="EGGS"}
test-command = ["pyproject"]
[[tool.cibuildwheel.overrides]]
select = "cp311*"
inherit.test-command = "prepend"
test-command = ["pyproject-before"]
inherit.environment="append"
environment = {FOO="BAZ", "PYTHON"="MONTY"}
[[tool.cibuildwheel.overrides]]
select = "cp311*"
inherit.test-command = "append"
test-command = ["pyproject-after"]
```
This example will provide the command `"pyproject-before && pyproject && pyproject-after"`
on Python 3.11, and will have `environment = {FOO="BAZ", "PYTHON"="MONTY", "HAM"="EGGS"}`.
## Extending existing options {: #inherit }
In the TOML configuration, you can choose how tables and lists are inherited.
By default, all values are overridden completely (`"none"`) but sometimes you'd
rather `"append"` or `"prepend"` to an existing list or table. You can do this
with the `inherit` table in overrides. For example, if you want to add an environment
variable for CPython 3.11, without `inherit` you'd have to repeat all the
original environment variables in the override. With `inherit`, it's just:
```toml
[[tool.cibuildwheel.overrides]]
select = "cp311*"
inherit.environment = "append"
environment.NEWVAR = "Added!"
```
For a table, `"append"` will replace a key if it exists, while `"prepend"` will
only add a new key, older keys take precedence.
Lists are also supported (and keep in mind that commands are lists). For
example, you can print a message before and after a wheel is repaired:
```toml
[[tool.cibuildwheel.overrides]]
select = "*"
inherit.repair-wheel-command = "prepend"
repair-wheel-command = "echo 'Before repair'"
[[tool.cibuildwheel.overrides]]
select = "*"
inherit.repair-wheel-command = "append"
repair-wheel-command = "echo 'After repair'"
```
As seen in this example, you can have multiple overrides match - they match top
to bottom, with the config being accumulated. If you need platform-specific
inheritance, you can use `select = "*-????linux_*"` for Linux, `select =
"*-win_*"` for Windows, and `select = "*-macosx_*"` for macOS. As always,
environment variables will completely override any TOML configuration.
+12 -32
View File
@@ -17,11 +17,11 @@ Everyone contributing to the cibuildwheel project is expected to follow the [PSF
- `cibuildwheel` should wrap the complexity of wheel building. - `cibuildwheel` should wrap the complexity of wheel building.
- The user interface to `cibuildwheel` is the build script (e.g. `.travis.yml`). Feature additions should not increase the complexity of this script. - The user interface to `cibuildwheel` is the build script (e.g. `.travis.yml`). Feature additions should not increase the complexity of this script.
- Options should be environment variables (these lend themselves better to YML config files). They should be prefixed with `CIBW_`. - Options should be environment variables (these lend themselves better to YML config files). They should be prefixed with `CIBW_`.
- Options should be generalised to all platforms. If platform-specific options are required, they should be namespaced e.g. `CIBW_TEST_COMMAND_MACOS` - Options should be generalise to all platforms. If platform-specific options are required, they should be namespaced e.g. `CIBW_TEST_COMMAND_MACOS`
Other notes: Other notes:
- The platforms are very similar, until they're not. I'd rather have straightforward code than totally DRY code, so let's keep airy platform abstractions to a minimum. - The platforms are very similar, until they're not. I'd rather have straight-forward code than totally DRY code, so let's keep airy platform abstractions to a minimum.
- I might want to break the options into a shared config file one day, so that config is more easily shared. That has motivated some of the design decisions. - I might want to break the options into a shared config file one day, so that config is more easily shared. That has motivated some of the design decisions.
### cibuildwheel's relationship with build errors ### cibuildwheel's relationship with build errors
@@ -36,7 +36,7 @@ So, if we can, I'd like to improve the experience on errors as well. In [this](h
### Running the tests ### Running the tests
When making a change to the codebase, you can run tests locally for quicker feedback than the CI runs on a PR. You can run them directly, but the easiest way to run tests is using [nox](https://nox.thea.codes/). When making a change to the codebase, you can run tests locally for quicker feedback than the CI runs on a PR. You can [run them directly](#making-a-venv), but the easiest way to run tests is using [nox](https://nox.thea.codes/).
You can run all the tests locally by doing: You can run all the tests locally by doing:
@@ -78,17 +78,18 @@ nox -s tests -- test -k before_build
A few notes- A few notes-
- Because they run inside a container, Linux tests can run on all platforms where Docker is installed, so they're convenient for running integration tests locally. Set the `--platform` flag on pytest to do this: `nox -s tests -- test --platform linux`. - Because they run inside a container, Linux tests can run on all platforms where Docker is installed, so they're convenient for running integration tests locally. Set CIBW_PLATFORM to do this: `CIBW_PLATFORM=linux nox -s tests -- test`.
- Running the macOS integration tests requires _system installs_ of Python from python.org for all the versions that are tested. We won't attempt to install these when running locally, but you can do so manually using the URL in the error message that is printed when the install is not found. - Running the macOS integration tests requires _system installs_ of Python from python.org for all the versions that are tested. We won't attempt to install these when running locally, but you can do so manually using the URL in the error message that is printed when the install is not found.
- The ['enable groups'](options.md#enable) run by default are just 'cpython-prerelease' and 'cpython-freethreading'. You can add other groups like pypy or graalpy by passing the `--enable` argument to pytest, i.e. `nox -s tests -- test --enable pypy`. On GitHub PRs, you can add a label to the PR to enable these groups. #### Making a venv
#### Running pytest directly More advanced users might prefer to invoke pytest directly-
More advanced users might prefer to invoke pytest directly. Set up a [dev environment](#setting-up-a-dev-environment), then,
```bash ```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -e .[dev]
# run the unit tests # run the unit tests
pytest unit_test pytest unit_test
# run the whole integration test suite # run the whole integration test suite
@@ -101,7 +102,7 @@ CIBW_PLATFORM=linux pytest test -k test_build_frontend_args
### Linting, docs ### Linting, docs
Most developer tasks have a nox interface. This allows you to very simply run tasks without worrying about setting up a development environment (as shown below). This is slower than setting up a development environment and reusing it, but has the (important) benefit of being highly reproducible; an earlier run does not affect a current run, or anything else on your machine. Most developer tasks have a nox interface. This allows you to very simply run tasks without worrying about setting up a development environment (as shown below). This is a slower than setting up a development environment and reusing it, but has the (important) benefit of being highly reproducible; an earlier run does not affect a current run, or anything else on your machine.
You can see a list of sessions by typing `nox -l`; here are a few common ones: You can see a list of sessions by typing `nox -l`; here are a few common ones:
@@ -112,33 +113,12 @@ nox -s docs # Build and serve the documentation
nox -s build # Make SDist and wheel nox -s build # Make SDist and wheel
``` ```
More advanced users can run the update scripts: More advanced users can run the update scripts. `update_pins` should work directly, but `update_constraints` needs all versions of Python installed. If you don't want to do that locally, a fast way to run it to use docker to run nox:
```console ```console
nox -s update_constraints # update all constraints files in cibuildwheel/resources docker run --rm -itv $PWD:/src -w /src quay.io/pypa/manylinux_2_24_x86_64:latest pipx run nox -s update_constraints
nox -s update_pins # update tools, python interpreters & docker images used by cibuildwheel
``` ```
### Setting up a dev environment
A dev environment isn't required for any of the `nox` tasks above. However, a dev environment is still useful, to be able to point an editor at, and a few other jobs.
cibuildwheel uses dependency groups. Set up a dev environment with UV by doing
```bash
uv sync
```
Or, if you're not using `uv`, you can do:
```bash
python3 -m venv .venv
source .venv/bin/activate
pipx run dependency-groups dev | xargs pip install -e.
```
Your virtualenv is at `.venv`.
## Maintainer notes ## Maintainer notes
### Testing sample configs ### Testing sample configs
+7 -6
View File
@@ -7,19 +7,20 @@ title: Modern C++ standards
Building Python wheels with modern C++ standards (C++11 and later) requires a few tricks. Building Python wheels with modern C++ standards (C++11 and later) requires a few tricks.
## manylinux2014 and C++20 ## manylinux1 and C++14
The old `manylinux1` image (based on CentOS 5) contains a version of GCC and libstdc++ that only supports C++11 and earlier standards. There are however ways to compile wheels with the C++14 standard (and later): https://github.com/pypa/manylinux/issues/118
The past end-of-life `manylinux2014` image (based on CentOS 7) contains a version of GCC and libstdc++ that only supports C++17 and earlier standards. `manylinux2010` and `manylinux2014` are newer and support all C++ standards (up to C++17).
`manylinux_2_28` are newer and support all C++ standards (up to C++20).
## macOS and deployment target versions ## macOS and deployment target versions
The [`MACOSX_DEPLOYMENT_TARGET` environment variable](platforms.md#macos-version-compatibility) is used to set the minimum deployment target for macOS. OS X/macOS allows you to specify a so-called "deployment target" version that will ensure backwards compatibility with older versions of macOS. One way to do this is by setting the `MACOSX_DEPLOYMENT_TARGET` environment variable.
However, to enable modern C++ standards, the deployment target needs to be set high enough (since older OS X/macOS versions did not have the necessary modern C++ standard library). However, to enable modern C++ standards, the deployment target needs to be set high enough (since older OS X/macOS versions did not have the necessary modern C++ standard library).
To get C++17 support, Xcode 9.3+ is needed, requiring at least macOS 10.13 on the build machine. To use C++17 library features and link against the C++ runtime library, set `MACOSX_DEPLOYMENT_TARGET` to `"10.13"` or `"10.14"` (or higher) - macOS 10.13 offers partial C++17 support (e.g., the filesystem header is in experimental, offering `#include <experimental/filesystem>` instead of `#include <filesystem>`); macOS 10.14 has full C++17 support. CPython 3.12+ require 10.13+ anyway. To get C++11 and C++14 support, `MACOSX_DEPLOYMENT_TARGET` needs to be set to (at least) `"10.9"`. By default, `cibuildwheel` already does this, building 64-bit-only wheels for macOS 10.9 and later.
To get C++17 support, Xcode 9.3+ is needed, requiring at least macOS 10.13 on the build machine. To use C++17 library features and link against the C++ runtime library, set `MACOSX_DEPLOYMENT_TARGET` to `"10.13"` or `"10.14"` (or higher) - macOS 10.13 offers partial C++17 support (e.g., the filesystem header is in experimental, offering `#include <experimental/filesystem>` instead of `#include <filesystem>`); macOS 10.14 has full C++17 support.
However, if only C++17 compiler and standard template library (STL) features are used (not needing a C++17 runtime) it might be possible to set `MACOSX_DEPLOYMENT_TARGET` to a lower value, such as `"10.9"`. To find out if this is the case, try compiling and running with a lower `MACOSX_DEPLOYMENT_TARGET`: if C++17 features are used that require a more recent deployment target, building the wheel should fail. However, if only C++17 compiler and standard template library (STL) features are used (not needing a C++17 runtime) it might be possible to set `MACOSX_DEPLOYMENT_TARGET` to a lower value, such as `"10.9"`. To find out if this is the case, try compiling and running with a lower `MACOSX_DEPLOYMENT_TARGET`: if C++17 features are used that require a more recent deployment target, building the wheel should fail.
+1
View File
@@ -18,6 +18,7 @@
"enum": [ "enum": [
"github", "github",
"travisci", "travisci",
"appveyor",
"circleci", "circleci",
"gitlab", "gitlab",
"cirrusci", "cirrusci",
+6 -6
View File
@@ -4,7 +4,7 @@
# stars: GitHub repo (optional, if different from package, such as for Twisted) # stars: GitHub repo (optional, if different from package, such as for Twisted)
# pypi: The pypi name, if different from the GitHub package name # pypi: The pypi name, if different from the GitHub package name
# os: Operating system list, [windows, apple, linux] (optional) # os: Operating system list, [windows, apple, linux] (optional)
# ci: [github, azurepipelines, circleci, gitlab, travisci, cirrusci] (optional) # ci: [appveyor, github, azurepipelines, circleci, gitlab, travisci, cirrusci] (optional)
# notes: (text, optional) # notes: (text, optional)
- name: abess - name: abess
@@ -19,11 +19,11 @@
ci: [github] ci: [github]
os: [windows, apple, linux] os: [windows, apple, linux]
- name: pyinstrument - name: pyinstrument_cext
gh: joerick/pyinstrument gh: joerick/pyinstrument_cext
ci: [github] ci: [travisci, appveyor]
os: [windows, apple, linux] os: [windows, apple, linux]
notes: Python profiler with a C extension. No external dependencies. notes: A simple C extension, without external dependencies
- name: websockets - name: websockets
gh: python-websockets/websockets gh: python-websockets/websockets
@@ -182,7 +182,7 @@
- name: python-rapidjson - name: python-rapidjson
gh: python-rapidjson/python-rapidjson gh: python-rapidjson/python-rapidjson
ci: [travisci, gitlab] ci: [travisci, gitlab, appveyor]
os: [windows, linux] os: [windows, linux]
- name: jq.py - name: jq.py
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" role="img" viewBox="0 0 24 24" width="16px" fill="#606060"><title>AppVeyor icon</title><path d="M 12,0 C 18.6,0 24,5.4 24,12 24,18.6 18.6,24 12,24 5.4,24 0,18.6 0,12 0,5.4 5.4,0 12,0 Z m 2.94,14.34 C 16.26,12.66 16.08,10.26 14.4,9 12.78,7.74 10.38,8.04 9,9.72 7.68,11.4 7.86,13.8 9.54,15.06 c 1.68,1.26 4.08,0.96 5.4,-0.72 z m -6.42,7.8 c 0.72,0.3 2.28,0.6 3.06,0.6 l 5.22,-7.56 c 1.68,-2.52 1.26,-5.94 -1.08,-7.8 -2.1,-1.68 -5.04,-1.62 -7.14,0 l -7.26,5.58 c 0.18,1.92 0.72,2.88 0.72,2.94 l 4.14,-4.5 c -0.3,1.98 0.42,4.02 2.1,5.28 1.44,1.14 3.18,1.44 4.86,1.08 z"/></svg>

After

Width:  |  Height:  |  Size: 613 B

+33 -30
View File
@@ -6,23 +6,42 @@ title: Delivering to PyPI
After you've built your wheels, you'll probably want to deliver them to PyPI. After you've built your wheels, you'll probably want to deliver them to PyPI.
## Manual method
On your development machine, install [pipx](https://pypa.github.io/pipx/) and do the following:
```bash
# Either download the SDist from your CI, or make it:
# Clear out your 'dist' folder.
rm -rf dist
# Make a source distribution
pipx run build --sdist
# 🏃🏻
# Go and download your wheel files from wherever you put them. e.g. your CI
# provider can be configured to store them for you. Put them all into the
# 'dist' folder.
# Upload using 'twine'
pipx run twine upload dist/*
```
## Automatic method ## Automatic method
If you don't need much control over the release of a package, you can set up If you don't need much control over the release of a package, you can set up
your CI provider to deliver the wheels straight to PyPI. You just need to bump the cibuildwheel to deliver the wheels straight to PyPI. You just need to bump the
version and tag it. version and tag it.
The exact way to set it up varies, depending on which CI provider you're using. But generally, the process goes like this: ### Generic instructions
- Build your wheels with cibuildwheel Make your SDist with the [build](https://github.com/pypa/build) tool, and your wheels with cibuildwheel. If you can make the files available as
- Build an sdist with the [build](https://github.com/pypa/build) tool downloadable artifacts, this make testing before releases easier (depending on your CI provider's options). The "publish" job/step should collect the
- Check that the current CI run is happening during a release (e.g. it's in response to a vXX tag) files, and then run `twine upload <paths>` (possibly via [pipx](https://github.com/pypa/pipx)); this should only happen on tags or "releases".
- Collect these assets together onto one runner
- Upload them to PyPI using `twine upload <paths>`
### GitHub Actions ### GitHub Actions
GitHub actions has pipx in all the runners as a supported package manager, as well as `pypa/gh-action-pypi-publish`, which can be used instead of twine. Alongside your existing job(s) that runs cibuildwheel to make wheels, you will probably want to build an sdist: GitHub actions has pipx in all the runners as a supported package manager, as
well as several useful actions. Alongside your existing job(s) that runs cibuildwheel to make wheels, you will probably want to build an SDist:
```yaml ```yaml
make_sdist: make_sdist:
@@ -45,6 +64,8 @@ GitHub actions has pipx in all the runners as a supported package manager, as we
Then, you need to publish the artifacts that the previous jobs have built. This final job should run only on release or tag, depending on your preference. It gathers the artifacts from the sdist and wheel jobs and uploads them to PyPI. The release environment (`pypi` in the example below) will be created the first time this workflow runs. Then, you need to publish the artifacts that the previous jobs have built. This final job should run only on release or tag, depending on your preference. It gathers the artifacts from the sdist and wheel jobs and uploads them to PyPI. The release environment (`pypi` in the example below) will be created the first time this workflow runs.
This requires setting this GitHub workflow in your project's PyPI settings (for a [new project](https://docs.pypi.org/trusted-publishers/creating-a-project-through-oidc)/[existing project](https://docs.pypi.org/trusted-publishers/adding-a-publisher)).
```yaml ```yaml
upload_all: upload_all:
needs: [build_wheels, make_sdist] needs: [build_wheels, make_sdist]
@@ -63,11 +84,13 @@ Then, you need to publish the artifacts that the previous jobs have built. This
- uses: pypa/gh-action-pypi-publish@release/v1 - uses: pypa/gh-action-pypi-publish@release/v1
``` ```
The above example uses PyPI Trusted Publishing to deliver the wheels, which requires some configuration on the PyPI side for a [new project](https://docs.pypi.org/trusted-publishers/creating-a-project-through-oidc) or an [existing project](https://docs.pypi.org/trusted-publishers/adding-a-publisher). You can use Dependabot to keep the publish action up to date. You should use Dependabot to keep the publish action up to date. In the above
example, the same name (the default, "artifact" is used for all upload-artifact
runs, so we can just download all of the in one step into a common directory.
See See
[`examples/github-deploy.yml`](https://github.com/pypa/cibuildwheel/blob/main/examples/github-deploy.yml) [`examples/github-deploy.yml`](https://github.com/pypa/cibuildwheel/blob/main/examples/github-deploy.yml)
for an example configuration that automatically uploads wheels to PyPI. Also see for an example configuration that automatically upload wheels to PyPI. Also see
[scikit-hep.org/developer/gha_wheels](https://scikit-hep.org/developer/gha_wheels) [scikit-hep.org/developer/gha_wheels](https://scikit-hep.org/developer/gha_wheels)
for a complete guide. for a complete guide.
@@ -76,23 +99,3 @@ for a complete guide.
See See
[`examples/travis-ci-deploy.yml`](https://github.com/pypa/cibuildwheel/blob/main/examples/travis-ci-deploy.yml) [`examples/travis-ci-deploy.yml`](https://github.com/pypa/cibuildwheel/blob/main/examples/travis-ci-deploy.yml)
for an example configuration. for an example configuration.
## Manual method
On your development machine, install [pipx](https://pypa.github.io/pipx/) and do the following:
```bash
# Either download the SDist from your CI, or make it:
# Clear out your 'dist' folder.
rm -rf dist
# Make a source distribution
pipx run build --sdist
# 🏃🏻
# Go and download your wheel files from wherever you put them. e.g. your CI
# provider can be configured to store them for you. Put them all into the
# 'dist' folder.
# Upload using 'twine'
pipx run twine upload dist/*
```
+52 -13
View File
@@ -258,24 +258,63 @@ h1, h2, h3, h4, h5, h6 {
font-size: 80%; font-size: 80%;
} }
.rst-content table.docutils td code,
.rst-content table.docutils th code, /* expand all the toctree entries */
.rst-content table.field-list td code, .wy-menu-vertical .toctree-l1.current .toctree-l2>ul,
.rst-content table.field-list th code, .wy-menu-vertical .toctree-l2.current .toctree-l3>ul,
.wy-table td code, .wy-menu-vertical .toctree-l3.current .toctree-l4>ul,
.wy-table th code { .wy-menu-vertical .toctree-l4.current .toctree-l5>ul,
/* table elements are already made smaller, the code styling on top of that makes the text too small */ .wy-menu-vertical .toctree-l5.current .toctree-l6>ul,
font-size: 82.5%; .wy-menu-vertical .toctree-l6.current .toctree-l7>ul,
.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,
.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,
.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,
.wy-menu-vertical .toctree-l10.current .toctree-l11>ul {
display: block;
} }
/* hide all the buttons */
/* hide the l1 buttons */ .wy-menu-vertical li.current>a button.toctree-expand,
.wy-menu-vertical li.toctree-l1.current>a button.toctree-expand, .wy-menu-vertical li.on a button.toctree-expand,
.wy-menu-vertical li.toctree-l1.on>a button.toctree-expand, .wy-menu-vertical li.toctree-l2 button.toctree-expand,
.wy-menu-vertical li.toctree-l1>a button.toctree-expand { .wy-menu-vertical li a button.toctree-expand {
display: none; display: none;
} }
/* toctree layout improvements */
.wy-menu-vertical a {
padding: 0.4em 1.2em;
}
.wy-menu-vertical li.current>a,
.wy-menu-vertical li.on a {
font-weight: normal;
padding: 0.4em 1.2em;
/* border-right: 1px solid #f0f0f0; */
}
.wy-menu-vertical li.current a {
padding: 0.4em 1.2em;
}
.wy-menu-vertical li.toctree-l3 a,
.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a {
padding-left: 1.8em;
}
.wy-menu-vertical li.toctree-l2.current>a {
padding: 0.4em 1.2em;
}
.wy-menu-vertical li.toctree-l2.current>a,
.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a {
background: transparent;
}
.wy-menu-vertical li.toctree-l2.current>a:hover,
.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a:hover {
background: #d6d6d6;
}
.wy-menu-vertical li.current>a.current {
background: #fafafa !important;
}
.wy-menu-vertical li.current a {
}
/* word wrap in table cells */ /* word wrap in table cells */
.wy-table-responsive table td, .wy-table-responsive table th { .wy-table-responsive table td, .wy-table-responsive table th {
-51
View File
@@ -58,54 +58,3 @@ while (true) {
// this will catch infinite loops which can occur when editing the above // this will catch infinite loops which can occur when editing the above
if (tabConversionIterations++ > 1000) throw 'too many iterations' if (tabConversionIterations++ > 1000) throw 'too many iterations'
} }
/**
* Redirects the current page based on the path and fragment identifier (hash) in the URL.
*
* Example usage:
* fragmentRedirect([
* { source: 'setup/#github-actions', destination: 'ci-services' }
* { source: 'faq/#macosx', destination: 'platforms#apple' }
* ])
*/
function fragmentRedirect(redirects) {
const href = window.location.href;
const hash = window.location.hash;
for (const redirect of redirects) {
const source = redirect.source;
const destination = redirect.destination;
if (endswith(href, source)) {
// Redirect to the destination path, with the same fragment identifier
// specified in the destination path, otherwise, keep the same hash
// from the current URL.
const destinationIncludesHash = destination.includes('#');
let newUrl = href.replace(source, destination);
if (!destinationIncludesHash) {
newUrl += hash;
}
console.log('Redirecting to:', newUrl);
window.location.replace(newUrl);
return
}
}
}
function endswith(str, suffix) {
return str.indexOf(suffix, str.length - suffix.length) !== -1;
}
fragmentRedirect([
{ source: 'setup/#github-actions', destination: 'ci-services/' },
{ source: 'setup/#azure-pipelines', destination: 'ci-services/' },
{ source: 'setup/#travis-ci', destination: 'ci-services/' },
{ source: 'setup/#appveyor', destination: 'ci-services/' },
{ source: 'setup/#circleci', destination: 'ci-services/' },
{ source: 'setup/#gitlab-ci', destination: 'ci-services/' },
{ source: 'setup/#cirrus-ci', destination: 'ci-services/' },
{ source: 'faq/#linux-builds-in-containers', destination: 'platforms/#linux-containers' },
{ source: 'faq/#apple-silicon', destination: 'platforms/#macos-architectures' },
{ source: 'faq/#windows-arm64', destination: 'platforms/#windows-arm64' },
]);
+105 -16
View File
@@ -6,6 +6,88 @@ title: Tips and tricks
## Tips ## Tips
### Linux builds in containers
Linux wheels are built in [`manylinux`/`musllinux` containers](https://github.com/pypa/manylinux) to provide binary compatible wheels on Linux, according to [PEP 600](https://www.python.org/dev/peps/pep-0600/) / [PEP 656](https://www.python.org/dev/peps/pep-0656/). Because of this, when building with `cibuildwheel` on Linux, a few things should be taken into account:
- Programs and libraries are not installed on the CI runner host, but rather should be installed inside the container - using `yum` for `manylinux2010` or `manylinux2014`, `apt-get` for `manylinux_2_24`, `dnf` for `manylinux_2_28` and `apk` for `musllinux_1_1` or `musllinux_1_2`, or manually. The same goes for environment variables that are potentially needed to customize the wheel building.
`cibuildwheel` supports this by providing the [`CIBW_ENVIRONMENT`](options.md#environment) and [`CIBW_BEFORE_ALL`](options.md#before-all) options to setup the build environment inside the running container.
- The project directory is copied into the container as `/project`, the output directory for the wheels to be copied out is `/output`. In general, this is handled transparently by `cibuildwheel`. For a more finegrained level of control however, the root of the host file system is mounted as `/host`, allowing for example to access shared files, caches, etc. on the host file system. Note that `/host` is not available on CircleCI and GitLab CI due to their Docker policies.
- Alternative Docker images can be specified with the `CIBW_MANYLINUX_*_IMAGE`/`CIBW_MUSLLINUX_*_IMAGE` options to allow for a custom, preconfigured build environment for the Linux builds. See [options](options.md#linux-image) for more details.
### Building macOS wheels for Apple Silicon {: #apple-silicon}
`cibuildwheel` supports both native builds and cross-compiling between `arm64` (Apple Silicon) and `x86_64` (Intel) architectures, including the cross-compatible `universal2` format.
#### Overview of Mac architectures
You have several choices for wheels for Python 3.8+:
##### `x86_64`
The traditional wheel for Apple, loads on Intel machines, and on
Apple Silicon when running Python under Rosetta 2 emulation.
Due to a change in naming, Pip 20.3+ (or an installer using packaging 20.5+)
is required to install a binary wheel on macOS Big Sur.
##### `arm64`
The native wheel for macOS on Apple Silicon.
Requires Pip 20.3+ (or packaging 20.5+) to install.
##### `universal2`
This wheel contains both architectures, causing it to be up to twice the
size (data files do not get doubled, only compiled code). It requires
Pip 20.3 (Packaging 20.6+) to load on Intel, and Pip 21.0.1 (Packaging 20.9+)
to load on Apple Silicon.
The dual-architecture `universal2` has a few benefits, but a key benefit
to a universal wheel is that a user can bundle these wheels into an
application and ship a single binary.
However, if you have a large library, then you might prefer to ship
the two single-arch wheels instead - `x86_64` and `arm64`. In rare cases,
you might want to build all three, but in that case, pip will not download
the universal wheels, because it prefers the most specific wheel
available.
#### What to provide?
Generally speaking, because Pip 20.3 is required for the `universal2` wheel,
most packages should provide both `x86_64` and one of `universal2`/`arm64`
wheels. When Pip 20.3+ is common on macOS, then it might be possible to ship
only the `universal2` wheel.
Opinions vary on which of arch-specific or `universal2` wheels are best - some packagers prefer `universal2` because it's one wheel for all Mac users, so simpler, and easier to build into apps for downstream users. However, because they contain code for both architectures, their file size is larger, meaning they consume more disk space and bandwidth, and are harder to build for some projects.
See [GitHub issue 1333](https://github.com/pypa/cibuildwheel/issues/1333) for more discussion.
#### How?
It's easiest to build `x86_64` wheels on `x86_64` runners, and `arm64` wheels on `arm64` runners.
On GitHub Actions, `macos-14` runners are `arm64`, and `macos-13` runners are `x86_64`. So all you need to do is ensure both are in your build matrix.
#### Cross-compiling
If your CI provider doesn't offer arm64 runners yet, or you want to create `universal2`, you'll have to cross-compile. Cross-compilation can be enabled by adding extra archs to the [`CIBW_ARCHS_MACOS` option](options.md#archs) - e.g. `CIBW_ARCHS_MACOS="x86_64 universal2"`. Cross-compilation is provided by Xcode toolchain v12.2+.
Regarding testing,
- On an arm64 runner, it is possible to test x86_64 wheels and both parts of a universal2 wheel using Rosetta 2 emulation.
- On an x86_64 runner, arm64 code can be compiled but it can't be tested. `cibuildwheel` will raise a warning to notify you of this - these warnings can be silenced by skipping testing on these platforms: `CIBW_TEST_SKIP: "*_arm64 *_universal2:arm64"`.
!!! note
If your project uses **Poetry** as a build backend, cross-compiling on macOS [does not currently work](https://github.com/python-poetry/poetry/issues/7107). In some cases arm64 wheels can be built but their tags will be incorrect, with the platform tag showing `x86_64` instead of `arm64`.
As a workaround, the tag can be fixed before running delocate to repair the wheel. The [`wheel tags`](https://wheel.readthedocs.io/en/stable/reference/wheel_tags.html) command is ideal for this. See [this workflow](https://gist.github.com/anderssonjohan/49f07e33fc5cb2420515a8ac76dc0c95#file-build-pendulum-wheels-yml-L39-L53) for an example usage of `wheel tags`.
### Building Linux wheels for non-native archs using emulation {: #emulation} ### Building Linux wheels for non-native archs using emulation {: #emulation}
cibuildwheel supports building non-native architectures on Linux, via cibuildwheel supports building non-native architectures on Linux, via
@@ -51,7 +133,7 @@ myextension = Extension(
### Automatic updates using Dependabot {: #automatic-updates} ### Automatic updates using Dependabot {: #automatic-updates}
Selecting a moving target (like the latest release) is generally a bad idea in CI. If something breaks, you can't tell whether it was your code or an upstream update that caused the breakage, and in a worst-case scenario, it could occur during a release. Selecting a moving target (like the latest release) is generally a bad idea in CI. If something breaks, you can't tell whether it was your code or an upstream update that caused the breakage, and in a worse-case scenario, it could occur during a release.
There are two suggested methods for keeping cibuildwheel up to date that instead involve scheduled pull requests using GitHub's Dependabot. There are two suggested methods for keeping cibuildwheel up to date that instead involve scheduled pull requests using GitHub's Dependabot.
@@ -60,7 +142,7 @@ There are two suggested methods for keeping cibuildwheel up to date that instead
If you use GitHub Actions for builds, you can use cibuildwheel as an action: If you use GitHub Actions for builds, you can use cibuildwheel as an action:
```yaml ```yaml
uses: pypa/cibuildwheel@v3.0.0b1 uses: pypa/cibuildwheel@v2.20.0
``` ```
This is a composite step that just runs cibuildwheel using pipx. You can set command-line options as `with:` parameters, and use `env:` as normal. This is a composite step that just runs cibuildwheel using pipx. You can set command-line options as `with:` parameters, and use `env:` as normal.
@@ -82,7 +164,7 @@ The second option, and the only one that supports other CI systems, is using a `
```bash ```bash
# requirements-cibw.txt # requirements-cibw.txt
cibuildwheel==3.0.0b1 cibuildwheel==2.20.0
``` ```
Then your install step would have `python -m pip install -r requirements-cibw.txt` in it. Your `.github/dependabot.yml` file could look like this: Then your install step would have `python -m pip install -r requirements-cibw.txt` in it. Your `.github/dependabot.yml` file could look like this:
@@ -102,7 +184,7 @@ This will also try to update other pins in all requirement files, so be sure you
### Alternatives to cibuildwheel options {: #cibw-options-alternatives} ### Alternatives to cibuildwheel options {: #cibw-options-alternatives}
cibuildwheel provides lots of opportunities to configure the build cibuildwheel provides lots of opportunities to configure the build
environment. However, you might consider adding this build configuration into environment. However, you might consider adding this build configuration into
the package itself - in general, this is preferred, because users of your the package itself - in general, this is preferred, because users of your
package 'sdist' will also benefit. package 'sdist' will also benefit.
@@ -165,11 +247,15 @@ Your build might need some compiler flags to be set through environment variable
Consider incorporating these into your package, for example, in `setup.py` using [`extra_compile_args` or Consider incorporating these into your package, for example, in `setup.py` using [`extra_compile_args` or
`extra_link_args`](https://docs.python.org/3/distutils/setupscript.html#other-options). `extra_link_args`](https://docs.python.org/3/distutils/setupscript.html#other-options).
### Python 2.7 / PyPy2 wheels
See the [cibuildwheel version 1 docs](https://cibuildwheel.pypa.io/en/1.x/) for information about building Python 2.7 or PyPy2 wheels. There are lots of tricks and workaround there that are no longer required for Python 3 in cibuildwheel 2.
## Troubleshooting ## Troubleshooting
If your wheel didn't compile, you might have a mistake in your config. If your wheel didn't compile, you might have a mistake in your config.
To quickly test your config without doing a git push and waiting for your code to build on CI, you can [test the Linux build in a local Docker container](platforms.md#linux). To quickly test your config without doing a git push and waiting for your code to build on CI, you can [test the Linux build in a local Docker container](setup.md#local).
### Missing dependencies ### Missing dependencies
@@ -208,16 +294,7 @@ CIBW_BEFORE_ALL_WINDOWS: rustup target add i686-pc-windows-msvc
CIBW_ENVIRONMENT_LINUX: "PATH=$HOME/.cargo/bin:$PATH" CIBW_ENVIRONMENT_LINUX: "PATH=$HOME/.cargo/bin:$PATH"
``` ```
Rust's minimum macOS target is 10.12, while CPython supports 10.9 before Rust does not provide Cargo for musllinux 32-bit, so that needs to be skipped:
Python 3.12, so you'll need to raise the minimum:
```toml
[tool.cibuildwheel.macos.environment]
MACOSX_DEPLOYMENT_TARGET = "10.12"
```
And Rust does not provide Cargo for musllinux 32-bit, so that needs to be
skipped:
```toml ```toml
[tool.cibuildwheel] [tool.cibuildwheel]
@@ -226,6 +303,10 @@ skip = ["*-musllinux_i686"]
Also see [maturin-action](https://github.com/PyO3/maturin-action) which is optimized for Rust wheels, builds the non-Python Rust modules once, and can cross-compile (and can build 32-bit musl, for example). Also see [maturin-action](https://github.com/PyO3/maturin-action) which is optimized for Rust wheels, builds the non-Python Rust modules once, and can cross-compile (and can build 32-bit musl, for example).
### macOS: ModuleNotFoundError
Calling cibuildwheel from a python3 script and getting a `ModuleNotFoundError`? Due to a (fixed) [bug](https://bugs.python.org/issue22490) in CPython, you'll need to [unset the `__PYVENV_LAUNCHER__` variable](https://github.com/pypa/cibuildwheel/issues/133#issuecomment-478288597) before activating a venv.
### macOS: 'No module named XYZ' errors after running cibuildwheel ### macOS: 'No module named XYZ' errors after running cibuildwheel
`cibuildwheel` on Mac installs the distributions from Python.org system-wide during its operation. This is necessary, but it can cause some confusing errors after cibuildwheel has finished. `cibuildwheel` on Mac installs the distributions from Python.org system-wide during its operation. This is necessary, but it can cause some confusing errors after cibuildwheel has finished.
@@ -247,7 +328,7 @@ Solutions to this vary, but the simplest is to use pipx:
# most runners have pipx preinstalled, but in case you don't # most runners have pipx preinstalled, but in case you don't
python3 -m pip install pipx python3 -m pip install pipx
pipx run cibuildwheel==3.0.0b1 --output-dir wheelhouse pipx run cibuildwheel==2.20.0 --output-dir wheelhouse
pipx run twine upload wheelhouse/*.whl pipx run twine upload wheelhouse/*.whl
``` ```
@@ -330,3 +411,11 @@ To add the `/d2FH4-` flag to a standard `setup.py` using `setuptools`, the `extr
``` ```
To investigate the dependencies of a C extension (i.e., the `.pyd` file, a DLL in disguise) on Windows, [Dependency Walker](http://www.dependencywalker.com/) is a great tool. For diagnosing a failing import, the [dlltracer](https://pypi.org/project/dlltracer/) tool may also provide additional details. To investigate the dependencies of a C extension (i.e., the `.pyd` file, a DLL in disguise) on Windows, [Dependency Walker](http://www.dependencywalker.com/) is a great tool. For diagnosing a failing import, the [dlltracer](https://pypi.org/project/dlltracer/) tool may also provide additional details.
### Windows ARM64 builds {: #windows-arm64}
`cibuildwheel` supports cross-compiling `ARM64` wheels on all Windows runners, but a native ARM64 runner is required for testing. On non-native runners, tests for ARM64 wheels will be automatically skipped with a warning. Add `"*-win_arm64"` to your `CIBW_TEST_SKIP` setting to suppress the warning.
Cross-compilation on Windows relies on a supported build backend. Supported backends use an environment variable to specify their target platform (the one they are compiling native modules for, as opposed to the one they are running on), which is set in [cibuildwheels/windows.py](https://github.com/pypa/cibuildwheel/blob/main/cibuildwheel/windows.py) before building. Currently, `setuptools>=65.4.1` and `setuptools_rust` are the only supported backends.
By default, `ARM64` is not enabled when running on non-ARM64 runners. Use [`CIBW_ARCHS`](options.md#archs) to select it.
+3 -1
View File
@@ -1,3 +1,5 @@
from __future__ import annotations
import os import os
import subprocess import subprocess
import sysconfig import sysconfig
@@ -7,7 +9,7 @@ from typing import Any
def define_env(env: Any) -> None: def define_env(env: Any) -> None:
"Hook function for mkdocs-macros" "Hook function for mkdocs-macros"
@env.macro # type: ignore[misc] @env.macro
def subprocess_run(*args: str) -> str: def subprocess_run(*args: str) -> str:
"Run a subprocess and return the stdout" "Run a subprocess and return the stdout"
env = os.environ.copy() env = os.environ.copy()
+476 -418
View File
File diff suppressed because it is too large Load Diff
-236
View File
@@ -1,236 +0,0 @@
---
title: Platforms
---
# Platforms
## Linux
### System requirements
If you've got [Docker](https://www.docker.com/get-started/) installed on your development machine, you can run a Linux build.
!!! tip
You can run the Linux build on any platform. Even Windows can run
Linux containers these days, but there are a few hoops to jump
through. Check [this document](https://docs.microsoft.com/en-us/virtualization/windowscontainers/quick-start/quick-start-windows-10-linux)
for more info.
Because the builds are happening in manylinux Docker containers, they're perfectly reproducible.
The only side effect to your system will be docker images being pulled.
### Build containers {: #linux-containers}
Linux wheels are built in [`manylinux`/`musllinux` containers](https://github.com/pypa/manylinux) to provide binary compatible wheels on Linux, according to [PEP 600](https://www.python.org/dev/peps/pep-0600/) / [PEP 656](https://www.python.org/dev/peps/pep-0656/). Because of this, when building with `cibuildwheel` on Linux, a few things should be taken into account:
- Programs and libraries are not installed on the CI runner host, but rather should be installed inside the container - using `yum` for `manylinux2014`, `apt-get` for `manylinux_2_31`, `dnf` for `manylinux_2_28` and `apk` for `musllinux_1_1` or `musllinux_1_2`, or manually. The same goes for environment variables that are potentially needed to customize the wheel building.
`cibuildwheel` supports this by providing the [`CIBW_ENVIRONMENT`](options.md#environment) and [`CIBW_BEFORE_ALL`](options.md#before-all) options to setup the build environment inside the running container.
- The project directory is copied into the container as `/project`, the output directory for the wheels to be copied out is `/output`. In general, this is handled transparently by `cibuildwheel`. For a more finegrained level of control however, the root of the host file system is mounted as `/host`, allowing for example to access shared files, caches, etc. on the host file system. Note that `/host` is not available on CircleCI and GitLab CI due to their Docker policies.
- Alternative Docker images can be specified with the `CIBW_MANYLINUX_*_IMAGE`/`CIBW_MUSLLINUX_*_IMAGE` options to allow for a custom, preconfigured build environment for the Linux builds. See [options](options.md#linux-image) for more details.
## macOS
### System requirements
You need to have native build tools installed. Use `xcode-select --install` to install the Xcode command line tools.
Because the builds are happening without full isolation, there might be some differences compared to CI builds (Xcode version, OS version, local files, ...) that might prevent you from finding an issue only seen in CI.
In order to speed-up builds, cibuildwheel will cache the tools it needs to be reused for future builds. The folder used for caching is system/user dependent and is reported in the printed preamble of each run (e.g. `Cache folder: /Users/Matt/Library/Caches/cibuildwheel`). You can override the cache folder using the `CIBW_CACHE_PATH` environment variable.
!!! warning
cibuildwheel uses official python.org macOS installers for CPython but those can only be installed globally.
In order not to mess with your system, cibuildwheel won't install those if they are missing. Instead, it will error out with a message to let you install the missing CPython:
```console
Error: CPython 3.9 is not installed.
cibuildwheel will not perform system-wide installs when running outside of CI.
To build locally, install CPython 3.9 on this machine, or, disable this version of Python using CIBW_SKIP=cp39-macosx_*
Download link: https://www.python.org/ftp/python/3.9.8/python-3.9.8-macosx10.9.pkg
```
### macOS Version Compatibility
macOS allows you to specify a "deployment target" version that will ensure backwards compatibility with older versions of macOS. For most projects, the way to do this is to set the `MACOSX_DEPLOYMENT_TARGET` environment variable.
macOS builds will honor the `MACOSX_DEPLOYMENT_TARGET` environment variable to control the minimum supported macOS version for generated wheels. The lowest value you can set `MACOSX_DEPLOYMENT_TARGET` is as follows:
| Arch | Python version range | Minimum target |
|-------|----------------------|----------------|
| Intel | CPython 3.8-3.11 | 10.9 |
| Intel | CPython 3.12+ | 10.13 |
| AS | CPython or PyPy | 11 |
| Intel | PyPy 3.8 | 10.13 |
| Intel | PyPy 3.9+ | 10.15 |
If you set the value lower, cibuildwheel will cap it to the lowest supported value for each target as needed.
!!! note
For Rust-based extensions, `Rustc` requires `MACOSX_DEPLOYMENT_TARGET` to be at
least 10.12. However, `cibuildwheel` defaults to 10.9 for
**Intel / CPython 3.8-3.11** builds. Users must manually set
`MACOSX_DEPLOYMENT_TARGET` to 10.12 or higher when building Rust extensions.
### macOS architectures
`cibuildwheel` supports both native builds and cross-compiling between `arm64` (Apple Silicon) and `x86_64` (Intel) architectures, including the cross-compatible `universal2` format. By default, macOS builds will build a single architecture wheel, using the build machine's architecture.
If you need to support both x86_64 and Apple Silicon, you can use the `CIBW_ARCHS` environment variable to specify the architectures you want to build, or the value `universal2` to build a multi-architecture wheel. cibuildwheel _will_ test x86_64 wheels (or the x86_64 slice of a `universal2` wheel) when running on Apple Silicon hardware using Rosetta 2 emulation, but it is *not* possible to test Apple Silicon wheels on x86_64 hardware.
#### Overview of Mac architectures
##### `x86_64`
The traditional wheel for Apple, loads on Intel machines, and on
Apple Silicon when running Python under Rosetta 2 emulation.
Due to a change in naming, Pip 20.3+ (or an installer using packaging 20.5+)
is required to install a binary wheel on macOS Big Sur.
##### `arm64`
The native wheel for macOS on Apple Silicon.
##### `universal2`
This wheel contains both architectures, causing it to be up to twice the
size (data files do not get doubled, only compiled code).
The dual-architecture `universal2` has a few benefits, but a key benefit
to a universal wheel is that a user can bundle these wheels into an
application and ship a single binary.
However, if you have a large library, then you might prefer to ship
the two single-arch wheels instead - `x86_64` and `arm64`. In rare cases,
you might want to build all three, but in that case, pip will not download
the universal wheels, because it prefers the most specific wheel
available.
#### What to provide?
Opinions vary on which of arch-specific or `universal2` wheels are best - some packagers prefer `universal2` because it's one wheel for all Mac users, so simpler, and easier to build into apps for downstream users. However, because they contain code for both architectures, their file size is larger, meaning they consume more disk space and bandwidth, and are harder to build for some projects.
See [GitHub issue 1333](https://github.com/pypa/cibuildwheel/issues/1333) for more discussion.
#### How?
It's easiest to build `x86_64` wheels on `x86_64` runners, and `arm64` wheels on `arm64` runners.
On GitHub Actions, `macos-14` runners are `arm64`, and `macos-13` runners are `x86_64`. So all you need to do is ensure both are in your build matrix.
#### Cross-compiling
If your CI provider doesn't offer arm64 runners yet, or you want to create `universal2`, you'll have to cross-compile. Cross-compilation can be enabled by adding extra archs to the [`CIBW_ARCHS_MACOS` option](options.md#archs) - e.g. `CIBW_ARCHS_MACOS="x86_64 universal2"`. Cross-compilation is provided by Xcode toolchain v12.2+.
Regarding testing,
- On an arm64 runner, it is possible to test x86_64 wheels and both parts of a universal2 wheel using Rosetta 2 emulation.
- On an x86_64 runner, arm64 code can be compiled but it can't be tested. `cibuildwheel` will raise a warning to notify you of this - these warnings can be silenced by skipping testing on these platforms: `CIBW_TEST_SKIP: "*_arm64 *_universal2:arm64"`.
!!! note
If your project uses **Poetry** as a build backend, cross-compiling on macOS [does not currently work](https://github.com/python-poetry/poetry/issues/7107). In some cases arm64 wheels can be built but their tags will be incorrect, with the platform tag showing `x86_64` instead of `arm64`.
As a workaround, the tag can be fixed before running delocate to repair the wheel. The [`wheel tags`](https://wheel.readthedocs.io/en/stable/reference/wheel_tags.html) command is ideal for this. See [this workflow](https://gist.github.com/anderssonjohan/49f07e33fc5cb2420515a8ac76dc0c95#file-build-pendulum-wheels-yml-L39-L53) for an example usage of `wheel tags`.
## Windows
### System requirements
You must have native build tools (i.e., Visual Studio) installed.
Because the builds are happening without full isolation, there might be some differences compared to CI builds (Visual Studio version, OS version, local files, ...) that might prevent you from finding an issue only seen in CI.
In order to speed-up builds, cibuildwheel will cache the tools it needs to be reused for future builds. The folder used for caching is system/user dependent and is reported in the printed preamble of each run (e.g. `Cache folder: C:\Users\Matt\AppData\Local\pypa\cibuildwheel\Cache`). You can override the cache folder using the ``CIBW_CACHE_PATH`` environment variable.
### Windows ARM64 builds {: #windows-arm64}
`cibuildwheel` supports cross-compiling `ARM64` wheels on all Windows runners, but a native ARM64 runner is required for testing. On non-native runners, tests for ARM64 wheels will be automatically skipped with a warning. Add `"*-win_arm64"` to your `CIBW_TEST_SKIP` setting to suppress the warning.
Cross-compilation on Windows relies on a supported build backend. Supported backends use an environment variable to specify their target platform (the one they are compiling native modules for, as opposed to the one they are running on), which is set in [cibuildwheels/windows.py](https://github.com/pypa/cibuildwheel/blob/main/cibuildwheel/windows.py) before building. Currently, `setuptools>=65.4.1` and `setuptools_rust` are the only supported backends.
By default, `ARM64` is not enabled when running on non-ARM64 runners. Use [`CIBW_ARCHS`](options.md#archs) to select it.
## Pyodide/WebAssembly {: #pyodide}
Pyodide is offered as an experimental feature in cibuildwheel.
### Prerequisites
You need to have a matching host version of Python (unlike all other cibuildwheel platforms). Linux host highly recommended; macOS hosts may work (e.g. invoking `pytest` directly in [`CIBW_TEST_COMMAND`](options.md#test-command) is [currently failing](https://github.com/pyodide/pyodide/issues/4802)) and Windows hosts will not work.
### Specifying a pyodide build
You must target pyodide with `--platform pyodide` (or use `--only` on the identifier).
## iOS
### System requirements
You must be building on a macOS machine, with Xcode installed. The Xcode installation must have an iOS SDK available, with all license agreements agreed to by the user. To check if an iOS SDK is available, open the Xcode settings panel, and check the Platforms tab. This will also ensure that license agreements have been acknowledged.
Building iOS wheels also requires a working macOS Python installation. See the notes on [macOS builds](#macos) for details about configuration of the macOS environment.
### Specifying an iOS build
iOS is effectively 2 platforms - physical devices, and simulators. While the API for these two platforms are identical, the ABI is not compatible, even when dealing with a device and simulator with the same CPU architecture. For this reason, the architecture specification for iOS builds includes *both* the CPU architecture *and* the ABI that is being targeted. There are three possible values for architecture on iOS; the values match those used by `sys.implementation._multiarch` when running on iOS (with hyphens replaced with underscores, matching wheel filename normalization):
* `arm64_iphoneos` (for physical iOS devices);
* `arm64_iphonesimulator` (for iOS simulators running on Apple Silicon macOS machines); and
* `x64_64_iphonesimulator` (for iOS simulators running on Intel macOS machines).
By default, cibuildwheel will build wheels for all three of these targets.
If you need to specify different compilation flags or other properties on a per-ABI or per-CPU basis, you can use [configuration overrides](configuration.md#overrides) with a `select` clause that targets the specific ABI or architecture. For example, consider the following example:
```
[tool.cibuildwheel.ios]
test-sources = ["tests"]
test-requires = ["pytest"]
[[tool.cibuildwheel.overrides]]
select = "*_iphoneos"
environment.PATH = "/path/to/special/device/details:..."
[[tool.cibuildwheel.overrides]]
select = "*-ios_arm64_*"
inherit.test-requires = "append"
test-requires = ["arm64-testing-helper"]
```
This configuration would:
* Specify a `test-sources` and `test-requires` for all iOS targets;
* Add a `PATH` setting that will be used on physical iOS devices; and
* Add `arm64-testing-helper` to the test environment for all ARM64 iOS devices (whether simulator or device).
### iOS version compatibility
iOS builds will honor the `IPHONEOS_DEPLOYMENT_TARGET` environment variable to set the minimum supported API version for generated wheels. This will default to `13.0` if the environment variable isn't set.
### Cross platform builds
iOS builds are *cross platform builds*, as it not possible to run compilers and other build tools "on device". The pre-compiled iOS binaries used to support iOS builds include tooling that can convert any virtual environment into a cross platform virtual environment - that is, an environment that can run binaries on the build machine (macOS), but, if asked, will respond as if it is an iOS machine. This allows `pip`, `build`, and other build tools to perform iOS-appropriate behaviour.
### Build frontend support
iOS builds support both the `pip` and `build` build frontends. In principle, support for `uv` with the `build[uv]` frontend should be possible, but `uv` [doesn't currently have support for cross-platform builds](https://github.com/astral-sh/uv/issues/7957), and [doesn't have support for iOS (or Android) tags](https://github.com/astral-sh/uv/issues/8029).
### Build environment
The environment used to run builds does not inherit the full user environment - in particular, `PATH` is deliberately re-written. This is because UNIX C tooling doesn't do a great job differentiating between "macOS ARM64" and "iOS ARM64" binaries. If (for example) Homebrew is on the path when compilation commands are invoked, it's easy for a macOS version of a library to be linked into the iOS binary, rendering it unusable on iOS. To prevent this, iOS builds always force `PATH` to a "known minimal" path, that includes only the bare system utilities, and the iOS compiler toolchain.
If your project requires additional tools to build (such as `cmake`, `ninja`, or `rustc`), those tools must be explicitly declared as cross-build tools using [`CIBW_XBUILD_TOOLS`](options.md#xbuild-tools). *Any* tool used by the build process must be included in the `CIBW_XBUILD_TOOLS` list, not just tools that cibuildwheel will invoke directly. For example, if your build script invokes `cmake`, and the `cmake` script invokes `magick` to perform some image transformations, both `cmake` and `magick` must be included in your cross-build tools list.
### Tests
If tests have been configured, the test suite will be executed on the simulator matching the architecture of the build machine - that is, if you're building on an ARM64 macOS machine, the ARM64 wheel will be tested on an ARM64 simulator. It is not possible to use cibuildwheel to test wheels on other simulators, or on physical devices.
The iOS test environment can't support running shell scripts, so the [`CIBW_TEST_COMMAND`](options.md#test-command) value must be specified as if it were a command line being passed to `python -m ...`. In addition, the project must use [`CIBW_TEST_SOURCES`](options.md#test-sources) to specify the minimum subset of files that should be copied to the test environment. This is because the test must be run "on device", and the simulator device will not have access to the local project directory.
The test process uses the same testbed used by CPython itself to run the CPython test suite. It is an Xcode project that has been configured to have a single Xcode "XCUnit" test - the result of which reports the success or failure of running `python -m <CIBW_TEST_COMMAND>`.
+294 -16
View File
@@ -1,29 +1,26 @@
--- ---
title: 'Getting started' title: 'Setup'
--- ---
# Getting started # Setup
Before getting to [CI setup](ci-services.md), it can be convenient to test cibuildwheel locally to quickly iterate and track down issues without having to commit each change, push, and then check CI logs. ## Run cibuildwheel locally (optional) {: #local}
Before getting to CI setup, it can be convenient to test cibuildwheel
locally to quickly iterate and track down issues without even touching CI.
Install cibuildwheel and run a build like this: Install cibuildwheel and run a build like this:
```sh ```sh
# run using uv # using pipx (https://github.com/pypa/pipx)
uvx cibuildwheel
# or pipx
pipx run cibuildwheel pipx run cibuildwheel
# or, install it first # or,
pip install cibuildwheel pip install cibuildwheel
cibuildwheel cibuildwheel
``` ```
!!!tip You should see the builds taking place. You can experiment with options using environment variables or pyproject.toml.
You can pass the `--platform linux` option to cibuildwheel to build Linux wheels, even if you're not on Linux. On most machines, the easiest builds to try are the Linux builds. You don't need any software installed except a Docker daemon, such as [Docker Desktop](https://www.docker.com/get-started/). Each platform that cibuildwheel supports has its own system requirements and platform-specific behaviors. See the [platforms page](platforms.md) for details.
You should see the builds taking place. You can experiment with [options](options.md) using environment variables or pyproject.toml.
!!! tab "Environment variables" !!! tab "Environment variables"
@@ -48,7 +45,7 @@ You should see the builds taking place. You can experiment with [options](option
!!! tab "pyproject.toml" !!! tab "pyproject.toml"
If you write your options into [`pyproject.toml`](configuration.md#configuration-file), you can work on your options locally, and they'll be automatically picked up when running in CI. If you write your options into [`pyproject.toml`](options.md#configuration-file), you can work on your options locally, and they'll be automatically picked up when running in CI.
> pyproject.toml > pyproject.toml
@@ -63,6 +60,287 @@ You should see the builds taking place. You can experiment with [options](option
cibuildwheel cibuildwheel
``` ```
- Once you've got a build working locally, you can move on to [setting up a CI service](ci-services.md). ### Linux builds
- View the [full options reference](options.md) to see what cibuildwheel can do.
- Check out the [FAQ](faq.md) for common questions. If you've got [Docker](https://www.docker.com/products/docker-desktop) installed on
your development machine, you can run a Linux build.
!!! tip
You can run the Linux build on any platform. Even Windows can run
Linux containers these days, but there are a few hoops to jump
through. Check [this document](https://docs.microsoft.com/en-us/virtualization/windowscontainers/quick-start/quick-start-windows-10-linux)
for more info.
Because the builds are happening in manylinux Docker containers,
they're perfectly reproducible.
The only side effect to your system will be docker images being pulled.
### macOS / Windows builds
Pre-requisite: you need to have native build tools installed.
Because the builds are happening without full isolation, there might be some
differences compared to CI builds (Xcode version, Visual Studio version,
OS version, local files, ...) that might prevent you from finding an issue only
seen in CI.
In order to speed-up builds, cibuildwheel will cache the tools it needs to be
reused for future builds. The folder used for caching is system/user dependent and is
reported in the printed preamble of each run (e.g. "Cache folder: /Users/Matt/Library/Caches/cibuildwheel").
You can override the cache folder using the ``CIBW_CACHE_PATH`` environment variable.
!!! warning
cibuildwheel uses official python.org macOS installers for CPython but
those can only be installed globally.
In order not to mess with your system, cibuildwheel won't install those if they are
missing. Instead, it will error out with a message to let you install the missing
CPython:
```console
Error: CPython 3.6 is not installed.
cibuildwheel will not perform system-wide installs when running outside of CI.
To build locally, install CPython 3.6 on this machine, or, disable this version of Python using CIBW_SKIP=cp36-macosx_*
Download link: https://www.python.org/ftp/python/3.6.8/python-3.6.8-macosx10.9.pkg
```
### Pyodide (WebAssembly) builds (experimental)
Pre-requisite: you need to have a matching host version of Python (unlike all
other cibuildwheel platforms). Linux host highly recommended; macOS hosts may
work (e.g. invoking `pytest` directly in [`CIBW_TEST_COMMAND`](options.md#test-command) is [currently failing](https://github.com/pyodide/pyodide/issues/4802)) and Windows hosts will not work.
You must target pyodide with `--platform pyodide` (or use `--only` on the identifier).
## Configure a CI service
### GitHub Actions [linux/mac/windows] {: #github-actions}
To build Linux, Mac, and Windows wheels using GitHub Actions, create a `.github/workflows/build_wheels.yml` file in your repo.
!!! tab "Action"
For GitHub Actions, `cibuildwheel` provides an action you can use. This is
concise and enables easier auto updating via GitHub's Dependabot; see
[Automatic updates](faq.md#automatic-updates).
> .github/workflows/build_wheels.yml
```yaml
{% include "../examples/github-minimal.yml" %}
```
Use `env:` to pass [build options](options.md) and `with:` to set
`package-dir: .`, `output-dir: wheelhouse` and `config-file: ''`
locations (those values are the defaults).
!!! tab "pipx"
The GitHub Actions runners have pipx installed, so you can easily build in
just one line. This is internally how the action works; the main benefit of
the action form is easy updates via GitHub's Dependabot.
> .github/workflows/build_wheels.yml
```yaml
name: Build
on: [push, pull_request]
jobs:
build_wheels:
name: Build wheels on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
# macos-13 is an intel runner, macos-14 is apple silicon
os: [ubuntu-latest, windows-latest, macos-13, macos-14]
steps:
- uses: actions/checkout@v4
- name: Build wheels
run: pipx run cibuildwheel==2.20.0
- uses: actions/upload-artifact@v4
with:
name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }}
path: ./wheelhouse/*.whl
```
!!! tab "Generic"
This is the most generic form using setup-python and pip; it looks the most
like the other CI examples. If you want to avoid having setup that takes
advantage of GitHub Actions features or pipx being preinstalled, this might
appeal to you.
> .github/workflows/build_wheels.yml
```yaml
name: Build
on: [push, pull_request]
jobs:
build_wheels:
name: Build wheels on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
# macos-13 is an intel runner, macos-14 is apple silicon
os: [ubuntu-latest, windows-latest, macos-13, macos-14]
steps:
- uses: actions/checkout@v4
# Used to host cibuildwheel
- uses: actions/setup-python@v5
- name: Install cibuildwheel
run: python -m pip install cibuildwheel==2.20.0
- name: Build wheels
run: python -m cibuildwheel --output-dir wheelhouse
- uses: actions/upload-artifact@v4
with:
name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }}
path: ./wheelhouse/*.whl
```
Commit this file, and push to GitHub - either to your default branch, or to a PR branch. The build should start automatically.
For more info on this file, check out the [docs](https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions).
[`examples/github-deploy.yml`](https://github.com/pypa/cibuildwheel/blob/main/examples/github-deploy.yml) extends this minimal example with a demonstration of how to automatically upload the built wheels to PyPI.
### Azure Pipelines [linux/mac/windows] {: #azure-pipelines}
To build Linux, Mac, and Windows wheels on Azure Pipelines, create a `azure-pipelines.yml` file in your repo.
> azure-pipelines.yml
```yaml
{% include "../examples/azure-pipelines-minimal.yml" %}
```
Commit this file, enable building of your repo on Azure Pipelines, and push.
Wheels will be stored for you and available through the Pipelines interface. For more info on this file, check out the [docs](https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema).
### Travis CI [linux/windows] {: #travis-ci}
To build Linux and Windows wheels on Travis CI, create a `.travis.yml` file in your repo.
> .travis.yml
```yaml
{% include "../examples/travis-ci-minimal.yml" %}
```
Commit this file, enable building of your repo on Travis CI, and push.
Then setup a deployment method by following the [Travis CI deployment docs](https://docs.travis-ci.com/user/deployment/), or see [Delivering to PyPI](deliver-to-pypi.md). For more info on `.travis.yml`, check out the [docs](https://docs.travis-ci.com/).
[`examples/travis-ci-deploy.yml`](https://github.com/pypa/cibuildwheel/blob/main/examples/travis-ci-deploy.yml) extends this minimal example with a demonstration of how to automatically upload the built wheels to PyPI.
### AppVeyor [linux/mac/windows] {: #appveyor}
To build Linux, Mac, and Windows wheels on AppVeyor, create an `appveyor.yml` file in your repo.
> appveyor.yml
```yaml
{% include "../examples/appveyor-minimal.yml" %}
```
Commit this file, enable building of your repo on AppVeyor, and push.
AppVeyor will store the built wheels for you - you can access them from the project console. Alternatively, you may want to store them in the same place as the Travis CI build. See [AppVeyor deployment docs](https://www.appveyor.com/docs/deployment/) for more info, or see [Delivering to PyPI](deliver-to-pypi.md) below.
For more info on this config file, check out the [docs](https://www.appveyor.com/docs/).
### CircleCI [linux/mac] {: #circleci}
To build Linux and Mac wheels on CircleCI, create a `.circleci/config.yml` file in your repo,
> .circleci/config.yml
```yaml
{% include "../examples/circleci-minimal.yml" %}
```
Commit this file, enable building of your repo on CircleCI, and push.
!!! note
CircleCI doesn't enable free macOS containers for open source by default, but you can ask for access. See [here](https://circleci.com/docs/2.0/oss/#overview) for more information.
CircleCI will store the built wheels for you - you can access them from the project console. Check out the CircleCI [docs](https://circleci.com/docs/2.0/configuration-reference/#section=configuration) for more info on this config file.
### Gitlab CI [linux] {: #gitlab-ci}
To build Linux wheels on Gitlab CI, create a `.gitlab-ci.yml` file in your repo,
> .gitlab-ci.yml
```yaml
{% include "../examples/gitlab-minimal.yml" %}
```
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.
### 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).
## 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).
<script>
document.addEventListener('DOMContentLoaded', function() {
$('.toctree-l3>a, .rst-content h3').each(function(i, el) {
var text = $(el).text()
var match = text.match(/(.*) \[([a-z/]+)\]/);
if (match) {
var iconHTML = $.map(match[2].split('/'), function(ident) {
switch (ident) {
case 'linux':
return '<i class="fa fa-linux" aria-hidden="true"></i>'
case 'windows':
return '<i class="fa fa-windows" aria-hidden="true"></i>'
case 'mac':
return '<i class="fa fa-apple" aria-hidden="true"></i>'
}
}).join(' ');
$(el).append(
$('<div>')
.append(iconHTML)
.css({float: 'right'})
)
$(el).contents()
.filter(function(){ return this.nodeType == 3; }).first()
.replaceWith(match[1]);
}
});
});
</script>
+57 -56
View File
@@ -15,11 +15,11 @@ title: Working examples
| [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. |
| [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][] | 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 |
| [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. |
| [MyPy][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | The compiled version of MyPy using MyPyC. |
| [Kivy][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Open source UI framework written in Python, running on Windows, Linux, macOS, Android and iOS | | [Kivy][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Open source UI framework written in Python, running on Windows, Linux, macOS, Android and iOS |
| [Triton][] | ![github icon][] | ![linux icon][] | Self hosted runners |
| [MemRay][] | ![github icon][] | ![linux icon][] | Memray is a memory profiler for Python | | [MemRay][] | ![github icon][] | ![linux icon][] | Memray is a memory profiler for Python |
| [Triton][] | ![github icon][] | ![linux icon][] | Self hosted runners |
| [uvloop][] | ![github icon][] | ![apple icon][] ![linux icon][] | Ultra fast asyncio event loop. | | [uvloop][] | ![github icon][] | ![apple icon][] ![linux icon][] | Ultra fast asyncio event loop. |
| [psutil][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Cross-platform lib for process and system monitoring in Python | | [psutil][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Cross-platform lib for process and system monitoring in Python |
| [Google Benchmark][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | A microbenchmark support library | | [Google Benchmark][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | A microbenchmark support library |
@@ -28,38 +28,37 @@ title: Working examples
| [PyGame][] | ![github icon][] | ![apple icon][] ![linux icon][] | 🐍🎮 pygame (the library) is a Free and Open Source python programming language library for making multimedia applications like games built on top of the excellent SDL library. C, Python, Native, OpenGL. | | [PyGame][] | ![github icon][] | ![apple icon][] ![linux icon][] | 🐍🎮 pygame (the library) is a Free and Open Source python programming language library for making multimedia applications like games built on top of the excellent SDL library. C, Python, Native, OpenGL. |
| [asyncpg][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | A fast PostgreSQL Database Client Library for Python/asyncio. | | [asyncpg][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | A fast PostgreSQL Database Client Library for Python/asyncio. |
| [cmake][] | ![github icon][] ![travisci icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Multitagged binary builds for all supported platforms, using cibw 2 config configuration. | | [cmake][] | ![github icon][] ![travisci icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Multitagged binary builds for all supported platforms, using cibw 2 config configuration. |
| [pyinstrument][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Python profiler with a C extension. No external dependencies. |
| [scikit-image][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Image processing library. Uses cibuildwheel to build and test a project that uses Cython with platform-native code. | | [scikit-image][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Image processing library. Uses cibuildwheel to build and test a project that uses Cython with platform-native code. |
| [PyOxidizer][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | A modern Python application packaging and distribution tool |
| [twisted-iocpsupport][] | ![github icon][] | ![windows icon][] | A submodule of Twisted that hooks into native C APIs using Cython. | | [twisted-iocpsupport][] | ![github icon][] | ![windows icon][] | A submodule of Twisted that hooks into native C APIs using Cython. |
| [PyOxidizer][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | A modern Python application packaging and distribution tool |
| [cvxpy][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | A Python-embedded modeling language for convex optimization problems. | | [cvxpy][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | A Python-embedded modeling language for convex optimization problems. |
| [pedalboard][] | ![github icon][] | ![windows icon][] ![linux icon][] ![apple icon][] | A Python library for working with audio data and audio plugins by wrapping the [JUCE](https://github.com/juce-framework/JUCE/) C++ framework. Uses cibuildwheel to deploy on as many operating systems and Python versions as possible with only one dependency (any NumPy). |
| [websockets][] | ![travisci icon][] | ![apple icon][] ![linux icon][] | Library for building WebSocket servers and clients. Mostly written in Python, with a small C 'speedups' extension module. | | [websockets][] | ![travisci icon][] | ![apple icon][] ![linux icon][] | Library for building WebSocket servers and clients. Mostly written in Python, with a small C 'speedups' extension module. |
| [pedalboard][] | ![github icon][] | ![windows icon][] ![linux icon][] ![apple icon][] | A Python library for working with audio data and audio plugins by wrapping the [JUCE](https://github.com/juce-framework/JUCE/) C++ framework. Uses cibuildwheel to deploy on as many operating systems and Python versions as possible with only one dependency (any NumPy). |
| [River][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | 🌊 Online machine learning in Python | | [River][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | 🌊 Online machine learning in Python |
| [aiortc][] | ![github icon][] | ![apple icon][] ![linux icon][] | WebRTC and ORTC implementation for Python using asyncio. |
| [OpenSpiel][] | ![github icon][] | ![apple icon][] ![linux icon][] | OpenSpiel is a collection of environments and algorithms for research in general reinforcement learning and search/planning in games. |
| [UltraJSON][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Ultra fast JSON decoder and encoder written in C with Python bindings | | [UltraJSON][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Ultra fast JSON decoder and encoder written in C with Python bindings |
| [OpenSpiel][] | ![github icon][] | ![apple icon][] ![linux icon][] | OpenSpiel is a collection of environments and algorithms for research in general reinforcement learning and search/planning in games. |
| [aiortc][] | ![github icon][] | ![apple icon][] ![linux icon][] | WebRTC and ORTC implementation for Python using asyncio. |
| [Dependency Injector][] | ![travisci icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Dependency injection framework for Python, uses Windows TravisCI | | [Dependency Injector][] | ![travisci icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Dependency injection framework for Python, uses Windows TravisCI |
| [pyzmq][] | ![github icon][] ![circleci icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Python bindings for zeromq, the networking library. Uses Cython on CPython and CFFI on PyPy. ARM wheels for linux are built natively on CircleCI. | | [pyzmq][] | ![github icon][] ![circleci icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Python bindings for zeromq, the networking library. Uses Cython on CPython and CFFI on PyPy. ARM wheels for linux are built natively on CircleCI. |
| [CTranslate2][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Includes libraries from the [Intel oneAPI toolkit](https://www.intel.com/content/www/us/en/developer/tools/oneapi/base-toolkit.html) and CUDA kernels compiled for multiple GPU architectures. |
| [Implicit][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Includes GPU support for linux wheels | | [Implicit][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Includes GPU support for linux wheels |
| [tinyobjloader][] | ![azurepipelines icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Tiny but powerful single file wavefront obj loader |
| [vispy][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Main repository for Vispy | | [vispy][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Main repository for Vispy |
| [tinyobjloader][] | ![azurepipelines icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Tiny but powerful single file wavefront obj loader |
| [CTranslate2][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Includes libraries from the [Intel oneAPI toolkit](https://www.intel.com/content/www/us/en/developer/tools/oneapi/base-toolkit.html) and CUDA kernels compiled for multiple GPU architectures. |
| [coverage.py][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | The coverage tool for Python | | [coverage.py][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | The coverage tool for Python |
| [PyCryptodome][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | A self-contained cryptographic library for Python | | [PyCryptodome][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | A self-contained cryptographic library for Python |
| [Line Profiler][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Line-by-line profiling for Python | | [Line Profiler][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Line-by-line profiling for Python |
| [PyAV][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Pythonic bindings for FFmpeg's libraries. |
| [PyYAML][] | ![github icon][] | ![apple icon][] | Canonical source repository for PyYAML | | [PyYAML][] | ![github icon][] | ![apple icon][] | Canonical source repository for PyYAML |
| [pikepdf][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | A Python library for reading and writing PDF, powered by QPDF | | [PyAV][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Pythonic bindings for FFmpeg's libraries. |
| [numexpr][] | ![github icon][] ![travisci icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Fast numerical array expression evaluator for Python, NumPy, Pandas, PyTables and more | | [numexpr][] | ![github icon][] ![travisci icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Fast numerical array expression evaluator for Python, NumPy, Pandas, PyTables and more |
| [pikepdf][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | A Python library for reading and writing PDF, powered by QPDF |
| [h5py][] | ![azurepipelines icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | HDF5 for Python -- The h5py package is a Pythonic interface to the HDF5 binary data format. | | [h5py][] | ![azurepipelines icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | HDF5 for Python -- The h5py package is a Pythonic interface to the HDF5 binary data format. |
| [Wrapt][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | A Python module for decorators, wrappers and monkey patching. | | [Wrapt][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | A Python module for decorators, wrappers and monkey patching. |
| [envd][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | A machine learning development environment build tool | | [envd][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | A machine learning development environment build tool |
| [Psycopg 3][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | A modern implementation of a PostgreSQL adapter for Python |
| [OpenColorIO][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | A color management framework for visual effects and animation. | | [OpenColorIO][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | A color management framework for visual effects and animation. |
| [ruptures][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Extensive Cython + NumPy [pyproject.toml](https://github.com/deepcharles/ruptures/blob/master/pyproject.toml) example. | | [Psycopg 3][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | A modern implementation of a PostgreSQL adapter for Python |
| [aioquic][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | QUIC and HTTP/3 implementation in Python | | [aioquic][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | QUIC and HTTP/3 implementation in Python |
| [SimpleJSON][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | simplejson is a simple, fast, extensible JSON encoder/decoder for Python | | [SimpleJSON][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | simplejson is a simple, fast, extensible JSON encoder/decoder for Python |
| [ruptures][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Extensive Cython + NumPy [pyproject.toml](https://github.com/deepcharles/ruptures/blob/master/pyproject.toml) example. |
| [OpenTimelineIO][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Open Source API and interchange format for editorial timeline information. | | [OpenTimelineIO][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Open Source API and interchange format for editorial timeline information. |
| [PyTables][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | A Python package to manage extremely large amounts of data | | [PyTables][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | A Python package to manage extremely large amounts of data |
| [google neuroglancer][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | WebGL-based viewer for volumetric data | | [google neuroglancer][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | WebGL-based viewer for volumetric data |
@@ -67,46 +66,46 @@ title: Working examples
| [DeepForest][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | An Efficient, Scalable and Optimized Python Framework for Deep Forest (2021.2.1) | | [DeepForest][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | An Efficient, Scalable and Optimized Python Framework for Deep Forest (2021.2.1) |
| [AutoPy][] | ![travisci icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Includes a Windows Travis build. | | [AutoPy][] | ![travisci icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Includes a Windows Travis build. |
| [H3-py][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Python bindings for H3, a hierarchical hexagonal geospatial indexing system | | [H3-py][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Python bindings for H3, a hierarchical hexagonal geospatial indexing system |
| [time-machine][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Time mocking library using only the CPython C API. |
| [mosec][] | ![github icon][] | ![linux icon][] ![apple icon][] | A machine learning model serving framework powered by Rust | | [mosec][] | ![github icon][] | ![linux icon][] ![apple icon][] | A machine learning model serving framework powered by Rust |
| [time-machine][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Time mocking library using only the CPython C API. |
| [Picologging][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | A high-performance logging library for Python. | | [Picologging][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | A high-performance logging library for Python. |
| [pybind11 cmake_example][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Example pybind11 module built with a CMake-based build system |
| [markupsafe][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Safely add untrusted strings to HTML/XML markup. | | [markupsafe][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Safely add untrusted strings to HTML/XML markup. |
| [Rtree][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Rtree: spatial index for Python GIS | | [Rtree][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Rtree: spatial index for Python GIS |
| [pybind11 cmake_example][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Example pybind11 module built with a CMake-based build system |
| [KDEpy][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Kernel Density Estimation in Python | | [KDEpy][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Kernel Density Estimation in Python |
| [dd-trace-py][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Uses custom alternate arch emulation on GitHub | | [dd-trace-py][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Uses custom alternate arch emulation on GitHub |
| [tgcalls][] | ![github icon][] | ![apple icon][] ![windows icon][] | Python `pybind11` binding to Telegram's WebRTC library with third party dependencies like `OpenSSL`, `MozJPEG`, `FFmpeg`, etc. | | [tgcalls][] | ![github icon][] | ![apple icon][] ![windows icon][] | Python `pybind11` binding to Telegram's WebRTC library with third party dependencies like `OpenSSL`, `MozJPEG`, `FFmpeg`, etc. |
| [python-rapidjson][] | ![travisci icon][] ![gitlab icon][] ![appveyor icon][] | ![windows icon][] ![linux icon][] | Python wrapper around rapidjson |
| [pybind11 python_example][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Example pybind11 module built with a Python-based build system | | [pybind11 python_example][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Example pybind11 module built with a Python-based build system |
| [python-rapidjson][] | ![travisci icon][] ![gitlab icon][] | ![windows icon][] ![linux icon][] | Python wrapper around rapidjson |
| [sourmash][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Quickly search, compare, and analyze genomic and metagenomic data sets. |
| [abess][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | A fast best-subset selection library. It uses cibuildwheel to build a large project with C++ extensions. |
| [python-snappy][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Python bindings for the snappy google library | | [python-snappy][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Python bindings for the snappy google library |
| [jq.py][] | ![travisci icon][] | ![apple icon][] ![linux icon][] | Python bindings for jq | | [abess][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | A fast best-subset selection library. It uses cibuildwheel to build a large project with C++ extensions. |
| [sourmash][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Quickly search, compare, and analyze genomic and metagenomic data sets. |
| [cyvcf2][] | ![github icon][] | ![apple icon][] ![linux icon][] | cython + htslib == fast VCF and BCF processing | | [cyvcf2][] | ![github icon][] | ![apple icon][] ![linux icon][] | cython + htslib == fast VCF and BCF processing |
| [matrixprofile][] | ![travisci icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | A Python 3 library making time series data mining tasks, utilizing matrix profile algorithms, accessible to everyone. | | [matrixprofile][] | ![travisci icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | A Python 3 library making time series data mining tasks, utilizing matrix profile algorithms, accessible to everyone. |
| [Tokenizer][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Fast and customizable text tokenization library with BPE and SentencePiece support | | [jq.py][] | ![travisci icon][] | ![apple icon][] ![linux icon][] | Python bindings for jq |
| [iminuit][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Jupyter-friendly Python interface for C++ MINUIT2 | | [iminuit][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Jupyter-friendly Python interface for C++ MINUIT2 |
| [Confluent client for Kafka][] | ![travisci icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | setup in `tools/wheels/build-wheels.bat` | | [Tokenizer][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Fast and customizable text tokenization library with BPE and SentencePiece support |
| [pillow-heif][] | ![github icon][] ![cirrusci icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Bindings to libheif library with third party dependencies. Fully automated CI for tests and publishing including Apple Silicon builds. |
| [keyvi][] | ![github icon][] | ![linux icon][] ![apple icon][] | FST based key value index highly optimized for size and lookup performance, utilizes ccache action for improved runtime | | [keyvi][] | ![github icon][] | ![linux icon][] ![apple icon][] | FST based key value index highly optimized for size and lookup performance, utilizes ccache action for improved runtime |
| [PyGLM][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Fast OpenGL Mathematics (GLM) for Python | | [PyGLM][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Fast OpenGL Mathematics (GLM) for Python |
| [pillow-heif][] | ![github icon][] ![cirrusci icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Bindings to libheif library with third party dependencies. Fully automated CI for tests and publishing including Apple Silicon builds. |
| [TgCrypto][] | ![travisci icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Includes a Windows Travis build. | | [TgCrypto][] | ![travisci icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Includes a Windows Travis build. |
| [iDynTree][] | ![github icon][] | ![linux icon][] | Uses manylinux_2_24 | | [iDynTree][] | ![github icon][] | ![linux icon][] | Uses manylinux_2_24 |
| [streaming-form-data][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Streaming parser for multipart/form-data written in Cython | | [streaming-form-data][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Streaming parser for multipart/form-data written in Cython |
| [power-grid-model][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Python/C++ library for distribution power system analysis |
| [bx-python][] | ![travisci icon][] | ![apple icon][] ![linux icon][] | A library that includes Cython extensions. | | [bx-python][] | ![travisci icon][] | ![apple icon][] ![linux icon][] | A library that includes Cython extensions. |
| [pybase64][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Fast Base64 encoding/decoding in Python |
| [boost-histogram][] | ![github icon][] ![travisci icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Supports full range of wheels, including PyPy and alternate archs. | | [boost-histogram][] | ![github icon][] ![travisci icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Supports full range of wheels, including PyPy and alternate archs. |
| [Imagecodecs (fork)][] | ![azurepipelines icon][] | ![apple icon][] ![linux icon][] | Over 20 external dependencies in compiled libraries, custom docker image, `libomp`, `openblas` and `install_name_tool` for macOS. | | [power-grid-model][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Python/C++ library for distribution power system analysis |
| [Python-WebRTC][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | a Python extension that provides bindings to WebRTC M92 | | [Python-WebRTC][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | a Python extension that provides bindings to WebRTC M92 |
| [pybind11 scikit_build_example][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | An example combining scikit-build and pybind11 | | [pybase64][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Fast Base64 encoding/decoding in Python |
| [Imagecodecs (fork)][] | ![azurepipelines icon][] | ![apple icon][] ![linux icon][] | Over 20 external dependencies in compiled libraries, custom docker image, `libomp`, `openblas` and `install_name_tool` for macOS. |
| [fathon][] | ![travisci icon][] | ![apple icon][] ![linux icon][] | python package for DFA (Detrended Fluctuation Analysis) and related algorithms | | [fathon][] | ![travisci icon][] | ![apple icon][] ![linux icon][] | python package for DFA (Detrended Fluctuation Analysis) and related algorithms |
| [Arbor][] | ![github icon][] | ![apple icon][] ![linux icon][] | Arbor is a multi-compartment neuron simulation library; compatible with next-generation accelerators; best-practices applied to research software; focused on community-driven development. Includes a [small script](https://github.com/arbor-sim/arbor/blob/master/scripts/patchwheel.py) patching `rpath` in bundled libraries. | | [Arbor][] | ![github icon][] | ![apple icon][] ![linux icon][] | Arbor is a multi-compartment neuron simulation library; compatible with next-generation accelerators; best-practices applied to research software; focused on community-driven development. Includes a [small script](https://github.com/arbor-sim/arbor/blob/master/scripts/patchwheel.py) patching `rpath` in bundled libraries. |
| [clang-format][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Scikit-build wrapper around LLVM's CMake, all platforms, generic wheels. | | [pybind11 scikit_build_example][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | An example combining scikit-build and pybind11 |
| [polaroid][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Full range of wheels for setuptools rust, with auto release and PyPI deploy. | | [polaroid][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Full range of wheels for setuptools rust, with auto release and PyPI deploy. |
| [ninja][] | ![github icon][] ![travisci icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Multitagged binary builds for all supported platforms, using cibw 2 config configuration. | | [clang-format][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Scikit-build wrapper around LLVM's CMake, all platforms, generic wheels. |
| [etebase-py][] | ![travisci icon][] | ![linux icon][] | Python bindings to a Rust library using `setuptools-rust`, and `sccache` for improved speed. | | [etebase-py][] | ![travisci icon][] | ![linux icon][] | Python bindings to a Rust library using `setuptools-rust`, and `sccache` for improved speed. |
| [cf-units][] | ![github icon][] | ![apple icon][] ![linux icon][] | Units of measure as required by the Climate and Forecast (CF) Metadata Conventions | | [cf-units][] | ![github icon][] | ![apple icon][] ![linux icon][] | Units of measure as required by the Climate and Forecast (CF) Metadata Conventions |
| [Confluent client for Kafka][] | ![travisci icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | setup in `tools/wheels/build-wheels.bat` |
| [ninja][] | ![github icon][] ![travisci icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Multitagged binary builds for all supported platforms, using cibw 2 config configuration. |
| [numpythia][] | ![github icon][] | ![apple icon][] ![linux icon][] | The interface between PYTHIA and NumPy | | [numpythia][] | ![github icon][] | ![apple icon][] ![linux icon][] | The interface between PYTHIA and NumPy |
| [pyjet][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | The interface between FastJet and NumPy | | [pyjet][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | The interface between FastJet and NumPy |
| [ril][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | A python binding to Rust Imaging library using maturin and Pyo3, utilizes Github Action cache to improve speed. Builds abi3 wheels. | | [ril][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | A python binding to Rust Imaging library using maturin and Pyo3, utilizes Github Action cache to improve speed. Builds abi3 wheels. |
@@ -116,6 +115,7 @@ title: Working examples
| [CorrectionLib][] | ![github icon][] | ![apple icon][] ![linux icon][] | Structured JSON powered correction library for HEP, designed for the CMS experiment at CERN. | | [CorrectionLib][] | ![github icon][] | ![apple icon][] ![linux icon][] | Structured JSON powered correction library for HEP, designed for the CMS experiment at CERN. |
| [xmlstarlet][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Python 3.6+ CFFI bindings with true MSVC build. | | [xmlstarlet][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Python 3.6+ CFFI bindings with true MSVC build. |
| [werpy][] | ![github icon][] | ![windows icon][] ![linux icon][] ![apple icon][] | An ultra-fast python package using optimized dynamic programming to compute the Word Error Rate (WER). | | [werpy][] | ![github icon][] | ![windows icon][] ![linux icon][] ![apple icon][] | An ultra-fast python package using optimized dynamic programming to compute the Word Error Rate (WER). |
| [pyinstrument_cext][] | ![travisci icon][] ![appveyor icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | A simple C extension, without external dependencies |
| [pybind11 cross build example][] | ![github icon][] ![gitlab icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Same as pybind11 cmake_example but used to demo Linux ARM + Windows + macOS builds on GitLab | | [pybind11 cross build example][] | ![github icon][] ![gitlab icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Same as pybind11 cmake_example but used to demo Linux ARM + Windows + macOS builds on GitLab |
[scikit-learn]: https://github.com/scikit-learn/scikit-learn [scikit-learn]: https://github.com/scikit-learn/scikit-learn
@@ -125,11 +125,11 @@ title: Working examples
[Tornado]: https://github.com/tornadoweb/tornado [Tornado]: https://github.com/tornadoweb/tornado
[NCNN]: https://github.com/Tencent/ncnn [NCNN]: https://github.com/Tencent/ncnn
[Matplotlib]: https://github.com/matplotlib/matplotlib [Matplotlib]: https://github.com/matplotlib/matplotlib
[MyPy]: https://github.com/mypyc/mypy_mypyc-wheels
[Prophet]: https://github.com/facebook/prophet [Prophet]: https://github.com/facebook/prophet
[MyPy]: https://github.com/mypyc/mypy_mypyc-wheels
[Kivy]: https://github.com/kivy/kivy [Kivy]: https://github.com/kivy/kivy
[Triton]: https://github.com/openai/triton
[MemRay]: https://github.com/bloomberg/memray [MemRay]: https://github.com/bloomberg/memray
[Triton]: https://github.com/openai/triton
[uvloop]: https://github.com/MagicStack/uvloop [uvloop]: https://github.com/MagicStack/uvloop
[psutil]: https://github.com/giampaolo/psutil [psutil]: https://github.com/giampaolo/psutil
[Google Benchmark]: https://github.com/google/benchmark [Google Benchmark]: https://github.com/google/benchmark
@@ -138,38 +138,37 @@ title: Working examples
[PyGame]: https://github.com/pygame/pygame [PyGame]: https://github.com/pygame/pygame
[asyncpg]: https://github.com/MagicStack/asyncpg [asyncpg]: https://github.com/MagicStack/asyncpg
[cmake]: https://github.com/scikit-build/cmake-python-distributions [cmake]: https://github.com/scikit-build/cmake-python-distributions
[pyinstrument]: https://github.com/joerick/pyinstrument
[scikit-image]: https://github.com/scikit-image/scikit-image [scikit-image]: https://github.com/scikit-image/scikit-image
[PyOxidizer]: https://github.com/indygreg/PyOxidizer
[twisted-iocpsupport]: https://github.com/twisted/twisted-iocpsupport [twisted-iocpsupport]: https://github.com/twisted/twisted-iocpsupport
[PyOxidizer]: https://github.com/indygreg/PyOxidizer
[cvxpy]: https://github.com/cvxpy/cvxpy [cvxpy]: https://github.com/cvxpy/cvxpy
[pedalboard]: https://github.com/spotify/pedalboard
[websockets]: https://github.com/python-websockets/websockets [websockets]: https://github.com/python-websockets/websockets
[pedalboard]: https://github.com/spotify/pedalboard
[River]: https://github.com/online-ml/river [River]: https://github.com/online-ml/river
[aiortc]: https://github.com/aiortc/aiortc
[OpenSpiel]: https://github.com/google-deepmind/open_spiel
[UltraJSON]: https://github.com/ultrajson/ultrajson [UltraJSON]: https://github.com/ultrajson/ultrajson
[OpenSpiel]: https://github.com/google-deepmind/open_spiel
[aiortc]: https://github.com/aiortc/aiortc
[Dependency Injector]: https://github.com/ets-labs/python-dependency-injector [Dependency Injector]: https://github.com/ets-labs/python-dependency-injector
[pyzmq]: https://github.com/zeromq/pyzmq [pyzmq]: https://github.com/zeromq/pyzmq
[CTranslate2]: https://github.com/OpenNMT/CTranslate2
[Implicit]: https://github.com/benfred/implicit [Implicit]: https://github.com/benfred/implicit
[tinyobjloader]: https://github.com/tinyobjloader/tinyobjloader
[vispy]: https://github.com/vispy/vispy [vispy]: https://github.com/vispy/vispy
[tinyobjloader]: https://github.com/tinyobjloader/tinyobjloader
[CTranslate2]: https://github.com/OpenNMT/CTranslate2
[coverage.py]: https://github.com/nedbat/coveragepy [coverage.py]: https://github.com/nedbat/coveragepy
[PyCryptodome]: https://github.com/Legrandin/pycryptodome [PyCryptodome]: https://github.com/Legrandin/pycryptodome
[Line Profiler]: https://github.com/pyutils/line_profiler [Line Profiler]: https://github.com/pyutils/line_profiler
[PyAV]: https://github.com/PyAV-Org/PyAV
[PyYAML]: https://github.com/yaml/pyyaml [PyYAML]: https://github.com/yaml/pyyaml
[pikepdf]: https://github.com/pikepdf/pikepdf [PyAV]: https://github.com/PyAV-Org/PyAV
[numexpr]: https://github.com/pydata/numexpr [numexpr]: https://github.com/pydata/numexpr
[pikepdf]: https://github.com/pikepdf/pikepdf
[h5py]: https://github.com/h5py/h5py [h5py]: https://github.com/h5py/h5py
[Wrapt]: https://github.com/GrahamDumpleton/wrapt [Wrapt]: https://github.com/GrahamDumpleton/wrapt
[envd]: https://github.com/tensorchord/envd [envd]: https://github.com/tensorchord/envd
[Psycopg 3]: https://github.com/psycopg/psycopg
[OpenColorIO]: https://github.com/AcademySoftwareFoundation/OpenColorIO [OpenColorIO]: https://github.com/AcademySoftwareFoundation/OpenColorIO
[ruptures]: https://github.com/deepcharles/ruptures [Psycopg 3]: https://github.com/psycopg/psycopg
[aioquic]: https://github.com/aiortc/aioquic [aioquic]: https://github.com/aiortc/aioquic
[SimpleJSON]: https://github.com/simplejson/simplejson [SimpleJSON]: https://github.com/simplejson/simplejson
[ruptures]: https://github.com/deepcharles/ruptures
[OpenTimelineIO]: https://github.com/AcademySoftwareFoundation/OpenTimelineIO [OpenTimelineIO]: https://github.com/AcademySoftwareFoundation/OpenTimelineIO
[PyTables]: https://github.com/PyTables/PyTables [PyTables]: https://github.com/PyTables/PyTables
[google neuroglancer]: https://github.com/google/neuroglancer [google neuroglancer]: https://github.com/google/neuroglancer
@@ -177,46 +176,46 @@ title: Working examples
[DeepForest]: https://github.com/LAMDA-NJU/Deep-Forest [DeepForest]: https://github.com/LAMDA-NJU/Deep-Forest
[AutoPy]: https://github.com/autopilot-rs/autopy [AutoPy]: https://github.com/autopilot-rs/autopy
[H3-py]: https://github.com/uber/h3-py [H3-py]: https://github.com/uber/h3-py
[time-machine]: https://github.com/adamchainz/time-machine
[mosec]: https://github.com/mosecorg/mosec [mosec]: https://github.com/mosecorg/mosec
[time-machine]: https://github.com/adamchainz/time-machine
[Picologging]: https://github.com/microsoft/picologging [Picologging]: https://github.com/microsoft/picologging
[pybind11 cmake_example]: https://github.com/pybind/cmake_example
[markupsafe]: https://github.com/pallets/markupsafe [markupsafe]: https://github.com/pallets/markupsafe
[Rtree]: https://github.com/Toblerity/rtree [Rtree]: https://github.com/Toblerity/rtree
[pybind11 cmake_example]: https://github.com/pybind/cmake_example
[KDEpy]: https://github.com/tommyod/KDEpy [KDEpy]: https://github.com/tommyod/KDEpy
[dd-trace-py]: https://github.com/DataDog/dd-trace-py [dd-trace-py]: https://github.com/DataDog/dd-trace-py
[tgcalls]: https://github.com/MarshalX/tgcalls [tgcalls]: https://github.com/MarshalX/tgcalls
[pybind11 python_example]: https://github.com/pybind/python_example
[python-rapidjson]: https://github.com/python-rapidjson/python-rapidjson [python-rapidjson]: https://github.com/python-rapidjson/python-rapidjson
[sourmash]: https://github.com/sourmash-bio/sourmash [pybind11 python_example]: https://github.com/pybind/python_example
[abess]: https://github.com/abess-team/abess
[python-snappy]: https://github.com/intake/python-snappy [python-snappy]: https://github.com/intake/python-snappy
[jq.py]: https://github.com/mwilliamson/jq.py [abess]: https://github.com/abess-team/abess
[sourmash]: https://github.com/sourmash-bio/sourmash
[cyvcf2]: https://github.com/brentp/cyvcf2 [cyvcf2]: https://github.com/brentp/cyvcf2
[matrixprofile]: https://github.com/matrix-profile-foundation/matrixprofile [matrixprofile]: https://github.com/matrix-profile-foundation/matrixprofile
[Tokenizer]: https://github.com/OpenNMT/Tokenizer [jq.py]: https://github.com/mwilliamson/jq.py
[iminuit]: https://github.com/scikit-hep/iminuit [iminuit]: https://github.com/scikit-hep/iminuit
[Confluent client for Kafka]: https://github.com/confluentinc/confluent-kafka-python [Tokenizer]: https://github.com/OpenNMT/Tokenizer
[pillow-heif]: https://github.com/bigcat88/pillow_heif
[keyvi]: https://github.com/KeyviDev/keyvi [keyvi]: https://github.com/KeyviDev/keyvi
[PyGLM]: https://github.com/Zuzu-Typ/PyGLM [PyGLM]: https://github.com/Zuzu-Typ/PyGLM
[pillow-heif]: https://github.com/bigcat88/pillow_heif
[TgCrypto]: https://github.com/pyrogram/tgcrypto [TgCrypto]: https://github.com/pyrogram/tgcrypto
[iDynTree]: https://github.com/robotology/idyntree [iDynTree]: https://github.com/robotology/idyntree
[streaming-form-data]: https://github.com/siddhantgoel/streaming-form-data [streaming-form-data]: https://github.com/siddhantgoel/streaming-form-data
[power-grid-model]: https://github.com/PowerGridModel/power-grid-model
[bx-python]: https://github.com/bxlab/bx-python [bx-python]: https://github.com/bxlab/bx-python
[pybase64]: https://github.com/mayeut/pybase64
[boost-histogram]: https://github.com/scikit-hep/boost-histogram [boost-histogram]: https://github.com/scikit-hep/boost-histogram
[Imagecodecs (fork)]: https://github.com/czaki/imagecodecs_build [power-grid-model]: https://github.com/PowerGridModel/power-grid-model
[Python-WebRTC]: https://github.com/MarshalX/python-webrtc [Python-WebRTC]: https://github.com/MarshalX/python-webrtc
[pybind11 scikit_build_example]: https://github.com/pybind/scikit_build_example [pybase64]: https://github.com/mayeut/pybase64
[Imagecodecs (fork)]: https://github.com/czaki/imagecodecs_build
[fathon]: https://github.com/stfbnc/fathon [fathon]: https://github.com/stfbnc/fathon
[Arbor]: https://github.com/arbor-sim/arbor [Arbor]: https://github.com/arbor-sim/arbor
[clang-format]: https://github.com/ssciwr/clang-format-wheel [pybind11 scikit_build_example]: https://github.com/pybind/scikit_build_example
[polaroid]: https://github.com/daggy1234/polaroid [polaroid]: https://github.com/daggy1234/polaroid
[ninja]: https://github.com/scikit-build/ninja-python-distributions [clang-format]: https://github.com/ssciwr/clang-format-wheel
[etebase-py]: https://github.com/etesync/etebase-py [etebase-py]: https://github.com/etesync/etebase-py
[cf-units]: https://github.com/SciTools/cf-units [cf-units]: https://github.com/SciTools/cf-units
[Confluent client for Kafka]: https://github.com/confluentinc/confluent-kafka-python
[ninja]: https://github.com/scikit-build/ninja-python-distributions
[numpythia]: https://github.com/scikit-hep/numpythia [numpythia]: https://github.com/scikit-hep/numpythia
[pyjet]: https://github.com/scikit-hep/pyjet [pyjet]: https://github.com/scikit-hep/pyjet
[ril]: https://github.com/Cryptex-github/ril-py [ril]: https://github.com/Cryptex-github/ril-py
@@ -226,8 +225,10 @@ title: Working examples
[CorrectionLib]: https://github.com/cms-nanoAOD/correctionlib [CorrectionLib]: https://github.com/cms-nanoAOD/correctionlib
[xmlstarlet]: https://github.com/dimitern/xmlstarlet [xmlstarlet]: https://github.com/dimitern/xmlstarlet
[werpy]: https://github.com/analyticsinmotion/werpy [werpy]: https://github.com/analyticsinmotion/werpy
[pyinstrument_cext]: https://github.com/joerick/pyinstrument_cext
[pybind11 cross build example]: https://github.com/wbarnha/pybind_cmake_example_crossbuild [pybind11 cross build example]: https://github.com/wbarnha/pybind_cmake_example_crossbuild
[appveyor icon]: data/readme_icons/appveyor.svg
[github icon]: data/readme_icons/github.svg [github icon]: data/readme_icons/github.svg
[azurepipelines icon]: data/readme_icons/azurepipelines.svg [azurepipelines icon]: data/readme_icons/azurepipelines.svg
[circleci icon]: data/readme_icons/circleci.svg [circleci icon]: data/readme_icons/circleci.svg
+18
View File
@@ -0,0 +1,18 @@
environment:
matrix:
- APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu2204
APPVEYOR_JOB_NAME: "linux-x64"
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022
APPVEYOR_JOB_NAME: "windows-x64"
- APPVEYOR_BUILD_WORKER_IMAGE: macos-sonoma
APPVEYOR_JOB_NAME: "macos-x64"
stack: python 3.12
install: python -m pip install cibuildwheel==2.20.0
build_script: python -m cibuildwheel --output-dir wheelhouse
artifacts:
- path: "wheelhouse\\*.whl"
name: Wheels

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