cibuildwheel

cibuildwheel simplifies the creation of Python Wheels for the different platforms and Python versions through Continuous Integration (CI) workflows. More precisely it builds manylinux, macOS 10.9+, and Windows wheels for CPython and PyPy with GitHub Actions, Azure Pipelines, Travis CI, AppVeyor, CircleCI, or GitLab CI/CD.

In addition, it bundles shared library dependencies on Linux and macOS through auditwheel and delocate.

Finally, the tests can also run against the wheels.

See also

To build Linux, macOS, and Windows wheels, create a .github/workflows/build_wheels.yml file in your GitHub repo:

.github/workflows/build_wheels.yml
name: Build

on:
  push:
    branches: [main]
  release:
    types:
      - published
  workflow_dispatch:

concurrency:
  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: true

permissions: {}
release

is executed when a tagged version is transferred.

See also

workflow_dispatch

allows you to click a button in the graphical user interface to trigger a build. This is perfect for manually testing wheels before a release, as you can easily download them from artifacts.

Now the wheels can be built with:

jobs:
  build_wheels:
    name: Build wheels on ${{ matrix.os }}
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        # macos-13 is an intel runner, macos-14 is apple silicon
        os: [ubuntu-latest, windows-latest, macos-13, macos-14]

    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: Build wheels
        uses: pypa/cibuildwheel@8d2b08b68458a16aeb24b64e68a09ab1c8e82084 # v3.4.1

This runs the CI workflow with the following default settings:

  • package-dir: .

  • output-dir: wheelhouse

  • config-file: "{package}/pyproject.toml"

Now you can finally upload the artefacts of both jobs to the PyPI:

      - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
        with:
          name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }}
          path: ./wheelhouse/*.whl

To build wheels for Linux, macOS, and Windows with GitLab CI/CD, create a .gitlab-ci.yml file in your repository:

linux:
  image: python:3.13
  services:
    - name: docker:dind
      entrypoint: ["env", "-u", "DOCKER_HOST"]
      command: ["dockerd-entrypoint.sh"]
  variables:
    DOCKER_HOST: tcp://docker:2375/
    DOCKER_DRIVER: overlay2
    # See https://github.com/docker-library/docker/pull/166
    DOCKER_TLS_CERTDIR: ""

    # skip all but the basic tests
    # (comment the below line in a PR to debug a Gitlab-specific issue)
    PYTEST_ADDOPTS: -k "unit_test or test_0_basic" --suppress-no-test-exit-code
  rules:
    - if: '$CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH =~ /^gitlab/'
      variables:
        CIBW_ENABLE: "all"
  script:
    - curl -sSL https://get.docker.com/ | sh
    - docker run --rm --privileged docker.io/tonistiigi/binfmt:latest --install all
    - python -m pip install -U pip
    - python -m pip install -e. pytest-custom-exit-code --group test
    - python ./bin/run_tests.py

windows:
  variables:
    PYTEST_ADDOPTS: -k "unit_test or test_0_basic" --suppress-no-test-exit-code
  rules:
    - if: '$CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH =~ /^gitlab/'
      variables:
        # Everything except graalpy. GraalPy is JVM-based and very slow to
        # start; on the small (2-core) GitLab SaaS Windows runners,
        # virtualenv's interpreter query for graalpy.exe times out, failing
        # the build. (Other platforms run the full "all" group.)
        CIBW_ENABLE: "cpython-prerelease pypy pypy-eol pyodide-eol pyodide-prerelease"
  script:
    - python -m pip install -U pip
    - python -m pip install -e. pytest-custom-exit-code --group test
    - python bin\run_tests.py
  tags:
    - saas-windows-medium-amd64

macos:
  image: macos-15-xcode-16
  variables:
    PYTEST_ADDOPTS: -k "unit_test or test_0_basic" --suppress-no-test-exit-code
  rules:
    - if: '$CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH =~ /^gitlab/'
      variables:
        CIBW_ENABLE: "all"
  script:
    - python3 -m pip install -U pip
    - python3 -m pip install -e. pytest-custom-exit-code --group test
    - python3 ./bin/run_tests.py
  tags:
    - saas-macos-medium-m1

Options

cibuildwheel can be configured either via environment variables or via a configuration file such as pyproject.toml, for example:

[tool.cibuildwheel]
test-requires = "pytest"
test-command = "pytest {project}/tests"
build-verbosity = 1
# support Universal2 for Apple Silicon:
macos.archs = [ "auto", "universal2" ]
macos.test-skip = [ "*universal2:arm64" ]

Examples