diff --git a/.circleci/prepare.sh b/.circleci/prepare.sh index 272ddae4d..86ab4cbea 100644 --- a/.circleci/prepare.sh +++ b/.circleci/prepare.sh @@ -10,7 +10,7 @@ fi $PYTHON --version $PYTHON -m venv venv -venv/bin/python -m pip install -U pip -venv/bin/python -m pip install -e ".[dev]" +venv/bin/python -m pip install -U pip dependency-groups +venv/bin/python -m dependency_groups test | xargs venv/bin/python -m pip install -e. venv/bin/python -m pip freeze venv/bin/python --version diff --git a/.cirrus.yml b/.cirrus.yml index c0f7d8ce5..6faa9bf56 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -2,7 +2,8 @@ only_if: changesInclude('.cirrus.yml') || ($CIRRUS_BRANCH == "main" && !changesI run_tests: &RUN_TESTS install_cibuildwheel_script: - - 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. run_cibuildwheel_tests_script: - python ./bin/run_tests.py @@ -52,7 +53,7 @@ windows_x86_task: macos_arm64_task: macos_instance: - image: ghcr.io/cirruslabs/macos-sonoma-xcode + image: ghcr.io/cirruslabs/macos-runner:sonoma env: PATH: /opt/homebrew/opt/python@3.10/libexec/bin:$PATH @@ -62,7 +63,7 @@ macos_arm64_task: macos_arm64_cp38_task: macos_instance: - image: ghcr.io/cirruslabs/macos-sonoma-xcode + image: ghcr.io/cirruslabs/macos-runner:sonoma env: PATH: /opt/homebrew/opt/python@3.10/libexec/bin:$PATH diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b123df1e5..8f97b1c99 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,16 +13,7 @@ jobs: steps: - uses: actions/checkout@v4 - - 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/* + - uses: hynek/build-and-inspect-python-package@v2 publish: needs: [dist] @@ -38,13 +29,14 @@ jobs: steps: - uses: actions/download-artifact@v4 with: - pattern: cibw-* + name: Packages path: dist - merge-multiple: true - name: Generate artifact attestation for sdist and wheel - uses: actions/attest-build-provenance@1c608d11d69870c2092266b3f9a6f3abbf17002c # v1.4.3 + uses: actions/attest-build-provenance@ef244123eb79f2f7a7e75d99086184180e6d0018 # v1.4.4 with: subject-path: "dist/cibuildwheel-*" - uses: pypa/gh-action-pypi-publish@release/v1 + with: + attestations: true diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 54e950e80..f3ae2f604 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -38,7 +38,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest, macos-13, macos-14] - python_version: ['3.12'] + python_version: ['3.13'] include: - os: ubuntu-latest python_version: '3.8' @@ -49,6 +49,7 @@ jobs: name: Install Python ${{ matrix.python_version }} with: python-version: ${{ matrix.python_version }} + allow-prereleases: true - uses: astral-sh/setup-uv@v3 @@ -66,11 +67,11 @@ jobs: - name: Install dependencies run: | - uv pip install --system ".[test]" + uv sync --no-dev --group test - name: Generate a sample project run: | - python -m test.test_projects test.test_0_basic.basic_project sample_proj + uv run -m test.test_projects test.test_0_basic.basic_project sample_proj - name: Run a sample build (GitHub Action) uses: ./ @@ -123,7 +124,7 @@ jobs: - name: Test cibuildwheel run: | - python ./bin/run_tests.py --run-podman + uv run bin/run_tests.py --run-podman emulated-archs: name: Get qemu emulated architectures @@ -136,12 +137,13 @@ jobs: - uses: actions/setup-python@v5 with: python-version: "3.x" + - uses: astral-sh/setup-uv@v3 - name: Install dependencies - run: python -m pip install ".[test]" + run: uv sync --no-dev --group test - name: Get qemu emulated architectures id: archs run: | - OUTPUT=$(python -c "from json import dumps; from test.utils import EMULATED_ARCHS; print(dumps(EMULATED_ARCHS))") + OUTPUT=$(uv run python -c "from json import dumps; from test.utils import EMULATED_ARCHS; print(dumps(EMULATED_ARCHS))") echo "${OUTPUT}" echo "archs=${OUTPUT}" >> "$GITHUB_OUTPUT" @@ -158,17 +160,18 @@ jobs: - uses: actions/setup-python@v5 with: python-version: "3.x" + - uses: astral-sh/setup-uv@v3 - name: Install dependencies - run: python -m pip install ".[test,uv]" + run: uv sync --no-dev --group test - name: Set up QEMU uses: docker/setup-qemu-action@v3 - name: Run the emulation tests - run: pytest --run-emulation ${{ matrix.arch }} test/test_emulation.py + run: uv run pytest --run-emulation ${{ matrix.arch }} test/test_emulation.py test-pyodide: - name: Test cibuildwheel building pyodide wheels + name: Test cibuildwheel building Pyodide wheels needs: lint runs-on: ubuntu-24.04 timeout-minutes: 180 @@ -178,14 +181,14 @@ jobs: name: Install Python 3.12 with: python-version: '3.12' + - uses: astral-sh/setup-uv@v3 - name: Install dependencies - run: | - python -m pip install ".[test]" + run: uv sync --no-dev --group test - name: Generate a sample project run: | - python -m test.test_projects test.test_0_basic.basic_project sample_proj + uv run -m test.test_projects test.test_0_basic.basic_project sample_proj - name: Run a sample build (GitHub Action) uses: ./ @@ -197,6 +200,6 @@ jobs: - name: Run tests with 'CIBW_PLATFORM' set to 'pyodide' run: | - python ./bin/run_tests.py + uv run ./bin/run_tests.py env: CIBW_PLATFORM: pyodide diff --git a/.github/workflows/update-dependencies.yml b/.github/workflows/update-dependencies.yml index 2163d3c76..8b4a09998 100644 --- a/.github/workflows/update-dependencies.yml +++ b/.github/workflows/update-dependencies.yml @@ -20,9 +20,19 @@ jobs: if: github.repository_owner == 'pypa' || github.event_name != 'schedule' runs-on: ubuntu-latest 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@v1 + 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: wntrblm/nox@2024.04.15 + - uses: wntrblm/nox@2024.10.09 with: python-versions: "3.7, 3.8, 3.9, 3.10, 3.11, 3.12, 3.13" @@ -33,15 +43,6 @@ jobs: - name: "Run update: docs user projects" 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 if: github.ref == 'refs/heads/main' && github.repository == 'pypa/cibuildwheel' uses: peter-evans/create-pull-request@v7 @@ -53,7 +54,6 @@ jobs: PR generated by "Update dependencies" [workflow](https://github.com/${{github.repository}}/actions/runs/${{github.run_id}}). branch: update-dependencies-pr - committer: "cibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com>" - author: "cibuildwheel-bot[bot] <83877280+cibuildwheel-bot[bot]@users.noreply.github.com>" + sign-commits: true token: ${{ steps.generate-token.outputs.token }} delete-branch: true diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 53c0d477f..ecf0361fb 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -16,7 +16,8 @@ linux: script: - 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 windows: @@ -26,7 +27,8 @@ windows: before_script: - choco install python -y --version 3.12.4 script: - - py -m pip install -e ".[dev]" pytest-custom-exit-code + - py -m pip install dependency-groups + - py -m pip install -e. pytest-custom-exit-code $(py -m dependency_groups test) - py bin\run_tests.py tags: - saas-windows-medium-amd64 @@ -36,7 +38,8 @@ macos: variables: PYTEST_ADDOPTS: -k "unit_test or test_0_basic" --suppress-no-test-exit-code script: - - python3 -m pip install -e ".[dev]" pytest-custom-exit-code + - python3 -m pip install dependency-groups + - python3 -m dependency_groups test | xargs python3 -m pip install -e. pytest-custom-exit-code - python3 ./bin/run_tests.py tags: - saas-macos-medium-m1 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2ec789f6b..4c0e3e99c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 + rev: v5.0.0 hooks: - id: check-case-conflict - id: check-merge-conflict @@ -14,14 +14,14 @@ repos: - id: trailing-whitespace - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.6.5 + rev: v0.8.0 hooks: - id: ruff args: ["--fix", "--show-fixes"] - id: ruff-format - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.11.2 + rev: v1.13.0 hooks: - id: mypy name: mypy 3.8 on cibuildwheel/ @@ -29,9 +29,12 @@ repos: args: ["--python-version=3.8"] additional_dependencies: &mypy-dependencies - bracex + - dependency-groups>=1.2 - nox + - orjson - packaging - pygithub + - pytest - rich - tomli - tomli_w @@ -40,6 +43,7 @@ repos: - types-jinja2 - types-pyyaml - types-requests + - types-setuptools - uv - validate-pyproject - id: mypy @@ -77,7 +81,7 @@ repos: - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.29.2 + rev: 0.29.4 hooks: - id: check-dependabot - id: check-github-actions diff --git a/.readthedocs.yml b/.readthedocs.yml index 30a381ecc..60724f36e 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -10,6 +10,4 @@ build: - asdf plugin add uv - asdf install uv latest - asdf global uv latest - - uv venv - - uv pip install -e.[docs] - - NO_COLOR=1 .venv/bin/mkdocs build --strict --site-dir $READTHEDOCS_OUTPUT/html + - NO_COLOR=1 uv run --no-dev --group docs mkdocs build --strict --site-dir $READTHEDOCS_OUTPUT/html diff --git a/.travis.yml b/.travis.yml index 709127e61..b610edf00 100644 --- a/.travis.yml +++ b/.travis.yml @@ -57,8 +57,8 @@ jobs: 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 -e ".[test]" pytest-custom-exit-code +- $PYTHON -m pip install -U pip dependency-groups +- $PYTHON -m dependency_groups test | xargs $PYTHON -m pip install -e. script: | # travis_wait disable the output while waiting diff --git a/README.md b/README.md index 5e86ca72b..986ef5537 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ 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) [![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/pypa/cibuildwheel) +[![Travis Status](https://img.shields.io/travis/com/pypa/cibuildwheel/main?logo=travis)](https://travis-ci.com/github/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) [![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) @@ -22,27 +22,30 @@ Python wheels are great. Building them across **Mac, Linux, Windows**, on **mult What does it do? ---------------- -| | macOS Intel | macOS Apple Silicon | Windows 64bit | Windows 32bit | Windows Arm64 | manylinux
musllinux x86_64 | manylinux
musllinux i686 | manylinux
musllinux aarch64 | manylinux
musllinux ppc64le | manylinux
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 | +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
musllinux x86_64 | manylinux
musllinux i686 | manylinux
musllinux aarch64 | manylinux
musllinux ppc64le | manylinux
musllinux s390x | manylinux
musllinux armv7l | 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 | N/A | +| PyPy 3.8 v7.3 | ✅ | ✅ | ✅ | 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 | +| PyPy 3.10 v7.3 | ✅ | ✅ | ✅ | N/A | N/A | ✅¹ | ✅¹ | ✅¹ | N/A | N/A | N/A | N/A | ¹ PyPy is only supported for manylinux wheels.
² Windows arm64 support is experimental.
-³ 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).
+³ Free-threaded mode requires opt-in using [`CIBW_FREE_THREADED_SUPPORT`](https://cibuildwheel.pypa.io/en/stable/options/#free-threaded-support).
⁴ Experimental, not yet supported on PyPI, but can be used directly in web deployment. Use `--platform pyodide` to build.
+⁵ manylinux armv7l support is experimental. As there are no RHEL based image for this architecture, it's using an Ubuntu based image instead.
-- Builds manylinux, musllinux, macOS 10.9+, and Windows wheels for CPython and PyPy +- Builds manylinux, musllinux, macOS 10.9+ (10.13+ for Python 3.12+), and Windows wheels for CPython and PyPy - 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) - Runs your library's tests against the wheel-installed version of your library @@ -85,7 +88,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, windows-latest, macos-13, macos-14] + os: [ubuntu-latest, windows-latest, macos-13, macos-latest] steps: - uses: actions/checkout@v4 @@ -94,7 +97,7 @@ jobs: - uses: actions/setup-python@v5 - name: Install cibuildwheel - run: python -m pip install cibuildwheel==2.21.1 + run: python -m pip install cibuildwheel==2.22.0 - name: Build wheels run: python -m cibuildwheel --output-dir wheelhouse @@ -211,6 +214,43 @@ Changelog +### 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_ @@ -231,43 +271,6 @@ _13 September 2024_ - 🐛 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 - -_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) -- ✨ 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) - - -### v2.19.2 - -_2 July 2024_ - -- 🐛 Update manylinux2014 pins to versions that support past-EoL CentOS 7 mirrors. (#1917) -- 🐛 Support `--no-isolation` with `build[uv]` build-frontend. (#1889) -- 🛠 Provide attestations for releases at . (#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) - - -### v2.19.1 - -_13 June 2024_ - -- 🐛 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) - --- diff --git a/azure-pipelines.yml b/azure-pipelines.yml index f6f066f04..fa720d84a 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -14,7 +14,8 @@ jobs: versionSpec: '3.8' - bash: | docker run --rm --privileged docker.io/tonistiigi/binfmt:latest --install all - python -m pip install -e ".[dev]" + python -m pip install dependency-groups + python -m dependency_groups test | xargs python -m pip install -e. python ./bin/run_tests.py - job: macos_38 @@ -24,7 +25,8 @@ jobs: inputs: versionSpec: '3.8' - bash: | - python -m pip install -e ".[dev]" + python -m pip install dependency-groups + python -m dependency_groups test | xargs python -m pip install -e. python ./bin/run_tests.py --num-processes 2 - job: windows_38 @@ -35,5 +37,6 @@ jobs: inputs: versionSpec: '3.8' - bash: | - python -m pip install -e ".[dev]" + python -m pip install dependency-groups + python -m dependency_groups test | xargs python -m pip install -e. python ./bin/run_tests.py diff --git a/bin/generate_schema.py b/bin/generate_schema.py index 2c90fe925..0ef4f0125 100755 --- a/bin/generate_schema.py +++ b/bin/generate_schema.py @@ -26,6 +26,12 @@ - append default: none description: How to inherit the parent's value. + enable: + enum: + - cpython-freethreading + - cpython-prerelease + - pypy + description: A Python version or flavor to enable. additionalProperties: false description: cibuildwheel's settings. type: object @@ -99,6 +105,13 @@ default: pinned description: Specify how cibuildwheel controls the versions of the tools it uses type: string + enable: + description: Enable or disable certain builds. + oneOf: + - $ref: "#/$defs/enable" + - type: array + items: + $ref: "#/$defs/enable" environment: description: Set environment variables needed during the build. type: string_table @@ -110,9 +123,13 @@ type: boolean default: false description: The project supports free-threaded builds of Python (PEP703) + deprecated: Use the `enable` option instead. manylinux-aarch64-image: type: string description: Specify alternative manylinux / musllinux container images + manylinux-armv7l-image: + type: string + description: Specify alternative manylinux / musllinux container images manylinux-i686-image: type: string description: Specify alternative manylinux / musllinux container images @@ -137,6 +154,9 @@ musllinux-aarch64-image: type: string description: Specify alternative manylinux / musllinux container images + musllinux-armv7l-image: + type: string + description: Specify alternative manylinux / musllinux container images musllinux-i686-image: type: string description: Specify alternative manylinux / musllinux container images @@ -161,6 +181,9 @@ test-extras: description: Install your wheel for testing using `extras_require` type: string_array + test-groups: + description: Install extra groups when testing + type: string_array test-requires: description: Install Python dependencies before running the tests type: string_array @@ -255,6 +278,7 @@ del non_global_options["skip"] del non_global_options["test-skip"] del non_global_options["free-threaded-support"] +del non_global_options["enable"] overrides["items"]["properties"]["select"]["oneOf"] = string_array overrides["items"]["properties"] |= non_global_options.copy() diff --git a/bin/make_dependency_update_pr.py b/bin/make_dependency_update_pr.py index cfbdef913..54796641a 100755 --- a/bin/make_dependency_update_pr.py +++ b/bin/make_dependency_update_pr.py @@ -3,27 +3,29 @@ from __future__ import annotations import os +import subprocess import sys import textwrap import time from pathlib import Path -from subprocess import run import click -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]: + return subprocess.run([cmd], shell=True, check=check, **kwargs) # type: ignore[call-overload, no-any-return] -def git_repo_has_changes(): - 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 +def git_repo_has_changes() -> bool: + unstaged_changes: bool = shell("git diff-index --quiet HEAD --", check=False).returncode != 0 + staged_changes: bool = ( + shell("git diff-index --quiet --cached HEAD --", check=False).returncode != 0 + ) return unstaged_changes or staged_changes @click.command() -def main(): +def main() -> None: project_root = Path(__file__).parent / ".." os.chdir(project_root) @@ -57,7 +59,7 @@ def main(): PR generated by `{os.path.basename(__file__)}`. """ ) - run( + subprocess.run( [ "gh", "pr", diff --git a/bin/projects.py b/bin/projects.py index 3b39c3da4..09c5dc7f4 100755 --- a/bin/projects.py +++ b/bin/projects.py @@ -172,7 +172,9 @@ def get_projects( return sorted((Project(item, github) for item in config), reverse=online) -def render_projects(projects: Sequence[Project], *, dest_path: Path, include_info: bool = True): +def render_projects( + projects: Sequence[Project], *, dest_path: Path, include_info: bool = True +) -> str: io = StringIO() print = functools.partial(builtins.print, file=io) @@ -203,7 +205,7 @@ def insert_projects_table( projects: Sequence[Project], input_filename: str, include_info: bool = True, -): +) -> None: text = file.read_text() projects_table = render_projects(projects, include_info=include_info, dest_path=file) diff --git a/bin/run_example_ci_configs.py b/bin/run_example_ci_configs.py index 5c5dfbe34..a24529518 100755 --- a/bin/run_example_ci_configs.py +++ b/bin/run_example_ci_configs.py @@ -4,29 +4,29 @@ import os import shutil +import subprocess import sys import textwrap import time import typing from glob import glob from pathlib import Path -from subprocess import run from urllib.parse import quote import click -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]: + return subprocess.run([cmd], shell=True, check=check, **kwargs) # type: ignore[call-overload, no-any-return] -def git_repo_has_changes(): +def git_repo_has_changes() -> bool: 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 return unstaged_changes or staged_changes -def generate_basic_project(path): +def generate_basic_project(path: Path) -> None: sys.path.insert(0, "") from test.test_projects.c import new_c_project @@ -79,7 +79,7 @@ class CIService(typing.NamedTuple): ] -def ci_service_for_config_file(config_file): +def ci_service_for_config_file(config_file: str) -> CIService: filename = Path(config_file).name try: @@ -134,7 +134,7 @@ def run_example_ci_configs(config_files=None): dst_config_file.parent.mkdir(parents=True, exist_ok=True) shutil.copyfile(src_config_file, dst_config_file) - run(["git", "add", example_project], check=True) + subprocess.run(["git", "add", example_project], check=True) message = textwrap.dedent( f"""\ Test example minimal configs @@ -144,7 +144,7 @@ def run_example_ci_configs(config_files=None): Time: {timestamp} """ ) - run(["git", "commit", "--no-verify", "--message", message], check=True) + subprocess.run(["git", "commit", "--no-verify", "--message", message], check=True) shell(f"git subtree --prefix={example_project} push origin {branch_name}", check=True) print("---") diff --git a/bin/update_docker.py b/bin/update_docker.py index 9be3ae03d..e090f3f0f 100755 --- a/bin/update_docker.py +++ b/bin/update_docker.py @@ -6,6 +6,7 @@ from pathlib import Path import requests +from packaging.version import Version DIR = Path(__file__).parent.resolve() RESOURCES = DIR.parent / "cibuildwheel/resources" @@ -53,6 +54,8 @@ class Image: Image("manylinux_2_28", "s390x", "quay.io/pypa/manylinux_2_28_s390x", None), Image("manylinux_2_28", "pypy_x86_64", "quay.io/pypa/manylinux_2_28_x86_64", None), Image("manylinux_2_28", "pypy_aarch64", "quay.io/pypa/manylinux_2_28_aarch64", None), + # manylinux_2_31 images + Image("manylinux_2_31", "armv7l", "ghcr.io/mayeut/manylinux_2_31", None), # musllinux_1_1 images 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), @@ -65,6 +68,7 @@ class Image: 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), + Image("musllinux_1_2", "armv7l", "quay.io/pypa/musllinux_1_2_armv7l", None), ] config = configparser.ConfigParser() @@ -90,6 +94,21 @@ class Image: for (name, info) in tags_dict.items() 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: response = requests.get(f"https://hub.docker.com/v2/repositories/{image.image_name}/tags") response.raise_for_status() diff --git a/bin/update_how_it_works_image.py b/bin/update_how_it_works_image.py index 9a8b320f5..0ed36fafd 100755 --- a/bin/update_how_it_works_image.py +++ b/bin/update_how_it_works_image.py @@ -18,7 +18,7 @@ ) -def main(): +def main() -> None: subprocess.run(["mkdocs", "build"], check=True) hti = Html2Image(custom_flags=["--force-device-scale-factor=2"]) diff --git a/bin/update_readme_changelog.py b/bin/update_readme_changelog.py index 4a4625ff5..65756902c 100755 --- a/bin/update_readme_changelog.py +++ b/bin/update_readme_changelog.py @@ -20,7 +20,7 @@ ) -def main(): +def main() -> None: changelog_text = CHANGELOG_FILE.read_text() readme_text = README_FILE.read_text() diff --git a/bin/update_virtualenv.py b/bin/update_virtualenv.py index fc69cf63d..b458c3f89 100755 --- a/bin/update_virtualenv.py +++ b/bin/update_virtualenv.py @@ -36,7 +36,7 @@ class VersionTuple: version_string: str -def git_ls_remote_versions(url) -> list[VersionTuple]: +def git_ls_remote_versions(url: str) -> list[VersionTuple]: versions: list[VersionTuple] = [] tags = subprocess.run( ["git", "ls-remote", "--tags", url], check=True, text=True, capture_output=True diff --git a/cibuildwheel/__init__.py b/cibuildwheel/__init__.py index 6062c1e48..413fd1d68 100644 --- a/cibuildwheel/__init__.py +++ b/cibuildwheel/__init__.py @@ -1,3 +1,3 @@ from __future__ import annotations -__version__ = "2.21.1" +__version__ = "2.22.0" diff --git a/cibuildwheel/__main__.py b/cibuildwheel/__main__.py index 6d97cdc67..ffdde57bf 100644 --- a/cibuildwheel/__main__.py +++ b/cibuildwheel/__main__.py @@ -271,7 +271,6 @@ def get_python_configurations( def build(self, options: Options, tmp_path: Path) -> None: ... -# pylint: disable-next=inconsistent-return-statements def get_platform_module(platform: PlatformName) -> PlatformModule: if platform == "linux": return cibuildwheel.linux @@ -381,14 +380,13 @@ def print_preamble(platform: str, options: Options, identifiers: Sequence[str]) print() print(f"Cache folder: {CIBW_CACHE_PATH}") + print() warnings = detect_warnings(options=options, identifiers=identifiers) - if warnings: - print("\nWarnings:") - for warning in warnings: - print(" " + warning) + for warning in warnings: + log.warning(warning) - print("\nHere we go!\n") + print("Here we go!\n") def get_build_identifiers( @@ -403,6 +401,16 @@ def get_build_identifiers( def detect_warnings(*, options: Options, identifiers: Iterable[str]) -> list[str]: 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} for option_name in ["test_command", "before_build"]: option_values = [getattr(options.build_options(i), option_name) for i in identifiers] @@ -411,7 +419,7 @@ def detect_warnings(*, options: Options, identifiers: Iterable[str]) -> list[str # Reminder: in an f-string, double braces means literal single brace msg = ( f"{option_name}: '{{python}}' and '{{pip}}' are no longer needed, " - "and will be removed in a future release. Simply use 'python' or 'pip' instead." + "and will be removed in cibuildwheel 3. Simply use 'python' or 'pip' instead." ) warnings.append(msg) diff --git a/cibuildwheel/_compat/typing.py b/cibuildwheel/_compat/typing.py index 05bae73e0..eb11302ab 100644 --- a/cibuildwheel/_compat/typing.py +++ b/cibuildwheel/_compat/typing.py @@ -8,7 +8,7 @@ from typing import NotRequired, Self, assert_never __all__ = ( - "assert_never", "NotRequired", "Self", + "assert_never", ) diff --git a/cibuildwheel/architecture.py b/cibuildwheel/architecture.py index c6c4b623f..0f622def9 100644 --- a/cibuildwheel/architecture.py +++ b/cibuildwheel/architecture.py @@ -37,6 +37,7 @@ class Architecture(Enum): aarch64 = "aarch64" ppc64le = "ppc64le" s390x = "s390x" + armv7l = "armv7l" # mac archs universal2 = "universal2" @@ -132,6 +133,7 @@ def all_archs(platform: PlatformName) -> set[Architecture]: Architecture.aarch64, Architecture.ppc64le, Architecture.s390x, + Architecture.armv7l, }, "macos": {Architecture.x86_64, Architecture.arm64, Architecture.universal2}, "windows": {Architecture.x86, Architecture.AMD64, Architecture.ARM64}, @@ -140,17 +142,15 @@ def all_archs(platform: PlatformName) -> set[Architecture]: return all_archs_map[platform] @staticmethod - # pylint: disable-next=inconsistent-return-statements def bitness_archs(platform: PlatformName, bitness: Literal["64", "32"]) -> set[Architecture]: - archs_32 = {Architecture.i686, Architecture.x86} + archs_32 = {Architecture.i686, Architecture.x86, Architecture.armv7l} auto_archs = Architecture.auto_archs(platform) if bitness == "64": return auto_archs - archs_32 - elif bitness == "32": + if bitness == "32": return auto_archs & archs_32 - else: - assert_never(bitness) + assert_never(bitness) def allowed_architectures_check( diff --git a/cibuildwheel/errors.py b/cibuildwheel/errors.py index e441bd40b..28939719f 100644 --- a/cibuildwheel/errors.py +++ b/cibuildwheel/errors.py @@ -35,11 +35,14 @@ def __init__(self) -> None: """ Build failed because a pure Python wheel was generated. - If you intend to build a pure-Python wheel, you don't need cibuildwheel - use - `pip wheel -w DEST_DIR .` instead. - - If you expected a platform wheel, check your project configuration, or run - cibuildwheel with CIBW_BUILD_VERBOSITY=1 to view build logs. + If you intend to build a pure-Python wheel, you don't need + cibuildwheel - use `pip wheel .`, `pipx run build --wheel`, `uv + 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, + or run cibuildwheel with CIBW_BUILD_VERBOSITY=1 to view build logs. """ ) super().__init__(message) @@ -61,4 +64,24 @@ def __init__(self, wheel_name: str) -> None: class OCIEngineTooOldError(FatalError): - return_code = 7 + 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 diff --git a/cibuildwheel/linux.py b/cibuildwheel/linux.py index 863536c92..1ffe96f9a 100644 --- a/cibuildwheel/linux.py +++ b/cibuildwheel/linux.py @@ -1,5 +1,6 @@ from __future__ import annotations +import contextlib import subprocess import sys import textwrap @@ -35,6 +36,7 @@ Architecture.aarch64: OCIPlatform.ARM64, Architecture.ppc64le: OCIPlatform.PPC64LE, Architecture.s390x: OCIPlatform.S390X, + Architecture.armv7l: OCIPlatform.ARMV7, } @@ -132,26 +134,33 @@ def check_all_python_exist( *, platform_configs: Iterable[PythonConfiguration], container: OCIContainer ) -> None: exist = True - has_manylinux_interpreters = True + has_manylinux_interpreters = False messages = [] - try: + with contextlib.suppress(subprocess.CalledProcessError): # use capture_output to keep quiet container.call(["manylinux-interpreters", "--help"], capture_output=True) - except subprocess.CalledProcessError: - has_manylinux_interpreters = False + has_manylinux_interpreters = True for config in platform_configs: python_path = config.path / "bin" / "python" - try: - if has_manylinux_interpreters: + if has_manylinux_interpreters: + try: container.call(["manylinux-interpreters", "ensure", config.path.name]) - 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 + except subprocess.CalledProcessError: + messages.append( + f" 'manylinux-interpreters ensure {config.path.name}' needed to build '{config.identifier}' failed in container running image '{container.image}'." + " Either the installation failed or this interpreter is not available in that image. Please check the logs." + ) + 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: message = "\n".join(messages) raise errors.FatalError(message) @@ -179,7 +188,7 @@ def build_in_container( log.step("Running before_all...") env = container.get_environment() - env["PATH"] = f'/opt/python/cp38-cp38/bin:{env["PATH"]}' + env["PATH"] = f'/opt/python/cp39-cp39/bin:{env["PATH"]}' env["PIP_DISABLE_PIP_VERSION_CHECK"] = "1" env["PIP_ROOT_USER_ACTION"] = "ignore" env = before_all_options.environment.as_dictionary( @@ -325,6 +334,9 @@ def build_in_container( repaired_wheels = container.glob(repaired_wheel_dir, "*.whl") + if not repaired_wheels: + raise errors.RepairStepProducedNoWheelError() + for repaired_wheel in repaired_wheels: if repaired_wheel.name in {wheel.name for wheel in built_wheels}: raise errors.AlreadyBuiltWheelError(repaired_wheel.name) diff --git a/cibuildwheel/logger.py b/cibuildwheel/logger.py index 312e2f559..542576076 100644 --- a/cibuildwheel/logger.py +++ b/cibuildwheel/logger.py @@ -23,11 +23,13 @@ "manylinux_aarch64": "manylinux aarch64", "manylinux_ppc64le": "manylinux ppc64le", "manylinux_s390x": "manylinux s390x", + "manylinux_armv7l": "manylinux armv7l", "musllinux_x86_64": "musllinux x86_64", "musllinux_i686": "musllinux i686", "musllinux_aarch64": "musllinux aarch64", "musllinux_ppc64le": "musllinux ppc64le", - "musllinux_s390x": "manylinux s390x", + "musllinux_s390x": "musllinux s390x", + "musllinux_armv7l": "musllinux armv7l", "win32": "Windows 32bit", "win_amd64": "Windows 64bit", "win_arm64": "Windows on ARM 64bit", diff --git a/cibuildwheel/macos.py b/cibuildwheel/macos.py index 0a07bb651..fcc88dac9 100644 --- a/cibuildwheel/macos.py +++ b/cibuildwheel/macos.py @@ -1,6 +1,7 @@ from __future__ import annotations import functools +import inspect import os import platform import re @@ -149,11 +150,15 @@ def install_cpython(tmp: Path, version: str, url: str, free_threading: bool) -> if detect_ci_provider() is None: # if running locally, we don't want to install CPython with sudo # let the user know & provide a link to the installer - msg = ( - f"Error: CPython {version} is not installed.\n" - "cibuildwheel will not perform system-wide installs when running outside of CI.\n" - f"To build locally, install CPython {version} on this machine, or, disable this version of Python using CIBW_SKIP=cp{version.replace('.', '')}-macosx_*\n" - f"\nDownload link: {url}" + msg = inspect.cleandoc( + f""" + Error: CPython {version} is not installed. + cibuildwheel will not perform system-wide installs when running outside of CI. + To build locally, install CPython {version} on this machine, or, disable this + 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) pkg_path = tmp / "Python.pkg" @@ -273,23 +278,23 @@ def setup_python( # 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 if not use_uv: 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() + 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) - - # 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) + call("pip", "--version", env=env) config_is_arm64 = python_configuration.identifier.endswith("arm64") config_is_universal2 = python_configuration.identifier.endswith("universal2") @@ -546,7 +551,10 @@ def build(options: Options, tmp_path: Path) -> None: else: shutil.move(str(built_wheel), repaired_wheel_dir) - repaired_wheel = next(repaired_wheel_dir.glob("*.whl")) + try: + repaired_wheel = next(repaired_wheel_dir.glob("*.whl")) + except StopIteration: + raise errors.RepairStepProducedNoWheelError() from None if repaired_wheel.name in {wheel.name for wheel in built_wheels}: raise errors.AlreadyBuiltWheelError(repaired_wheel.name) diff --git a/cibuildwheel/oci_container.py b/cibuildwheel/oci_container.py index b13417e3b..646bbc643 100644 --- a/cibuildwheel/oci_container.py +++ b/cibuildwheel/oci_container.py @@ -8,6 +8,7 @@ import shutil import subprocess import sys +import textwrap import typing import uuid from collections.abc import Mapping, Sequence @@ -17,14 +18,13 @@ from types import TracebackType from typing import IO, Dict, Literal -from packaging.version import InvalidVersion, Version - from ._compat.typing import Self, assert_never from .errors import OCIEngineTooOldError from .logger import log from .typing import PathOrStr, PopenBytes from .util import ( CIProvider, + FlexibleVersion, call, detect_ci_provider, parse_key_value_string, @@ -38,6 +38,7 @@ class OCIPlatform(Enum): i386 = "linux/386" AMD64 = "linux/amd64" + ARMV7 = "linux/arm/v7" ARM64 = "linux/arm64" PPC64LE = "linux/ppc64le" S390X = "linux/s390x" @@ -103,25 +104,45 @@ def _check_engine_version(engine: OCIContainerEngineConfig) -> None: version_string = call(engine.name, "version", "-f", "{{json .}}", capture_stdout=True) version_info = json.loads(version_string.strip()) if engine.name == "docker": - # --platform support was introduced in 1.32 as experimental - # docker cp, as used by cibuildwheel, has been fixed in v24 => API 1.43, https://github.com/moby/moby/issues/38995 - client_api_version = Version(version_info["Client"]["ApiVersion"]) - engine_api_version = Version(version_info["Server"]["ApiVersion"]) - version_supported = min(client_api_version, engine_api_version) >= Version("1.43") + 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": - client_api_version = Version(version_info["Client"]["APIVersion"]) + # podman uses the same version string for "Version" & "ApiVersion" + client_version = FlexibleVersion(version_info["Client"]["Version"]) if "Server" in version_info: - engine_api_version = Version(version_info["Server"]["APIVersion"]) + server_version = FlexibleVersion(version_info["Server"]["Version"]) else: - engine_api_version = client_api_version + server_version = client_version # --platform support was introduced in v3 - version_supported = min(client_api_version, engine_api_version) >= Version("3") + 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 not version_supported: - raise OCIEngineTooOldError() from None - except (subprocess.CalledProcessError, KeyError, InvalidVersion) as e: - raise OCIEngineTooOldError() from e + 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: @@ -147,7 +168,7 @@ class OCIContainer: ... print(self.debug_info()) """ - UTILITY_PYTHON = "/opt/python/cp38-cp38/bin/python" + UTILITY_PYTHON = "/opt/python/cp39-cp39/bin/python" process: PopenBytes bash_stdin: IO[bytes] @@ -188,7 +209,11 @@ def _get_platform_args(self, *, oci_platform: OCIPlatform | None = None) -> tupl "inspect", self.image, "--format", - "{{.Os}}/{{.Architecture}}", + ( + "{{.Os}}/{{.Architecture}}/{{.Variant}}" + if len(oci_platform.value.split("/")) == 3 + else "{{.Os}}/{{.Architecture}}" + ), capture_stdout=True, ).strip() if image_platform == oci_platform.value: @@ -215,7 +240,7 @@ def __enter__(self) -> Self: platform_args = self._get_platform_args() simulate_32_bit = False - if self.oci_platform == OCIPlatform.i386: + if self.oci_platform in {OCIPlatform.i386, OCIPlatform.ARMV7}: # 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 # simulate this @@ -226,13 +251,16 @@ def __enter__(self) -> Self: *run_cmd, *platform_args, self.image, *ctr_cmd, capture_stdout=True ).strip() except subprocess.CalledProcessError: - # 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() - simulate_32_bit = container_machine != "i686" + 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"] @@ -337,8 +365,7 @@ def copy_into(self, from_path: Path, to_path: PurePath) -> None: ) as exec_process: assert exec_process.stdin with open(from_path, "rb") as from_file: - # Bug in mypy, https://github.com/python/mypy/issues/15031 - shutil.copyfileobj(from_file, exec_process.stdin) # type: ignore[misc] + shutil.copyfileobj(from_file, exec_process.stdin) exec_process.stdin.close() exec_process.wait() diff --git a/cibuildwheel/options.py b/cibuildwheel/options.py index 66da2ad49..3fb9ebb73 100644 --- a/cibuildwheel/options.py +++ b/cibuildwheel/options.py @@ -22,7 +22,7 @@ from .environment import EnvironmentParseError, ParsedEnvironment, parse_environment from .logger import log from .oci_container import OCIContainerEngineConfig -from .projectfiles import get_requires_python_str +from .projectfiles import get_requires_python_str, resolve_dependency_groups from .typing import PLATFORMS, PlatformName from .util import ( MANYLINUX_ARCHS, @@ -30,8 +30,10 @@ BuildFrontendConfig, BuildSelector, DependencyConstraints, + EnableGroups, TestSelector, format_safe, + read_python_configs, resources_dir, selector_matches, strtobool, @@ -92,6 +94,7 @@ class BuildOptions: before_test: str | None test_requires: list[str] test_extras: str + test_groups: list[str] build_verbosity: int build_frontend: BuildFrontendConfig | None config_settings: str @@ -307,7 +310,6 @@ def _resolve_cascade( return result -# pylint: disable-next=inconsistent-return-statements def _apply_inherit_rule( before: str | None, after: str, rule: InheritRule, option_format: OptionFormat | None ) -> str: @@ -329,10 +331,10 @@ def _apply_inherit_rule( if rule == InheritRule.APPEND: return option_format.merge_values(before, after) - elif rule == InheritRule.PREPEND: + if rule == InheritRule.PREPEND: return option_format.merge_values(after, before) - else: - assert_never(rule) + + assert_never(rule) def _stringify_setting( @@ -512,6 +514,7 @@ def get( env_plat: bool = True, option_format: OptionFormat | None = None, ignore_empty: bool = False, + env_rule: InheritRule = InheritRule.NONE, ) -> str: """ Get and return the value for the named option from environment, @@ -543,33 +546,43 @@ def get( (o.options.get(name), o.inherit.get(name, InheritRule.NONE)) for o in self.active_config_overrides ], - (self.env.get(envvar), InheritRule.NONE), - (self.env.get(plat_envvar) if env_plat else None, InheritRule.NONE), + (self.env.get(envvar), env_rule), + (self.env.get(plat_envvar) if env_plat else None, env_rule), ignore_empty=ignore_empty, option_format=option_format, ) class Options: + pyproject_toml: dict[str, Any] | None + def __init__( self, platform: PlatformName, command_line_arguments: CommandLineArguments, env: Mapping[str, str], - read_config_file: bool = True, + defaults: bool = False, ): self.platform = platform self.command_line_arguments = command_line_arguments self.env = env + self._defaults = defaults self.reader = OptionsReader( - self.config_file_path if read_config_file else None, + None if defaults else self.config_file_path, platform=platform, env=env, disallow=DISALLOWED_OPTIONS, ) - @property + self.package_dir = Path(command_line_arguments.package_dir) + try: + with self.package_dir.joinpath("pyproject.toml").open("rb") as f: + self.pyproject_toml = tomllib.load(f) + except FileNotFoundError: + self.pyproject_toml = None + + @functools.cached_property def config_file_path(self) -> Path | None: args = self.command_line_arguments @@ -585,10 +598,9 @@ def config_file_path(self) -> Path | None: @functools.cached_property def package_requires_python_str(self) -> str | None: - args = self.command_line_arguments - return get_requires_python_str(Path(args.package_dir)) + return get_requires_python_str(self.package_dir, self.pyproject_toml) - @property + @functools.cached_property def globals(self) -> GlobalOptions: args = self.command_line_arguments package_dir = args.package_dir @@ -600,16 +612,34 @@ def globals(self) -> GlobalOptions: 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=" ")) + allow_empty = args.allow_empty or strtobool(self.env.get("CIBW_ALLOW_EMPTY", "0")) + + enable_groups = self.reader.get( + "enable", env_plat=False, option_format=ListFormat(sep=" "), env_rule=InheritRule.APPEND + ) + enable = {EnableGroups(group) for group in enable_groups.split()} + 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")) - prerelease_pythons = args.prerelease_pythons or strtobool( self.env.get("CIBW_PRERELEASE_PYTHONS", "0") ) + if free_threaded_support or prerelease_pythons: + msg = ( + "free-threaded-support and prerelease-pythons should be specified by enable instead" + ) + if enable: + raise OptionsReaderError(msg) + log.warning(msg) + + if free_threaded_support: + enable.add(EnableGroups.CPythonFreeThreading) + if prerelease_pythons: + enable.add(EnableGroups.CPythonPrerelease) + # 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 requires_python_str: str | None = ( @@ -625,18 +655,30 @@ def globals(self) -> GlobalOptions: build_config = args.only skip_config = "" architectures = Architecture.all_archs(self.platform) - prerelease_pythons = True - free_threaded_support = True + enable = set(EnableGroups) build_selector = BuildSelector( build_config=build_config, skip_config=skip_config, requires_python=requires_python, - prerelease_pythons=prerelease_pythons, - free_threaded_support=free_threaded_support, + enable=frozenset( + enable | {EnableGroups.PyPy} + ), # For backwards compatibility, we are adding PyPy for now ) test_selector = TestSelector(skip_config=test_skip) + all_configs = read_python_configs(self.platform) + all_pypy_ids = { + config["identifier"] for config in all_configs if config["identifier"].startswith("pp") + } + if ( + not self._defaults + and EnableGroups.PyPy not in enable + and any(build_selector(build_id) for build_id in all_pypy_ids) + ): + msg = "PyPy builds will be disabled by default in version 3. Enabling PyPy builds should be specified by enable" + log.warning(msg) + return GlobalOptions( package_dir=package_dir, output_dir=output_dir, @@ -673,6 +715,11 @@ def build_options(self, identifier: str | None) -> BuildOptions: "test-requires", option_format=ListFormat(sep=" ") ).split() 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_frontend_str = self.reader.get( @@ -772,8 +819,9 @@ def build_options(self, identifier: str | None) -> BuildOptions: return BuildOptions( globals=self.globals, test_command=test_command, - test_requires=test_requires, + test_requires=[*test_requires, *test_requirements_from_groups], test_extras=test_extras, + test_groups=test_groups, before_test=before_test, before_build=before_build, before_all=before_all, @@ -817,7 +865,7 @@ def defaults(self) -> Options: platform=self.platform, command_line_arguments=CommandLineArguments.defaults(), env={}, - read_config_file=False, + defaults=True, ) def summary(self, identifiers: Iterable[str]) -> str: diff --git a/cibuildwheel/projectfiles.py b/cibuildwheel/projectfiles.py index c1a376696..d2fd635e0 100644 --- a/cibuildwheel/projectfiles.py +++ b/cibuildwheel/projectfiles.py @@ -4,8 +4,9 @@ import configparser import contextlib from pathlib import Path +from typing import Any -from ._compat import tomllib +import dependency_groups def get_parent(node: ast.AST | None, depth: int = 1) -> ast.AST | None: @@ -84,15 +85,12 @@ def setup_py_python_requires(content: str) -> str | None: return None -def get_requires_python_str(package_dir: Path) -> str | None: +def get_requires_python_str(package_dir: Path, pyproject_toml: dict[str, Any] | None) -> str | None: """Return the python requires string from the most canonical source available, or None""" # Read in from pyproject.toml:project.requires-python - with contextlib.suppress(FileNotFoundError): - 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"]) + with contextlib.suppress(KeyError, IndexError, TypeError): + return str((pyproject_toml or {})["project"]["requires-python"]) # Read in from setup.cfg:options.python_requires config = configparser.ConfigParser() @@ -106,3 +104,26 @@ def get_requires_python_str(package_dir: Path) -> str | None: return setup_py_python_requires(f2.read()) 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) diff --git a/cibuildwheel/pyodide.py b/cibuildwheel/pyodide.py index 3b721a10a..19d47e5ed 100644 --- a/cibuildwheel/pyodide.py +++ b/cibuildwheel/pyodide.py @@ -41,6 +41,7 @@ class PythonConfiguration: version: str identifier: str pyodide_version: str + pyodide_build_version: str emscripten_version: str node_version: str @@ -65,10 +66,15 @@ def install_emscripten(tmp: Path, version: str) -> Path: return emcc_path -def install_xbuildenv(env: dict[str, str], pyodide_version: str) -> str: +def install_xbuildenv(env: dict[str, str], pyodide_build_version: str, pyodide_version: str) -> str: + """Install a particular Pyodide xbuildenv version and set a path to the Pyodide root.""" + # 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 = ( CIBW_CACHE_PATH - / f".pyodide-xbuildenv-{pyodide_version}/{pyodide_version}/xbuildenv/pyodide-root" + / f".pyodide-xbuildenv-{pyodide_build_version}/{pyodide_version}/xbuildenv/pyodide-root" ) with FileLock(CIBW_CACHE_PATH / "xbuildenv.lock"): if pyodide_root.exists(): @@ -136,22 +142,22 @@ def setup_python( 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 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() + print(which_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." 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 = "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("pip", "--version", env=env) log.step("Installing build tools...") call( @@ -165,13 +171,15 @@ def setup_python( env=env, ) - log.step("Installing emscripten...") + log.step(f"Installing Emscripten version: {python_configuration.emscripten_version} ...") emcc_path = install_emscripten(tmp, python_configuration.emscripten_version) env["PATH"] = os.pathsep.join([str(emcc_path.parent), env["PATH"]]) - log.step("Installing Pyodide xbuildenv...") - env["PYODIDE_ROOT"] = install_xbuildenv(env, python_configuration.pyodide_version) + log.step(f"Installing Pyodide xbuildenv version: {python_configuration.pyodide_version} ...") + env["PYODIDE_ROOT"] = install_xbuildenv( + env, python_configuration.pyodide_build_version, python_configuration.pyodide_version + ) return env diff --git a/cibuildwheel/resources/build-platforms.toml b/cibuildwheel/resources/build-platforms.toml index 2c8e1e5c1..2c2fbede9 100644 --- a/cibuildwheel/resources/build-platforms.toml +++ b/cibuildwheel/resources/build-platforms.toml @@ -49,6 +49,15 @@ python_configurations = [ { 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 = "cp313t-manylinux_s390x", version = "3.13", path_str = "/opt/python/cp313-cp313t" }, + { identifier = "cp36-manylinux_armv7l", version = "3.6", path_str = "/opt/python/cp36-cp36m" }, + { identifier = "cp37-manylinux_armv7l", version = "3.7", path_str = "/opt/python/cp37-cp37m" }, + { 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 = "pp37-manylinux_aarch64", version = "3.7", path_str = "/opt/python/pp37-pypy37_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" }, @@ -102,6 +111,15 @@ python_configurations = [ { 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 = "cp313t-musllinux_s390x", version = "3.13", path_str = "/opt/python/cp313-cp313t" }, + { identifier = "cp36-musllinux_armv7l", version = "3.6", path_str = "/opt/python/cp36-cp36m" }, + { identifier = "cp37-musllinux_armv7l", version = "3.7", path_str = "/opt/python/cp37-cp37m" }, + { 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" }, ] [macos] @@ -120,15 +138,15 @@ 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_arm64", version = "3.11", url = "https://www.python.org/ftp/python/3.11.9/python-3.11.9-macos11.pkg" }, { identifier = "cp311-macosx_universal2", version = "3.11", url = "https://www.python.org/ftp/python/3.11.9/python-3.11.9-macos11.pkg" }, - { identifier = "cp312-macosx_x86_64", version = "3.12", url = "https://www.python.org/ftp/python/3.12.6/python-3.12.6-macos11.pkg" }, - { identifier = "cp312-macosx_arm64", version = "3.12", url = "https://www.python.org/ftp/python/3.12.6/python-3.12.6-macos11.pkg" }, - { identifier = "cp312-macosx_universal2", version = "3.12", url = "https://www.python.org/ftp/python/3.12.6/python-3.12.6-macos11.pkg" }, - { identifier = "cp313-macosx_x86_64", version = "3.13", url = "https://www.python.org/ftp/python/3.13.0/python-3.13.0rc2-macos11.pkg" }, - { identifier = "cp313-macosx_arm64", version = "3.13", url = "https://www.python.org/ftp/python/3.13.0/python-3.13.0rc2-macos11.pkg" }, - { identifier = "cp313-macosx_universal2", version = "3.13", url = "https://www.python.org/ftp/python/3.13.0/python-3.13.0rc2-macos11.pkg" }, - { identifier = "cp313t-macosx_x86_64", version = "3.13", url = "https://www.python.org/ftp/python/3.13.0/python-3.13.0rc2-macos11.pkg" }, - { identifier = "cp313t-macosx_arm64", version = "3.13", url = "https://www.python.org/ftp/python/3.13.0/python-3.13.0rc2-macos11.pkg" }, - { identifier = "cp313t-macosx_universal2", version = "3.13", url = "https://www.python.org/ftp/python/3.13.0/python-3.13.0rc2-macos11.pkg" }, + { identifier = "cp312-macosx_x86_64", version = "3.12", url = "https://www.python.org/ftp/python/3.12.7/python-3.12.7-macos11.pkg" }, + { identifier = "cp312-macosx_arm64", version = "3.12", url = "https://www.python.org/ftp/python/3.12.7/python-3.12.7-macos11.pkg" }, + { identifier = "cp312-macosx_universal2", version = "3.12", url = "https://www.python.org/ftp/python/3.12.7/python-3.12.7-macos11.pkg" }, + { identifier = "cp313-macosx_x86_64", version = "3.13", url = "https://www.python.org/ftp/python/3.13.0/python-3.13.0-macos11.pkg" }, + { identifier = "cp313-macosx_arm64", version = "3.13", url = "https://www.python.org/ftp/python/3.13.0/python-3.13.0-macos11.pkg" }, + { identifier = "cp313-macosx_universal2", version = "3.13", url = "https://www.python.org/ftp/python/3.13.0/python-3.13.0-macos11.pkg" }, + { identifier = "cp313t-macosx_x86_64", version = "3.13", url = "https://www.python.org/ftp/python/3.13.0/python-3.13.0-macos11.pkg" }, + { identifier = "cp313t-macosx_arm64", version = "3.13", url = "https://www.python.org/ftp/python/3.13.0/python-3.13.0-macos11.pkg" }, + { identifier = "cp313t-macosx_universal2", version = "3.13", url = "https://www.python.org/ftp/python/3.13.0/python-3.13.0-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 = "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" }, @@ -152,18 +170,18 @@ python_configurations = [ { identifier = "cp310-win_amd64", version = "3.10.11", arch = "64" }, { identifier = "cp311-win32", version = "3.11.9", arch = "32" }, { identifier = "cp311-win_amd64", version = "3.11.9", arch = "64" }, - { identifier = "cp312-win32", version = "3.12.6", arch = "32" }, - { identifier = "cp312-win_amd64", version = "3.12.6", arch = "64" }, - { identifier = "cp313-win32", version = "3.13.0-rc2", arch = "32" }, - { identifier = "cp313t-win32", version = "3.13.0-rc2", arch = "32" }, - { identifier = "cp313-win_amd64", version = "3.13.0-rc2", arch = "64" }, - { identifier = "cp313t-win_amd64", version = "3.13.0-rc2", arch = "64" }, + { identifier = "cp312-win32", version = "3.12.7", arch = "32" }, + { identifier = "cp312-win_amd64", version = "3.12.7", arch = "64" }, + { identifier = "cp313-win32", version = "3.13.0", arch = "32" }, + { identifier = "cp313t-win32", version = "3.13.0", arch = "32" }, + { identifier = "cp313-win_amd64", version = "3.13.0", arch = "64" }, + { identifier = "cp313t-win_amd64", version = "3.13.0", arch = "64" }, { identifier = "cp39-win_arm64", version = "3.9.10", arch = "ARM64" }, { identifier = "cp310-win_arm64", version = "3.10.11", arch = "ARM64" }, { identifier = "cp311-win_arm64", version = "3.11.9", arch = "ARM64" }, - { identifier = "cp312-win_arm64", version = "3.12.6", arch = "ARM64" }, - { identifier = "cp313-win_arm64", version = "3.13.0-rc2", arch = "ARM64" }, - { identifier = "cp313t-win_arm64", version = "3.13.0-rc2", arch = "ARM64" }, + { identifier = "cp312-win_arm64", version = "3.12.7", arch = "ARM64" }, + { identifier = "cp313-win_arm64", version = "3.13.0", arch = "ARM64" }, + { identifier = "cp313t-win_arm64", version = "3.13.0", 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 = "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" }, @@ -172,5 +190,5 @@ python_configurations = [ [pyodide] python_configurations = [ - { identifier = "cp312-pyodide_wasm32", version = "3.12.1", pyodide_version = "0.26.1", emscripten_version = "3.1.58", node_version = "v20" }, + { identifier = "cp312-pyodide_wasm32", version = "3.12", pyodide_version = "0.26.4", pyodide_build_version = "0.29.0", emscripten_version = "3.1.58", node_version = "v20" }, ] diff --git a/cibuildwheel/resources/cibuildwheel.schema.json b/cibuildwheel/resources/cibuildwheel.schema.json index 976751a55..68c195ab2 100644 --- a/cibuildwheel/resources/cibuildwheel.schema.json +++ b/cibuildwheel/resources/cibuildwheel.schema.json @@ -10,7 +10,16 @@ ], "default": "none", "description": "How to inherit the parent's value." - } + }, + "enable": { + "enum": [ + "cpython-eol", + "cpython-freethreading", + "cpython-prerelease", + "pypy-eol" + ] + }, + "description": "A Python version or flavor to enable." }, "additionalProperties": false, "description": "cibuildwheel's settings.", @@ -228,6 +237,21 @@ "type": "string", "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": { "description": "Set environment variables needed during the build.", "oneOf": [ @@ -272,6 +296,11 @@ "description": "Specify alternative manylinux / musllinux container images", "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": { "type": "string", "description": "Specify alternative manylinux / musllinux container images", @@ -312,6 +341,11 @@ "description": "Specify alternative manylinux / musllinux container images", "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": { "type": "string", "description": "Specify alternative manylinux / musllinux container images", @@ -392,6 +426,21 @@ ], "title": "CIBW_TEST_EXTRAS" }, + "test-groups": { + "description": "Install extra groups when testing", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ], + "title": "CIBW_TEST_GROUPS" + }, "test-requires": { "description": "Install Python dependencies before running the tests", "oneOf": [ @@ -518,6 +567,9 @@ "manylinux-aarch64-image": { "$ref": "#/properties/manylinux-aarch64-image" }, + "manylinux-armv7l-image": { + "$ref": "#/properties/manylinux-armv7l-image" + }, "manylinux-i686-image": { "$ref": "#/properties/manylinux-i686-image" }, @@ -542,6 +594,9 @@ "musllinux-aarch64-image": { "$ref": "#/properties/musllinux-aarch64-image" }, + "musllinux-armv7l-image": { + "$ref": "#/properties/musllinux-armv7l-image" + }, "musllinux-i686-image": { "$ref": "#/properties/musllinux-i686-image" }, @@ -563,6 +618,9 @@ "test-extras": { "$ref": "#/properties/test-extras" }, + "test-groups": { + "$ref": "#/properties/test-groups" + }, "test-requires": { "$ref": "#/properties/test-requires" } @@ -606,6 +664,9 @@ "manylinux-aarch64-image": { "$ref": "#/properties/manylinux-aarch64-image" }, + "manylinux-armv7l-image": { + "$ref": "#/properties/manylinux-armv7l-image" + }, "manylinux-i686-image": { "$ref": "#/properties/manylinux-i686-image" }, @@ -630,6 +691,9 @@ "musllinux-aarch64-image": { "$ref": "#/properties/musllinux-aarch64-image" }, + "musllinux-armv7l-image": { + "$ref": "#/properties/musllinux-armv7l-image" + }, "musllinux-i686-image": { "$ref": "#/properties/musllinux-i686-image" }, @@ -664,6 +728,9 @@ "test-extras": { "$ref": "#/properties/test-extras" }, + "test-groups": { + "$ref": "#/properties/test-groups" + }, "test-requires": { "$ref": "#/properties/test-requires" } @@ -709,6 +776,9 @@ "test-extras": { "$ref": "#/properties/test-extras" }, + "test-groups": { + "$ref": "#/properties/test-groups" + }, "test-requires": { "$ref": "#/properties/test-requires" } @@ -767,6 +837,9 @@ "test-extras": { "$ref": "#/properties/test-extras" }, + "test-groups": { + "$ref": "#/properties/test-groups" + }, "test-requires": { "$ref": "#/properties/test-requires" } @@ -812,6 +885,9 @@ "test-extras": { "$ref": "#/properties/test-extras" }, + "test-groups": { + "$ref": "#/properties/test-groups" + }, "test-requires": { "$ref": "#/properties/test-requires" } diff --git a/cibuildwheel/resources/constraints-pyodide312.txt b/cibuildwheel/resources/constraints-pyodide312.txt index 33c90179d..a0fcc3bee 100644 --- a/cibuildwheel/resources/constraints-pyodide312.txt +++ b/cibuildwheel/resources/constraints-pyodide312.txt @@ -2,11 +2,11 @@ # nox -s update_constraints annotated-types==0.7.0 # via pydantic -anyio==4.4.0 +anyio==4.6.2.post1 # via httpx auditwheel-emscripten==0.0.16 # via pyodide-build -build==1.2.2 +build==1.2.2.post1 # via # -r .nox/update_constraints/tmp/constraints-pyodide.in # pyodide-build @@ -15,21 +15,19 @@ certifi==2024.8.30 # httpcore # httpx # requests -charset-normalizer==3.3.2 +charset-normalizer==3.4.0 # via requests click==8.1.7 # via typer -cloudpickle==3.0.0 - # via loky -cmake==3.30.3 +cmake==3.31.0.1 # via pyodide-build -distlib==0.3.8 +distlib==0.3.9 # via virtualenv -filelock==3.16.0 +filelock==3.16.1 # via virtualenv h11==0.14.0 # via httpcore -httpcore==1.0.5 +httpcore==1.0.7 # via httpx httpx==0.27.2 # via unearth @@ -40,54 +38,50 @@ idna==3.10 # requests leb128==1.0.8 # via auditwheel-emscripten -loky==3.4.1 - # via pyodide-build markdown-it-py==3.0.0 # via rich mdurl==0.1.2 # via markdown-it-py -packaging==24.1 +packaging==24.2 # via # auditwheel-emscripten # build # pyodide-build # unearth -pip==24.2 +pip==24.3.1 # via -r .nox/update_constraints/tmp/constraints-pyodide.in -platformdirs==4.3.3 +platformdirs==4.3.6 # via virtualenv -pydantic==2.9.1 +pydantic==2.10.0 # via # pyodide-build # pyodide-lock -pydantic-core==2.23.3 +pydantic-core==2.27.0 # via pydantic pygments==2.18.0 # via rich -pyodide-build==0.26.1 +pyodide-build==0.29.0 # via -r .nox/update_constraints/tmp/constraints-pyodide.in pyodide-cli==0.2.4 # via # auditwheel-emscripten # pyodide-build -pyodide-lock==0.1.0a6 +pyodide-lock==0.1.0a7 # via pyodide-build -pyproject-hooks==1.1.0 +pyproject-hooks==1.2.0 # via build -pyyaml==6.0.2 - # via pyodide-build requests==2.32.3 # via pyodide-build -resolvelib==1.0.1 +resolvelib==1.1.0 # via pyodide-build -rich==13.8.1 +rich==13.9.4 # via # pyodide-build # pyodide-cli # typer ruamel-yaml==0.18.6 # via pyodide-build -ruamel-yaml-clib==0.2.8 +ruamel-yaml-clib==0.2.12 # via ruamel-yaml shellingham==1.5.4 # via typer @@ -95,13 +89,11 @@ sniffio==1.3.1 # via # anyio # httpx -typer==0.12.5 +typer==0.13.1 # via # auditwheel-emscripten # pyodide-build # pyodide-cli -types-requests==2.32.0.20240914 - # via pyodide-build typing-extensions==4.12.2 # via # pydantic @@ -110,14 +102,12 @@ typing-extensions==4.12.2 unearth==0.17.2 # via pyodide-build urllib3==2.2.3 - # via - # requests - # types-requests -virtualenv==20.26.4 + # via requests +virtualenv==20.27.1 # via # build # pyodide-build -wheel==0.44.0 +wheel==0.45.0 # via # auditwheel-emscripten # pyodide-build diff --git a/cibuildwheel/resources/constraints-python310.txt b/cibuildwheel/resources/constraints-python310.txt index 45a592ec8..04f6ef2dc 100644 --- a/cibuildwheel/resources/constraints-python310.txt +++ b/cibuildwheel/resources/constraints-python310.txt @@ -2,33 +2,33 @@ # nox -s update_constraints altgraph==0.17.4 # via macholib -build==1.2.2 +build==1.2.2.post1 # via -r cibuildwheel/resources/constraints.in delocate==0.12.0 # via -r cibuildwheel/resources/constraints.in -distlib==0.3.8 +distlib==0.3.9 # via virtualenv -filelock==3.16.0 +filelock==3.16.1 # via virtualenv importlib-metadata==8.5.0 # via build macholib==1.16.3 # via delocate -packaging==24.1 +packaging==24.2 # via # build # delocate -pip==24.2 +pip==24.3.1 # via -r cibuildwheel/resources/constraints.in -platformdirs==4.3.3 +platformdirs==4.3.6 # via virtualenv -pyproject-hooks==1.1.0 +pyproject-hooks==1.2.0 # via build -tomli==2.0.1 +tomli==2.1.0 # via build typing-extensions==4.12.2 # via delocate -virtualenv==20.26.4 +virtualenv==20.27.1 # via -r cibuildwheel/resources/constraints.in -zipp==3.20.2 +zipp==3.21.0 # via importlib-metadata diff --git a/cibuildwheel/resources/constraints-python311.txt b/cibuildwheel/resources/constraints-python311.txt index 20ec68126..6b9c935ec 100644 --- a/cibuildwheel/resources/constraints-python311.txt +++ b/cibuildwheel/resources/constraints-python311.txt @@ -2,27 +2,27 @@ # nox -s update_constraints altgraph==0.17.4 # via macholib -build==1.2.2 +build==1.2.2.post1 # via -r cibuildwheel/resources/constraints.in delocate==0.12.0 # via -r cibuildwheel/resources/constraints.in -distlib==0.3.8 +distlib==0.3.9 # via virtualenv -filelock==3.16.0 +filelock==3.16.1 # via virtualenv macholib==1.16.3 # via delocate -packaging==24.1 +packaging==24.2 # via # build # delocate -pip==24.2 +pip==24.3.1 # via -r cibuildwheel/resources/constraints.in -platformdirs==4.3.3 +platformdirs==4.3.6 # via virtualenv -pyproject-hooks==1.1.0 +pyproject-hooks==1.2.0 # via build typing-extensions==4.12.2 # via delocate -virtualenv==20.26.4 +virtualenv==20.27.1 # via -r cibuildwheel/resources/constraints.in diff --git a/cibuildwheel/resources/constraints-python312.txt b/cibuildwheel/resources/constraints-python312.txt index 20ec68126..6b9c935ec 100644 --- a/cibuildwheel/resources/constraints-python312.txt +++ b/cibuildwheel/resources/constraints-python312.txt @@ -2,27 +2,27 @@ # nox -s update_constraints altgraph==0.17.4 # via macholib -build==1.2.2 +build==1.2.2.post1 # via -r cibuildwheel/resources/constraints.in delocate==0.12.0 # via -r cibuildwheel/resources/constraints.in -distlib==0.3.8 +distlib==0.3.9 # via virtualenv -filelock==3.16.0 +filelock==3.16.1 # via virtualenv macholib==1.16.3 # via delocate -packaging==24.1 +packaging==24.2 # via # build # delocate -pip==24.2 +pip==24.3.1 # via -r cibuildwheel/resources/constraints.in -platformdirs==4.3.3 +platformdirs==4.3.6 # via virtualenv -pyproject-hooks==1.1.0 +pyproject-hooks==1.2.0 # via build typing-extensions==4.12.2 # via delocate -virtualenv==20.26.4 +virtualenv==20.27.1 # via -r cibuildwheel/resources/constraints.in diff --git a/cibuildwheel/resources/constraints-python313.txt b/cibuildwheel/resources/constraints-python313.txt index 20ec68126..6b9c935ec 100644 --- a/cibuildwheel/resources/constraints-python313.txt +++ b/cibuildwheel/resources/constraints-python313.txt @@ -2,27 +2,27 @@ # nox -s update_constraints altgraph==0.17.4 # via macholib -build==1.2.2 +build==1.2.2.post1 # via -r cibuildwheel/resources/constraints.in delocate==0.12.0 # via -r cibuildwheel/resources/constraints.in -distlib==0.3.8 +distlib==0.3.9 # via virtualenv -filelock==3.16.0 +filelock==3.16.1 # via virtualenv macholib==1.16.3 # via delocate -packaging==24.1 +packaging==24.2 # via # build # delocate -pip==24.2 +pip==24.3.1 # via -r cibuildwheel/resources/constraints.in -platformdirs==4.3.3 +platformdirs==4.3.6 # via virtualenv -pyproject-hooks==1.1.0 +pyproject-hooks==1.2.0 # via build typing-extensions==4.12.2 # via delocate -virtualenv==20.26.4 +virtualenv==20.27.1 # via -r cibuildwheel/resources/constraints.in diff --git a/cibuildwheel/resources/constraints-python37.txt b/cibuildwheel/resources/constraints-python37.txt index 7198fa8d3..55e33eec5 100644 --- a/cibuildwheel/resources/constraints-python37.txt +++ b/cibuildwheel/resources/constraints-python37.txt @@ -6,7 +6,7 @@ build==1.1.1 # via -r cibuildwheel/resources/constraints.in delocate==0.12.0 # via -r cibuildwheel/resources/constraints.in -distlib==0.3.8 +distlib==0.3.9 # via virtualenv filelock==3.12.2 # via virtualenv @@ -24,7 +24,7 @@ pip==24.0 # via -r cibuildwheel/resources/constraints.in platformdirs==4.0.0 # via virtualenv -pyproject-hooks==1.1.0 +pyproject-hooks==1.2.0 # via build tomli==2.0.1 # via build @@ -33,7 +33,7 @@ typing-extensions==4.7.1 # delocate # importlib-metadata # platformdirs -virtualenv==20.26.4 +virtualenv==20.26.6 # via -r cibuildwheel/resources/constraints.in zipp==3.15.0 # via importlib-metadata diff --git a/cibuildwheel/resources/constraints-python38.txt b/cibuildwheel/resources/constraints-python38.txt index 45a592ec8..95b03162c 100644 --- a/cibuildwheel/resources/constraints-python38.txt +++ b/cibuildwheel/resources/constraints-python38.txt @@ -2,33 +2,33 @@ # nox -s update_constraints altgraph==0.17.4 # via macholib -build==1.2.2 +build==1.2.2.post1 # via -r cibuildwheel/resources/constraints.in delocate==0.12.0 # via -r cibuildwheel/resources/constraints.in -distlib==0.3.8 +distlib==0.3.9 # via virtualenv -filelock==3.16.0 +filelock==3.16.1 # via virtualenv importlib-metadata==8.5.0 # via build macholib==1.16.3 # via delocate -packaging==24.1 +packaging==24.2 # via # build # delocate -pip==24.2 +pip==24.3.1 # via -r cibuildwheel/resources/constraints.in -platformdirs==4.3.3 +platformdirs==4.3.6 # via virtualenv -pyproject-hooks==1.1.0 +pyproject-hooks==1.2.0 # via build -tomli==2.0.1 +tomli==2.1.0 # via build typing-extensions==4.12.2 # via delocate -virtualenv==20.26.4 +virtualenv==20.27.1 # via -r cibuildwheel/resources/constraints.in zipp==3.20.2 # via importlib-metadata diff --git a/cibuildwheel/resources/constraints-python39.txt b/cibuildwheel/resources/constraints-python39.txt index 45a592ec8..04f6ef2dc 100644 --- a/cibuildwheel/resources/constraints-python39.txt +++ b/cibuildwheel/resources/constraints-python39.txt @@ -2,33 +2,33 @@ # nox -s update_constraints altgraph==0.17.4 # via macholib -build==1.2.2 +build==1.2.2.post1 # via -r cibuildwheel/resources/constraints.in delocate==0.12.0 # via -r cibuildwheel/resources/constraints.in -distlib==0.3.8 +distlib==0.3.9 # via virtualenv -filelock==3.16.0 +filelock==3.16.1 # via virtualenv importlib-metadata==8.5.0 # via build macholib==1.16.3 # via delocate -packaging==24.1 +packaging==24.2 # via # build # delocate -pip==24.2 +pip==24.3.1 # via -r cibuildwheel/resources/constraints.in -platformdirs==4.3.3 +platformdirs==4.3.6 # via virtualenv -pyproject-hooks==1.1.0 +pyproject-hooks==1.2.0 # via build -tomli==2.0.1 +tomli==2.1.0 # via build typing-extensions==4.12.2 # via delocate -virtualenv==20.26.4 +virtualenv==20.27.1 # via -r cibuildwheel/resources/constraints.in -zipp==3.20.2 +zipp==3.21.0 # via importlib-metadata diff --git a/cibuildwheel/resources/constraints.txt b/cibuildwheel/resources/constraints.txt index 20ec68126..6b9c935ec 100644 --- a/cibuildwheel/resources/constraints.txt +++ b/cibuildwheel/resources/constraints.txt @@ -2,27 +2,27 @@ # nox -s update_constraints altgraph==0.17.4 # via macholib -build==1.2.2 +build==1.2.2.post1 # via -r cibuildwheel/resources/constraints.in delocate==0.12.0 # via -r cibuildwheel/resources/constraints.in -distlib==0.3.8 +distlib==0.3.9 # via virtualenv -filelock==3.16.0 +filelock==3.16.1 # via virtualenv macholib==1.16.3 # via delocate -packaging==24.1 +packaging==24.2 # via # build # delocate -pip==24.2 +pip==24.3.1 # via -r cibuildwheel/resources/constraints.in -platformdirs==4.3.3 +platformdirs==4.3.6 # via virtualenv -pyproject-hooks==1.1.0 +pyproject-hooks==1.2.0 # via build typing-extensions==4.12.2 # via delocate -virtualenv==20.26.4 +virtualenv==20.27.1 # via -r cibuildwheel/resources/constraints.in diff --git a/cibuildwheel/resources/defaults.toml b/cibuildwheel/resources/defaults.toml index 984af48ea..3c56dfc58 100644 --- a/cibuildwheel/resources/defaults.toml +++ b/cibuildwheel/resources/defaults.toml @@ -3,6 +3,7 @@ build = "*" skip = "" test-skip = "" free-threaded-support = false +enable = [] archs = ["auto"] build-frontend = "default" @@ -20,6 +21,7 @@ test-command = "" before-test = "" test-requires = [] test-extras = [] +test-groups = [] container-engine = "docker" @@ -28,6 +30,7 @@ manylinux-i686-image = "manylinux2014" manylinux-aarch64-image = "manylinux2014" manylinux-ppc64le-image = "manylinux2014" manylinux-s390x-image = "manylinux2014" +manylinux-armv7l-image = "manylinux_2_31" manylinux-pypy_x86_64-image = "manylinux2014" manylinux-pypy_i686-image = "manylinux2014" manylinux-pypy_aarch64-image = "manylinux2014" @@ -37,6 +40,7 @@ musllinux-i686-image = "musllinux_1_2" musllinux-aarch64-image = "musllinux_1_2" musllinux-ppc64le-image = "musllinux_1_2" musllinux-s390x-image = "musllinux_1_2" +musllinux-armv7l-image = "musllinux_1_2" [tool.cibuildwheel.linux] diff --git a/cibuildwheel/resources/nodejs.toml b/cibuildwheel/resources/nodejs.toml index e2ad94919..6d3f3f8c2 100644 --- a/cibuildwheel/resources/nodejs.toml +++ b/cibuildwheel/resources/nodejs.toml @@ -1,2 +1,3 @@ url = "https://nodejs.org/dist/" -v20 = "v20.17.0" +v22 = "v22.11.0" +v20 = "v20.18.0" diff --git a/cibuildwheel/resources/pinned_docker_images.cfg b/cibuildwheel/resources/pinned_docker_images.cfg index a27ba95a9..75b031913 100644 --- a/cibuildwheel/resources/pinned_docker_images.cfg +++ b/cibuildwheel/resources/pinned_docker_images.cfg @@ -1,54 +1,58 @@ [x86_64] manylinux1 = quay.io/pypa/manylinux1_x86_64:2024-04-29-76807b8 manylinux2010 = quay.io/pypa/manylinux2010_x86_64:2022-08-05-4535177 -manylinux2014 = quay.io/pypa/manylinux2014_x86_64:2024.09.16-1 +manylinux2014 = quay.io/pypa/manylinux2014_x86_64:2024.11.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.16-1 -musllinux_1_1 = quay.io/pypa/musllinux_1_1_x86_64:2024.09.16-1 -musllinux_1_2 = quay.io/pypa/musllinux_1_2_x86_64:2024.09.16-1 +manylinux_2_28 = quay.io/pypa/manylinux_2_28_x86_64:2024.11.16-1 +musllinux_1_1 = quay.io/pypa/musllinux_1_1_x86_64:2024.10.26-1 +musllinux_1_2 = quay.io/pypa/musllinux_1_2_x86_64:2024.11.16-1 [i686] manylinux1 = quay.io/pypa/manylinux1_i686:2024-04-29-76807b8 manylinux2010 = quay.io/pypa/manylinux2010_i686:2022-08-05-4535177 -manylinux2014 = quay.io/pypa/manylinux2014_i686:2024.09.16-1 +manylinux2014 = quay.io/pypa/manylinux2014_i686:2024.11.16-1 manylinux_2_24 = quay.io/pypa/manylinux_2_24_i686:2022-12-26-0d38463 -musllinux_1_1 = quay.io/pypa/musllinux_1_1_i686:2024.09.16-1 -musllinux_1_2 = quay.io/pypa/musllinux_1_2_i686:2024.09.16-1 +musllinux_1_1 = quay.io/pypa/musllinux_1_1_i686:2024.10.26-1 +musllinux_1_2 = quay.io/pypa/musllinux_1_2_i686:2024.11.16-1 [pypy_x86_64] manylinux2010 = quay.io/pypa/manylinux2010_x86_64:2022-08-05-4535177 -manylinux2014 = quay.io/pypa/manylinux2014_x86_64:2024.09.16-1 +manylinux2014 = quay.io/pypa/manylinux2014_x86_64:2024.11.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.16-1 +manylinux_2_28 = quay.io/pypa/manylinux_2_28_x86_64:2024.11.16-1 [pypy_i686] manylinux2010 = quay.io/pypa/manylinux2010_i686:2022-08-05-4535177 -manylinux2014 = quay.io/pypa/manylinux2014_i686:2024.09.16-1 +manylinux2014 = quay.io/pypa/manylinux2014_i686:2024.11.16-1 manylinux_2_24 = quay.io/pypa/manylinux_2_24_i686:2022-12-26-0d38463 [aarch64] -manylinux2014 = quay.io/pypa/manylinux2014_aarch64:2024.09.16-1 +manylinux2014 = quay.io/pypa/manylinux2014_aarch64:2024.11.16-1 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.09-0 -musllinux_1_1 = quay.io/pypa/musllinux_1_1_aarch64:2024.09.09-0 -musllinux_1_2 = quay.io/pypa/musllinux_1_2_aarch64:2024.09.09-0 +manylinux_2_28 = quay.io/pypa/manylinux_2_28_aarch64:2024.11.16-1 +musllinux_1_1 = quay.io/pypa/musllinux_1_1_aarch64:2024.10.26-1 +musllinux_1_2 = quay.io/pypa/musllinux_1_2_aarch64:2024.11.16-1 [ppc64le] -manylinux2014 = quay.io/pypa/manylinux2014_ppc64le:2024.09.09-0 +manylinux2014 = quay.io/pypa/manylinux2014_ppc64le:2024.11.16-1 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.09-0 -musllinux_1_1 = quay.io/pypa/musllinux_1_1_ppc64le:2024.09.09-0 -musllinux_1_2 = quay.io/pypa/musllinux_1_2_ppc64le:2024.09.09-0 +manylinux_2_28 = quay.io/pypa/manylinux_2_28_ppc64le:2024.11.16-1 +musllinux_1_1 = quay.io/pypa/musllinux_1_1_ppc64le:2024.10.26-1 +musllinux_1_2 = quay.io/pypa/musllinux_1_2_ppc64le:2024.11.16-1 [s390x] -manylinux2014 = quay.io/pypa/manylinux2014_s390x:2024.09.09-0 +manylinux2014 = quay.io/pypa/manylinux2014_s390x:2024.11.16-1 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.09-0 -musllinux_1_1 = quay.io/pypa/musllinux_1_1_s390x:2024.09.09-0 -musllinux_1_2 = quay.io/pypa/musllinux_1_2_s390x:2024.09.09-0 +manylinux_2_28 = quay.io/pypa/manylinux_2_28_s390x:2024.11.16-1 +musllinux_1_1 = quay.io/pypa/musllinux_1_1_s390x:2024.10.26-1 +musllinux_1_2 = quay.io/pypa/musllinux_1_2_s390x:2024.11.16-1 [pypy_aarch64] -manylinux2014 = quay.io/pypa/manylinux2014_aarch64:2024.09.16-1 +manylinux2014 = quay.io/pypa/manylinux2014_aarch64:2024.11.16-1 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.09-0 +manylinux_2_28 = quay.io/pypa/manylinux_2_28_aarch64:2024.11.16-1 + +[armv7l] +manylinux_2_31 = ghcr.io/mayeut/manylinux_2_31:2024.11.16-1 +musllinux_1_2 = quay.io/pypa/musllinux_1_2_armv7l:2024.11.16-1 diff --git a/cibuildwheel/resources/virtualenv.toml b/cibuildwheel/resources/virtualenv.toml index b29f2fe8e..2a5981d3a 100644 --- a/cibuildwheel/resources/virtualenv.toml +++ b/cibuildwheel/resources/virtualenv.toml @@ -1,2 +1,2 @@ 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.4", url = "https://github.com/pypa/get-virtualenv/blob/20.26.4/public/virtualenv.pyz?raw=true" } +default = { version = "20.27.1", url = "https://github.com/pypa/get-virtualenv/blob/20.27.1/public/virtualenv.pyz?raw=true" } diff --git a/cibuildwheel/typing.py b/cibuildwheel/typing.py index 61e1c96f9..367cfcdf1 100644 --- a/cibuildwheel/typing.py +++ b/cibuildwheel/typing.py @@ -9,7 +9,6 @@ "PLATFORMS", "PathOrStr", "PlatformName", - "PLATFORMS", "PopenBytes", ) diff --git a/cibuildwheel/util.py b/cibuildwheel/util.py index 31b3eaf86..905dd5410 100644 --- a/cibuildwheel/util.py +++ b/cibuildwheel/util.py @@ -1,6 +1,7 @@ from __future__ import annotations import contextlib +import enum import fnmatch import itertools import os @@ -19,11 +20,11 @@ from collections.abc import Generator, Iterable, Mapping, MutableMapping, Sequence from dataclasses import dataclass from enum import Enum -from functools import lru_cache +from functools import lru_cache, total_ordering from pathlib import Path, PurePath from tempfile import TemporaryDirectory from time import sleep -from typing import Any, ClassVar, Final, Literal, TextIO, TypeVar +from typing import Any, Final, Literal, TextIO, TypeVar from zipfile import ZipFile import bracex @@ -37,10 +38,12 @@ from ._compat import tomllib from .architecture import Architecture +from .errors import FatalError from .typing import PathOrStr, PlatformName __all__ = [ "MANYLINUX_ARCHS", + "EnableGroups", "call", "chdir", "combine_constraints", @@ -66,6 +69,16 @@ test_fail_cwd_file: Final[Path] = resources_dir / "testing_temp_dir_file.py" +class EnableGroups(enum.Enum): + """ + Groups of build selectors that are not enabled by default. + """ + + CPythonFreeThreading = "cpython-freethreading" + CPythonPrerelease = "cpython-prerelease" + PyPy = "pypy" + + MANYLINUX_ARCHS: Final[tuple[str, ...]] = ( "x86_64", "i686", @@ -73,6 +86,7 @@ "aarch64", "ppc64le", "s390x", + "armv7l", "pypy_aarch64", "pypy_i686", ) @@ -83,6 +97,7 @@ "aarch64", "ppc64le", "s390x", + "armv7l", ) DEFAULT_CIBW_CACHE_PATH: Final[Path] = user_cache_path(appname="cibuildwheel", appauthor="pypa") @@ -126,13 +141,32 @@ def call( 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) + # 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) @@ -246,12 +280,7 @@ class BuildSelector: 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 + enable: frozenset[EnableGroups] = frozenset() def __call__(self, build_id: str) -> bool: # Filter build selectors by python_requires if set @@ -265,12 +294,16 @@ def __call__(self, build_id: str) -> bool: 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): + # filter out groups that are not enabled + if EnableGroups.CPythonFreeThreading not in self.enable and selector_matches( + "cp3??t-*", 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): + if EnableGroups.CPythonPrerelease not in self.enable and selector_matches( + "cp314*", build_id + ): + return False + if EnableGroups.PyPy not in self.enable and selector_matches("pp*", build_id): return False should_build = selector_matches(self.build_config, build_id) @@ -283,8 +316,7 @@ def options_summary(self) -> Any: "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, + "enable": sorted(group.value for group in self.enable), } @@ -899,3 +931,51 @@ def combine_constraints( env["UV_CONSTRAINT"] = env["PIP_CONSTRAINT"] = " ".join( c for c in [our_constraints, user_constraints] if c ) + + +@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) + + def _remove_trailing_zeros(self, 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 diff --git a/cibuildwheel/windows.py b/cibuildwheel/windows.py index 909a022cd..8f5633d67 100644 --- a/cibuildwheel/windows.py +++ b/cibuildwheel/windows.py @@ -302,22 +302,22 @@ def setup_python( env = environment.as_dictionary(prev_environment=env) # 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() + print(where_python) 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." 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 if not use_uv: assert (venv_path / "Scripts" / "pip.exe").exists() where_pip = call("where", "pip", env=env, capture_stdout=True).splitlines()[0].strip() + print(where_pip) if where_pip.strip() != str(venv_path / "Scripts" / "pip.exe"): 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) log.step("Installing build tools...") @@ -490,7 +490,10 @@ def build(options: Options, tmp_path: Path) -> None: else: shutil.move(str(built_wheel), repaired_wheel_dir) - repaired_wheel = next(repaired_wheel_dir.glob("*.whl")) + try: + repaired_wheel = next(repaired_wheel_dir.glob("*.whl")) + except StopIteration: + raise errors.RepairStepProducedNoWheelError() from None if repaired_wheel.name in {wheel.name for wheel in built_wheels}: raise errors.AlreadyBuiltWheelError(repaired_wheel.name) diff --git a/docs/changelog.md b/docs/changelog.md index b0f885b44..d43a70cb6 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -4,6 +4,43 @@ title: Changelog # Changelog +### 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_ diff --git a/docs/contributing.md b/docs/contributing.md index 5a6b7ac12..237708999 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -17,11 +17,11 @@ Everyone contributing to the cibuildwheel project is expected to follow the [PSF - `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. - Options should be environment variables (these lend themselves better to YML config files). They should be prefixed with `CIBW_`. -- Options should be generalise to all platforms. If platform-specific options are required, they should be namespaced e.g. `CIBW_TEST_COMMAND_MACOS` +- Options should be generalised to all platforms. If platform-specific options are required, they should be namespaced e.g. `CIBW_TEST_COMMAND_MACOS` Other notes: -- 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. +- 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. - 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 @@ -82,14 +82,11 @@ A few notes- - 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. -#### 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 -python3 -m venv .venv -source .venv/bin/activate -pip install -e .[dev] # run the unit tests pytest unit_test # run the whole integration test suite @@ -102,7 +99,7 @@ CIBW_PLATFORM=linux pytest test -k test_build_frontend_args ### 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 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. +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. You can see a list of sessions by typing `nox -l`; here are a few common ones: @@ -113,12 +110,33 @@ nox -s docs # Build and serve the documentation nox -s build # Make SDist and wheel ``` -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: +More advanced users can run the update scripts: ```console -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_constraints # update all constraints files in cibuildwheel/resources +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 ### Testing sample configs diff --git a/docs/cpp_standards.md b/docs/cpp_standards.md index 93a548a09..ac820300a 100644 --- a/docs/cpp_standards.md +++ b/docs/cpp_standards.md @@ -8,7 +8,7 @@ Building Python wheels with modern C++ standards (C++11 and later) requires a fe ## 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 `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 `manylinux2010` and `manylinux2014` are newer and support all C++ standards (up to C++17). @@ -18,9 +18,8 @@ OS X/macOS allows you to specify a so-called "deployment target" version that wi 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++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 ` instead of `#include `); macOS 10.14 has full C++17 support. +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 ` instead of `#include `); macOS 10.14 has full C++17 support. CPython 3.12+ require 10.13+ anyway. 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. diff --git a/docs/deliver-to-pypi.md b/docs/deliver-to-pypi.md index 292d89604..d03e2d6f9 100644 --- a/docs/deliver-to-pypi.md +++ b/docs/deliver-to-pypi.md @@ -86,7 +86,7 @@ This requires setting this GitHub workflow in your project's PyPI settings (for 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. +runs, so we can just download all of them in one step into a common directory. See [`examples/github-deploy.yml`](https://github.com/pypa/cibuildwheel/blob/main/examples/github-deploy.yml) diff --git a/docs/extra.css b/docs/extra.css index 403a4ffdd..9cd1c17f4 100644 --- a/docs/extra.css +++ b/docs/extra.css @@ -258,6 +258,16 @@ h1, h2, h3, h4, h5, h6 { font-size: 80%; } +.rst-content table.docutils td code, +.rst-content table.docutils th code, +.rst-content table.field-list td code, +.rst-content table.field-list th code, +.wy-table td code, +.wy-table th code { + /* table elements are already made smaller, the code styling on top of that makes the text too small */ + font-size: 82.5%; +} + /* expand all the toctree entries */ .wy-menu-vertical .toctree-l1.current .toctree-l2>ul, diff --git a/docs/faq.md b/docs/faq.md index 4c402f475..78a5c2390 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -10,7 +10,7 @@ title: Tips and tricks 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. +- 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`/`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. @@ -133,7 +133,7 @@ myextension = Extension( ### 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 worse-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 worst-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. @@ -142,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: ```yaml -uses: pypa/cibuildwheel@v2.21.1 +uses: pypa/cibuildwheel@v2.22.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. @@ -164,7 +164,7 @@ The second option, and the only one that supports other CI systems, is using a ` ```bash # requirements-cibw.txt -cibuildwheel==2.21.1 +cibuildwheel==2.22.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: @@ -184,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} 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 package 'sdist' will also benefit. @@ -328,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 python3 -m pip install pipx -pipx run cibuildwheel==2.21.1 --output-dir wheelhouse +pipx run cibuildwheel==2.22.0 --output-dir wheelhouse pipx run twine upload wheelhouse/*.whl ``` diff --git a/docs/main.py b/docs/main.py index 106cbf241..66066fa2e 100644 --- a/docs/main.py +++ b/docs/main.py @@ -9,7 +9,7 @@ def define_env(env: Any) -> None: "Hook function for mkdocs-macros" - @env.macro + @env.macro # type: ignore[misc] def subprocess_run(*args: str) -> str: "Run a subprocess and return the stdout" env = os.environ.copy() diff --git a/docs/options.md b/docs/options.md index 5cb238784..327bfe1f8 100644 --- a/docs/options.md +++ b/docs/options.md @@ -288,25 +288,35 @@ When setting the options, you can use shell-style globbing syntax, as per [fnmat
-| | macOS | Windows | Linux Intel | Linux Other | -|---------------|------------------------------------------------------------------------|-----------------------------------------------------|-----------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Python 3.6 | cp36-macosx_x86_64 | cp36-win_amd64
cp36-win32 | cp36-manylinux_x86_64
cp36-manylinux_i686
cp36-musllinux_x86_64
cp36-musllinux_i686 | cp36-manylinux_aarch64
cp36-manylinux_ppc64le
cp36-manylinux_s390x
cp36-musllinux_aarch64
cp36-musllinux_ppc64le
cp36-musllinux_s390x | -| Python 3.7 | cp37-macosx_x86_64 | cp37-win_amd64
cp37-win32 | cp37-manylinux_x86_64
cp37-manylinux_i686
cp37-musllinux_x86_64
cp37-musllinux_i686 | cp37-manylinux_aarch64
cp37-manylinux_ppc64le
cp37-manylinux_s390x
cp37-musllinux_aarch64
cp37-musllinux_ppc64le
cp37-musllinux_s390x | -| Python 3.8 | cp38-macosx_x86_64
cp38-macosx_universal2
cp38-macosx_arm64 | cp38-win_amd64
cp38-win32 | cp38-manylinux_x86_64
cp38-manylinux_i686
cp38-musllinux_x86_64
cp38-musllinux_i686 | cp38-manylinux_aarch64
cp38-manylinux_ppc64le
cp38-manylinux_s390x
cp38-musllinux_aarch64
cp38-musllinux_ppc64le
cp38-musllinux_s390x | -| Python 3.9 | cp39-macosx_x86_64
cp39-macosx_universal2
cp39-macosx_arm64 | cp39-win_amd64
cp39-win32
cp39-win_arm64 | cp39-manylinux_x86_64
cp39-manylinux_i686
cp39-musllinux_x86_64
cp39-musllinux_i686 | cp39-manylinux_aarch64
cp39-manylinux_ppc64le
cp39-manylinux_s390x
cp39-musllinux_aarch64
cp39-musllinux_ppc64le
cp39-musllinux_s390x | -| Python 3.10 | cp310-macosx_x86_64
cp310-macosx_universal2
cp310-macosx_arm64 | cp310-win_amd64
cp310-win32
cp310-win_arm64 | cp310-manylinux_x86_64
cp310-manylinux_i686
cp310-musllinux_x86_64
cp310-musllinux_i686 | cp310-manylinux_aarch64
cp310-manylinux_ppc64le
cp310-manylinux_s390x
cp310-musllinux_aarch64
cp310-musllinux_ppc64le
cp310-musllinux_s390x | -| Python 3.11 | cp311-macosx_x86_64
cp311-macosx_universal2
cp311-macosx_arm64 | cp311-win_amd64
cp311-win32
cp311-win_arm64 | cp311-manylinux_x86_64
cp311-manylinux_i686
cp311-musllinux_x86_64
cp311-musllinux_i686 | cp311-manylinux_aarch64
cp311-manylinux_ppc64le
cp311-manylinux_s390x
cp311-musllinux_aarch64
cp311-musllinux_ppc64le
cp311-musllinux_s390x | -| Python 3.12 | cp312-macosx_x86_64
cp312-macosx_universal2
cp312-macosx_arm64 | cp312-win_amd64
cp312-win32
cp312-win_arm64 | cp312-manylinux_x86_64
cp312-manylinux_i686
cp312-musllinux_x86_64
cp312-musllinux_i686 | cp312-manylinux_aarch64
cp312-manylinux_ppc64le
cp312-manylinux_s390x
cp312-musllinux_aarch64
cp312-musllinux_ppc64le
cp312-musllinux_s390x | -| Python 3.13 | cp313-macosx_x86_64
cp313-macosx_universal2
cp313-macosx_arm64 | cp313-win_amd64
cp313-win32
cp313-win_arm64 | cp313-manylinux_x86_64
cp313-manylinux_i686
cp313-musllinux_x86_64
cp313-musllinux_i686 | cp313-manylinux_aarch64
cp313-manylinux_ppc64le
cp313-manylinux_s390x
cp313-musllinux_aarch64
cp313-musllinux_ppc64le
cp313-musllinux_s390x | -| PyPy3.7 v7.3 | pp37-macosx_x86_64 | pp37-win_amd64 | pp37-manylinux_x86_64
pp37-manylinux_i686 | pp37-manylinux_aarch64 | -| PyPy3.8 v7.3 | pp38-macosx_x86_64
pp38-macosx_arm64 | pp38-win_amd64 | pp38-manylinux_x86_64
pp38-manylinux_i686 | pp38-manylinux_aarch64 | -| PyPy3.9 v7.3 | pp39-macosx_x86_64
pp39-macosx_arm64 | pp39-win_amd64 | pp39-manylinux_x86_64
pp39-manylinux_i686 | pp39-manylinux_aarch64 | -| PyPy3.10 v7.3 | pp310-macosx_x86_64
pp310-macosx_arm64 | pp310-win_amd64 | pp310-manylinux_x86_64
pp310-manylinux_i686 | pp310-manylinux_aarch64 | +| | macOS | Windows | Linux Intel | Linux Other | +|---------------|------------------------------------------------------------------------|-----------------------------------------------------|-----------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Python 3.6 | cp36-macosx_x86_64 | cp36-win_amd64
cp36-win32 | cp36-manylinux_x86_64
cp36-manylinux_i686
cp36-musllinux_x86_64
cp36-musllinux_i686 | cp36-manylinux_aarch64
cp36-manylinux_ppc64le
cp36-manylinux_s390x
cp36-manylinux_armv7l
cp36-musllinux_aarch64
cp36-musllinux_ppc64le
cp36-musllinux_s390x
cp36-musllinux_armv7l | +| Python 3.7 | cp37-macosx_x86_64 | cp37-win_amd64
cp37-win32 | cp37-manylinux_x86_64
cp37-manylinux_i686
cp37-musllinux_x86_64
cp37-musllinux_i686 | cp37-manylinux_aarch64
cp37-manylinux_ppc64le
cp37-manylinux_s390x
cp37-manylinux_armv7l
cp37-musllinux_aarch64
cp37-musllinux_ppc64le
cp37-musllinux_s390x
cp37-musllinux_armv7l | +| Python 3.8 | cp38-macosx_x86_64
cp38-macosx_universal2
cp38-macosx_arm64 | cp38-win_amd64
cp38-win32 | cp38-manylinux_x86_64
cp38-manylinux_i686
cp38-musllinux_x86_64
cp38-musllinux_i686 | cp38-manylinux_aarch64
cp38-manylinux_ppc64le
cp38-manylinux_s390x
cp38-manylinux_armv7l
cp38-musllinux_aarch64
cp38-musllinux_ppc64le
cp38-musllinux_s390x
cp38-musllinux_armv7l | +| Python 3.9 | cp39-macosx_x86_64
cp39-macosx_universal2
cp39-macosx_arm64 | cp39-win_amd64
cp39-win32
cp39-win_arm64 | cp39-manylinux_x86_64
cp39-manylinux_i686
cp39-musllinux_x86_64
cp39-musllinux_i686 | cp39-manylinux_aarch64
cp39-manylinux_ppc64le
cp39-manylinux_s390x
cp39-manylinux_armv7l
cp39-musllinux_aarch64
cp39-musllinux_ppc64le
cp39-musllinux_s390x
cp39-musllinux_armv7l | +| Python 3.10 | cp310-macosx_x86_64
cp310-macosx_universal2
cp310-macosx_arm64 | cp310-win_amd64
cp310-win32
cp310-win_arm64 | cp310-manylinux_x86_64
cp310-manylinux_i686
cp310-musllinux_x86_64
cp310-musllinux_i686 | cp310-manylinux_aarch64
cp310-manylinux_ppc64le
cp310-manylinux_s390x
cp310-manylinux_armv7l
cp310-musllinux_aarch64
cp310-musllinux_ppc64le
cp310-musllinux_s390x
cp310-musllinux_armv7l | +| Python 3.11 | cp311-macosx_x86_64
cp311-macosx_universal2
cp311-macosx_arm64 | cp311-win_amd64
cp311-win32
cp311-win_arm64 | cp311-manylinux_x86_64
cp311-manylinux_i686
cp311-musllinux_x86_64
cp311-musllinux_i686 | cp311-manylinux_aarch64
cp311-manylinux_ppc64le
cp311-manylinux_s390x
cp311-manylinux_armv7l
cp311-musllinux_aarch64
cp311-musllinux_ppc64le
cp311-musllinux_s390x
cp311-musllinux_armv7l | +| Python 3.12 | cp312-macosx_x86_64
cp312-macosx_universal2
cp312-macosx_arm64 | cp312-win_amd64
cp312-win32
cp312-win_arm64 | cp312-manylinux_x86_64
cp312-manylinux_i686
cp312-musllinux_x86_64
cp312-musllinux_i686 | cp312-manylinux_aarch64
cp312-manylinux_ppc64le
cp312-manylinux_s390x
cp312-musllinux_armv7l
cp312-musllinux_ppc64le
cp312-musllinux_s390x
cp312-musllinux_armv7l | +| Python 3.13 | cp313-macosx_x86_64
cp313-macosx_universal2
cp313-macosx_arm64 | cp313-win_amd64
cp313-win32
cp313-win_arm64 | cp313-manylinux_x86_64
cp313-manylinux_i686
cp313-musllinux_x86_64
cp313-musllinux_i686 | cp313-manylinux_aarch64
cp313-manylinux_ppc64le
cp313-manylinux_s390x
cp313-manylinux_armv7l
cp313-musllinux_aarch64
cp313-musllinux_ppc64le
cp313-musllinux_s390x
cp313-musllinux_armv7l | +| PyPy3.7 v7.3 | pp37-macosx_x86_64 | pp37-win_amd64 | pp37-manylinux_x86_64
pp37-manylinux_i686 | pp37-manylinux_aarch64 | +| PyPy3.8 v7.3 | pp38-macosx_x86_64
pp38-macosx_arm64 | pp38-win_amd64 | pp38-manylinux_x86_64
pp38-manylinux_i686 | pp38-manylinux_aarch64 | +| PyPy3.9 v7.3 | pp39-macosx_x86_64
pp39-macosx_arm64 | pp39-win_amd64 | pp39-manylinux_x86_64
pp39-manylinux_i686 | pp39-manylinux_aarch64 | +| PyPy3.10 v7.3 | pp310-macosx_x86_64
pp310-macosx_arm64 | pp310-win_amd64 | pp310-manylinux_x86_64
pp310-manylinux_i686 | pp310-manylinux_aarch64 | The list of supported and currently selected build identifiers can also be retrieved by passing the `--print-build-identifiers` flag to cibuildwheel. The format is `python_tag-platform_tag`, with tags similar to those in [PEP 425](https://www.python.org/dev/peps/pep-0425/#details). -For CPython, the minimally supported macOS version is 10.9; for PyPy 3.7, macOS 10.13 or higher is required. +The lowest value you can set `MACOSX_DEPLOYMENT_TARGET` is as follows: + +| Arch | Python version range | Minimum target | +|-------|----------------------|----------------| +| Intel | CPython 3.6-3.11 | 10.9 | +| Intel | CPython 3.12+ | 10.13 | +| AS | CPython or PyPy | 11 | +| Intel | PyPy 3.7-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. Windows arm64 platform support is experimental. @@ -412,52 +422,6 @@ See the [cibuildwheel 1 documentation](https://cibuildwheel.pypa.io/en/1.x/) for } -### `CIBW_FREE_THREADED_SUPPORT` {: #free-threaded-support} - -> Choose whether free-threaded variants should be built - -[PEP 703](https://www.python.org/dev/peps/pep-0703) introduced variants of CPython that can be built without the Global Interpreter Lock (GIL). -Those variants are also known as free-threaded / no-gil. - -Building for free-threaded variants is disabled by default. - -Building can be enabled by setting this option to `true`. The free-threaded compatible wheels will be built in addition to the standard wheels. - -This option doesn't support overrides. -If you need to enable/disable it per platform or python version, set this option to `true` and use [`CIBW_BUILD`](#build-skip)/[`CIBW_SKIP`](#build-skip) options to filter the builds. - -The build identifiers for those variants have a `t` suffix in their `python_tag` (e.g. `cp313t-manylinux_x86_64`) - -!!! note - This feature is experimental: [What’s New In Python 3.13](https://docs.python.org/3.13/whatsnew/3.13.html#free-threaded-cpython) - -#### Examples - -!!! tab examples "Environment variables" - - ```yaml - # Enable free-threaded support - CIBW_FREE_THREADED_SUPPORT: 1 - - # Skip building free-threaded compatible wheels on Windows - CIBW_FREE_THREADED_SUPPORT: 1 - CIBW_SKIP: *t-win* - ``` - - It is generally recommended to use `free-threaded-support` in a config file as you can statically declare that you - support free-threaded builds. - -!!! tab examples "pyproject.toml" - - ```toml - [tool.cibuildwheel] - # Enable free-threaded support - free-threaded-support = true - - # Skip building free-threaded compatible wheels on Windows - free-threaded-support = true - skip = "*t-win*" - ``` ### `CIBW_ARCHS` {: #archs} > Change the architectures built on your machine by default. @@ -475,7 +439,7 @@ machine, provided the cross-compiling tools are installed. Options: -- Linux: `x86_64` `i686` `aarch64` `ppc64le` `s390x` +- Linux: `x86_64` `i686` `aarch64` `ppc64le` `s390x` `armv7l` - macOS: `x86_64` `arm64` `universal2` - Windows: `AMD64` `x86` `ARM64` - Pyodide: `wasm32` @@ -593,24 +557,53 @@ the package is compatible with all versions of Python that it can build. CIBW_PROJECT_REQUIRES_PYTHON: ">=3.6" ``` -### `CIBW_PRERELEASE_PYTHONS` {: #prerelease-pythons} -> Enable building with pre-release versions of Python if available +### `CIBW_ENABLE` {: #enable} +> Enable building with extra categories of selectors present. + +This option lets you opt-in to non-default builds, like pre-releases and +free-threaded Python. These are not included by default to give a nice default +for new users, but can be added to the selectors available here. The allowed +values are: + + +- `cypython-prerelease`: Enables beta versions of Pythons if any are available + (May-July, approximately). For backward compatibility, `CIBW_PRERELEASE_PYTHONS` + is also supported until cibuildwheel 3. +- `cpython-freethreading`: [PEP 703](https://www.python.org/dev/peps/pep-0703) + introduced variants of CPython that can be built without the Global + Interpreter Lock (GIL). Those variants are also known as free-threaded / + no-gil. This will enable building these wheels while they are experimental. + The build identifiers for those variants have a `t` suffix in their + `python_tag` (e.g. `cp313t-manylinux_x86_64`). For backward compatibility, + `CIBW_FREE_THREADED_SUPPORT` is also supported until cibuildwheel 3. +- `pypy`: Enable PyPy. For backward compatibility, this is always enabled until + cibuildwheel 3 is released. -During the beta period, when new versions of Python are being tested, -cibuildwheel will often gain early support for beta releases. If you would -like to test wheel building with these versions, you can enable this flag. !!! caution - This option is provided for testing purposes only. It is not - recommended to distribute wheels built when `CIBW_PRERELEASE_PYTHONS` is - set, such as uploading to PyPI. Please _do not_ upload these wheels to - PyPI, as they are not guaranteed to work with the final Python release. - Once Python is ABI stable and enters the release candidate phase, that - version of Python will become available without this flag. + `cpython-prerelease` is provided for testing purposes only. It is not + recommended to distribute wheels built with beta releases, such as + uploading to PyPI. Please _do not_ upload these wheels to PyPI, as they are + not guaranteed to work with the final Python release. Once Python is ABI + stable and enters the release candidate phase, that version of Python will + become available without this flag. + +!!! note + Free threading is experimental: [What’s New In Python 3.13](https://docs.python.org/3.13/whatsnew/3.13.html#free-threaded-cpython) -Default: Off (0) if Python is available in beta phase. No effect otherwise. +Default: empty (`pypy` is always injected). + +This option doesn't support overrides or platform specific variants; it is +intended as a way to acknowledge that a project is aware that these extra +selectors exist. If you need to enable/disable it per platform or python +version, set this option to `true` and use +[`CIBW_BUILD`](#build-skip)/[`CIBW_SKIP`](#build-skip) options to filter the +builds. + +Unlike all other cibuildwheel options, the environment variable setting will +only add to the TOML config; you can't remove an enable by setting an empty or +partial list in environment variables; use `CIBW_SKIP` instead. -This option can also be set using the [command-line option](#command-line) `--prerelease-pythons`. This option is not available in the `pyproject.toml` config. #### Examples @@ -618,9 +611,38 @@ This option can also be set using the [command-line option](#command-line) `--pr ```yaml # Include latest Python beta - CIBW_PRERELEASE_PYTHONS: True + CIBW_ENABLE: cpython-prerelease + + # Include free-threaded support + CIBW_ENABLE: cpython-freethreading + + # Include both + CIBW_ENABLE: cpython-prerelease cpython-freethreading + + # Skip building free-threaded compatible wheels on Windows + CIBW_ENABLE: cpython-freethreading + CIBW_SKIP: *t-win* ``` + It is generally recommended to use `cpython-freethreading` in a config + file as you can statically declare that you support free-threaded builds. + +!!! tab examples "pyproject.toml" + + ```toml + [tool.cibuildwheel] + # Enable free-threaded support + enable = ["cpython-freethreading"] + + # Skip building free-threaded compatible wheels on Windows + enable = ["cpython-freethreading"] + skip = "*t-win*" + ``` + + It is generally not recommended to use `cpython-prerelease` in a config file, + as it's intended for testing pre-releases for a 2-3 month period only. + + ### `CIBW_ALLOW_EMPTY` {: #allow-empty} > Suppress the error code if no wheels match the specified build identifiers @@ -953,9 +975,9 @@ Platform-specific environment variables also available:
In configuration files, you can use a TOML array, and each line will be run sequentially - joined with `&&`. -Note that manylinux_2_24 builds occur inside a Debian9 docker, where +Note that manylinux_2_24/manylinux_2_31 builds occur inside a debian derivative docker container, where manylinux2010 and manylinux2014 builds occur inside a CentOS one. So for -`manylinux_2_24` the `CIBW_BEFORE_ALL_LINUX` command must use `apt-get -y` +`manylinux_2_24`/`manylinux_2_31` the `CIBW_BEFORE_ALL_LINUX` command must use `apt-get -y` instead. ### `CIBW_BEFORE_BUILD` {: #before-build} @@ -1110,6 +1132,7 @@ Platform-specific environment variables are also available:
delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel} && pipx run abi3audit --strict --report {wheel} CIBW_REPAIR_WHEEL_COMMAND_WINDOWS: > + copy {wheel} {dest_dir} && pipx run abi3audit --strict --report {wheel} ``` @@ -1148,7 +1171,10 @@ Platform-specific environment variables are also available:
"pipx run abi3audit --strict --report {wheel}", ] [tool.cibuildwheel.windows] - repair-wheel-command = "pipx run abi3audit --strict --report {wheel}" + repair-wheel-command = [ + "copy {wheel} {dest_dir}", + "pipx run abi3audit --strict --report {wheel}", + ] ``` In configuration mode, you can use an inline array, and the items will be joined with `&&`. @@ -1157,28 +1183,36 @@ Platform-specific environment variables are also available:
### `CIBW_MANYLINUX_*_IMAGE`, `CIBW_MUSLLINUX_*_IMAGE` {: #linux-image} -> Specify alternative manylinux / musllinux container images - -The available options are (default value): - -- `CIBW_MANYLINUX_X86_64_IMAGE` ([`quay.io/pypa/manylinux2014_x86_64`](https://quay.io/pypa/manylinux2014_x86_64)) -- `CIBW_MANYLINUX_I686_IMAGE` ([`quay.io/pypa/manylinux2014_i686`](https://quay.io/pypa/manylinux2014_i686)) -- `CIBW_MANYLINUX_PYPY_X86_64_IMAGE` ([`quay.io/pypa/manylinux2014_x86_64`](https://quay.io/pypa/manylinux2014_x86_64)) -- `CIBW_MANYLINUX_AARCH64_IMAGE` ([`quay.io/pypa/manylinux2014_aarch64`](https://quay.io/pypa/manylinux2014_aarch64)) -- `CIBW_MANYLINUX_PPC64LE_IMAGE` ([`quay.io/pypa/manylinux2014_ppc64le`](https://quay.io/pypa/manylinux2014_ppc64le)) -- `CIBW_MANYLINUX_S390X_IMAGE` ([`quay.io/pypa/manylinux2014_s390x`](https://quay.io/pypa/manylinux2014_s390x)) -- `CIBW_MANYLINUX_PYPY_AARCH64_IMAGE` ([`quay.io/pypa/manylinux2014_aarch64`](https://quay.io/pypa/manylinux2014_aarch64)) -- `CIBW_MANYLINUX_PYPY_I686_IMAGE` ([`quay.io/pypa/manylinux2014_i686`](https://quay.io/pypa/manylinux2014_i686)) -- `CIBW_MUSLLINUX_X86_64_IMAGE` ([`quay.io/pypa/musllinux_1_2_x86_64`](https://quay.io/pypa/musllinux_1_2_x86_64)) -- `CIBW_MUSLLINUX_I686_IMAGE` ([`quay.io/pypa/musllinux_1_2_i686`](https://quay.io/pypa/musllinux_1_2_i686)) -- `CIBW_MUSLLINUX_AARCH64_IMAGE` ([`quay.io/pypa/musllinux_1_2_aarch64`](https://quay.io/pypa/musllinux_1_2_aarch64)) -- `CIBW_MUSLLINUX_PPC64LE_IMAGE` ([`quay.io/pypa/musllinux_1_2_ppc64le`](https://quay.io/pypa/musllinux_1_2_ppc64le)) -- `CIBW_MUSLLINUX_S390X_IMAGE` ([`quay.io/pypa/musllinux_1_2_s390x`](https://quay.io/pypa/musllinux_1_2_s390x)) - -Set an alternative Docker image to be used for building [manylinux / musllinux](https://github.com/pypa/manylinux) wheels. - -For `CIBW_MANYLINUX_*_IMAGE`, the value of this option can either be set to `manylinux1`, `manylinux2010`, `manylinux2014`, `manylinux_2_24` or `manylinux_2_28` to use a pinned version of the [official manylinux images](https://github.com/pypa/manylinux). Alternatively, set these options to any other valid Docker image name. For PyPy, the `manylinux1` image is not available. For architectures other + +> Specify manylinux / musllinux container images + +The available options are: + +| Option | Default | Future default* | +|-----------------------------------|----------------------------------------------------------------------------------------------|-----------------------------------------------------------------| +| CIBW_MANYLINUX_X86_64_IMAGE | [`manylinux2014`](https://quay.io/pypa/manylinux2014_x86_64) | [`manylinux_2_28`](https://quay.io/pypa/manylinux_2_28_x86_64) | +| CIBW_MANYLINUX_I686_IMAGE | [`manylinux2014`](https://quay.io/pypa/manylinux2014_i686) | | +| CIBW_MANYLINUX_PYPY_X86_64_IMAGE | [`manylinux2014`](https://quay.io/pypa/manylinux2014_x86_64) | [`manylinux_2_28`](https://quay.io/pypa/manylinux_2_28_x86_64) | +| CIBW_MANYLINUX_AARCH64_IMAGE | [`manylinux2014`](https://quay.io/pypa/manylinux2014_aarch64) | [`manylinux_2_28`](https://quay.io/pypa/manylinux_2_28_aarch64) | +| CIBW_MANYLINUX_PPC64LE_IMAGE | [`manylinux2014`](https://quay.io/pypa/manylinux2014_ppc64le) | [`manylinux_2_28`](https://quay.io/pypa/manylinux_2_28_ppc64le) | +| CIBW_MANYLINUX_S390X_IMAGE | [`manylinux2014`](https://quay.io/pypa/manylinux2014_s390x) | [`manylinux_2_28`](https://quay.io/pypa/manylinux_2_28_s390x) | +| CIBW_MANYLINUX_ARMV7L_IMAGE | [`manylinux_2_31`](https://github.com/mayeut/manylinux-ubuntu/pkgs/container/manylinux_2_31) | | +| CIBW_MANYLINUX_PYPY_AARCH64_IMAGE | [`manylinux2014`](https://quay.io/pypa/manylinux2014_aarch64) | [`manylinux_2_28`](https://quay.io/pypa/manylinux_2_28_aarch64) | +| CIBW_MANYLINUX_PYPY_I686_IMAGE | [`manylinux2014`](https://quay.io/pypa/manylinux2014_i686) | | +| CIBW_MUSLLINUX_X86_64_IMAGE | [`musllinux_1_2`](https://quay.io/pypa/musllinux_1_2_x86_64) | | +| CIBW_MUSLLINUX_I686_IMAGE | [`musllinux_1_2`](https://quay.io/pypa/musllinux_1_2_i686) | | +| CIBW_MUSLLINUX_AARCH64_IMAGE | [`musllinux_1_2`](https://quay.io/pypa/musllinux_1_2_aarch64) | | +| CIBW_MUSLLINUX_PPC64LE_IMAGE | [`musllinux_1_2`](https://quay.io/pypa/musllinux_1_2_ppc64le) | | +| CIBW_MUSLLINUX_S390X_IMAGE | [`musllinux_1_2`](https://quay.io/pypa/musllinux_1_2_s390x) | | +| CIBW_MUSLLINUX_ARMV7L_IMAGE | [`musllinux_1_2`](https://quay.io/pypa/musllinux_1_2_armv7l) | | + +* The default is scheduled to change in a cibuildwheel release on or after 6th May 2025 - if you don't want the new default, you should set the value to `manylinux2014`. + +Set the Docker image to be used for building [manylinux / musllinux](https://github.com/pypa/manylinux) wheels. + +For `CIBW_MANYLINUX_*_IMAGE`, except `CIBW_MANYLINUX_ARMV7L_IMAGE`, the value of this option can either be set to `manylinux1`, `manylinux2010`, `manylinux2014`, `manylinux_2_24` or `manylinux_2_28` to use a pinned version of the [official manylinux images](https://github.com/pypa/manylinux). Alternatively, set these options to any other valid Docker image name. For PyPy, the `manylinux1` image is not available. For architectures other than x86 (x86\_64 and i686) `manylinux2014`, `manylinux_2_24` or `manylinux_2_28` must be used, because the first version of the manylinux specification that supports additional architectures is `manylinux2014`. `manylinux_2_28` is not supported for `i686` architecture. +For `CIBW_MANYLINUX_ARMV7L_IMAGE`, the value of this option can either be set to `manylinux_2_31` or a custom image. Support is experimental for now. The `manylinux_2_31` value is only available for `armv7`. For `CIBW_MUSLLINUX_*_IMAGE`, the value of this option can either be set to `musllinux_1_1` or `musllinux_1_2` to use a pinned version of the [official musllinux images](https://github.com/pypa/musllinux). Alternatively, set these options to any other valid Docker image name. @@ -1584,6 +1618,40 @@ Platform-specific environment variables are also available:
In configuration files, you can use an inline array, and the items will be joined with a comma. + +### `CIBW_TEST_GROUPS` {: #test-groups} +> Specify test dependencies from your project's `dependency-groups` + +List of +[dependency-groups](https://peps.python.org/pep-0735) +that should be included when installing the wheel prior to running the +tests. This can be used to avoid having to redefine test dependencies in +`CIBW_TEST_REQUIRES` if they are already defined in `pyproject.toml`. + +Platform-specific environment variables are also available:
+`CIBW_TEST_GROUPS_MACOS` | `CIBW_TEST_GROUPS_WINDOWS` | `CIBW_TEST_GROUPS_LINUX` | `CIBW_TEST_GROUPS_PYODIDE` + +#### Examples + +!!! tab examples "Environment variables" + + ```yaml + # Will cause the wheel to be installed with these groups of dependencies + CIBW_TEST_GROUPS: "test qt" + ``` + + Separate multiple items with a space. + +!!! tab examples "pyproject.toml" + + ```toml + [tool.cibuildwheel] + # Will cause the wheel to be installed with these groups of dependencies + test-groups = ["test", "qt"] + ``` + + In configuration files, you can use an inline array, and the items will be joined with a space. + ### `CIBW_TEST_SKIP` {: #test-skip} > Skip running tests on some builds @@ -1599,7 +1667,7 @@ This option is not supported in the overrides section in `pyproject.toml`. ```yaml # Will avoid testing on emulated architectures - CIBW_TEST_SKIP: "*-*linux_{aarch64,ppc64le,s390x}" + CIBW_TEST_SKIP: "*-*linux_{aarch64,ppc64le,s390x,armv7l}" # Skip trying to test arm64 builds on Intel Macs CIBW_TEST_SKIP: "*-macosx_arm64 *-macosx_universal2:arm64" @@ -1610,7 +1678,7 @@ This option is not supported in the overrides section in `pyproject.toml`. ```toml [tool.cibuildwheel] # Will avoid testing on emulated architectures - test-skip = "*-*linux_{aarch64,ppc64le,s390x}" + test-skip = "*-*linux_{aarch64,ppc64le,s390x,armv7l}" # Skip trying to test arm64 builds on Intel Macs test-skip = "*-macosx_arm64 *-macosx_universal2:arm64" diff --git a/docs/setup.md b/docs/setup.md index 0ff803726..e118bb94d 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -161,7 +161,7 @@ To build Linux, Mac, and Windows wheels using GitHub Actions, create a `.github/ - uses: actions/checkout@v4 - name: Build wheels - run: pipx run cibuildwheel==2.21.1 + run: pipx run cibuildwheel==2.22.0 - uses: actions/upload-artifact@v4 with: @@ -198,7 +198,7 @@ To build Linux, Mac, and Windows wheels using GitHub Actions, create a `.github/ - uses: actions/setup-python@v5 - name: Install cibuildwheel - run: python -m pip install cibuildwheel==2.21.1 + run: python -m pip install cibuildwheel==2.22.0 - name: Build wheels run: python -m cibuildwheel --output-dir wheelhouse diff --git a/docs/working-examples.md b/docs/working-examples.md index 8e09fe90c..becc363b4 100644 --- a/docs/working-examples.md +++ b/docs/working-examples.md @@ -18,8 +18,8 @@ title: Working examples | [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 | -| [MemRay][] | ![github icon][] | ![linux icon][] | Memray is a memory profiler for Python | | [Triton][] | ![github icon][] | ![linux icon][] | Self hosted runners | +| [MemRay][] | ![github icon][] | ![linux icon][] | Memray is a memory profiler for Python | | [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 | | [Google Benchmark][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | A microbenchmark support library | @@ -32,18 +32,18 @@ title: Working examples | [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. | -| [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). | +| [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. | | [River][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | 🌊 Online machine learning in Python | | [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. | +| [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. | | [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. | | [Implicit][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Includes GPU support for linux wheels | -| [vispy][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Main repository for Vispy | -| [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. | +| [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 | | [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 | | [Line Profiler][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Line-by-line profiling for Python | @@ -78,8 +78,8 @@ title: Working examples | [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 | | [python-snappy][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Python bindings for the snappy google library | -| [abess][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | A fast best-subset selection library. It uses cibuildwheel to build a large project with C++ extensions. | | [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. | | [cyvcf2][] | ![github icon][] | ![apple icon][] ![linux icon][] | cython + htslib == fast VCF and BCF processing | | [jq.py][] | ![travisci icon][] | ![apple icon][] ![linux icon][] | Python bindings for jq | | [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. | @@ -92,16 +92,16 @@ title: Working examples | [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 | | [bx-python][] | ![travisci icon][] | ![apple icon][] ![linux icon][] | A library that includes Cython extensions. | -| [boost-histogram][] | ![github icon][] ![travisci icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Supports full range of wheels, including PyPy and alternate archs. | | [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 | +| [boost-histogram][] | ![github icon][] ![travisci icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Supports full range of wheels, including PyPy and alternate archs. | | [pybase64][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Fast Base64 encoding/decoding in Python | +| [Python-WebRTC][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | a Python extension that provides bindings to WebRTC M92 | +| [Confluent client for Kafka][] | ![travisci icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | setup in `tools/wheels/build-wheels.bat` | | [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 | -| [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. | | [pybind11 scikit_build_example][] | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | An example combining scikit-build and pybind11 | +| [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. | | [polaroid][] | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Full range of wheels for setuptools rust, with auto release and PyPI deploy. | -| [Confluent client for Kafka][] | ![travisci icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | setup in `tools/wheels/build-wheels.bat` | | [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. | | [cf-units][] | ![github icon][] | ![apple icon][] ![linux icon][] | Units of measure as required by the Climate and Forecast (CF) Metadata Conventions | @@ -128,8 +128,8 @@ title: Working examples [Prophet]: https://github.com/facebook/prophet [MyPy]: https://github.com/mypyc/mypy_mypyc-wheels [Kivy]: https://github.com/kivy/kivy -[MemRay]: https://github.com/bloomberg/memray [Triton]: https://github.com/openai/triton +[MemRay]: https://github.com/bloomberg/memray [uvloop]: https://github.com/MagicStack/uvloop [psutil]: https://github.com/giampaolo/psutil [Google Benchmark]: https://github.com/google/benchmark @@ -142,18 +142,18 @@ title: Working examples [twisted-iocpsupport]: https://github.com/twisted/twisted-iocpsupport [PyOxidizer]: https://github.com/indygreg/PyOxidizer [cvxpy]: https://github.com/cvxpy/cvxpy -[websockets]: https://github.com/python-websockets/websockets [pedalboard]: https://github.com/spotify/pedalboard +[websockets]: https://github.com/python-websockets/websockets [River]: https://github.com/online-ml/river [UltraJSON]: https://github.com/ultrajson/ultrajson -[OpenSpiel]: https://github.com/google-deepmind/open_spiel [aiortc]: https://github.com/aiortc/aiortc +[OpenSpiel]: https://github.com/google-deepmind/open_spiel [Dependency Injector]: https://github.com/ets-labs/python-dependency-injector [pyzmq]: https://github.com/zeromq/pyzmq [Implicit]: https://github.com/benfred/implicit -[vispy]: https://github.com/vispy/vispy -[tinyobjloader]: https://github.com/tinyobjloader/tinyobjloader [CTranslate2]: https://github.com/OpenNMT/CTranslate2 +[tinyobjloader]: https://github.com/tinyobjloader/tinyobjloader +[vispy]: https://github.com/vispy/vispy [coverage.py]: https://github.com/nedbat/coveragepy [PyCryptodome]: https://github.com/Legrandin/pycryptodome [Line Profiler]: https://github.com/pyutils/line_profiler @@ -188,8 +188,8 @@ title: Working examples [python-rapidjson]: https://github.com/python-rapidjson/python-rapidjson [pybind11 python_example]: https://github.com/pybind/python_example [python-snappy]: https://github.com/intake/python-snappy -[abess]: https://github.com/abess-team/abess [sourmash]: https://github.com/sourmash-bio/sourmash +[abess]: https://github.com/abess-team/abess [cyvcf2]: https://github.com/brentp/cyvcf2 [jq.py]: https://github.com/mwilliamson/jq.py [matrixprofile]: https://github.com/matrix-profile-foundation/matrixprofile @@ -202,16 +202,16 @@ title: Working examples [iDynTree]: https://github.com/robotology/idyntree [streaming-form-data]: https://github.com/siddhantgoel/streaming-form-data [bx-python]: https://github.com/bxlab/bx-python -[boost-histogram]: https://github.com/scikit-hep/boost-histogram [power-grid-model]: https://github.com/PowerGridModel/power-grid-model -[Python-WebRTC]: https://github.com/MarshalX/python-webrtc +[boost-histogram]: https://github.com/scikit-hep/boost-histogram [pybase64]: https://github.com/mayeut/pybase64 +[Python-WebRTC]: https://github.com/MarshalX/python-webrtc +[Confluent client for Kafka]: https://github.com/confluentinc/confluent-kafka-python [Imagecodecs (fork)]: https://github.com/czaki/imagecodecs_build [fathon]: https://github.com/stfbnc/fathon -[Arbor]: https://github.com/arbor-sim/arbor [pybind11 scikit_build_example]: https://github.com/pybind/scikit_build_example +[Arbor]: https://github.com/arbor-sim/arbor [polaroid]: https://github.com/daggy1234/polaroid -[Confluent client for Kafka]: https://github.com/confluentinc/confluent-kafka-python [clang-format]: https://github.com/ssciwr/clang-format-wheel [etebase-py]: https://github.com/etesync/etebase-py [cf-units]: https://github.com/SciTools/cf-units diff --git a/examples/appveyor-minimal.yml b/examples/appveyor-minimal.yml index 0e4ec74d6..e82d30e8e 100644 --- a/examples/appveyor-minimal.yml +++ b/examples/appveyor-minimal.yml @@ -9,7 +9,7 @@ environment: stack: python 3.12 -install: python -m pip install cibuildwheel==2.21.1 +install: python -m pip install cibuildwheel==2.22.0 build_script: python -m cibuildwheel --output-dir wheelhouse diff --git a/examples/azure-pipelines-minimal.yml b/examples/azure-pipelines-minimal.yml index 3f170b0f4..dfe3ec1ef 100644 --- a/examples/azure-pipelines-minimal.yml +++ b/examples/azure-pipelines-minimal.yml @@ -6,7 +6,7 @@ jobs: - bash: | set -o errexit python3 -m pip install --upgrade pip - pip3 install cibuildwheel==2.21.1 + pip3 install cibuildwheel==2.22.0 displayName: Install dependencies - bash: cibuildwheel --output-dir wheelhouse . displayName: Build wheels @@ -20,7 +20,7 @@ jobs: - bash: | set -o errexit python3 -m pip install --upgrade pip - python3 -m pip install cibuildwheel==2.21.1 + python3 -m pip install cibuildwheel==2.22.0 displayName: Install dependencies - bash: cibuildwheel --output-dir wheelhouse . displayName: Build wheels @@ -34,7 +34,7 @@ jobs: - bash: | set -o errexit python -m pip install --upgrade pip - pip install cibuildwheel==2.21.1 + pip install cibuildwheel==2.22.0 displayName: Install dependencies - bash: cibuildwheel --output-dir wheelhouse . displayName: Build wheels diff --git a/examples/circleci-minimal.yml b/examples/circleci-minimal.yml index c30b18189..c0ab2bb6c 100644 --- a/examples/circleci-minimal.yml +++ b/examples/circleci-minimal.yml @@ -11,7 +11,7 @@ jobs: - run: name: Build the Linux wheels. command: | - python3 -m pip install --user cibuildwheel==2.21.1 + python3 -m pip install --user cibuildwheel==2.22.0 cibuildwheel --output-dir wheelhouse - store_artifacts: path: wheelhouse/ @@ -28,7 +28,7 @@ jobs: - run: name: Build the Linux aarch64 wheels. command: | - python3 -m pip install --user cibuildwheel==2.21.1 + python3 -m pip install --user cibuildwheel==2.22.0 python3 -m cibuildwheel --output-dir wheelhouse - store_artifacts: path: wheelhouse/ @@ -44,7 +44,7 @@ jobs: name: Build the OS X wheels. command: | sudo softwareupdate --install-rosetta --agree-to-license # for python<=3.8 or x86_64/universal2 tests - pip3 install cibuildwheel==2.21.1 + pip3 install cibuildwheel==2.22.0 cibuildwheel --output-dir wheelhouse - store_artifacts: path: wheelhouse/ diff --git a/examples/cirrus-ci-intel-mac.yml b/examples/cirrus-ci-intel-mac.yml index d1ffcb78f..02f111633 100644 --- a/examples/cirrus-ci-intel-mac.yml +++ b/examples/cirrus-ci-intel-mac.yml @@ -1,6 +1,6 @@ build_and_store_wheels: &BUILD_AND_STORE_WHEELS install_cibuildwheel_script: - - python -m pip install cibuildwheel==2.21.1 + - python -m pip install cibuildwheel==2.22.0 run_cibuildwheel_script: - cibuildwheel wheels_artifacts: @@ -10,7 +10,7 @@ build_and_store_wheels: &BUILD_AND_STORE_WHEELS macos_task: name: Build macOS x86_64 and arm64 wheels. macos_instance: - image: ghcr.io/cirruslabs/macos-sonoma-xcode + image: ghcr.io/cirruslabs/macos-runner:sonoma env: VENV_ROOT: ${HOME}/venv-cibuildwheel PATH: ${VENV_ROOT}/bin:${PATH} diff --git a/examples/cirrus-ci-minimal.yml b/examples/cirrus-ci-minimal.yml index dddbe065d..a5101fec8 100644 --- a/examples/cirrus-ci-minimal.yml +++ b/examples/cirrus-ci-minimal.yml @@ -1,6 +1,6 @@ build_and_store_wheels: &BUILD_AND_STORE_WHEELS install_cibuildwheel_script: - - python -m pip install cibuildwheel==2.21.1 + - python -m pip install cibuildwheel==2.22.0 run_cibuildwheel_script: - cibuildwheel wheels_artifacts: @@ -60,7 +60,7 @@ windows_x86_task: macos_arm64_task: name: Build macOS arm64 wheels. macos_instance: - image: ghcr.io/cirruslabs/macos-sonoma-xcode + image: ghcr.io/cirruslabs/macos-runner:sonoma env: VENV_ROOT: ${HOME}/venv-cibuildwheel PATH: ${VENV_ROOT}/bin:${PATH} diff --git a/examples/github-deploy.yml b/examples/github-deploy.yml index ecea95a8e..23cf6cea2 100644 --- a/examples/github-deploy.yml +++ b/examples/github-deploy.yml @@ -23,7 +23,7 @@ jobs: - uses: actions/checkout@v4 - name: Build wheels - uses: pypa/cibuildwheel@v2.21.1 + uses: pypa/cibuildwheel@v2.22.0 - uses: actions/upload-artifact@v4 with: diff --git a/examples/github-minimal.yml b/examples/github-minimal.yml index c52115a31..b83d8210c 100644 --- a/examples/github-minimal.yml +++ b/examples/github-minimal.yml @@ -15,7 +15,7 @@ jobs: - uses: actions/checkout@v4 - name: Build wheels - uses: pypa/cibuildwheel@v2.21.1 + uses: pypa/cibuildwheel@v2.22.0 # env: # CIBW_SOME_OPTION: value # ... diff --git a/examples/github-with-qemu.yml b/examples/github-with-qemu.yml index 78cf5c432..390b872ee 100644 --- a/examples/github-with-qemu.yml +++ b/examples/github-with-qemu.yml @@ -21,7 +21,7 @@ jobs: platforms: all - name: Build wheels - uses: pypa/cibuildwheel@v2.21.1 + uses: pypa/cibuildwheel@v2.22.0 env: # configure cibuildwheel to build native archs ('auto'), and some # emulated ones diff --git a/examples/gitlab-minimal.yml b/examples/gitlab-minimal.yml index 2cc46b1a4..a4cbf0008 100644 --- a/examples/gitlab-minimal.yml +++ b/examples/gitlab-minimal.yml @@ -12,7 +12,7 @@ linux: DOCKER_TLS_CERTDIR: "" script: - curl -sSL https://get.docker.com/ | sh - - python -m pip install cibuildwheel==2.21.1 + - python -m pip install cibuildwheel==2.22.0 - cibuildwheel --output-dir wheelhouse artifacts: paths: @@ -23,7 +23,7 @@ windows: before_script: - choco install python -y --version 3.12.4 - choco install git.install -y - - py -m pip install cibuildwheel==2.21.1 + - py -m pip install cibuildwheel==2.22.0 script: - py -m cibuildwheel --output-dir wheelhouse --platform windows artifacts: @@ -35,7 +35,7 @@ windows: macos: image: macos-14-xcode-15 before_script: - - python3 -m pip install cibuildwheel==2.21.1 + - python3 -m pip install cibuildwheel==2.22.0 script: - python3 -m cibuildwheel --output-dir wheelhouse artifacts: diff --git a/examples/gitlab-with-qemu.yml b/examples/gitlab-with-qemu.yml index 96eef80dc..0eb2783d8 100644 --- a/examples/gitlab-with-qemu.yml +++ b/examples/gitlab-with-qemu.yml @@ -14,7 +14,7 @@ linux: - curl -sSL https://get.docker.com/ | sh # Warning: This is extremely slow, be careful with how many wheels you build - docker run --rm --privileged multiarch/qemu-user-static --reset -p yes - - python -m pip install cibuildwheel==2.21.1 + - python -m pip install cibuildwheel==2.22.0 # Assuming your CI runner's default architecture is x86_64... - cibuildwheel --output-dir wheelhouse --platform linux --archs aarch64 artifacts: diff --git a/examples/travis-ci-deploy.yml b/examples/travis-ci-deploy.yml index e60132481..14457de76 100644 --- a/examples/travis-ci-deploy.yml +++ b/examples/travis-ci-deploy.yml @@ -20,7 +20,7 @@ jobs: - ln -s /c/Python312/python.exe /c/Python312/python3.exe install: - - python3 -m pip install cibuildwheel==2.21.1 + - python3 -m pip install cibuildwheel==2.22.0 script: # build the wheels, put them into './dist' diff --git a/examples/travis-ci-minimal.yml b/examples/travis-ci-minimal.yml index ad93a1a89..5dcd28d61 100644 --- a/examples/travis-ci-minimal.yml +++ b/examples/travis-ci-minimal.yml @@ -26,7 +26,7 @@ jobs: - ln -s /c/Python312/python.exe /c/Python312/python3.exe install: - - python3 -m pip install cibuildwheel==2.21.1 + - python3 -m pip install cibuildwheel==2.22.0 script: # build the wheels, put them into './wheelhouse' diff --git a/examples/travis-ci-test-and-deploy.yml b/examples/travis-ci-test-and-deploy.yml index 8fb134440..6e8d181a4 100644 --- a/examples/travis-ci-test-and-deploy.yml +++ b/examples/travis-ci-test-and-deploy.yml @@ -52,7 +52,7 @@ jobs: - stage: deploy name: Build and deploy Linux wheels services: docker - install: python3 -m pip install cibuildwheel==2.21.1 twine + install: python3 -m pip install cibuildwheel==2.22.0 twine script: python3 -m cibuildwheel --output-dir wheelhouse after_success: python3 -m twine upload --skip-existing wheelhouse/*.whl # Deploy on windows @@ -60,7 +60,7 @@ jobs: name: Build and deploy Windows wheels os: windows language: shell - install: python3 -m pip install cibuildwheel==2.21.1 twine + install: python3 -m pip install cibuildwheel==2.22.0 twine script: python3 -m cibuildwheel --output-dir wheelhouse after_success: python3 -m twine upload --skip-existing wheelhouse/*.whl diff --git a/noxfile.py b/noxfile.py index 0c2057b6d..c1f52f4c2 100644 --- a/noxfile.py +++ b/noxfile.py @@ -21,12 +21,16 @@ def install_and_run(session: nox.Session, script: str, *args: str, **kwargs: Any return session.run("python", script, *args, **kwargs) +def dep_group(group: str) -> list[str]: + return nox.project.load_toml("pyproject.toml")["dependency-groups"][group] # type: ignore[no-any-return] + + @nox.session def tests(session: nox.Session) -> None: """ Run the unit and regular tests. """ - session.install("-e.[test]") + session.install("-e.", *dep_group("test")) if session.posargs: session.run("pytest", *session.posargs) else: @@ -102,10 +106,10 @@ def update_constraints(session: nox.Session) -> None: pyodides = build_platforms["pyodide"]["python_configurations"] for pyodide in pyodides: python_version = ".".join(pyodide["version"].split(".")[:2]) - pyodide_version = pyodide["pyodide_version"] + pyodide_build_version = pyodide["pyodide_build_version"] output_file = resources / f"constraints-pyodide{python_version.replace('.', '')}.txt" tmp_file = Path(session.create_tmp()) / "constraints-pyodide.in" - tmp_file.write_text(f"pip\nbuild[virtualenv]\npyodide-build=={pyodide_version}") + tmp_file.write_text(f"pip\nbuild[virtualenv]\npyodide-build=={pyodide_build_version}") session.run( "uv", "pip", @@ -123,7 +127,7 @@ def update_pins(session: nox.Session) -> None: """ Update the python, docker and virtualenv pins version inplace. """ - session.install("-e.[bin]") + session.install("-e.", *dep_group("bin")) session.run("python", "bin/update_pythons.py", "--force") session.run("python", "bin/update_docker.py") session.run("python", "bin/update_virtualenv.py", "--force") @@ -166,7 +170,7 @@ def docs(session: nox.Session) -> None: """ Build the docs. Will serve unless --non-interactive """ - session.install("-e.[docs]") + session.install("-e.", *dep_group("docs")) session.run("mkdocs", "serve" if session.interactive else "build", "--strict", *session.posargs) diff --git a/pyproject.toml b/pyproject.toml index 77a07fe90..3b0d72d1f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "cibuildwheel" -version = "2.21.1" +version = "2.22.0" description = "Build Python wheels on CI with minimal configuration." readme = "README.md" license = "BSD-2-Clause" @@ -35,6 +35,7 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Programming Language :: Python :: Implementation :: CPython", "Topic :: Software Development :: Build Tools", ] @@ -42,6 +43,7 @@ dependencies = [ "bashlex!=0.13", "bracex", "certifi", + "dependency-groups>=1.2", "filelock", "packaging>=20.9", "platformdirs", @@ -50,6 +52,20 @@ dependencies = [ ] [project.optional-dependencies] +uv = ["uv"] + +[project.scripts] +cibuildwheel = "cibuildwheel.__main__:main" + +[project.entry-points."validate_pyproject.tool_schema"] +cibuildwheel = "cibuildwheel.schema:get_schema" + +[project.urls] +Changelog = "https://github.com/pypa/cibuildwheel#changelog" +Documentation = "https://cibuildwheel.pypa.io" +Homepage = "https://github.com/pypa/cibuildwheel" + +[dependency-groups] bin = [ "click", "packaging>=21.0", @@ -59,9 +75,6 @@ bin = [ "requests", "rich>=9.6", ] -dev = [ - "cibuildwheel[test,bin]", -] docs = [ "jinja2>=3.1.2", "mkdocs-include-markdown-plugin==6.2.2", @@ -79,18 +92,12 @@ test = [ "tomli_w", "validate-pyproject", ] -uv = ["uv"] - -[project.scripts] -cibuildwheel = "cibuildwheel.__main__:main" +dev = [ + {include-group = "bin"}, + {include-group = "docs"}, + {include-group = "test"}, +] -[project.entry-points."validate_pyproject.tool_schema"] -cibuildwheel = "cibuildwheel.schema:get_schema" - -[project.urls] -Changelog = "https://github.com/pypa/cibuildwheel#changelog" -Documentation = "https://cibuildwheel.pypa.io" -Homepage = "https://github.com/pypa/cibuildwheel" [tool.pytest.ini_options] minversion = "6.0" @@ -112,33 +119,20 @@ files = [ ] warn_unused_configs = true -warn_redundant_casts = true -no_implicit_reexport = true -strict_equality = true -warn_unused_ignores = true -check_untyped_defs = true - -disallow_subclassing_any = true -disallow_any_generics = true -warn_return_any = true -no_implicit_optional = true +strict = true +disallow_untyped_defs = false enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] -warn_unreachable = true +warn_unreachable = false [[tool.mypy.overrides]] module = "cibuildwheel.*" disallow_untyped_defs = true -disallow_untyped_calls = true -disallow_incomplete_defs = true -disallow_untyped_decorators = true [[tool.mypy.overrides]] module = [ - "setuptools", "setuptools._distutils", # needed even if only directly import setuptools._distutils.util "setuptools._distutils.util", - "pytest", # ignored in pre-commit to speed up check "bashlex", "bashlex.*", "importlib_resources", @@ -213,7 +207,6 @@ ignore = [ "PLR", # Design related pylint codes "E501", # Line too long "RET504", "RET505", "RET508", # else after control flow - "PT004", # Rename suggested for returnless fixtures "PT007", # False positive "PYI025", # Set as AbstractSet "ISC001", # Conflicts with formatter @@ -239,3 +232,6 @@ flake8-unused-arguments.ignore-variadic-names = true [tool.repo-review] ignore = ["PC170", "PP303"] + +[tool.check-wheel-contents] +ignore = ["W002"] # requirements-*.txt are allowed to be duplicates of one another diff --git a/requirements-dev.txt b/requirements-dev.txt deleted file mode 100644 index 18dc383fb..000000000 --- a/requirements-dev.txt +++ /dev/null @@ -1 +0,0 @@ --e .[dev,docs] diff --git a/test/conftest.py b/test/conftest.py index e86c199d4..a42a3ce9a 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -11,7 +11,7 @@ from .utils import EMULATED_ARCHS, platform -def pytest_addoption(parser) -> None: +def pytest_addoption(parser: pytest.Parser) -> None: parser.addoption( "--run-emulation", action="store", diff --git a/test/test_abi_variants.py b/test/test_abi_variants.py index a7879943d..a2dd43ac0 100644 --- a/test/test_abi_variants.py +++ b/test/test_abi_variants.py @@ -18,27 +18,16 @@ IS_CPYTHON = sys.implementation.name == "cpython" Py_GIL_DISABLED = sysconfig.get_config_var("Py_GIL_DISABLED") CAN_USE_ABI3 = IS_CPYTHON and not Py_GIL_DISABLED - cmdclass = {} + setup_options = {} extension_kwargs = {} if CAN_USE_ABI3 and sys.version_info[:2] >= (3, 10): - from wheel.bdist_wheel import bdist_wheel as _bdist_wheel - - class bdist_wheel_abi3(_bdist_wheel): - def finalize_options(self): - _bdist_wheel.finalize_options(self) - self.root_is_pure = False - - def get_tag(self): - python, abi, plat = _bdist_wheel.get_tag(self) - return python, "abi3", plat - - cmdclass["bdist_wheel"] = bdist_wheel_abi3 extension_kwargs["define_macros"] = [("Py_LIMITED_API", "0x030A0000")] extension_kwargs["py_limited_api"] = True + setup_options = {"bdist_wheel": {"py_limited_api": "cp310"}} """ ), setup_py_extension_args_add="**extension_kwargs", - setup_py_setup_args_add="cmdclass=cmdclass", + setup_py_setup_args_add="options=setup_options", ) limited_api_project.files["pyproject.toml"] = pyproject_toml @@ -65,7 +54,7 @@ def test_abi3(tmp_path): if utils.platform == "pyodide": # there's only 1 possible configuration for pyodide, the single_python_tag one expected_wheels = [ - w.replace(f"{single_python_tag}-{single_python_tag}", f"{single_python_tag}-abi3") + w.replace(f"{single_python_tag}-{single_python_tag}", "cp310-abi3") for w in expected_wheels ] else: diff --git a/test/test_custom_repair_wheel.py b/test/test_custom_repair_wheel.py index bf9825665..833639e3f 100644 --- a/test/test_custom_repair_wheel.py +++ b/test/test_custom_repair_wheel.py @@ -33,10 +33,9 @@ def test(tmp_path, capfd): basic_project.generate(project_dir) num_builds = len(utils.cibuildwheel_get_build_identifiers(project_dir)) - if num_builds > 1: - expectation = pytest.raises(subprocess.CalledProcessError) - else: - expectation = does_not_raise() + expectation = ( + pytest.raises(subprocess.CalledProcessError) if num_builds > 1 else does_not_raise() + ) with expectation as exc_info: result = utils.cibuildwheel_run( @@ -48,6 +47,7 @@ def test(tmp_path, capfd): captured = capfd.readouterr() if num_builds > 1: + assert exc_info is not None assert "Build failed because a wheel named" in captured.err assert exc_info.value.returncode == 6 else: diff --git a/test/test_dependency_versions.py b/test/test_dependency_versions.py index a7c573b25..3b1dead7a 100644 --- a/test/test_dependency_versions.py +++ b/test/test_dependency_versions.py @@ -3,6 +3,7 @@ import platform import re import textwrap +from pathlib import Path import pytest @@ -46,8 +47,8 @@ VERSION_REGEX = r"([\w-]+)==([^\s]+)" -def get_versions_from_constraint_file(constraint_file): - constraint_file_text = constraint_file.read_text(encoding="utf8") +def get_versions_from_constraint_file(constraint_file: Path) -> dict[str, str]: + constraint_file_text = constraint_file.read_text(encoding="utf-8") return dict(re.findall(VERSION_REGEX, constraint_file_text)) diff --git a/test/test_emscripten.py b/test/test_emscripten.py index 97ceb7f9f..7a8fd9953 100644 --- a/test/test_emscripten.py +++ b/test/test_emscripten.py @@ -20,7 +20,7 @@ def check_node(): # cibuildwheel adds a pinned node version to the PATH - # check it's in the PATH then, check it's the one that runs pyoodide + # check it's in the PATH then, check it's the one that runs Pyodide cibw_cache_path = Path(sys.argv[1]).resolve(strict=True) # find the node executable in PATH node = shutil.which("node") diff --git a/test/test_from_sdist.py b/test/test_from_sdist.py index 631c03812..23f8b6b90 100644 --- a/test/test_from_sdist.py +++ b/test/test_from_sdist.py @@ -4,6 +4,7 @@ import subprocess import sys import textwrap +from collections.abc import Mapping from pathlib import Path from tempfile import TemporaryDirectory @@ -28,7 +29,11 @@ def make_sdist(project: TestProject, working_dir: Path) -> Path: return next(sdist_dir.glob("*.tar.gz")) -def cibuildwheel_from_sdist_run(sdist_path, add_env=None, config_file=None): +def cibuildwheel_from_sdist_run( + sdist_path: Path | str, + add_env: Mapping[str, str] | None = None, + config_file: str | None = None, +) -> list[str]: env = os.environ.copy() if add_env: diff --git a/test/test_projects/__main__.py b/test/test_projects/__main__.py index 0d3cf2520..155c375cb 100644 --- a/test/test_projects/__main__.py +++ b/test/test_projects/__main__.py @@ -8,7 +8,7 @@ from pathlib import Path -def main(): +def main() -> None: parser = ArgumentParser( prog="python -m test.test_projects", description="Generate a test project to check it out" ) diff --git a/test/test_projects/base.py b/test/test_projects/base.py index 5da5928c0..d7a9305a5 100644 --- a/test/test_projects/base.py +++ b/test/test_projects/base.py @@ -22,11 +22,11 @@ class TestProject: files: FilesDict template_context: TemplateContext - def __init__(self): + def __init__(self) -> None: self.files = {} self.template_context = {} - def generate(self, path: Path): + def generate(self, path: Path) -> None: for filename, content in self.files.items(): file_path = path / filename file_path.parent.mkdir(parents=True, exist_ok=True) @@ -37,7 +37,7 @@ def generate(self, path: Path): f.write(content) - def copy(self): + def copy(self) -> TestProject: other = TestProject() other.files = self.files.copy() other.template_context = self.template_context.copy() diff --git a/test/test_projects/c.py b/test/test_projects/c.py index eee128f7f..cea72bdf5 100644 --- a/test/test_projects/c.py +++ b/test/test_projects/c.py @@ -78,13 +78,13 @@ def new_c_project( *, - spam_c_top_level_add="", - spam_c_function_add="", - setup_py_add="", - setup_py_extension_args_add="", - setup_py_setup_args_add="", - setup_cfg_add="", -): + spam_c_top_level_add: str = "", + spam_c_function_add: str = "", + setup_py_add: str = "", + setup_py_extension_args_add: str = "", + setup_py_setup_args_add: str = "", + setup_cfg_add: str = "", +) -> TestProject: project = TestProject() project.files.update( diff --git a/test/test_testing.py b/test/test_testing.py index b415fd403..b94e3ee90 100644 --- a/test/test_testing.py +++ b/test/test_testing.py @@ -1,5 +1,6 @@ from __future__ import annotations +import inspect import os import subprocess import textwrap @@ -114,6 +115,38 @@ def test_extras_require(tmp_path): assert set(actual_wheels) == set(expected_wheels) +def test_dependency_groups(tmp_path): + group_project = project_with_a_test.copy() + group_project.files["pyproject.toml"] = inspect.cleandoc(""" + [build-system] + requires = ["setuptools"] + build-backend = "setuptools.build_meta" + + [dependency-groups] + dev = ["pytest"] + """) + + project_dir = tmp_path / "project" + group_project.generate(project_dir) + + # build and test the wheels + actual_wheels = utils.cibuildwheel_run( + project_dir, + add_env={ + "CIBW_TEST_GROUPS": "dev", + # the 'false ||' bit is to ensure this command runs in a shell on + # mac/linux. + "CIBW_TEST_COMMAND": f"false || {utils.invoke_pytest()} {{project}}/test", + "CIBW_TEST_COMMAND_WINDOWS": "COLOR 00 || pytest {project}/test", + }, + single_python=True, + ) + + # also check that we got the right wheels + expected_wheels = utils.expected_wheels("spam", "0.1.0", single_python=True) + assert set(actual_wheels) == set(expected_wheels) + + project_with_a_failing_test = test_projects.new_c_project() project_with_a_failing_test.files["test/spam_test.py"] = r""" from unittest import TestCase @@ -155,7 +188,7 @@ def test_failing_test(tmp_path): @pytest.mark.parametrize("test_runner", ["pytest", "unittest"]) def test_bare_pytest_invocation( tmp_path: Path, capfd: pytest.CaptureFixture[str], test_runner: str -): +) -> None: """Check that if a user runs pytest in the the test cwd, it raises a helpful error""" project_dir = tmp_path / "project" output_dir = tmp_path / "output" diff --git a/test/test_windows.py b/test/test_windows.py index cf9f54c99..3ce50848b 100644 --- a/test/test_windows.py +++ b/test/test_windows.py @@ -11,7 +11,7 @@ basic_project = test_projects.new_c_project() -def skip_if_no_msvc(arm64=False): +def skip_if_no_msvc(arm64: bool = False) -> None: programfiles = os.getenv("PROGRAMFILES(X86)", "") or os.getenv("PROGRAMFILES", "") if not programfiles: pytest.skip("Requires %PROGRAMFILES(X86)% variable to be set") diff --git a/test/utils.py b/test/utils.py index 158527e81..aa23638d6 100644 --- a/test/utils.py +++ b/test/utils.py @@ -10,8 +10,10 @@ import platform as pm import subprocess import sys +from collections.abc import Mapping, Sequence +from pathlib import Path from tempfile import TemporaryDirectory -from typing import Final +from typing import Any, Final import pytest @@ -37,7 +39,9 @@ raise Exception(msg) -def cibuildwheel_get_build_identifiers(project_path, env=None, *, prerelease_pythons=False): +def cibuildwheel_get_build_identifiers( + project_path: Path, env: dict[str, str] | None = None, *, prerelease_pythons: bool = False +) -> list[str]: """ Returns the list of build identifiers that cibuildwheel will try to build for the current platform. @@ -75,14 +79,14 @@ def _update_pip_cache_dir(env: dict[str, str]) -> None: def cibuildwheel_run( - project_path, - package_dir=".", - env=None, - add_env=None, - output_dir=None, - add_args=None, - single_python=False, -): + project_path: str | Path, + package_dir: str | Path = ".", + env: dict[str, str] | None = None, + add_env: Mapping[str, str] | None = None, + output_dir: Path | None = None, + add_args: Sequence[str] | None = None, + single_python: bool = False, +) -> list[str]: """ Runs cibuildwheel as a subprocess, building the project at project_path. @@ -144,17 +148,17 @@ def _floor_macosx(*args: str) -> str: def expected_wheels( - package_name, - package_version, - manylinux_versions=None, - musllinux_versions=None, - macosx_deployment_target="10.9", - machine_arch=None, - python_abi_tags=None, - include_universal2=False, - single_python=False, - single_arch=False, -): + package_name: str, + package_version: str, + manylinux_versions: list[str] | None = None, + musllinux_versions: list[str] | None = None, + macosx_deployment_target: str = "10.9", + machine_arch: str | None = None, + python_abi_tags: list[str] | None = None, + include_universal2: bool = False, + single_python: bool = False, + single_arch: bool = False, +) -> list[str]: """ Returns a list of expected wheels from a run of cibuildwheel. """ @@ -170,7 +174,9 @@ def expected_wheels( machine_arch = "aarch64" if manylinux_versions is None: - if machine_arch == "x86_64": + if machine_arch == "armv7l": + manylinux_versions = ["manylinux_2_17", "manylinux2014", "manylinux_2_31"] + elif machine_arch == "x86_64": manylinux_versions = [ "manylinux_2_5", "manylinux1", @@ -305,7 +311,7 @@ def expected_wheels( return wheels -def get_macos_version(): +def get_macos_version() -> tuple[int, int]: """ Returns the macOS major/minor version, as a tuple, e.g. (10, 15) or (11, 0) @@ -314,10 +320,10 @@ def get_macos_version(): (11, 2) <= (11, 0) != True """ version_str, _, _ = pm.mac_ver() - return tuple(map(int, version_str.split(".")[:2])) + return tuple(map(int, version_str.split(".")[:2])) # type: ignore[return-value] -def skip_if_pyodide(reason: str): +def skip_if_pyodide(reason: str) -> Any: return pytest.mark.skipif(platform == "pyodide", reason=reason) @@ -328,7 +334,7 @@ def invoke_pytest() -> str: return "pytest" -def arch_name_for_linux(arch: str): +def arch_name_for_linux(arch: str) -> str: """ Archs have different names on different platforms, but it's useful to be able to run linux tests on dev machines. This function translates between diff --git a/unit_test/build_selector_test.py b/unit_test/build_selector_test.py index f89222461..886202007 100644 --- a/unit_test/build_selector_test.py +++ b/unit_test/build_selector_test.py @@ -2,11 +2,13 @@ from packaging.specifiers import SpecifierSet -from cibuildwheel.util import BuildSelector +from cibuildwheel.util import BuildSelector, EnableGroups def test_build(): - build_selector = BuildSelector(build_config="cp3*-* *-manylinux*", skip_config="") + build_selector = BuildSelector( + build_config="cp3*-* *-manylinux*", skip_config="", enable=frozenset([EnableGroups.PyPy]) + ) assert build_selector("cp36-manylinux_x86_64") assert build_selector("cp37-manylinux_x86_64") @@ -43,7 +45,7 @@ def test_build_filter_pre(): build_selector = BuildSelector( build_config="cp3*-* *-manylinux*", skip_config="", - prerelease_pythons=True, + enable=frozenset([EnableGroups.CPythonPrerelease, EnableGroups.PyPy]), ) assert build_selector("cp37-manylinux_x86_64") @@ -55,7 +57,9 @@ def test_build_filter_pre(): def test_skip(): build_selector = BuildSelector( - build_config="*", skip_config="pp36-* cp3?-manylinux_i686 cp36-win* *-win32" + build_config="*", + skip_config="pp36-* cp3?-manylinux_i686 cp36-win* *-win32", + enable=frozenset([EnableGroups.PyPy]), ) assert not build_selector("pp36-manylinux_x86_64") @@ -79,7 +83,9 @@ def test_skip(): def test_build_and_skip(): build_selector = BuildSelector( - build_config="cp36-* cp37-macosx* *-manylinux*", skip_config="pp37-* cp37-manylinux_i686" + build_config="cp36-* cp37-macosx* *-manylinux*", + skip_config="pp37-* cp37-manylinux_i686", + enable=frozenset([EnableGroups.PyPy]), ) assert not build_selector("pp37-manylinux_x86_64") @@ -110,7 +116,10 @@ def test_build_braces(): def test_build_limited_python(): build_selector = BuildSelector( - build_config="*", skip_config="", requires_python=SpecifierSet(">=3.7") + build_config="*", + skip_config="", + requires_python=SpecifierSet(">=3.7"), + enable=frozenset([EnableGroups.PyPy]), ) assert not build_selector("cp36-manylinux_x86_64") @@ -146,9 +155,7 @@ def test_build_limited_python_patch(): def test_build_free_threaded_python(): - build_selector = BuildSelector( - build_config="*", skip_config="", prerelease_pythons=True, free_threaded_support=True - ) + build_selector = BuildSelector(build_config="*", skip_config="", enable=frozenset(EnableGroups)) assert build_selector("cp313t-manylinux_x86_64") diff --git a/unit_test/linux_build_steps_test.py b/unit_test/linux_build_steps_test.py index 4a1d9e9b8..ee608d0c7 100644 --- a/unit_test/linux_build_steps_test.py +++ b/unit_test/linux_build_steps_test.py @@ -4,12 +4,14 @@ from pathlib import Path from pprint import pprint +import pytest + import cibuildwheel.linux from cibuildwheel.oci_container import OCIContainerEngineConfig from cibuildwheel.options import CommandLineArguments, Options -def test_linux_container_split(tmp_path: Path, monkeypatch): +def test_linux_container_split(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: """ Tests splitting linux builds by container image, container engine, and before_all """ @@ -53,13 +55,13 @@ def test_linux_container_split(tmp_path: Path, monkeypatch): build_steps = list(cibuildwheel.linux.get_build_steps(options, python_configurations)) # helper functions to extract test info - def identifiers(step): + def identifiers(step: cibuildwheel.linux.BuildStep) -> list[str]: return [c.identifier for c in step.platform_configs] - def before_alls(step): + def before_alls(step: cibuildwheel.linux.BuildStep) -> list[str]: return [options.build_options(c.identifier).before_all for c in step.platform_configs] - def container_engines(step): + def container_engines(step: cibuildwheel.linux.BuildStep) -> list[OCIContainerEngineConfig]: return [options.build_options(c.identifier).container_engine for c in step.platform_configs] pprint(build_steps) diff --git a/unit_test/main_tests/conftest.py b/unit_test/main_tests/conftest.py index e4479db3b..20dfb158d 100644 --- a/unit_test/main_tests/conftest.py +++ b/unit_test/main_tests/conftest.py @@ -12,12 +12,12 @@ class ArgsInterceptor: - def __init__(self): + def __init__(self) -> None: self.call_count = 0 - self.args = None - self.kwargs = None + self.args: tuple[object, ...] | None = None + self.kwargs: dict[str, object] | None = None - def __call__(self, *args, **kwargs): + def __call__(self, *args: object, **kwargs: object) -> None: self.call_count += 1 self.args = args self.kwargs = kwargs diff --git a/unit_test/main_tests/main_options_test.py b/unit_test/main_tests/main_options_test.py index acd37c1bc..849dbe3ba 100644 --- a/unit_test/main_tests/main_options_test.py +++ b/unit_test/main_tests/main_options_test.py @@ -119,7 +119,7 @@ def test_manylinux_images( assert build_options.manylinux_images is None -def get_default_repair_command(platform): +def get_default_repair_command(platform: str) -> str: if platform == "linux": return "auditwheel repair -w {dest_dir} {wheel}" elif platform == "macos": @@ -270,7 +270,7 @@ def test_config_settings(platform_specific, platform, intercepted_build_args, mo config_settings = 'setting=value setting=value2 other="something else"' if platform_specific: monkeypatch.setenv("CIBW_CONFIG_SETTINGS_" + platform.upper(), config_settings) - monkeypatch.setenv("CIBW_CONFIG_SETTIGNS", "a=b") + monkeypatch.setenv("CIBW_CONFIG_SETTINGS", "a=b") else: monkeypatch.setenv("CIBW_CONFIG_SETTINGS", config_settings) diff --git a/unit_test/main_tests/main_platform_test.py b/unit_test/main_tests/main_platform_test.py index 5dca47d01..fbc861595 100644 --- a/unit_test/main_tests/main_platform_test.py +++ b/unit_test/main_tests/main_platform_test.py @@ -6,6 +6,7 @@ from cibuildwheel.__main__ import main from cibuildwheel.architecture import Architecture +from cibuildwheel.util import EnableGroups from ..conftest import MOCK_PACKAGE_DIR @@ -179,6 +180,7 @@ def test_archs_platform_all(platform, intercepted_build_args, monkeypatch): Architecture.aarch64, Architecture.ppc64le, Architecture.s390x, + Architecture.armv7l, } elif platform == "windows": assert options.globals.architectures == { @@ -215,7 +217,7 @@ def test_only_argument(intercepted_build_args, monkeypatch, only, plat): assert options.globals.build_selector.skip_config == "" assert options.platform == plat assert options.globals.architectures == Architecture.all_archs(plat) - assert options.globals.build_selector.prerelease_pythons is True + assert EnableGroups.PyPy in options.globals.build_selector.enable @pytest.mark.parametrize("only", ("cp311-manylxinux_x86_64", "some_linux_thing")) diff --git a/unit_test/oci_container_test.py b/unit_test/oci_container_test.py index d88ca52eb..11d991d11 100644 --- a/unit_test/oci_container_test.py +++ b/unit_test/oci_container_test.py @@ -8,13 +8,21 @@ import subprocess import sys import textwrap +from contextlib import nullcontext from pathlib import Path, PurePath, PurePosixPath import pytest import tomli_w +import cibuildwheel.oci_container from cibuildwheel.environment import EnvironmentAssignmentBash -from cibuildwheel.oci_container import OCIContainer, OCIContainerEngineConfig, OCIPlatform +from cibuildwheel.errors import OCIEngineTooOldError +from cibuildwheel.oci_container import ( + OCIContainer, + OCIContainerEngineConfig, + OCIPlatform, + _check_engine_version, +) from cibuildwheel.util import CIProvider, detect_ci_provider # Test utilities @@ -231,7 +239,7 @@ def test_binary_output(container_engine): assert output == binary_data_string -def test_file_operation(tmp_path: Path, container_engine): +def test_file_operation(tmp_path: Path, container_engine: OCIContainerEngineConfig) -> None: with OCIContainer( engine=container_engine, image=DEFAULT_IMAGE, oci_platform=DEFAULT_OCI_PLATFORM ) as container: @@ -251,7 +259,7 @@ def test_file_operation(tmp_path: Path, container_engine): assert test_binary_data == bytes(output, encoding="utf8", errors="surrogateescape") -def test_dir_operations(tmp_path: Path, container_engine): +def test_dir_operations(tmp_path: Path, container_engine: OCIContainerEngineConfig) -> None: with OCIContainer( engine=container_engine, image=DEFAULT_IMAGE, oci_platform=DEFAULT_OCI_PLATFORM ) as container: @@ -293,7 +301,7 @@ def test_dir_operations(tmp_path: Path, container_engine): assert test_binary_data == (new_test_dir / "test.dat").read_bytes() -def test_environment_executor(container_engine): +def test_environment_executor(container_engine: OCIContainerEngineConfig) -> None: with OCIContainer( engine=container_engine, image=DEFAULT_IMAGE, oci_platform=DEFAULT_OCI_PLATFORM ) as container: @@ -301,7 +309,9 @@ def test_environment_executor(container_engine): assert assignment.evaluated_value({}, container.environment_executor) == "42" -def test_podman_vfs(tmp_path: Path, monkeypatch, container_engine): +def test_podman_vfs( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch, container_engine: OCIContainerEngineConfig +) -> None: if container_engine.name != "podman": pytest.skip("only runs with podman") if sys.platform.startswith("darwin"): @@ -383,7 +393,7 @@ def test_podman_vfs(tmp_path: Path, monkeypatch, container_engine): subprocess.run(["podman", "unshare", "rm", "-rf", vfs_path], check=True) -def test_create_args_volume(tmp_path: Path, container_engine): +def test_create_args_volume(tmp_path: Path, container_engine: OCIContainerEngineConfig) -> None: if container_engine.name != "docker": pytest.skip("only runs with docker") @@ -505,7 +515,12 @@ def test_enforce_32_bit(container_engine): ("{name}; disable_host_mount: true", False), ], ) -def test_disable_host_mount(tmp_path: Path, container_engine, config, should_have_host_mount): +def test_disable_host_mount( + tmp_path: Path, + container_engine: OCIContainerEngineConfig, + config: str, + should_have_host_mount: bool, +) -> None: if detect_ci_provider() in {CIProvider.circle_ci, CIProvider.gitlab}: pytest.skip("Skipping test because docker on this platform does not support host mounts") if sys.platform.startswith("darwin"): @@ -527,16 +542,35 @@ def test_disable_host_mount(tmp_path: Path, container_engine, config, should_hav container.call(["cat", host_mount_path], capture_output=True) -def test_local_image(container_engine): - local_image = f"cibw_test_{container_engine.name}_local:latest" +@pytest.mark.parametrize("platform", list(OCIPlatform)) +def test_local_image( + container_engine: OCIContainerEngineConfig, platform: OCIPlatform, tmp_path: Path +) -> None: + if ( + detect_ci_provider() in {CIProvider.travis_ci} + and pm in {"s390x", "ppc64le"} + and platform != DEFAULT_OCI_PLATFORM + ): + pytest.skip("Skipping test because docker on this platform does not support QEMU") + if container_engine.name == "podman" and platform == OCIPlatform.ARMV7: + # both GHA & local macOS arm64 podman desktop are failing + pytest.xfail("podman fails with armv7l images") + + remote_image = "debian:12-slim" + platform_name = platform.value.replace("/", "_") + local_image = f"cibw_{container_engine.name}_{platform_name}_local:latest" + dockerfile = tmp_path / "Dockerfile" + dockerfile.write_text(f"FROM {remote_image}") subprocess.run( - [container_engine.name, "pull", f"--platform={DEFAULT_OCI_PLATFORM.value}", DEFAULT_IMAGE], + [container_engine.name, "pull", f"--platform={platform.value}", remote_image], check=True, ) - subprocess.run([container_engine.name, "image", "tag", DEFAULT_IMAGE, local_image], check=True) - with OCIContainer( - engine=container_engine, image=local_image, oci_platform=DEFAULT_OCI_PLATFORM - ): + subprocess.run( + [container_engine.name, "build", f"--platform={platform.value}", "-t", local_image, "."], + check=True, + cwd=tmp_path, + ) + with OCIContainer(engine=container_engine, image=local_image, oci_platform=platform): pass @@ -548,24 +582,90 @@ def test_multiarch_image(container_engine, platform): and platform != DEFAULT_OCI_PLATFORM ): pytest.skip("Skipping test because docker on this platform does not support QEMU") + if container_engine.name == "podman" and platform == OCIPlatform.ARMV7: + # both GHA & local macOS arm64 podman desktop are failing + pytest.xfail("podman fails with armv7l images") with OCIContainer( engine=container_engine, image="debian:12-slim", oci_platform=platform ) as container: output = container.call(["uname", "-m"], capture_output=True) - output_map = { - OCIPlatform.i386: "i686", - OCIPlatform.AMD64: "x86_64", - OCIPlatform.ARM64: "aarch64", - OCIPlatform.PPC64LE: "ppc64le", - OCIPlatform.S390X: "s390x", + output_map_kernel = { + OCIPlatform.i386: ("i686",), + OCIPlatform.AMD64: ("x86_64",), + OCIPlatform.ARMV7: ("armv7l", "armv8l"), + OCIPlatform.ARM64: ("aarch64",), + OCIPlatform.PPC64LE: ("ppc64le",), + OCIPlatform.S390X: ("s390x",), } - assert output_map[platform] == output.strip() + assert output.strip() in output_map_kernel[platform] output = container.call(["dpkg", "--print-architecture"], capture_output=True) - output_map = { + output_map_dpkg = { OCIPlatform.i386: "i386", OCIPlatform.AMD64: "amd64", + OCIPlatform.ARMV7: "armhf", OCIPlatform.ARM64: "arm64", OCIPlatform.PPC64LE: "ppc64el", OCIPlatform.S390X: "s390x", } - assert output_map[platform] == output.strip() + assert output_map_dpkg[platform] == output.strip() + + +@pytest.mark.parametrize( + ("engine_name", "version", "context"), + [ + ( + "docker", + None, # 17.12.1-ce does supports "docker version --format '{{json . }}'" so a version before that + pytest.raises(OCIEngineTooOldError), + ), + ( + "docker", + '{"Client":{"Version":"19.03.15","ApiVersion": "1.40"},"Server":{"ApiVersion": "1.40"}}', + pytest.raises(OCIEngineTooOldError), + ), + ( + "docker", + '{"Client":{"Version":"20.10.0","ApiVersion":"1.41"},"Server":{"ApiVersion":"1.41"}}', + nullcontext(), + ), + ( + "docker", + '{"Client":{"Version":"24.0.0","ApiVersion":"1.43"},"Server":{"ApiVersion":"1.43"}}', + nullcontext(), + ), + ( + "docker", + '{"Client":{"ApiVersion":"1.43"},"Server":{"ApiVersion":"1.30"}}', + pytest.raises(OCIEngineTooOldError), + ), + ( + "docker", + '{"Client":{"ApiVersion":"1.30"},"Server":{"ApiVersion":"1.43"}}', + pytest.raises(OCIEngineTooOldError), + ), + ("podman", '{"Client":{"Version":"5.2.0"},"Server":{"Version":"5.1.2"}}', nullcontext()), + ("podman", '{"Client":{"Version":"4.9.4-rhel"}}', nullcontext()), + ( + "podman", + '{"Client":{"Version":"5.2.0"},"Server":{"Version":"2.1.2"}}', + pytest.raises(OCIEngineTooOldError), + ), + ( + "podman", + '{"Client":{"Version":"2.2.0"},"Server":{"Version":"5.1.2"}}', + pytest.raises(OCIEngineTooOldError), + ), + ("podman", '{"Client":{"Version":"3.0~rc1-rhel"}}', nullcontext()), + ("podman", '{"Client":{"Version":"2.1.0~rc1"}}', pytest.raises(OCIEngineTooOldError)), + ], +) +def test_engine_version(engine_name, version, context, monkeypatch): + def mockcall(*args, **kwargs): + if version is None: + raise subprocess.CalledProcessError(1, " ".join(str(arg) for arg in args)) + return version + + monkeypatch.setattr(cibuildwheel.oci_container, "call", mockcall) + engine = OCIContainerEngineConfig.from_config_string(engine_name) + with context: + _check_engine_version(engine) diff --git a/unit_test/options_test.py b/unit_test/options_test.py index c19c6619f..fb6727b0c 100644 --- a/unit_test/options_test.py +++ b/unit_test/options_test.py @@ -15,6 +15,7 @@ Options, _get_pinned_container_images, ) +from cibuildwheel.util import EnableGroups PYPROJECT_1 = """ [tool.cibuildwheel] @@ -178,7 +179,7 @@ def test_toml_environment_evil(tmp_path, env_var_value): (r'TEST_VAR="before\\$after"', "before$after"), ], ) -def test_toml_environment_quoting(tmp_path: Path, toml_assignment, result_value): +def test_toml_environment_quoting(tmp_path: Path, toml_assignment: str, result_value: str) -> None: args = CommandLineArguments.defaults() args.package_dir = tmp_path @@ -255,8 +256,12 @@ def test_toml_environment_quoting(tmp_path: Path, toml_assignment, result_value) ], ) def test_container_engine_option( - tmp_path: Path, toml_assignment, result_name, result_create_args, result_disable_host_mount -): + tmp_path: Path, + toml_assignment: str, + result_name: str, + result_create_args: tuple[str, ...], + result_disable_host_mount: bool, +) -> None: args = CommandLineArguments.defaults() args.package_dir = tmp_path @@ -326,7 +331,9 @@ def test_environment_pass_references(): ), ], ) -def test_build_frontend_option(tmp_path: Path, toml_assignment, result_name, result_args): +def test_build_frontend_option( + tmp_path: Path, toml_assignment: str, result_name: str, result_args: list[str] +) -> None: args = CommandLineArguments.defaults() args.package_dir = tmp_path @@ -350,7 +357,7 @@ def test_build_frontend_option(tmp_path: Path, toml_assignment, result_name, res assert parsed_build_frontend is None -def test_override_inherit_environment(tmp_path: Path): +def test_override_inherit_environment(tmp_path: Path) -> None: args = CommandLineArguments.defaults() args.package_dir = tmp_path @@ -385,7 +392,7 @@ def test_override_inherit_environment(tmp_path: Path): } -def test_override_inherit_environment_with_references(tmp_path: Path): +def test_override_inherit_environment_with_references(tmp_path: Path) -> None: args = CommandLineArguments.defaults() args.package_dir = tmp_path @@ -434,7 +441,7 @@ def test_override_inherit_environment_with_references(tmp_path: Path): ) def test_free_threaded_support( tmp_path: Path, toml_assignment: str, env: dict[str, str], expected_result: bool -): +) -> None: args = CommandLineArguments.defaults() args.package_dir = tmp_path @@ -448,4 +455,7 @@ def test_free_threaded_support( ) ) options = Options(platform="linux", command_line_arguments=args, env=env) - assert options.globals.build_selector.free_threaded_support is expected_result + if expected_result: + assert EnableGroups.CPythonFreeThreading in options.globals.build_selector.enable + else: + assert EnableGroups.CPythonFreeThreading not in options.globals.build_selector.enable diff --git a/unit_test/options_toml_test.py b/unit_test/options_toml_test.py index da21d884b..5eda6cbb0 100644 --- a/unit_test/options_toml_test.py +++ b/unit_test/options_toml_test.py @@ -22,6 +22,7 @@ test-command = "pyproject" test-requires = "something" test-extras = ["one", "two"] +test-groups = ["three", "four"] manylinux-x86_64-image = "manylinux1" @@ -60,6 +61,7 @@ def test_simple_settings(tmp_path, platform, fname): == 'THING="OTHER" FOO="BAR"' ) assert options_reader.get("test-extras", option_format=ListFormat(",")) == "one,two" + assert options_reader.get("test-groups", option_format=ListFormat(" ")) == "three four" assert options_reader.get("manylinux-x86_64-image") == "manylinux1" assert options_reader.get("manylinux-i686-image") == "manylinux2014" @@ -85,7 +87,9 @@ def test_envvar_override(tmp_path, platform): "CIBW_MANYLINUX_X86_64_IMAGE": "manylinux_2_24", "CIBW_TEST_COMMAND": "mytest", "CIBW_TEST_REQUIRES": "docs", + "CIBW_TEST_GROUPS": "mgroup two", "CIBW_TEST_REQUIRES_LINUX": "scod", + "CIBW_TEST_GROUPS_LINUX": "lgroup", }, ) @@ -99,6 +103,10 @@ def test_envvar_override(tmp_path, platform): options_reader.get("test-requires", option_format=ListFormat(" ")) == {"windows": "docs", "macos": "docs", "linux": "scod"}[platform] ) + assert ( + options_reader.get("test-groups", option_format=ListFormat(" ")) + == {"windows": "mgroup two", "macos": "mgroup two", "linux": "lgroup"}[platform] + ) assert options_reader.get("test-command") == "mytest" diff --git a/unit_test/projectfiles_test.py b/unit_test/projectfiles_test.py index b1839eda3..179f648ef 100644 --- a/unit_test/projectfiles_test.py +++ b/unit_test/projectfiles_test.py @@ -2,7 +2,14 @@ from textwrap import dedent -from cibuildwheel.projectfiles import get_requires_python_str, setup_py_python_requires +import pytest + +from cibuildwheel._compat import tomllib +from cibuildwheel.projectfiles import ( + get_requires_python_str, + resolve_dependency_groups, + setup_py_python_requires, +) def test_read_setup_py_simple(tmp_path): @@ -23,7 +30,7 @@ def test_read_setup_py_simple(tmp_path): ) assert setup_py_python_requires(tmp_path.joinpath("setup.py").read_text()) == "1.23" - assert get_requires_python_str(tmp_path) == "1.23" + assert get_requires_python_str(tmp_path, {}) == "1.23" def test_read_setup_py_if_main(tmp_path): @@ -45,7 +52,7 @@ def test_read_setup_py_if_main(tmp_path): ) assert setup_py_python_requires(tmp_path.joinpath("setup.py").read_text()) == "1.23" - assert get_requires_python_str(tmp_path) == "1.23" + assert get_requires_python_str(tmp_path, {}) == "1.23" def test_read_setup_py_if_main_reversed(tmp_path): @@ -67,7 +74,7 @@ def test_read_setup_py_if_main_reversed(tmp_path): ) assert setup_py_python_requires(tmp_path.joinpath("setup.py").read_text()) == "1.23" - assert get_requires_python_str(tmp_path) == "1.23" + assert get_requires_python_str(tmp_path, {}) == "1.23" def test_read_setup_py_if_invalid(tmp_path): @@ -89,7 +96,7 @@ def test_read_setup_py_if_invalid(tmp_path): ) assert not setup_py_python_requires(tmp_path.joinpath("setup.py").read_text()) - assert not get_requires_python_str(tmp_path) + assert not get_requires_python_str(tmp_path, {}) def test_read_setup_py_full(tmp_path): @@ -115,7 +122,7 @@ def test_read_setup_py_full(tmp_path): assert ( setup_py_python_requires(tmp_path.joinpath("setup.py").read_text(encoding="utf8")) == "1.24" ) - assert get_requires_python_str(tmp_path) == "1.24" + assert get_requires_python_str(tmp_path, {}) == "1.24" def test_read_setup_py_assign(tmp_path): @@ -138,7 +145,7 @@ def test_read_setup_py_assign(tmp_path): ) assert setup_py_python_requires(tmp_path.joinpath("setup.py").read_text()) is None - assert get_requires_python_str(tmp_path) is None + assert get_requires_python_str(tmp_path, {}) is None def test_read_setup_py_None(tmp_path): @@ -161,7 +168,7 @@ def test_read_setup_py_None(tmp_path): ) assert setup_py_python_requires(tmp_path.joinpath("setup.py").read_text()) is None - assert get_requires_python_str(tmp_path) is None + assert get_requires_python_str(tmp_path, {}) is None def test_read_setup_py_empty(tmp_path): @@ -183,7 +190,7 @@ def test_read_setup_py_empty(tmp_path): ) assert setup_py_python_requires(tmp_path.joinpath("setup.py").read_text()) is None - assert get_requires_python_str(tmp_path) is None + assert get_requires_python_str(tmp_path, {}) is None def test_read_setup_cfg(tmp_path): @@ -199,7 +206,7 @@ def test_read_setup_cfg(tmp_path): ) ) - assert get_requires_python_str(tmp_path) == "1.234" + assert get_requires_python_str(tmp_path, {}) == "1.234" def test_read_setup_cfg_empty(tmp_path): @@ -215,7 +222,7 @@ def test_read_setup_cfg_empty(tmp_path): ) ) - assert get_requires_python_str(tmp_path) is None + assert get_requires_python_str(tmp_path, {}) is None def test_read_pyproject_toml(tmp_path): @@ -231,8 +238,10 @@ def test_read_pyproject_toml(tmp_path): """ ) ) + with open(tmp_path / "pyproject.toml", "rb") as f: + pyproject_toml = tomllib.load(f) - assert get_requires_python_str(tmp_path) == "1.654" + assert get_requires_python_str(tmp_path, pyproject_toml) == "1.654" def test_read_pyproject_toml_empty(tmp_path): @@ -245,5 +254,25 @@ def test_read_pyproject_toml_empty(tmp_path): """ ) ) + with open(tmp_path / "pyproject.toml", "rb") as f: + pyproject_toml = tomllib.load(f) + + assert get_requires_python_str(tmp_path, pyproject_toml) is None + + +def test_read_dep_groups(): + pyproject_toml = {"dependency-groups": {"group1": ["pkg1", "pkg2"], "group2": ["pkg3"]}} + assert resolve_dependency_groups(pyproject_toml) == () + assert resolve_dependency_groups(pyproject_toml, "group1") == ("pkg1", "pkg2") + assert resolve_dependency_groups(pyproject_toml, "group2") == ("pkg3",) + assert resolve_dependency_groups(pyproject_toml, "group1", "group2") == ("pkg1", "pkg2", "pkg3") + + +def test_dep_group_no_file_error(): + with pytest.raises(FileNotFoundError, match="pyproject.toml"): + resolve_dependency_groups(None, "test") + - assert get_requires_python_str(tmp_path) is None +def test_dep_group_no_section_error(): + with pytest.raises(KeyError, match="pyproject.toml"): + resolve_dependency_groups({}, "test") diff --git a/unit_test/utils_test.py b/unit_test/utils_test.py index 4725163ff..e3b87be86 100644 --- a/unit_test/utils_test.py +++ b/unit_test/utils_test.py @@ -6,6 +6,7 @@ import pytest from cibuildwheel.util import ( + FlexibleVersion, find_compatible_wheel, fix_ansi_codes_for_github_actions, format_safe, @@ -75,7 +76,7 @@ def test_prepare_command(): ("foo-0.1-py38-none-win_amd64.whl", "pp310-win_amd64"), ], ) -def test_find_compatible_wheel_found(wheel: str, identifier: str): +def test_find_compatible_wheel_found(wheel: str, identifier: str) -> None: wheel_ = PurePath(wheel) found = find_compatible_wheel([wheel_], identifier) assert found is wheel_ @@ -95,7 +96,7 @@ def test_find_compatible_wheel_found(wheel: str, identifier: str): ("foo-0.1-cp38-cp38-win_amd64.whl", "cp310-win_amd64"), ], ) -def test_find_compatible_wheel_not_found(wheel: str, identifier: str): +def test_find_compatible_wheel_not_found(wheel: str, identifier: str) -> None: assert find_compatible_wheel([PurePath(wheel)], identifier) is None @@ -206,3 +207,17 @@ def test_parse_key_value_string(): "name": ["docker"], "create_args": [], } + + +def test_flexible_version_comparisons(): + assert FlexibleVersion("2.0") == FlexibleVersion("2") + assert FlexibleVersion("2.0") < FlexibleVersion("2.1") + assert FlexibleVersion("2.1") > FlexibleVersion("2") + assert FlexibleVersion("1.9.9") < FlexibleVersion("2.0") + assert FlexibleVersion("1.10") > FlexibleVersion("1.9.9") + assert FlexibleVersion("3.0.1") > FlexibleVersion("3.0") + assert FlexibleVersion("3.0") < FlexibleVersion("3.0.1") + # Suffix should not affect comparisons + assert FlexibleVersion("1.0.1-rhel") > FlexibleVersion("1.0") + assert FlexibleVersion("1.0.1-rhel") < FlexibleVersion("1.1") + assert FlexibleVersion("1.0.1") == FlexibleVersion("v1.0.1") diff --git a/unit_test/validate_schema_test.py b/unit_test/validate_schema_test.py index 698353990..bb102bb25 100644 --- a/unit_test/validate_schema_test.py +++ b/unit_test/validate_schema_test.py @@ -45,7 +45,7 @@ def test_validate_container_engine(): @pytest.mark.parametrize("platform", ["macos", "windows"]) -def test_validate_bad_container_engine(platform: str): +def test_validate_bad_container_engine(platform: str) -> None: """ container-engine is not a valid option for macos or windows """ diff --git a/unit_test/wheel_print_test.py b/unit_test/wheel_print_test.py index 9ddb54050..e97d316d4 100644 --- a/unit_test/wheel_print_test.py +++ b/unit_test/wheel_print_test.py @@ -27,7 +27,7 @@ def test_no_printout_on_error(tmp_path, capsys): tmp_path.joinpath("example.1").touch() raise RuntimeError() - captured = capsys.readouterr() # type: ignore[unreachable] + captured = capsys.readouterr() assert captured.err == "" assert "example.0" not in captured.out